eidos-ui 3.3.0 → 3.4.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.
- package/README.md +17 -5
- package/dist/{Input.types-XrJTI0JC.d.ts → Input.types-CSDMaZNe.d.ts} +10 -0
- package/dist/{Input.types-DFZNyJGF.d.cts → Input.types-D7-sWq-q.d.cts} +10 -0
- package/dist/{Select.types-CAkT2ACt.d.cts → Select.types-2p-mpaBL.d.cts} +1 -1
- package/dist/{Select.types-B4kDpkGB.d.ts → Select.types-BqaD7bmB.d.ts} +1 -1
- package/dist/alert/index.cjs +5 -5
- package/dist/alert/index.js +4 -4
- package/dist/button/index.cjs +4 -4
- package/dist/button/index.js +3 -3
- package/dist/checkbox/index.cjs +2 -2
- package/dist/checkbox/index.js +1 -1
- package/dist/chip/index.cjs +4 -4
- package/dist/chip/index.js +3 -3
- package/dist/{chunk-RYOQYYDO.js → chunk-22EKNTWJ.js} +9 -5
- package/dist/chunk-22EKNTWJ.js.map +1 -0
- package/dist/{chunk-3E7TKBQI.cjs → chunk-27UANGPW.cjs} +14 -6
- package/dist/chunk-27UANGPW.cjs.map +1 -0
- package/dist/{chunk-CJGFRPAS.cjs → chunk-3QQKWXMV.cjs} +4 -4
- package/dist/chunk-3QQKWXMV.cjs.map +1 -0
- package/dist/{chunk-Q5KQVBQP.cjs → chunk-3S33BIN2.cjs} +18 -16
- package/dist/chunk-3S33BIN2.cjs.map +1 -0
- package/dist/{chunk-ZPDETHQ7.js → chunk-3WV6RTXX.js} +3 -3
- package/dist/{chunk-ZPDETHQ7.js.map → chunk-3WV6RTXX.js.map} +1 -1
- package/dist/chunk-44T4HMFU.js +88 -0
- package/dist/chunk-44T4HMFU.js.map +1 -0
- package/dist/{chunk-3EPND5M2.cjs → chunk-4LET2PEX.cjs} +12 -12
- package/dist/chunk-4LET2PEX.cjs.map +1 -0
- package/dist/{chunk-R4M6SNUU.cjs → chunk-5E4DYTZ3.cjs} +48 -37
- package/dist/chunk-5E4DYTZ3.cjs.map +1 -0
- package/dist/{chunk-I5XR2V4A.cjs → chunk-5V7DXZWT.cjs} +79 -76
- package/dist/chunk-5V7DXZWT.cjs.map +1 -0
- package/dist/{chunk-6CPCNQKV.js → chunk-67EXHO4T.js} +2 -2
- package/dist/{chunk-K7T25Y3A.cjs → chunk-6LCYO2HG.cjs} +10 -9
- package/dist/chunk-6LCYO2HG.cjs.map +1 -0
- package/dist/{chunk-ZNWIHUHZ.cjs → chunk-7KVXXHFX.cjs} +9 -3
- package/dist/chunk-7KVXXHFX.cjs.map +1 -0
- package/dist/{chunk-55J4LEKT.cjs → chunk-AHVDTWVM.cjs} +11 -8
- package/dist/chunk-AHVDTWVM.cjs.map +1 -0
- package/dist/{chunk-OAPOGYA5.cjs → chunk-AXPEJ5IG.cjs} +7 -4
- package/dist/chunk-AXPEJ5IG.cjs.map +1 -0
- package/dist/{chunk-GDQWSCJQ.js → chunk-BBU7QG7L.js} +4 -4
- package/dist/{chunk-T2KB3DJJ.js → chunk-BGBAZMFE.js} +9 -3
- package/dist/chunk-BGBAZMFE.js.map +1 -0
- package/dist/{chunk-EH42ND5Q.cjs → chunk-BIANFFJL.cjs} +5 -5
- package/dist/{chunk-EH42ND5Q.cjs.map → chunk-BIANFFJL.cjs.map} +1 -1
- package/dist/{chunk-O2AACVSF.js → chunk-BO4LTIJI.js} +3 -3
- package/dist/{chunk-G5N3C6AU.cjs → chunk-CBT3TTQR.cjs} +36 -36
- package/dist/{chunk-G5N3C6AU.cjs.map → chunk-CBT3TTQR.cjs.map} +1 -1
- package/dist/{chunk-JRLUZDKA.js → chunk-CEC3GO4R.js} +20 -7
- package/dist/chunk-CEC3GO4R.js.map +1 -0
- package/dist/{chunk-BNVQ34FD.js → chunk-CP335H4Q.js} +4 -4
- package/dist/{chunk-H6CWTMAH.js → chunk-DGXHR6FV.js} +5 -2
- package/dist/chunk-DGXHR6FV.js.map +1 -0
- package/dist/{chunk-OU7DCYIA.js → chunk-DMOZURHV.js} +2 -2
- package/dist/{chunk-L5W2XC6M.js → chunk-EWTMX4DK.js} +8 -8
- package/dist/{chunk-SXGCRHBZ.js → chunk-FDMYDIGI.js} +8 -6
- package/dist/chunk-FDMYDIGI.js.map +1 -0
- package/dist/{chunk-AOGNTG3W.cjs → chunk-FODRHCRP.cjs} +5 -4
- package/dist/chunk-FODRHCRP.cjs.map +1 -0
- package/dist/{chunk-E7FGY7BJ.js → chunk-FPLX5FIK.js} +4 -4
- package/dist/{chunk-NEYLNRVG.cjs → chunk-FXMRDNR6.cjs} +21 -16
- package/dist/chunk-FXMRDNR6.cjs.map +1 -0
- package/dist/{chunk-VXO2MYYH.cjs → chunk-G74DHVHV.cjs} +9 -9
- package/dist/{chunk-VXO2MYYH.cjs.map → chunk-G74DHVHV.cjs.map} +1 -1
- package/dist/{chunk-O3LG7B2J.cjs → chunk-GMDVW76A.cjs} +13 -5
- package/dist/chunk-GMDVW76A.cjs.map +1 -0
- package/dist/{chunk-NQ4L6PYW.js → chunk-GOF6A4M4.js} +2 -2
- package/dist/{chunk-NQ4L6PYW.js.map → chunk-GOF6A4M4.js.map} +1 -1
- package/dist/{chunk-TQGO5WK6.js → chunk-GPK2THIG.js} +7 -6
- package/dist/chunk-GPK2THIG.js.map +1 -0
- package/dist/{chunk-HYDNZXPU.js → chunk-H2RS6Z6Q.js} +41 -25
- package/dist/{chunk-HYDNZXPU.js.map → chunk-H2RS6Z6Q.js.map} +1 -1
- package/dist/{chunk-OJDYTCMO.cjs → chunk-H5D3ZFBM.cjs} +68 -61
- package/dist/chunk-H5D3ZFBM.cjs.map +1 -0
- package/dist/{chunk-MHZIBV2C.js → chunk-IXVM2XNS.js} +7 -6
- package/dist/chunk-IXVM2XNS.js.map +1 -0
- package/dist/{chunk-MCWTQT3M.cjs → chunk-JKBBHAIC.cjs} +21 -20
- package/dist/chunk-JKBBHAIC.cjs.map +1 -0
- package/dist/{chunk-QQIALSTX.cjs → chunk-KPMIIGHH.cjs} +27 -17
- package/dist/chunk-KPMIIGHH.cjs.map +1 -0
- package/dist/{chunk-QJRKXT54.cjs → chunk-KWDOOMHR.cjs} +7 -7
- package/dist/{chunk-QJRKXT54.cjs.map → chunk-KWDOOMHR.cjs.map} +1 -1
- package/dist/{chunk-HALVTKW7.js → chunk-LH5A2SIX.js} +78 -75
- package/dist/{chunk-HALVTKW7.js.map → chunk-LH5A2SIX.js.map} +1 -1
- package/dist/{chunk-27ZWUEPE.js → chunk-LIKSLFX6.js} +20 -9
- package/dist/chunk-LIKSLFX6.js.map +1 -0
- package/dist/{chunk-Z5U4NJTW.cjs → chunk-MLUHM3VG.cjs} +48 -19
- package/dist/chunk-MLUHM3VG.cjs.map +1 -0
- package/dist/{chunk-M7PF4FKQ.js → chunk-MP7LN5CW.js} +6 -3
- package/dist/chunk-MP7LN5CW.js.map +1 -0
- package/dist/{chunk-ACNTH2OQ.cjs → chunk-MW4YZVP5.cjs} +96 -97
- package/dist/chunk-MW4YZVP5.cjs.map +1 -0
- package/dist/{chunk-ZXJU3TQD.cjs → chunk-N5ZWQJTM.cjs} +11 -10
- package/dist/chunk-N5ZWQJTM.cjs.map +1 -0
- package/dist/{chunk-OGC44JFZ.cjs → chunk-N77VOYCK.cjs} +7 -7
- package/dist/{chunk-OGC44JFZ.cjs.map → chunk-N77VOYCK.cjs.map} +1 -1
- package/dist/{chunk-OAK7HFOJ.js → chunk-NACA72XI.js} +21 -16
- package/dist/chunk-NACA72XI.js.map +1 -0
- package/dist/{chunk-TEVZHN5I.cjs → chunk-NCZUQFKL.cjs} +28 -15
- package/dist/chunk-NCZUQFKL.cjs.map +1 -0
- package/dist/{chunk-NZO2I5AL.cjs → chunk-NF55JG2V.cjs} +7 -7
- package/dist/{chunk-NZO2I5AL.cjs.map → chunk-NF55JG2V.cjs.map} +1 -1
- package/dist/{chunk-ZWAFL2B3.cjs → chunk-O46UJUZM.cjs} +21 -19
- package/dist/chunk-O46UJUZM.cjs.map +1 -0
- package/dist/{chunk-25PSIDPC.js → chunk-OIXPYXR6.js} +79 -21
- package/dist/chunk-OIXPYXR6.js.map +1 -0
- package/dist/chunk-OWWPHXAF.cjs +88 -0
- package/dist/chunk-OWWPHXAF.cjs.map +1 -0
- package/dist/{chunk-65PC3YDT.cjs → chunk-OZQKWDFH.cjs} +7 -7
- package/dist/{chunk-65PC3YDT.cjs.map → chunk-OZQKWDFH.cjs.map} +1 -1
- package/dist/{chunk-P2JM22BF.cjs → chunk-P7J6WZTO.cjs} +3 -3
- package/dist/{chunk-P2JM22BF.cjs.map → chunk-P7J6WZTO.cjs.map} +1 -1
- package/dist/{chunk-KQJ3IAFT.js → chunk-PG2EII6U.js} +2 -2
- package/dist/{chunk-E6SE552P.cjs → chunk-RUDXHT76.cjs} +41 -25
- package/dist/chunk-RUDXHT76.cjs.map +1 -0
- package/dist/{chunk-S4S4LB63.js → chunk-SCLFJHN2.js} +11 -3
- package/dist/chunk-SCLFJHN2.js.map +1 -0
- package/dist/{chunk-WKDT2XFQ.js → chunk-SFRILDXY.js} +4 -4
- package/dist/{chunk-J5RMLZ4P.cjs → chunk-SKBKTNEQ.cjs} +80 -22
- package/dist/chunk-SKBKTNEQ.cjs.map +1 -0
- package/dist/{chunk-YW65NAKH.cjs → chunk-SKLMBE3Z.cjs} +7 -7
- package/dist/{chunk-YW65NAKH.cjs.map → chunk-SKLMBE3Z.cjs.map} +1 -1
- package/dist/{chunk-PY2376XI.js → chunk-SVCWSW2F.js} +8 -8
- package/dist/chunk-SVCWSW2F.js.map +1 -0
- package/dist/{chunk-2RXQMQYW.js → chunk-SXL4DMZ7.js} +27 -17
- package/dist/{chunk-2RXQMQYW.js.map → chunk-SXL4DMZ7.js.map} +1 -1
- package/dist/{chunk-7SVDYEKM.js → chunk-TLECMA3B.js} +5 -4
- package/dist/{chunk-7SVDYEKM.js.map → chunk-TLECMA3B.js.map} +1 -1
- package/dist/{chunk-FXZJL2AM.cjs → chunk-TWXZKUCG.cjs} +9 -5
- package/dist/chunk-TWXZKUCG.cjs.map +1 -0
- package/dist/{chunk-GG5YGESX.js → chunk-TYGVKSL6.js} +4 -3
- package/dist/chunk-TYGVKSL6.js.map +1 -0
- package/dist/{chunk-TTO7JXDB.cjs → chunk-TYSNOPMQ.cjs} +3 -3
- package/dist/{chunk-TTO7JXDB.cjs.map → chunk-TYSNOPMQ.cjs.map} +1 -1
- package/dist/{chunk-ZZSPQFMX.js → chunk-U233KQZX.js} +2 -1
- package/dist/{chunk-ZZSPQFMX.js.map → chunk-U233KQZX.js.map} +1 -1
- package/dist/{chunk-5HUZYY54.js → chunk-UDRAM6JU.js} +13 -5
- package/dist/chunk-UDRAM6JU.js.map +1 -0
- package/dist/{chunk-3EUTXQDB.js → chunk-UGTKZ7HP.js} +30 -23
- package/dist/chunk-UGTKZ7HP.js.map +1 -0
- package/dist/{chunk-FKXKBJUI.cjs → chunk-VHO6JPRR.cjs} +2 -1
- package/dist/chunk-VHO6JPRR.cjs.map +1 -0
- package/dist/chunk-VHSTB7GB.cjs +123 -0
- package/dist/chunk-VHSTB7GB.cjs.map +1 -0
- package/dist/{chunk-VPRUHE35.js → chunk-VRNCXYEP.js} +4 -4
- package/dist/{chunk-ITXDSQT2.js → chunk-WGCGSM5P.js} +96 -97
- package/dist/chunk-WGCGSM5P.js.map +1 -0
- package/dist/chunk-WT4WBHBR.js +123 -0
- package/dist/chunk-WT4WBHBR.js.map +1 -0
- package/dist/{chunk-L23WWO3D.js → chunk-X6UKU3C3.js} +2 -2
- package/dist/{chunk-5WH3JMR5.js → chunk-XK5USBHB.js} +7 -4
- package/dist/chunk-XK5USBHB.js.map +1 -0
- package/dist/{chunk-ND526PMG.cjs → chunk-XMXL6NVJ.cjs} +11 -11
- package/dist/{chunk-ND526PMG.cjs.map → chunk-XMXL6NVJ.cjs.map} +1 -1
- package/dist/{chunk-YV57RSPD.js → chunk-XO7WTE6O.js} +47 -18
- package/dist/chunk-XO7WTE6O.js.map +1 -0
- package/dist/{chunk-3CXULV4G.cjs → chunk-XRVRGRC3.cjs} +2 -2
- package/dist/{chunk-3CXULV4G.cjs.map → chunk-XRVRGRC3.cjs.map} +1 -1
- package/dist/{chunk-GPRJYPIG.js → chunk-Y7VSWZWM.js} +6 -4
- package/dist/chunk-Y7VSWZWM.js.map +1 -0
- package/dist/{chunk-2QEKSRVX.js → chunk-YIBPOLNN.js} +4 -4
- package/dist/{chunk-IJ4DXG7D.cjs → chunk-YJNQLH2L.cjs} +5 -2
- package/dist/chunk-YJNQLH2L.cjs.map +1 -0
- package/dist/{chunk-6CSUUQ5M.cjs → chunk-YUH56CJI.cjs} +6 -6
- package/dist/{chunk-6CSUUQ5M.cjs.map → chunk-YUH56CJI.cjs.map} +1 -1
- package/dist/color-picker/index.cjs +4 -4
- package/dist/color-picker/index.d.cts +8 -0
- package/dist/color-picker/index.d.ts +8 -0
- package/dist/color-picker/index.js +3 -3
- package/dist/combobox/index.cjs +6 -6
- package/dist/combobox/index.js +5 -5
- package/dist/command-palette/index.cjs +3 -2
- package/dist/command-palette/index.cjs.map +1 -1
- package/dist/command-palette/index.js +2 -1
- package/dist/context-menu/index.cjs +6 -6
- package/dist/context-menu/index.js +5 -5
- package/dist/conversation/index.cjs +8 -8
- package/dist/conversation/index.js +7 -7
- package/dist/data-grid/index.cjs +16 -16
- package/dist/data-grid/index.d.cts +2 -2
- package/dist/data-grid/index.d.ts +2 -2
- package/dist/data-grid/index.js +15 -15
- package/dist/date-picker/index.cjs +8 -8
- package/dist/date-picker/index.d.cts +1 -1
- package/dist/date-picker/index.d.ts +1 -1
- package/dist/date-picker/index.js +7 -7
- package/dist/drawer/index.cjs +5 -5
- package/dist/drawer/index.js +4 -4
- package/dist/dropdown/index.cjs +3 -3
- package/dist/dropdown/index.js +2 -2
- package/dist/file-upload/index.cjs +2 -2
- package/dist/file-upload/index.js +1 -1
- package/dist/header/index.cjs +6 -6
- package/dist/header/index.js +5 -5
- package/dist/index.cjs +42 -42
- package/dist/index.css +93 -38
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +41 -41
- package/dist/inline-edit/index.cjs +5 -5
- package/dist/inline-edit/index.js +4 -4
- package/dist/input/index.cjs +4 -4
- package/dist/input/index.d.cts +2 -2
- package/dist/input/index.d.ts +2 -2
- package/dist/input/index.js +3 -3
- package/dist/menu/index.cjs +5 -5
- package/dist/menu/index.js +4 -4
- package/dist/modal/index.cjs +5 -5
- package/dist/modal/index.js +4 -4
- package/dist/navigation/index.cjs +5 -5
- package/dist/navigation/index.js +4 -4
- package/dist/number-input/index.cjs +3 -3
- package/dist/number-input/index.js +2 -2
- package/dist/o-t-p-input/index.cjs +2 -2
- package/dist/o-t-p-input/index.js +1 -1
- package/dist/page-layout/index.cjs +12 -12
- package/dist/page-layout/index.js +11 -11
- package/dist/pagination/index.cjs +8 -8
- package/dist/pagination/index.js +7 -7
- package/dist/popover/index.cjs +3 -2
- package/dist/popover/index.cjs.map +1 -1
- package/dist/popover/index.js +2 -1
- package/dist/radio/index.cjs +2 -2
- package/dist/radio/index.js +1 -1
- package/dist/segmented-control/index.cjs +4 -4
- package/dist/segmented-control/index.js +3 -3
- package/dist/select/index.cjs +6 -6
- package/dist/select/index.d.cts +3 -3
- package/dist/select/index.d.ts +3 -3
- package/dist/select/index.js +5 -5
- package/dist/snackbar/index.cjs +5 -5
- package/dist/snackbar/index.d.cts +4 -0
- package/dist/snackbar/index.d.ts +4 -0
- package/dist/snackbar/index.js +4 -4
- package/dist/split-button/index.cjs +6 -6
- package/dist/split-button/index.js +5 -5
- package/dist/table/index.cjs +14 -14
- package/dist/table/index.js +13 -13
- package/dist/tabs/index.cjs +3 -3
- package/dist/tabs/index.js +2 -2
- package/dist/tag-input/index.cjs +6 -6
- package/dist/tag-input/index.js +5 -5
- package/dist/textarea/index.cjs +4 -4
- package/dist/textarea/index.js +3 -3
- package/dist/theme-editor/index.cjs +11 -11
- package/dist/theme-editor/index.js +10 -10
- package/dist/theme-provider/index.cjs +3 -3
- package/dist/theme-provider/index.js +2 -2
- package/dist/toolbar/index.cjs +8 -8
- package/dist/toolbar/index.js +7 -7
- package/dist/tooltip/index.cjs +3 -2
- package/dist/tooltip/index.cjs.map +1 -1
- package/dist/tooltip/index.js +2 -1
- package/dist/virtual-list/index.cjs +2 -2
- package/dist/virtual-list/index.js +1 -1
- package/package.json +7 -2
- package/dist/chunk-25PSIDPC.js.map +0 -1
- package/dist/chunk-27ZWUEPE.js.map +0 -1
- package/dist/chunk-3E7TKBQI.cjs.map +0 -1
- package/dist/chunk-3EPND5M2.cjs.map +0 -1
- package/dist/chunk-3EUTXQDB.js.map +0 -1
- package/dist/chunk-55J4LEKT.cjs.map +0 -1
- package/dist/chunk-5HUZYY54.js.map +0 -1
- package/dist/chunk-5WH3JMR5.js.map +0 -1
- package/dist/chunk-ACNTH2OQ.cjs.map +0 -1
- package/dist/chunk-AOGNTG3W.cjs.map +0 -1
- package/dist/chunk-CJGFRPAS.cjs.map +0 -1
- package/dist/chunk-CUJBNRJJ.cjs +0 -38
- package/dist/chunk-CUJBNRJJ.cjs.map +0 -1
- package/dist/chunk-E6SE552P.cjs.map +0 -1
- package/dist/chunk-EAJHEYZM.js +0 -38
- package/dist/chunk-EAJHEYZM.js.map +0 -1
- package/dist/chunk-FKXKBJUI.cjs.map +0 -1
- package/dist/chunk-FXZJL2AM.cjs.map +0 -1
- package/dist/chunk-GG5YGESX.js.map +0 -1
- package/dist/chunk-GPRJYPIG.js.map +0 -1
- package/dist/chunk-H6CWTMAH.js.map +0 -1
- package/dist/chunk-I5XR2V4A.cjs.map +0 -1
- package/dist/chunk-IJ4DXG7D.cjs.map +0 -1
- package/dist/chunk-ITXDSQT2.js.map +0 -1
- package/dist/chunk-J5RMLZ4P.cjs.map +0 -1
- package/dist/chunk-JRLUZDKA.js.map +0 -1
- package/dist/chunk-K7T25Y3A.cjs.map +0 -1
- package/dist/chunk-L5Q6UDY3.js +0 -69
- package/dist/chunk-L5Q6UDY3.js.map +0 -1
- package/dist/chunk-M7PF4FKQ.js.map +0 -1
- package/dist/chunk-MCWTQT3M.cjs.map +0 -1
- package/dist/chunk-MHZIBV2C.js.map +0 -1
- package/dist/chunk-NEYLNRVG.cjs.map +0 -1
- package/dist/chunk-O3LG7B2J.cjs.map +0 -1
- package/dist/chunk-OAK7HFOJ.js.map +0 -1
- package/dist/chunk-OAPOGYA5.cjs.map +0 -1
- package/dist/chunk-OJDYTCMO.cjs.map +0 -1
- package/dist/chunk-PY2376XI.js.map +0 -1
- package/dist/chunk-Q5KQVBQP.cjs.map +0 -1
- package/dist/chunk-QQIALSTX.cjs.map +0 -1
- package/dist/chunk-R4M6SNUU.cjs.map +0 -1
- package/dist/chunk-RYOQYYDO.js.map +0 -1
- package/dist/chunk-S4S4LB63.js.map +0 -1
- package/dist/chunk-SXGCRHBZ.js.map +0 -1
- package/dist/chunk-T2KB3DJJ.js.map +0 -1
- package/dist/chunk-TEVZHN5I.cjs.map +0 -1
- package/dist/chunk-TQGO5WK6.js.map +0 -1
- package/dist/chunk-WWYJNWJY.cjs +0 -69
- package/dist/chunk-WWYJNWJY.cjs.map +0 -1
- package/dist/chunk-YV57RSPD.js.map +0 -1
- package/dist/chunk-Z5U4NJTW.cjs.map +0 -1
- package/dist/chunk-ZNWIHUHZ.cjs.map +0 -1
- package/dist/chunk-ZWAFL2B3.cjs.map +0 -1
- package/dist/chunk-ZXJU3TQD.cjs.map +0 -1
- /package/dist/{chunk-6CPCNQKV.js.map → chunk-67EXHO4T.js.map} +0 -0
- /package/dist/{chunk-GDQWSCJQ.js.map → chunk-BBU7QG7L.js.map} +0 -0
- /package/dist/{chunk-O2AACVSF.js.map → chunk-BO4LTIJI.js.map} +0 -0
- /package/dist/{chunk-BNVQ34FD.js.map → chunk-CP335H4Q.js.map} +0 -0
- /package/dist/{chunk-OU7DCYIA.js.map → chunk-DMOZURHV.js.map} +0 -0
- /package/dist/{chunk-L5W2XC6M.js.map → chunk-EWTMX4DK.js.map} +0 -0
- /package/dist/{chunk-E7FGY7BJ.js.map → chunk-FPLX5FIK.js.map} +0 -0
- /package/dist/{chunk-KQJ3IAFT.js.map → chunk-PG2EII6U.js.map} +0 -0
- /package/dist/{chunk-WKDT2XFQ.js.map → chunk-SFRILDXY.js.map} +0 -0
- /package/dist/{chunk-VPRUHE35.js.map → chunk-VRNCXYEP.js.map} +0 -0
- /package/dist/{chunk-L23WWO3D.js.map → chunk-X6UKU3C3.js.map} +0 -0
- /package/dist/{chunk-2QEKSRVX.js.map → chunk-YIBPOLNN.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-ND526PMG.cjs","../src/components/Header/Header.component.tsx","../src/components/Header/IdentityHeader.component.tsx"],"names":["jsx"],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;ACdA,8BAAiF;AAsFzE,+CAAA;AA/ED,IAAM,OAAA,EAAgC,CAAC;AAAA,EAC5C,KAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA,EAAU,SAAA;AAAA,EACV,OAAA;AAAA,EACA,qBAAA,EAAuB,GAAA;AAAA,EACvB,4BAAA,EAA8B,CAAA;AAAA,EAC9B,UAAA,EAAY;AACd,CAAA,EAAA,GAAM;AACJ,EAAA,MAAM,QAAA,EAAU,CAAC,cAAA,EAAgB,CAAA,cAAA,EAAiB,OAAO,CAAA,CAAA;AAEd,EAAA;AACG,EAAA;AACQ,EAAA;AASpB,EAAA;AACR,IAAA;AACV,IAAA;AACuC,IAAA;AAC9B,EAAA;AAQH,EAAA;AACO,IAAA;AACR,MAAA;AACjB,MAAA;AACF,IAAA;AAEQ,IAAA;AAEgB,IAAA;AAC+B,IAAA;AAEZ,IAAA;AACnB,IAAA;AACS,IAAA;AACD,EAAA;AAEH,EAAA;AAC0B,EAAA;AACC,EAAA;AAIA,EAAA;AACL,EAAA;AAIrC,EAAA;AACyC,IAAA;AAC3B,EAAA;AAE6C,EAAA;AACZ,IAAA;AACrC,sBAAA;AACD,MAAA;AACvB,IAAA;AAKuD,IAAA;AAClB,MAAA;AAEE,MAAA;AAIvC,IAAA;AAE4C,IAAA;AAC9C,EAAA;AAGiB,EAAA;AACC,oBAAA;AACe,IAAA;AAEtB,IAAA;AAGqC,MAAA;AACf,MAAA;AACL,sBAAA;AAGtB,IAAA;AAEJ,EAAA;AAIyB,EAAA;AASJ,IAAA;AACE,sBAAA;AACd,MAAA;AAGH,IAAA;AAEa,oBAAA;AAEX,MAAA;AAAC,QAAA;AAAA,QAAA;AACS,UAAA;AACM,UAAA;AACJ,UAAA;AACA,UAAA;AAGA,UAAA;AACV,UAAA;AAAA;AAAA;AAGE,4BAAA;AAAC,cAAA;AAAA,cAAA;AACM,gBAAA;AACA,gBAAA;AACG,gBAAA;AACF,gBAAA;AAC+B,gBAAA;AAAA,cAAA;AACvC,YAAA;AAAA,UAAA;AAGa,UAAA;AAEf,QAAA;AACF,MAAA;AAGA,MAAA;AAEJ,IAAA;AAEJ,EAAA;AAEJ;ADpD4D;AACA;AEnFjB;AAdmB;AAC5D,EAAA;AACA,EAAA;AACU,EAAA;AACE,EAAA;AACT,EAAA;AACC;AACiB,EAAA;AACnB,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEyCA,EAAAA;AAGvCA,EAAAA;AAAC,IAAA;AAAA,IAAA;AACK,MAAA;AACJ,MAAA;AACO,MAAA;AACyC,MAAA;AAAwB,IAAA;AAC1E,EAAA;AAEJ;AFmG4D;AACA;AACA;AACA;AACA","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-ND526PMG.cjs","sourcesContent":[null,"import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type { HeaderActionProps, HeaderProps } from './Header.types';\nimport './Header.scss';\nimport { Button } from '../Button';\nimport { Popover } from '../Popover';\nimport { renderIcon } from '../../utils';\n\nexport const Header: React.FC<HeaderProps> = ({\n title,\n subtitle,\n media,\n meta,\n variant = 'default',\n actions,\n collapseActionsBelow = 640,\n actionsVisibleWhenCollapsed = 1,\n className = '',\n}) => {\n const classes = ['eidos-header', `eidos-header--${variant}`, className].filter(Boolean).join(' ');\n\n const rootRef = useRef<HTMLDivElement>(null);\n const [isNarrow, setIsNarrow] = useState(false);\n const [overflowOpen, setOverflowOpen] = useState(false);\n\n // Measures the header's own box rather than the viewport, so the collapse\n // point matches the container queries in Header.scss - a viewport query\n // would disagree with them by the width of PageLayout's navigation rail,\n // which is itself collapsible. The 1px hero border makes this border-box\n // reading differ from the container query's content-box one by 2px; that is\n // far below any threshold worth caring about, and using one measurement for\n // both the initial pass and the observer keeps it to a single code path.\n const measure = useCallback(() => {\n const element = rootRef.current;\n if (!element) return;\n setIsNarrow(element.getBoundingClientRect().width <= collapseActionsBelow);\n }, [collapseActionsBelow]);\n\n // `useLayoutEffect` for the first measurement specifically: ResizeObserver\n // delivers its initial callback asynchronously, so relying on it alone\n // would paint the full action row for a frame before collapsing it. Reading\n // the box synchronously here collapses before paint instead. Nothing runs\n // during SSR, so the server renders the uncollapsed row and hydration\n // matches - the same client-only posture Navigation's `collapseBelow` has.\n useLayoutEffect(() => {\n if (!collapseActionsBelow) {\n setIsNarrow(false);\n return;\n }\n\n measure();\n\n const element = rootRef.current;\n if (!element || typeof ResizeObserver === 'undefined') return;\n\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, [collapseActionsBelow, measure]);\n\n const allActions = actions ?? [];\n const keptCount = Math.max(0, actionsVisibleWhenCollapsed);\n const isCollapsed = isNarrow && !!collapseActionsBelow && allActions.length > keptCount;\n\n // The *last* actions stay inline: a primary call to action is conventionally\n // rightmost, which is also how Header's own stories order them.\n const overflowActions = isCollapsed ? allActions.slice(0, allActions.length - keptCount) : [];\n const inlineActions = isCollapsed ? allActions.slice(allActions.length - keptCount) : allActions;\n\n // A portalled popover would otherwise stay on screen after the header grows\n // back past the threshold and its trigger stops being rendered.\n useEffect(() => {\n if (!isCollapsed && overflowOpen) setOverflowOpen(false);\n }, [isCollapsed, overflowOpen]);\n\n const renderOverflowAction = (action: HeaderActionProps, index: number) => {\n const handleClick: HeaderActionProps['onClick'] = (event) => {\n action.onClick?.(event);\n setOverflowOpen(false);\n };\n\n // An icon-only action has no visible label, which is fine in a horizontal\n // row of small buttons and useless in a vertical list. Its tooltip is\n // already the label, so promote it rather than showing a bare glyph.\n if (action.icon && !action.children && action.tooltip) {\n const { icon, tooltip, ...rest } = action;\n return (\n <Button key={index} {...rest} size=\"md\" preIcon={icon} onClick={handleClick}>\n {tooltip}\n </Button>\n );\n }\n\n return <Button key={index} {...action} size=\"md\" onClick={handleClick} />;\n };\n\n const titles = (\n <div className=\"eidos-header__titles\">\n <h1 className=\"eidos-header__title\">{title}</h1>\n {subtitle && <span className=\"eidos-header__subtitle\">{subtitle}</span>}\n {!!meta?.length && (\n <div className=\"eidos-header__meta\">\n {meta.map((item, index) => (\n <span key={index} className=\"eidos-header__meta-item\">\n {item.icon && renderIcon(item.icon, 'eidos-header__meta-icon')}\n {item.label && <span className=\"eidos-header__meta-label\">{item.label}</span>}\n <span className=\"eidos-header__meta-value\">{item.value}</span>\n </span>\n ))}\n </div>\n )}\n </div>\n );\n\n return (\n <div className={classes} ref={rootRef}>\n <div className=\"eidos-header__content\">\n {/* The `__lead` wrapper only exists when there is media to group with\n the titles. Media and titles need a tighter gap than titles and\n actions do, and a flex container has a single gap - but rendering\n the wrapper unconditionally would change the markup for every\n existing consumer, and the `eidos-*` class names are documented as\n targetable. */}\n {media ? (\n <div className=\"eidos-header__lead\">\n <div className=\"eidos-header__media\">{media}</div>\n {titles}\n </div>\n ) : (\n titles\n )}\n <div className=\"eidos-header__actions\">\n {overflowActions.length > 0 && (\n <Popover\n isOpen={overflowOpen}\n onOpenChange={setOverflowOpen}\n placement=\"bottom\"\n maxWidth={260}\n // Popover puts `className` on its trigger wrapper, which is the\n // element that actually becomes a flex item of `__actions`.\n className=\"eidos-header__overflow-trigger\"\n trigger={\n // No `aria-haspopup`/`aria-expanded` here: Popover already\n // sets both on the wrapper it renders around this button.\n <Button\n icon=\"ellipsis\"\n size=\"md\"\n variant=\"outlined\"\n color=\"secondary\"\n aria-label={`${overflowActions.length} more actions`}\n />\n }\n >\n <div className=\"eidos-header__overflow-actions\">\n {overflowActions.map(renderOverflowAction)}\n </div>\n </Popover>\n )}\n {inlineActions.map((action, index) => (\n <Button key={index} size=\"md\" {...action} />\n ))}\n </div>\n </div>\n </div>\n );\n};\n","import React from 'react';\nimport type { IdentityHeaderProps } from './IdentityHeader.types';\nimport { Header } from './Header.component';\nimport { Avatar } from '../Avatar';\nimport { devWarn } from '../../utils';\n\n/**\n * `Header` preconfigured for the \"this page is about one person or entity\"\n * shape - an avatar beside the name, supporting facts below it, actions on\n * the end - which account and profile pages share. Settings-style pages want\n * a plain `Header` instead; they have no identity to lead with.\n *\n * Deliberately thin: it pins `variant=\"hero\"`, types the avatar so consumers\n * don't have to import `Avatar` to fill the slot, and gives the shape a name\n * worth discovering. Everything else is `Header`.\n */\nexport const IdentityHeader: React.FC<IdentityHeaderProps> = ({\n avatar,\n media,\n variant = 'hero',\n className = '',\n ...rest\n}) => {\n if (avatar && media) {\n devWarn(\n 'identity-header-avatar-and-media',\n 'IdentityHeader received both `avatar` and `media`. `media` takes precedence - drop `avatar` to silence this.',\n );\n }\n\n const resolvedMedia = media ?? (avatar ? <Avatar size=\"lg\" {...avatar} /> : undefined);\n\n return (\n <Header\n {...rest}\n variant={variant}\n media={resolvedMedia}\n className={['eidos-identity-header', className].filter(Boolean).join(' ')}\n />\n );\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-XMXL6NVJ.cjs","../src/components/Header/Header.component.tsx","../src/components/Header/IdentityHeader.component.tsx"],"names":["jsx"],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACA;ACdA,8BAAiF;AAsFzE,+CAAA;AA/ED,IAAM,OAAA,EAAgC,CAAC;AAAA,EAC5C,KAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA,EAAU,SAAA;AAAA,EACV,OAAA;AAAA,EACA,qBAAA,EAAuB,GAAA;AAAA,EACvB,4BAAA,EAA8B,CAAA;AAAA,EAC9B,UAAA,EAAY;AACd,CAAA,EAAA,GAAM;AACJ,EAAA,MAAM,QAAA,EAAU,CAAC,cAAA,EAAgB,CAAA,cAAA,EAAiB,OAAO,CAAA,CAAA;AAEd,EAAA;AACG,EAAA;AACQ,EAAA;AASpB,EAAA;AACR,IAAA;AACV,IAAA;AACuC,IAAA;AAC9B,EAAA;AAQH,EAAA;AACO,IAAA;AACR,MAAA;AACjB,MAAA;AACF,IAAA;AAEQ,IAAA;AAEgB,IAAA;AAC+B,IAAA;AAEZ,IAAA;AACnB,IAAA;AACS,IAAA;AACD,EAAA;AAEH,EAAA;AAC0B,EAAA;AACC,EAAA;AAIA,EAAA;AACL,EAAA;AAIrC,EAAA;AACyC,IAAA;AAC3B,EAAA;AAE6C,EAAA;AACZ,IAAA;AACrC,sBAAA;AACD,MAAA;AACvB,IAAA;AAKuD,IAAA;AAClB,MAAA;AAEE,MAAA;AAIvC,IAAA;AAE4C,IAAA;AAC9C,EAAA;AAGiB,EAAA;AACC,oBAAA;AACe,IAAA;AAEtB,IAAA;AAGqC,MAAA;AACf,MAAA;AACL,sBAAA;AAGtB,IAAA;AAEJ,EAAA;AAIyB,EAAA;AASJ,IAAA;AACE,sBAAA;AACd,MAAA;AAGH,IAAA;AAEa,oBAAA;AAEX,MAAA;AAAC,QAAA;AAAA,QAAA;AACS,UAAA;AACM,UAAA;AACJ,UAAA;AACA,UAAA;AAGA,UAAA;AACV,UAAA;AAAA;AAAA;AAGE,4BAAA;AAAC,cAAA;AAAA,cAAA;AACM,gBAAA;AACA,gBAAA;AACG,gBAAA;AACF,gBAAA;AAC+B,gBAAA;AAAA,cAAA;AACvC,YAAA;AAAA,UAAA;AAGa,UAAA;AAEf,QAAA;AACF,MAAA;AAGA,MAAA;AAEJ,IAAA;AAEJ,EAAA;AAEJ;ADpD4D;AACA;AEnFjB;AAdmB;AAC5D,EAAA;AACA,EAAA;AACU,EAAA;AACE,EAAA;AACT,EAAA;AACC;AACiB,EAAA;AACnB,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEyCA,EAAAA;AAGvCA,EAAAA;AAAC,IAAA;AAAA,IAAA;AACK,MAAA;AACJ,MAAA;AACO,MAAA;AACyC,MAAA;AAAwB,IAAA;AAC1E,EAAA;AAEJ;AFmG4D;AACA;AACA;AACA;AACA","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-XMXL6NVJ.cjs","sourcesContent":[null,"import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type { HeaderActionProps, HeaderProps } from './Header.types';\nimport './Header.scss';\nimport { Button } from '../Button';\nimport { Popover } from '../Popover';\nimport { renderIcon } from '../../utils';\n\nexport const Header: React.FC<HeaderProps> = ({\n title,\n subtitle,\n media,\n meta,\n variant = 'default',\n actions,\n collapseActionsBelow = 640,\n actionsVisibleWhenCollapsed = 1,\n className = '',\n}) => {\n const classes = ['eidos-header', `eidos-header--${variant}`, className].filter(Boolean).join(' ');\n\n const rootRef = useRef<HTMLDivElement>(null);\n const [isNarrow, setIsNarrow] = useState(false);\n const [overflowOpen, setOverflowOpen] = useState(false);\n\n // Measures the header's own box rather than the viewport, so the collapse\n // point matches the container queries in Header.scss - a viewport query\n // would disagree with them by the width of PageLayout's navigation rail,\n // which is itself collapsible. The 1px hero border makes this border-box\n // reading differ from the container query's content-box one by 2px; that is\n // far below any threshold worth caring about, and using one measurement for\n // both the initial pass and the observer keeps it to a single code path.\n const measure = useCallback(() => {\n const element = rootRef.current;\n if (!element) return;\n setIsNarrow(element.getBoundingClientRect().width <= collapseActionsBelow);\n }, [collapseActionsBelow]);\n\n // `useLayoutEffect` for the first measurement specifically: ResizeObserver\n // delivers its initial callback asynchronously, so relying on it alone\n // would paint the full action row for a frame before collapsing it. Reading\n // the box synchronously here collapses before paint instead. Nothing runs\n // during SSR, so the server renders the uncollapsed row and hydration\n // matches - the same client-only posture Navigation's `collapseBelow` has.\n useLayoutEffect(() => {\n if (!collapseActionsBelow) {\n setIsNarrow(false);\n return;\n }\n\n measure();\n\n const element = rootRef.current;\n if (!element || typeof ResizeObserver === 'undefined') return;\n\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, [collapseActionsBelow, measure]);\n\n const allActions = actions ?? [];\n const keptCount = Math.max(0, actionsVisibleWhenCollapsed);\n const isCollapsed = isNarrow && !!collapseActionsBelow && allActions.length > keptCount;\n\n // The *last* actions stay inline: a primary call to action is conventionally\n // rightmost, which is also how Header's own stories order them.\n const overflowActions = isCollapsed ? allActions.slice(0, allActions.length - keptCount) : [];\n const inlineActions = isCollapsed ? allActions.slice(allActions.length - keptCount) : allActions;\n\n // A portalled popover would otherwise stay on screen after the header grows\n // back past the threshold and its trigger stops being rendered.\n useEffect(() => {\n if (!isCollapsed && overflowOpen) setOverflowOpen(false);\n }, [isCollapsed, overflowOpen]);\n\n const renderOverflowAction = (action: HeaderActionProps, index: number) => {\n const handleClick: HeaderActionProps['onClick'] = (event) => {\n action.onClick?.(event);\n setOverflowOpen(false);\n };\n\n // An icon-only action has no visible label, which is fine in a horizontal\n // row of small buttons and useless in a vertical list. Its tooltip is\n // already the label, so promote it rather than showing a bare glyph.\n if (action.icon && !action.children && action.tooltip) {\n const { icon, tooltip, ...rest } = action;\n return (\n <Button key={index} {...rest} size=\"md\" preIcon={icon} onClick={handleClick}>\n {tooltip}\n </Button>\n );\n }\n\n return <Button key={index} {...action} size=\"md\" onClick={handleClick} />;\n };\n\n const titles = (\n <div className=\"eidos-header__titles\">\n <h1 className=\"eidos-header__title\">{title}</h1>\n {subtitle && <span className=\"eidos-header__subtitle\">{subtitle}</span>}\n {!!meta?.length && (\n <div className=\"eidos-header__meta\">\n {meta.map((item, index) => (\n <span key={index} className=\"eidos-header__meta-item\">\n {item.icon && renderIcon(item.icon, 'eidos-header__meta-icon')}\n {item.label && <span className=\"eidos-header__meta-label\">{item.label}</span>}\n <span className=\"eidos-header__meta-value\">{item.value}</span>\n </span>\n ))}\n </div>\n )}\n </div>\n );\n\n return (\n <div className={classes} ref={rootRef}>\n <div className=\"eidos-header__content\">\n {/* The `__lead` wrapper only exists when there is media to group with\n the titles. Media and titles need a tighter gap than titles and\n actions do, and a flex container has a single gap - but rendering\n the wrapper unconditionally would change the markup for every\n existing consumer, and the `eidos-*` class names are documented as\n targetable. */}\n {media ? (\n <div className=\"eidos-header__lead\">\n <div className=\"eidos-header__media\">{media}</div>\n {titles}\n </div>\n ) : (\n titles\n )}\n <div className=\"eidos-header__actions\">\n {overflowActions.length > 0 && (\n <Popover\n isOpen={overflowOpen}\n onOpenChange={setOverflowOpen}\n placement=\"bottom\"\n maxWidth={260}\n // Popover puts `className` on its trigger wrapper, which is the\n // element that actually becomes a flex item of `__actions`.\n className=\"eidos-header__overflow-trigger\"\n trigger={\n // No `aria-haspopup`/`aria-expanded` here: Popover already\n // sets both on the wrapper it renders around this button.\n <Button\n icon=\"ellipsis\"\n size=\"md\"\n variant=\"outlined\"\n color=\"secondary\"\n aria-label={`${overflowActions.length} more actions`}\n />\n }\n >\n <div className=\"eidos-header__overflow-actions\">\n {overflowActions.map(renderOverflowAction)}\n </div>\n </Popover>\n )}\n {inlineActions.map((action, index) => (\n <Button key={index} size=\"md\" {...action} />\n ))}\n </div>\n </div>\n </div>\n );\n};\n","import React from 'react';\nimport type { IdentityHeaderProps } from './IdentityHeader.types';\nimport { Header } from './Header.component';\nimport { Avatar } from '../Avatar';\nimport { devWarn } from '../../utils';\n\n/**\n * `Header` preconfigured for the \"this page is about one person or entity\"\n * shape - an avatar beside the name, supporting facts below it, actions on\n * the end - which account and profile pages share. Settings-style pages want\n * a plain `Header` instead; they have no identity to lead with.\n *\n * Deliberately thin: it pins `variant=\"hero\"`, types the avatar so consumers\n * don't have to import `Avatar` to fill the slot, and gives the shape a name\n * worth discovering. Everything else is `Header`.\n */\nexport const IdentityHeader: React.FC<IdentityHeaderProps> = ({\n avatar,\n media,\n variant = 'hero',\n className = '',\n ...rest\n}) => {\n if (avatar && media) {\n devWarn(\n 'identity-header-avatar-and-media',\n 'IdentityHeader received both `avatar` and `media`. `media` takes precedence - drop `avatar` to silence this.',\n );\n }\n\n const resolvedMedia = media ?? (avatar ? <Avatar size=\"lg\" {...avatar} /> : undefined);\n\n return (\n <Header\n {...rest}\n variant={variant}\n media={resolvedMedia}\n className={['eidos-identity-header', className].filter(Boolean).join(' ')}\n />\n );\n};\n"]}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FOCUSABLE_SELECTOR
|
|
3
|
+
} from "./chunk-WT4WBHBR.js";
|
|
4
|
+
|
|
1
5
|
// src/components/Tooltip/Tooltip.component.tsx
|
|
2
6
|
import { useState, useRef, useCallback, useEffect } from "react";
|
|
3
7
|
import { createPortal } from "react-dom";
|
|
4
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var HOVER_GRACE_MS = 150;
|
|
5
10
|
var Tooltip = ({
|
|
6
11
|
children,
|
|
7
12
|
message,
|
|
@@ -92,12 +97,13 @@ var Tooltip = ({
|
|
|
92
97
|
}, [tooltipState.isVisible, tooltipState.isPositioned, calculateOptimalPosition]);
|
|
93
98
|
const handleMouseEnter = useCallback(() => {
|
|
94
99
|
if (disabled || triggerType !== "hover") return;
|
|
100
|
+
if (timeoutRef.current) {
|
|
101
|
+
clearTimeout(timeoutRef.current);
|
|
102
|
+
}
|
|
95
103
|
timeoutRef.current = setTimeout(() => {
|
|
96
|
-
setTooltipState(
|
|
97
|
-
...prev,
|
|
98
|
-
|
|
99
|
-
isPositioned: false
|
|
100
|
-
}));
|
|
104
|
+
setTooltipState(
|
|
105
|
+
(prev) => prev.isVisible ? prev : { ...prev, isVisible: true, isPositioned: false }
|
|
106
|
+
);
|
|
101
107
|
}, delay);
|
|
102
108
|
}, [disabled, delay, triggerType]);
|
|
103
109
|
const handleMouseLeave = useCallback(() => {
|
|
@@ -105,11 +111,13 @@ var Tooltip = ({
|
|
|
105
111
|
if (timeoutRef.current) {
|
|
106
112
|
clearTimeout(timeoutRef.current);
|
|
107
113
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
timeoutRef.current = setTimeout(() => {
|
|
115
|
+
setTooltipState((prev) => ({
|
|
116
|
+
...prev,
|
|
117
|
+
isVisible: false,
|
|
118
|
+
isPositioned: false
|
|
119
|
+
}));
|
|
120
|
+
}, HOVER_GRACE_MS);
|
|
113
121
|
}, [triggerType]);
|
|
114
122
|
const handleClick = useCallback(() => {
|
|
115
123
|
if (disabled || triggerType !== "click") return;
|
|
@@ -175,7 +183,7 @@ var Tooltip = ({
|
|
|
175
183
|
);
|
|
176
184
|
const handleEscapeKey = useCallback(
|
|
177
185
|
(event) => {
|
|
178
|
-
if (closeOnEscape && event.key === "Escape" && tooltipState.isVisible
|
|
186
|
+
if (closeOnEscape && event.key === "Escape" && tooltipState.isVisible) {
|
|
179
187
|
setTooltipState((prev) => ({
|
|
180
188
|
...prev,
|
|
181
189
|
isVisible: false,
|
|
@@ -183,7 +191,7 @@ var Tooltip = ({
|
|
|
183
191
|
}));
|
|
184
192
|
}
|
|
185
193
|
},
|
|
186
|
-
[closeOnEscape, tooltipState.isVisible
|
|
194
|
+
[closeOnEscape, tooltipState.isVisible]
|
|
187
195
|
);
|
|
188
196
|
useEffect(() => {
|
|
189
197
|
if (tooltipState.isVisible && !tooltipState.isPositioned && tooltipRef.current && triggerRef.current) {
|
|
@@ -225,13 +233,19 @@ var Tooltip = ({
|
|
|
225
233
|
useEffect(() => {
|
|
226
234
|
if (tooltipState.isVisible && triggerType === "click") {
|
|
227
235
|
document.addEventListener("mousedown", handleClickOutside);
|
|
228
|
-
document.addEventListener("keydown", handleEscapeKey);
|
|
229
236
|
}
|
|
230
237
|
return () => {
|
|
231
238
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
239
|
+
};
|
|
240
|
+
}, [tooltipState.isVisible, triggerType, handleClickOutside]);
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
if (tooltipState.isVisible) {
|
|
243
|
+
document.addEventListener("keydown", handleEscapeKey);
|
|
244
|
+
}
|
|
245
|
+
return () => {
|
|
232
246
|
document.removeEventListener("keydown", handleEscapeKey);
|
|
233
247
|
};
|
|
234
|
-
}, [tooltipState.isVisible,
|
|
248
|
+
}, [tooltipState.isVisible, handleEscapeKey]);
|
|
235
249
|
useEffect(() => {
|
|
236
250
|
return () => {
|
|
237
251
|
if (timeoutRef.current) {
|
|
@@ -251,6 +265,11 @@ var Tooltip = ({
|
|
|
251
265
|
}
|
|
252
266
|
return null;
|
|
253
267
|
};
|
|
268
|
+
const [triggerNeedsFocus, setTriggerNeedsFocus] = useState(false);
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
const el = triggerRef.current;
|
|
271
|
+
setTriggerNeedsFocus(!!el && el.querySelector(FOCUSABLE_SELECTOR) === null);
|
|
272
|
+
}, [children, triggerType]);
|
|
254
273
|
const getTriggerProps = () => {
|
|
255
274
|
const props = {
|
|
256
275
|
ref: triggerRef,
|
|
@@ -263,13 +282,21 @@ var Tooltip = ({
|
|
|
263
282
|
break;
|
|
264
283
|
case "click":
|
|
265
284
|
props.onClick = handleClick;
|
|
266
|
-
|
|
267
|
-
|
|
285
|
+
if (triggerNeedsFocus) {
|
|
286
|
+
props.role = "button";
|
|
287
|
+
props.tabIndex = 0;
|
|
288
|
+
props.onKeyDown = (event) => {
|
|
289
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
290
|
+
event.preventDefault();
|
|
291
|
+
handleClick();
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
268
295
|
break;
|
|
269
296
|
case "focus":
|
|
270
297
|
props.onFocus = handleFocus;
|
|
271
298
|
props.onBlur = handleBlur;
|
|
272
|
-
props.tabIndex = 0;
|
|
299
|
+
if (triggerNeedsFocus) props.tabIndex = 0;
|
|
273
300
|
break;
|
|
274
301
|
}
|
|
275
302
|
return props;
|
|
@@ -281,6 +308,8 @@ var Tooltip = ({
|
|
|
281
308
|
"div",
|
|
282
309
|
{
|
|
283
310
|
ref: tooltipRef,
|
|
311
|
+
onMouseEnter: handleMouseEnter,
|
|
312
|
+
onMouseLeave: handleMouseLeave,
|
|
284
313
|
className: `eidos-tooltip eidos-tooltip--${tooltipState.position.placement} ${className}`,
|
|
285
314
|
style: {
|
|
286
315
|
top: tooltipState.position.top,
|
|
@@ -300,4 +329,4 @@ Tooltip.displayName = "Tooltip";
|
|
|
300
329
|
export {
|
|
301
330
|
Tooltip
|
|
302
331
|
};
|
|
303
|
-
//# sourceMappingURL=chunk-
|
|
332
|
+
//# sourceMappingURL=chunk-XO7WTE6O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Tooltip/Tooltip.component.tsx"],"sourcesContent":["import React, { useState, useRef, useCallback, useEffect } from 'react';\nimport { createPortal } from 'react-dom';\nimport type { TooltipProps, TooltipState } from './Tooltip.types';\nimport { FOCUSABLE_SELECTOR } from '../../utils';\n\n/**\n * How long the tooltip stays open after the pointer leaves, so it can be\n * moved onto the panel itself (SC 1.4.13, \"hoverable\"). Long enough to cross\n * the gap, short enough not to feel stuck.\n */\nconst HOVER_GRACE_MS = 150;\n\nexport const Tooltip: React.FC<TooltipProps> = ({\n children,\n message,\n component: Component,\n componentProps,\n placement: preferredPlacement = 'top',\n delay = 100,\n disabled = false,\n className = '',\n triggerType = 'hover',\n closeOnClickOutside = true,\n closeOnEscape = true,\n}) => {\n const [tooltipState, setTooltipState] = useState<TooltipState>({\n isVisible: false,\n position: { top: 0, left: 0, placement: 'top' },\n isPositioned: false,\n });\n\n const triggerRef = useRef<HTMLDivElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const animationFrameRef = useRef<number | undefined>(undefined);\n const isScrollingRef = useRef(false);\n\n const calculateOptimalPosition = useCallback(\n (triggerRect: DOMRect, tooltipRect: DOMRect) => {\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const gap = 8;\n\n let placement: 'top' | 'bottom' | 'left' | 'right' = preferredPlacement;\n let top = 0;\n let left = 0;\n\n switch (preferredPlacement) {\n case 'top':\n top = triggerRect.top - tooltipRect.height - gap;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n break;\n case 'bottom':\n top = triggerRect.bottom + gap;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n break;\n case 'left':\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.left - tooltipRect.width - gap;\n break;\n case 'right':\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.right + gap;\n break;\n }\n\n if (placement === 'top' && top < gap) {\n placement = 'bottom';\n top = triggerRect.bottom + gap;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n } else if (placement === 'bottom' && top + tooltipRect.height > viewportHeight - gap) {\n placement = 'top';\n top = triggerRect.top - tooltipRect.height - gap;\n left = triggerRect.left + triggerRect.width / 2 - tooltipRect.width / 2;\n } else if (placement === 'left' && left < gap) {\n placement = 'right';\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.right + gap;\n } else if (placement === 'right' && left + tooltipRect.width > viewportWidth - gap) {\n placement = 'left';\n top = triggerRect.top + triggerRect.height / 2 - tooltipRect.height / 2;\n left = triggerRect.left - tooltipRect.width - gap;\n }\n\n top = Math.max(gap, Math.min(top, viewportHeight - tooltipRect.height - gap));\n left = Math.max(gap, Math.min(left, viewportWidth - tooltipRect.width - gap));\n\n return { top, left, placement };\n },\n [preferredPlacement],\n );\n\n const handleScroll = useCallback(() => {\n if (!tooltipState.isVisible || !tooltipState.isPositioned || isScrollingRef.current) return;\n\n isScrollingRef.current = true;\n\n animationFrameRef.current = requestAnimationFrame(() => {\n if (triggerRef.current && tooltipRef.current) {\n const triggerRect = triggerRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n\n const { top, left, placement } = calculateOptimalPosition(triggerRect, tooltipRect);\n\n setTooltipState((prev) => ({\n ...prev,\n position: { top, left, placement },\n }));\n }\n\n isScrollingRef.current = false;\n });\n }, [tooltipState.isVisible, tooltipState.isPositioned, calculateOptimalPosition]);\n\n const handleMouseEnter = useCallback(() => {\n if (disabled || triggerType !== 'hover') return;\n\n // Cancels a pending hide as well as scheduling the show. Both handlers\n // are bound to the trigger *and* to the panel, so this is what catches\n // the pointer as it crosses from one to the other.\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n timeoutRef.current = setTimeout(() => {\n // Left untouched when already open. Re-entering from the panel would\n // otherwise re-run the open transition and reposition a tooltip the\n // user is in the middle of reading.\n setTooltipState((prev) =>\n prev.isVisible ? prev : { ...prev, isVisible: true, isPositioned: false },\n );\n }, delay);\n }, [disabled, delay, triggerType]);\n\n const handleMouseLeave = useCallback(() => {\n if (triggerType !== 'hover') return;\n\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n\n // Closing is delayed, not immediate.\n //\n // SC 1.4.13 requires the pointer to be able to reach the tooltip's own\n // content. The panel is portaled and sits a few pixels off the trigger,\n // so an immediate hide made that gap impossible to cross - the tooltip\n // disappeared the moment the pointer left the trigger, however fast the\n // user moved. The grace period is what makes \"hoverable\" reachable in\n // practice.\n timeoutRef.current = setTimeout(() => {\n setTooltipState((prev) => ({\n ...prev,\n isVisible: false,\n isPositioned: false,\n }));\n }, HOVER_GRACE_MS);\n }, [triggerType]);\n\n const handleClick = useCallback(() => {\n if (disabled || triggerType !== 'click') return;\n\n if (tooltipState.isVisible) {\n setTooltipState((prev) => ({\n ...prev,\n isVisible: false,\n isPositioned: false,\n }));\n } else {\n if (delay > 0) {\n timeoutRef.current = setTimeout(() => {\n setTooltipState((prev) => ({\n ...prev,\n isVisible: true,\n isPositioned: false,\n }));\n }, delay);\n } else {\n setTooltipState((prev) => ({\n ...prev,\n isVisible: true,\n isPositioned: false,\n }));\n }\n }\n }, [disabled, triggerType, tooltipState.isVisible, delay]);\n\n const handleFocus = useCallback(() => {\n if (disabled || triggerType !== 'focus') return;\n\n timeoutRef.current = setTimeout(() => {\n setTooltipState((prev) => ({\n ...prev,\n isVisible: true,\n isPositioned: false,\n }));\n }, delay);\n }, [disabled, triggerType, delay]);\n\n const handleBlur = useCallback(() => {\n if (triggerType !== 'focus') return;\n\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n setTooltipState((prev) => ({\n ...prev,\n isVisible: false,\n isPositioned: false,\n }));\n }, [triggerType]);\n\n const handleClickOutside = useCallback(\n (event: MouseEvent) => {\n if (!closeOnClickOutside || !tooltipState.isVisible || triggerType !== 'click') return;\n\n const target = event.target as Node;\n\n if (tooltipRef.current?.contains(target) || triggerRef.current?.contains(target)) {\n return;\n }\n\n setTooltipState((prev) => ({\n ...prev,\n isVisible: false,\n isPositioned: false,\n }));\n },\n [closeOnClickOutside, tooltipState.isVisible, triggerType],\n );\n\n const handleEscapeKey = useCallback(\n (event: KeyboardEvent) => {\n // Every trigger type, not just `click`.\n //\n // SC 1.4.13 requires content shown on hover *or focus* to be\n // dismissible without moving the pointer or focus - and `hover` is the\n // default, so the one configuration that could be dismissed was the\n // one least likely to be used. A tooltip covering the text you are\n // trying to read had to be escaped by physically moving the mouse.\n if (closeOnEscape && event.key === 'Escape' && tooltipState.isVisible) {\n setTooltipState((prev) => ({\n ...prev,\n isVisible: false,\n isPositioned: false,\n }));\n }\n },\n [closeOnEscape, tooltipState.isVisible],\n );\n\n useEffect(() => {\n if (\n tooltipState.isVisible &&\n !tooltipState.isPositioned &&\n tooltipRef.current &&\n triggerRef.current\n ) {\n const triggerRect = triggerRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n\n const { top, left, placement } = calculateOptimalPosition(triggerRect, tooltipRect);\n\n setTooltipState((prev) => ({\n ...prev,\n position: { top, left, placement },\n isPositioned: true,\n }));\n }\n }, [tooltipState.isVisible, tooltipState.isPositioned, calculateOptimalPosition]);\n\n useEffect(() => {\n const handleResize = () => {\n if (\n tooltipState.isVisible &&\n tooltipState.isPositioned &&\n tooltipRef.current &&\n triggerRef.current\n ) {\n const triggerRect = triggerRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n\n const { top, left, placement } = calculateOptimalPosition(triggerRect, tooltipRect);\n\n setTooltipState((prev) => ({\n ...prev,\n position: { top, left, placement },\n }));\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [tooltipState.isVisible, tooltipState.isPositioned, calculateOptimalPosition]);\n\n useEffect(() => {\n if (tooltipState.isVisible) {\n window.addEventListener('scroll', handleScroll, { passive: true });\n\n document.body.addEventListener('scroll', handleScroll, { passive: true });\n }\n\n return () => {\n window.removeEventListener('scroll', handleScroll);\n document.body.removeEventListener('scroll', handleScroll);\n };\n }, [tooltipState.isVisible, handleScroll]);\n\n // Click-outside is only meaningful for a click-triggered tooltip; Escape\n // is not. Registering them together meant a hover tooltip had no way to be\n // dismissed from the keyboard at all (SC 1.4.13, \"dismissible\") - and\n // `hover` is the default, so the failing case was the common one.\n useEffect(() => {\n if (tooltipState.isVisible && triggerType === 'click') {\n document.addEventListener('mousedown', handleClickOutside);\n }\n\n return () => {\n document.removeEventListener('mousedown', handleClickOutside);\n };\n }, [tooltipState.isVisible, triggerType, handleClickOutside]);\n\n useEffect(() => {\n if (tooltipState.isVisible) {\n document.addEventListener('keydown', handleEscapeKey);\n }\n\n return () => {\n document.removeEventListener('keydown', handleEscapeKey);\n };\n }, [tooltipState.isVisible, handleEscapeKey]);\n\n useEffect(() => {\n return () => {\n if (timeoutRef.current) {\n clearTimeout(timeoutRef.current);\n }\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }, []);\n\n const renderTooltipContent = () => {\n if (message) {\n return <span className=\"eidos-tooltip-message\">{message}</span>;\n }\n if (Component) {\n return <Component {...(componentProps || {})} />;\n }\n return null;\n };\n\n // Whether the trigger wrapper has to become focusable itself, i.e. whether\n // the consumer's child is not already a control. Measured from the DOM\n // rather than guessed from the node type, because `children` is an\n // arbitrary `ReactNode` and the control may be nested inside it.\n const [triggerNeedsFocus, setTriggerNeedsFocus] = useState(false);\n\n useEffect(() => {\n const el = triggerRef.current;\n setTriggerNeedsFocus(!!el && el.querySelector(FOCUSABLE_SELECTOR) === null);\n }, [children, triggerType]);\n\n const getTriggerProps = () => {\n const props: Record<string, unknown> = {\n ref: triggerRef,\n className: 'eidos-tooltip-trigger',\n };\n\n switch (triggerType) {\n case 'hover':\n props.onMouseEnter = handleMouseEnter;\n props.onMouseLeave = handleMouseLeave;\n break;\n case 'click':\n props.onClick = handleClick;\n // Only when the trigger has no control of its own. Making this\n // wrapper a button unconditionally put a button inside a button\n // whenever the child was one - which is the common case, since\n // `Button` wraps *itself* in a Tooltip when given `tooltip`. That is\n // invalid (`nested-interactive`) and gives one action two tab stops.\n if (triggerNeedsFocus) {\n props.role = 'button';\n props.tabIndex = 0;\n props.onKeyDown = (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleClick();\n }\n };\n }\n break;\n case 'focus':\n props.onFocus = handleFocus;\n props.onBlur = handleBlur;\n // Same reasoning: a focusable child already receives the focus that\n // opens the tooltip, and adding `tabIndex` here would just add a\n // second stop in front of it.\n if (triggerNeedsFocus) props.tabIndex = 0;\n break;\n }\n\n return props;\n };\n\n return (\n <>\n <div {...getTriggerProps()}>{children}</div>\n {tooltipState.isVisible &&\n createPortal(\n <div\n ref={tooltipRef}\n // SC 1.4.13 \"hoverable\": the pointer has to be able to reach the\n // tooltip's own content - to read a long message, or select text\n // from it - without it vanishing. The panel used to be\n // `pointer-events: none`, so moving towards it dismissed it.\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n className={`eidos-tooltip eidos-tooltip--${tooltipState.position.placement} ${className}`}\n style={{\n top: tooltipState.position.top,\n left: tooltipState.position.left,\n }}\n data-state={tooltipState.isPositioned ? 'entered' : 'entering'}\n role=\"tooltip\"\n >\n {renderTooltipContent()}\n </div>,\n document.body,\n )}\n </>\n );\n};\n\nTooltip.displayName = 'Tooltip';\n"],"mappings":"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","names":[]}
|
|
@@ -135,7 +135,7 @@ function FileUpload({
|
|
|
135
135
|
onKeyDown: handleKeyDown,
|
|
136
136
|
role: "button",
|
|
137
137
|
tabIndex: disabled ? -1 : 0,
|
|
138
|
-
"aria-label": "
|
|
138
|
+
"aria-label": "Click to upload, or drag and drop",
|
|
139
139
|
"aria-disabled": disabled || void 0,
|
|
140
140
|
children: [
|
|
141
141
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "eidos-file-upload-icon", "aria-hidden": "true", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Upload, {}) }),
|
|
@@ -191,4 +191,4 @@ FileUpload.displayName = "FileUpload";
|
|
|
191
191
|
|
|
192
192
|
|
|
193
193
|
exports.FileUpload = FileUpload;
|
|
194
|
-
//# sourceMappingURL=chunk-
|
|
194
|
+
//# sourceMappingURL=chunk-XRVRGRC3.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-3CXULV4G.cjs","../src/components/FileUpload/FileUpload.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-3CXULV4G.cjs","sourcesContent":[null,"import React, { useState, useRef } from 'react';\nimport { Upload, X, CircleAlert, FileText } from 'lucide-react';\nimport type { FileUploadProps } from './FileUpload.types';\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction isFileTypeAccepted(file: File, acceptedTypes: string[]): boolean {\n return acceptedTypes.some((type) => {\n const trimmed = type.trim();\n // Extension match: \".pdf\", \".docx\"\n if (trimmed.startsWith('.')) {\n return file.name.toLowerCase().endsWith(trimmed.toLowerCase());\n }\n // Wildcard MIME group: \"image/*\", \"video/*\"\n if (trimmed.endsWith('/*')) {\n const baseType = trimmed.slice(0, -2);\n return file.type.startsWith(`${baseType}/`);\n }\n // Exact MIME type: \"application/pdf\"\n return file.type === trimmed;\n });\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function FileUpload({\n accept,\n multiple = false,\n maxSize,\n maxFiles,\n onFilesAccepted,\n onFilesRejected,\n hint,\n disabled = false,\n className = '',\n}: FileUploadProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const [isDragging, setIsDragging] = useState(false);\n const [files, setFiles] = useState<File[]>([]);\n const [error, setError] = useState<string | null>(null);\n\n // ── Validation ─────────────────────────────────────────────────────────────\n\n const validateAndAccept = (fileList: FileList | File[]) => {\n const incoming = Array.from(fileList);\n\n if (maxFiles !== undefined && incoming.length > maxFiles) {\n setError(`Maximum ${maxFiles} file${maxFiles === 1 ? '' : 's'} allowed`);\n onFilesRejected?.(incoming, 'count');\n return;\n }\n\n if (accept) {\n const acceptedTypes = accept.split(',').map((t) => t.trim());\n const invalid = incoming.filter((f) => !isFileTypeAccepted(f, acceptedTypes));\n if (invalid.length > 0) {\n setError('One or more files have an unsupported type');\n onFilesRejected?.(incoming, 'type');\n return;\n }\n }\n\n if (maxSize !== undefined) {\n const oversized = incoming.filter((f) => f.size > maxSize);\n if (oversized.length > 0) {\n setError(`File size exceeds the ${formatBytes(maxSize)} limit`);\n onFilesRejected?.(oversized, 'size');\n return;\n }\n }\n\n setError(null);\n setFiles(incoming);\n onFilesAccepted?.(incoming);\n };\n\n // ── Drag handlers ──────────────────────────────────────────────────────────\n\n const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n if (!disabled) setIsDragging(true);\n };\n\n const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n // Prevent default to allow the drop event to fire\n e.preventDefault();\n };\n\n const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n // Only clear the dragging state when leaving the zone entirely,\n // not when moving over a child element inside it.\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n setIsDragging(false);\n }\n };\n\n const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragging(false);\n if (disabled) return;\n const { files: dropped } = e.dataTransfer;\n if (dropped.length > 0) validateAndAccept(dropped);\n };\n\n // ── Click / keyboard ───────────────────────────────────────────────────────\n\n const handleZoneClick = () => {\n if (!disabled) inputRef.current?.click();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleZoneClick();\n }\n };\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (e.target.files && e.target.files.length > 0) {\n validateAndAccept(e.target.files);\n }\n // Reset the input value so the same file(s) can be re-selected\n e.target.value = '';\n };\n\n // ── File list management ───────────────────────────────────────────────────\n\n const handleRemoveFile = (index: number) => {\n const remaining = files.filter((_, i) => i !== index);\n setFiles(remaining);\n onFilesAccepted?.(remaining);\n };\n\n // ── CSS classes ────────────────────────────────────────────────────────────\n\n const rootClasses = ['eidos-file-upload', className].filter(Boolean).join(' ');\n\n const zoneClasses = [\n 'eidos-file-upload-zone',\n isDragging && 'eidos-file-upload-zone--dragging',\n disabled && 'eidos-file-upload-zone--disabled',\n ]\n .filter(Boolean)\n .join(' ');\n\n // Build the auto-generated hint from the constraints when no hint is provided\n const autoHint = [\n accept ? `Accepted: ${accept}` : null,\n maxSize ? `Max size: ${formatBytes(maxSize)}` : null,\n maxFiles && multiple ? `Up to ${maxFiles} files` : null,\n ]\n .filter(Boolean)\n .join(' · ');\n\n const displayHint = hint ?? (autoHint.length > 0 ? autoHint : null);\n\n return (\n <div\n className={rootClasses}\n role=\"region\"\n aria-label=\"File upload\"\n aria-disabled={disabled || undefined}\n >\n <div\n className={zoneClasses}\n onClick={handleZoneClick}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n onKeyDown={handleKeyDown}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-label=\"Upload files - click or drag and drop\"\n aria-disabled={disabled || undefined}\n >\n <div className=\"eidos-file-upload-icon\" aria-hidden=\"true\">\n <Upload />\n </div>\n\n <div className=\"eidos-file-upload-text\">\n <p className=\"eidos-file-upload-title\">\n <em>Click to upload</em>\n </p>\n <span style={{ fontWeight: 'normal', fontSize: '0.875rem' }}>or drag and drop</span>\n {displayHint && <p className=\"eidos-file-upload-hint\">{displayHint}</p>}\n </div>\n\n {/* Hidden native input - triggered programmatically via click() */}\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleFileChange}\n className=\"eidos-file-upload-input-hidden\"\n tabIndex={-1}\n aria-hidden=\"true\"\n />\n </div>\n\n {error && (\n <p className=\"eidos-file-upload-error\" role=\"alert\">\n <CircleAlert aria-hidden=\"true\" />\n {error}\n </p>\n )}\n\n {files.length > 0 && (\n <ul className=\"eidos-file-upload-list\" aria-label=\"Selected files\">\n {files.map((file, index) => (\n <li key={`${file.name}-${index}`} className=\"eidos-file-upload-item\">\n <div className=\"eidos-file-upload-item-info\">\n <FileText aria-hidden=\"true\" />\n <span className=\"eidos-file-upload-item-name\">{file.name}</span>\n <span className=\"eidos-file-upload-item-size\">{formatBytes(file.size)}</span>\n </div>\n <button\n type=\"button\"\n className=\"eidos-file-upload-item-remove\"\n onClick={() => handleRemoveFile(index)}\n aria-label={`Remove ${file.name}`}\n >\n <X />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nFileUpload.displayName = 'FileUpload';\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-XRVRGRC3.cjs","../src/components/FileUpload/FileUpload.component.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-XRVRGRC3.cjs","sourcesContent":[null,"import React, { useState, useRef } from 'react';\nimport { Upload, X, CircleAlert, FileText } from 'lucide-react';\nimport type { FileUploadProps } from './FileUpload.types';\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction isFileTypeAccepted(file: File, acceptedTypes: string[]): boolean {\n return acceptedTypes.some((type) => {\n const trimmed = type.trim();\n // Extension match: \".pdf\", \".docx\"\n if (trimmed.startsWith('.')) {\n return file.name.toLowerCase().endsWith(trimmed.toLowerCase());\n }\n // Wildcard MIME group: \"image/*\", \"video/*\"\n if (trimmed.endsWith('/*')) {\n const baseType = trimmed.slice(0, -2);\n return file.type.startsWith(`${baseType}/`);\n }\n // Exact MIME type: \"application/pdf\"\n return file.type === trimmed;\n });\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport function FileUpload({\n accept,\n multiple = false,\n maxSize,\n maxFiles,\n onFilesAccepted,\n onFilesRejected,\n hint,\n disabled = false,\n className = '',\n}: FileUploadProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const [isDragging, setIsDragging] = useState(false);\n const [files, setFiles] = useState<File[]>([]);\n const [error, setError] = useState<string | null>(null);\n\n // ── Validation ─────────────────────────────────────────────────────────────\n\n const validateAndAccept = (fileList: FileList | File[]) => {\n const incoming = Array.from(fileList);\n\n if (maxFiles !== undefined && incoming.length > maxFiles) {\n setError(`Maximum ${maxFiles} file${maxFiles === 1 ? '' : 's'} allowed`);\n onFilesRejected?.(incoming, 'count');\n return;\n }\n\n if (accept) {\n const acceptedTypes = accept.split(',').map((t) => t.trim());\n const invalid = incoming.filter((f) => !isFileTypeAccepted(f, acceptedTypes));\n if (invalid.length > 0) {\n setError('One or more files have an unsupported type');\n onFilesRejected?.(incoming, 'type');\n return;\n }\n }\n\n if (maxSize !== undefined) {\n const oversized = incoming.filter((f) => f.size > maxSize);\n if (oversized.length > 0) {\n setError(`File size exceeds the ${formatBytes(maxSize)} limit`);\n onFilesRejected?.(oversized, 'size');\n return;\n }\n }\n\n setError(null);\n setFiles(incoming);\n onFilesAccepted?.(incoming);\n };\n\n // ── Drag handlers ──────────────────────────────────────────────────────────\n\n const handleDragEnter = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n if (!disabled) setIsDragging(true);\n };\n\n const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {\n // Prevent default to allow the drop event to fire\n e.preventDefault();\n };\n\n const handleDragLeave = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n // Only clear the dragging state when leaving the zone entirely,\n // not when moving over a child element inside it.\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n setIsDragging(false);\n }\n };\n\n const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n setIsDragging(false);\n if (disabled) return;\n const { files: dropped } = e.dataTransfer;\n if (dropped.length > 0) validateAndAccept(dropped);\n };\n\n // ── Click / keyboard ───────────────────────────────────────────────────────\n\n const handleZoneClick = () => {\n if (!disabled) inputRef.current?.click();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n handleZoneClick();\n }\n };\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (e.target.files && e.target.files.length > 0) {\n validateAndAccept(e.target.files);\n }\n // Reset the input value so the same file(s) can be re-selected\n e.target.value = '';\n };\n\n // ── File list management ───────────────────────────────────────────────────\n\n const handleRemoveFile = (index: number) => {\n const remaining = files.filter((_, i) => i !== index);\n setFiles(remaining);\n onFilesAccepted?.(remaining);\n };\n\n // ── CSS classes ────────────────────────────────────────────────────────────\n\n const rootClasses = ['eidos-file-upload', className].filter(Boolean).join(' ');\n\n const zoneClasses = [\n 'eidos-file-upload-zone',\n isDragging && 'eidos-file-upload-zone--dragging',\n disabled && 'eidos-file-upload-zone--disabled',\n ]\n .filter(Boolean)\n .join(' ');\n\n // Build the auto-generated hint from the constraints when no hint is provided\n const autoHint = [\n accept ? `Accepted: ${accept}` : null,\n maxSize ? `Max size: ${formatBytes(maxSize)}` : null,\n maxFiles && multiple ? `Up to ${maxFiles} files` : null,\n ]\n .filter(Boolean)\n .join(' · ');\n\n const displayHint = hint ?? (autoHint.length > 0 ? autoHint : null);\n\n return (\n <div\n className={rootClasses}\n role=\"region\"\n aria-label=\"File upload\"\n aria-disabled={disabled || undefined}\n >\n <div\n className={zoneClasses}\n onClick={handleZoneClick}\n onDragEnter={handleDragEnter}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n onKeyDown={handleKeyDown}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n // Starts with the visible \"Click to upload\" so the name contains the\n // label a speech-input user can see (SC 2.5.3). The previous\n // \"Upload files - click or drag and drop\" paraphrased it instead.\n aria-label=\"Click to upload, or drag and drop\"\n aria-disabled={disabled || undefined}\n >\n <div className=\"eidos-file-upload-icon\" aria-hidden=\"true\">\n <Upload />\n </div>\n\n <div className=\"eidos-file-upload-text\">\n <p className=\"eidos-file-upload-title\">\n <em>Click to upload</em>\n </p>\n <span style={{ fontWeight: 'normal', fontSize: '0.875rem' }}>or drag and drop</span>\n {displayHint && <p className=\"eidos-file-upload-hint\">{displayHint}</p>}\n </div>\n\n {/* Hidden native input - triggered programmatically via click() */}\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n onChange={handleFileChange}\n className=\"eidos-file-upload-input-hidden\"\n tabIndex={-1}\n aria-hidden=\"true\"\n />\n </div>\n\n {error && (\n <p className=\"eidos-file-upload-error\" role=\"alert\">\n <CircleAlert aria-hidden=\"true\" />\n {error}\n </p>\n )}\n\n {files.length > 0 && (\n <ul className=\"eidos-file-upload-list\" aria-label=\"Selected files\">\n {files.map((file, index) => (\n <li key={`${file.name}-${index}`} className=\"eidos-file-upload-item\">\n <div className=\"eidos-file-upload-item-info\">\n <FileText aria-hidden=\"true\" />\n <span className=\"eidos-file-upload-item-name\">{file.name}</span>\n <span className=\"eidos-file-upload-item-size\">{formatBytes(file.size)}</span>\n </div>\n <button\n type=\"button\"\n className=\"eidos-file-upload-item-remove\"\n onClick={() => handleRemoveFile(index)}\n aria-label={`Remove ${file.name}`}\n >\n <X />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nFileUpload.displayName = 'FileUpload';\n"]}
|
|
@@ -12,16 +12,16 @@ import {
|
|
|
12
12
|
} from "./chunk-3TUCQAYL.js";
|
|
13
13
|
import {
|
|
14
14
|
Chip
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-NACA72XI.js";
|
|
16
16
|
import {
|
|
17
17
|
Menu
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CP335H4Q.js";
|
|
19
19
|
import {
|
|
20
20
|
Avatar
|
|
21
21
|
} from "./chunk-XJBWFDDE.js";
|
|
22
22
|
import {
|
|
23
23
|
Button
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-44T4HMFU.js";
|
|
25
25
|
|
|
26
26
|
// src/components/Conversation/Chat.component.tsx
|
|
27
27
|
import { useCallback as useCallback3, useMemo } from "react";
|
|
@@ -380,6 +380,8 @@ var MessageComposer = ({
|
|
|
380
380
|
multiple: true,
|
|
381
381
|
accept: attachmentAccept,
|
|
382
382
|
className: "eidos-composer-file-input",
|
|
383
|
+
tabIndex: -1,
|
|
384
|
+
"aria-hidden": "true",
|
|
383
385
|
onChange: (event) => addFiles(event.target.files),
|
|
384
386
|
disabled: disabled || pending
|
|
385
387
|
}
|
|
@@ -883,4 +885,4 @@ export {
|
|
|
883
885
|
Chat,
|
|
884
886
|
CommentThread
|
|
885
887
|
};
|
|
886
|
-
//# sourceMappingURL=chunk-
|
|
888
|
+
//# sourceMappingURL=chunk-Y7VSWZWM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Conversation/Chat.component.tsx","../src/components/Conversation/MessageComposer.component.tsx","../src/components/Conversation/Conversation.hooks.ts","../src/components/Conversation/Conversation.utils.ts","../src/components/Conversation/MessageItem.component.tsx","../src/components/Conversation/CommentThread.component.tsx"],"sourcesContent":["import React, { useCallback, useMemo } from 'react';\nimport { ArrowDown, MessagesSquare } from 'lucide-react';\nimport { Button } from '../Button';\nimport { EmptyState } from '../EmptyState';\nimport { Skeleton } from '../Skeleton';\nimport { Spinner } from '../Spinner';\nimport { MessageComposer } from './MessageComposer.component';\nimport { MessageItem } from './MessageItem.component';\nimport type { ChatProps, ConversationMessage } from './Conversation.types';\nimport {\n useLoadMoreOnScrollTop,\n useNewMessageIds,\n useScrollAnchor,\n useStickToBottom,\n} from './Conversation.hooks';\nimport { classNames, continuesRun, formatDateSeparator, isSameDay } from './Conversation.utils';\n\nconst ChatSkeleton: React.FC = () => (\n <div className=\"eidos-chat-skeleton\" aria-hidden=\"true\">\n {[0, 1, 2, 3].map((row) => (\n <div\n key={row}\n className={classNames(\n 'eidos-chat-skeleton-row',\n row % 2 === 1 && 'eidos-chat-skeleton-row--own',\n )}\n >\n <Skeleton variant=\"circular\" width={28} height={28} />\n <Skeleton variant=\"rounded\" height={48} />\n </div>\n ))}\n </div>\n);\n\n/**\n * A bottom-anchored message thread with a composer.\n *\n * Controlled and transport-agnostic: it renders the `messages` it is given and\n * reports intent through `onSend` / `onLoadMore`. It never fetches, so the\n * same array can be persisted and replayed verbatim.\n */\nexport const Chat: React.FC<ChatProps> = ({\n messages,\n currentUserId,\n onSend,\n onRetry,\n readOnly = false,\n readOnlyMessage,\n renderBody,\n messageActions,\n renderMessageFooter,\n loading = false,\n emptyContent,\n hasMore = false,\n loadingMore = false,\n onLoadMore,\n groupConsecutive = true,\n groupWindowMinutes = 5,\n showDateSeparators = true,\n formatTimestamp,\n animateNewMessages = true,\n allowAttachments = false,\n attachmentAccept,\n typing,\n unreadFromId,\n placeholder = 'Write a message...',\n sendOnEnter = true,\n ariaLabel = 'Conversation',\n className = '',\n}) => {\n const { scrollRef, atBottom, scrollToBottom, handleScroll } = useStickToBottom(messages);\n const newIds = useNewMessageIds(messages, animateNewMessages);\n useScrollAnchor(scrollRef, messages);\n\n const loadMoreOnScroll = useLoadMoreOnScrollTop(scrollRef, { hasMore, loadingMore, onLoadMore });\n\n const onScroll = useCallback(() => {\n handleScroll();\n loadMoreOnScroll();\n }, [handleScroll, loadMoreOnScroll]);\n\n const byId = useMemo(() => new Map(messages.map((message) => [message.id, message])), [messages]);\n\n // An id that isn't in the current page must not draw a divider above\n // nothing - it would float at the top of whatever happens to be loaded.\n const unreadIndex = useMemo(() => {\n if (!unreadFromId) return -1;\n return messages.findIndex((message) => message.id === unreadFromId);\n }, [messages, unreadFromId]);\n\n const showComposer = !readOnly && Boolean(onSend);\n const isEmpty = !loading && messages.length === 0;\n\n const renderMessage = (message: ConversationMessage, index: number) => {\n const previous = messages[index - 1];\n const actions = readOnly ? undefined : messageActions?.(message);\n const quoted = message.parentId ? byId.get(message.parentId) : undefined;\n\n const nodes: React.ReactNode[] = [];\n\n if (showDateSeparators && (!previous || !isSameDay(previous.sentAt, message.sentAt))) {\n nodes.push(\n <li key={`date-${message.id}`} className=\"eidos-conversation-separator\">\n <span>{formatDateSeparator(message.sentAt)}</span>\n </li>,\n );\n }\n\n if (index === unreadIndex) {\n nodes.push(\n <li\n key={`unread-${message.id}`}\n className=\"eidos-conversation-separator eidos-conversation-separator--unread\"\n >\n <span>Unread messages</span>\n </li>,\n );\n }\n\n // A separator breaks a run: the message under it needs its own header.\n const grouped =\n groupConsecutive &&\n index !== unreadIndex &&\n nodes.length === 0 &&\n continuesRun(previous, message, groupWindowMinutes);\n\n nodes.push(\n <MessageItem\n key={message.id}\n message={message}\n layout=\"chat\"\n isOwn={Boolean(currentUserId) && message.author.id === currentUserId}\n grouped={grouped}\n isNew={newIds.has(message.id)}\n actions={actions}\n footer={renderMessageFooter?.(message)}\n body={renderBody?.(message)}\n onRetry={!readOnly && onRetry ? () => onRetry(message) : undefined}\n quoted={quoted}\n formatTimestamp={formatTimestamp}\n />,\n );\n\n return nodes;\n };\n\n return (\n <div className={classNames('eidos-chat', className)}>\n <div\n ref={scrollRef}\n className=\"eidos-chat-scroll\"\n onScroll={onScroll}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions\"\n aria-busy={loading}\n aria-label={ariaLabel}\n tabIndex={0}\n >\n {hasMore && (\n <div className=\"eidos-chat-load-more\">\n {loadingMore ? (\n <span className=\"eidos-chat-loading-more\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Loading earlier messages…\n </span>\n ) : (\n <Button variant=\"text\" color=\"secondary\" size=\"sm\" onClick={onLoadMore}>\n Load earlier messages\n </Button>\n )}\n </div>\n )}\n\n {loading && <ChatSkeleton />}\n\n {isEmpty &&\n (emptyContent ?? (\n <EmptyState\n icon={<MessagesSquare />}\n title=\"No messages yet\"\n description={\n showComposer ? 'Send the first message to start the conversation.' : undefined\n }\n size=\"sm\"\n />\n ))}\n\n {!loading && messages.length > 0 && (\n <ol className=\"eidos-conversation-list\">{messages.map(renderMessage)}</ol>\n )}\n\n {typing && typing.length > 0 && (\n <div className=\"eidos-chat-typing\" aria-live=\"polite\">\n <span className=\"eidos-chat-typing-dots\" aria-hidden=\"true\">\n <i />\n <i />\n <i />\n </span>\n {typing.length === 1\n ? `${typing[0].name} is typing…`\n : `${typing.length} people are typing…`}\n </div>\n )}\n </div>\n\n {!atBottom && messages.length > 0 && (\n <div className=\"eidos-chat-jump\">\n <Button\n size=\"sm\"\n variant=\"filled\"\n color=\"secondary\"\n preIcon={ArrowDown}\n onClick={() => scrollToBottom('smooth')}\n >\n Latest\n </Button>\n </div>\n )}\n\n {showComposer && (\n <div className=\"eidos-chat-composer\">\n <MessageComposer\n onSubmit={onSend as NonNullable<typeof onSend>}\n placeholder={placeholder}\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n />\n </div>\n )}\n\n {readOnly && readOnlyMessage && (\n <p className=\"eidos-conversation-readonly\">{readOnlyMessage}</p>\n )}\n </div>\n );\n};\n\nChat.displayName = 'Chat';\n","import React, { useCallback, useId, useRef, useState } from 'react';\nimport { Paperclip, Send, X } from 'lucide-react';\nimport { Avatar } from '../Avatar';\nimport { Button } from '../Button';\nimport { Chip } from '../Chip';\nimport type { MessageComposerProps } from './Conversation.types';\nimport { useAutoGrow } from './Conversation.hooks';\nimport { classNames, formatFileSize } from './Conversation.utils';\n\n/**\n * The authoring half of a conversation - a growing text field, optional\n * attachments, and a submit control.\n *\n * Exported on its own so a consumer building a bespoke list can still reuse\n * the input, and used internally by both `Chat` and `CommentThread`.\n */\nexport const MessageComposer: React.FC<MessageComposerProps> = ({\n value,\n onChange,\n onSubmit,\n onCancel,\n placeholder = 'Write a message...',\n submitLabel = 'Send',\n sendOnEnter = false,\n parentId,\n replyingTo,\n allowAttachments = false,\n attachmentAccept,\n author,\n disabled = false,\n requireBody = true,\n autoFocus = false,\n maxLength,\n className = '',\n}) => {\n const [internalBody, setInternalBody] = useState('');\n const [attachments, setAttachments] = useState<File[]>([]);\n const [pending, setPending] = useState(false);\n\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const fileInputId = useId();\n\n const isControlled = value !== undefined;\n const body = isControlled ? value : internalBody;\n\n useAutoGrow(textareaRef, body);\n\n const setBody = useCallback(\n (next: string) => {\n if (!isControlled) setInternalBody(next);\n onChange?.(next);\n },\n [isControlled, onChange],\n );\n\n const canSubmit = !disabled && !pending && (!requireBody || body.trim().length > 0);\n\n const submit = useCallback(async () => {\n if (!canSubmit) return;\n\n const draft = { body: body.trim(), attachments, parentId };\n const result = onSubmit(draft);\n\n // A synchronous handler clears immediately; an async one keeps the draft\n // until it resolves, so a rejected send never loses what was typed.\n if (!(result instanceof Promise)) {\n setBody('');\n setAttachments([]);\n return;\n }\n\n setPending(true);\n try {\n await result;\n setBody('');\n setAttachments([]);\n } catch {\n // Intentionally swallowed: the draft stays put and the consumer owns\n // error reporting (a Snackbar, an Alert, ...). Rethrowing here would\n // surface as an unhandled rejection from a keystroke.\n } finally {\n setPending(false);\n }\n }, [canSubmit, body, attachments, parentId, onSubmit, setBody]);\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {\n if (!sendOnEnter) return;\n // Shift+Enter always inserts a newline; the modifier check also leaves\n // IME composition (which fires Enter to commit) alone.\n if (event.key !== 'Enter' || event.shiftKey || event.nativeEvent.isComposing) return;\n event.preventDefault();\n void submit();\n };\n\n const addFiles = (list: FileList | null) => {\n if (!list?.length) return;\n setAttachments((current) => [...current, ...Array.from(list)]);\n // Reset so selecting the same file twice in a row still fires `change`.\n if (fileInputRef.current) fileInputRef.current.value = '';\n };\n\n const removeAttachment = (index: number) =>\n setAttachments((current) => current.filter((_, i) => i !== index));\n\n return (\n <form\n className={classNames('eidos-message-composer', className)}\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {replyingTo && (\n <div className=\"eidos-composer-reply-hint\">\n <span>\n Replying to <strong>{replyingTo}</strong>\n </span>\n {onCancel && (\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={X}\n onClick={onCancel}\n tooltip=\"Cancel reply\"\n aria-label=\"Cancel reply\"\n />\n )}\n </div>\n )}\n\n <div className=\"eidos-composer-row\">\n {author && (\n <div className=\"eidos-composer-avatar\">\n <Avatar\n src={author.avatarSrc}\n name={author.name}\n size=\"sm\"\n color={author.avatarColor ?? 'gray'}\n />\n </div>\n )}\n\n <div className=\"eidos-composer-field\">\n <textarea\n ref={textareaRef}\n className=\"eidos-composer-input\"\n value={body}\n onChange={(event) => setBody(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={disabled || pending}\n rows={1}\n maxLength={maxLength}\n // Opt-in and off by default. Only the reply composer sets it, where\n // the field appears in direct response to the user pressing Reply -\n // moving focus there is the expected outcome, not a hijack.\n autoFocus={autoFocus}\n aria-label={placeholder}\n />\n\n {attachments.length > 0 && (\n <ul className=\"eidos-composer-attachments\">\n {attachments.map((file, index) => (\n <li key={`${file.name}-${file.size}-${index}`}>\n <Chip\n size=\"sm\"\n variant=\"outlined\"\n color=\"secondary\"\n preIcon={Paperclip}\n onRemove={() => removeAttachment(index)}\n >\n {file.name}\n {file.size > 0 && ` (${formatFileSize(file.size)})`}\n </Chip>\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n\n <div className=\"eidos-composer-actions\">\n <div className=\"eidos-composer-actions-start\">\n {allowAttachments && (\n <>\n <input\n ref={fileInputRef}\n id={fileInputId}\n type=\"file\"\n multiple\n accept={attachmentAccept}\n // Hidden from both the tab order and the accessibility\n // tree. It is a proxy: the visible button above opens the\n // file dialog via `.click()`, so this input is an\n // implementation detail, not a control.\n //\n // `visually-hidden` keeps an element *focusable* - that is\n // the point of it, for skip links and screen-reader-only\n // text - so a keyboard user landed on an invisible control\n // and met the same \"Attach files\" action twice. `FileUpload`\n // already gets this right with `display: none` +\n // `tabIndex={-1}`; these two had drifted from it.\n //\n // `aria-hidden` is safe here only *because* of the\n // `tabIndex={-1}` beside it - hiding a focusable element is\n // its own violation.\n className=\"eidos-composer-file-input\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => addFiles(event.target.files)}\n disabled={disabled || pending}\n />\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={Paperclip}\n tooltip=\"Attach files\"\n aria-label=\"Attach files\"\n disabled={disabled || pending}\n onClick={() => fileInputRef.current?.click()}\n />\n </>\n )}\n {sendOnEnter && (\n <span className=\"eidos-composer-hint\">\n <kbd>Enter</kbd> to send · <kbd>Shift</kbd>+<kbd>Enter</kbd> for a new line\n </span>\n )}\n </div>\n\n <div className=\"eidos-composer-actions-end\">\n {onCancel && !replyingTo && (\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n onClick={onCancel}\n disabled={pending}\n >\n Cancel\n </Button>\n )}\n <Button\n type=\"submit\"\n size=\"sm\"\n color=\"primary\"\n preIcon={Send}\n loading={pending}\n disabled={!canSubmit}\n >\n {submitLabel}\n </Button>\n </div>\n </div>\n </form>\n );\n};\n\nMessageComposer.displayName = 'MessageComposer';\n","import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type { ConversationMessage } from './Conversation.types';\n\n/**\n * Ids that arrived *after* the first render.\n *\n * Seeded from the first commit so an initial page of history is never\n * animated - otherwise opening a thread fades forty messages in at once,\n * which reads as a glitch rather than as feedback. Only genuinely new ids get\n * the entrance class, and each one only once.\n */\nexport const useNewMessageIds = (\n messages: ConversationMessage[],\n enabled: boolean,\n): Set<string> => {\n const seen = useRef<Set<string> | null>(null);\n const [fresh, setFresh] = useState<Set<string>>(() => new Set());\n\n useEffect(() => {\n // First commit: everything already on screen counts as pre-existing.\n if (seen.current === null) {\n seen.current = new Set(messages.map((message) => message.id));\n return;\n }\n\n if (!enabled) {\n for (const message of messages) seen.current.add(message.id);\n return;\n }\n\n const added = messages.filter((message) => !seen.current?.has(message.id));\n if (!added.length) return;\n\n for (const message of added) seen.current.add(message.id);\n setFresh(new Set(added.map((message) => message.id)));\n }, [messages, enabled]);\n\n return fresh;\n};\n\ninterface StickToBottom {\n scrollRef: React.RefObject<HTMLDivElement | null>;\n /** The viewport is at (or near) the newest message. */\n atBottom: boolean;\n scrollToBottom: (behavior?: ScrollBehavior) => void;\n handleScroll: () => void;\n}\n\n/** Distance from the bottom still counted as \"reading the newest message\". */\nconst BOTTOM_THRESHOLD_PX = 48;\n\n/**\n * Keeps a bottom-anchored list pinned to the newest message - but only while\n * the reader is already there.\n *\n * Scrolling someone back down while they are reading scrollback is the single\n * most common defect in chat UIs, so arrival of a new message only forces a\n * scroll when `atBottom` was already true. Otherwise the caller surfaces a\n * jump-to-latest affordance and leaves the viewport alone.\n */\nexport const useStickToBottom = (messages: ConversationMessage[]): StickToBottom => {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [atBottom, setAtBottom] = useState(true);\n // Read during a layout effect, so it must not be state - it has to be\n // correct for the commit that is happening right now, not the next render.\n const atBottomRef = useRef(true);\n const lastCount = useRef(messages.length);\n\n const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {\n const node = scrollRef.current;\n if (!node) return;\n node.scrollTo({ top: node.scrollHeight, behavior });\n }, []);\n\n const handleScroll = useCallback(() => {\n const node = scrollRef.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n const next = distance <= BOTTOM_THRESHOLD_PX;\n atBottomRef.current = next;\n setAtBottom((current) => (current === next ? current : next));\n }, []);\n\n // Jump (not animate) to the bottom on first paint so a thread opens on its\n // newest message instead of visibly scrolling there.\n useLayoutEffect(() => {\n scrollToBottom('auto');\n // eslint-disable-next-line react-hooks/exhaustive-deps -- mount only\n }, []);\n\n useLayoutEffect(() => {\n if (messages.length > lastCount.current && atBottomRef.current) {\n scrollToBottom('smooth');\n }\n lastCount.current = messages.length;\n }, [messages.length, scrollToBottom]);\n\n return { scrollRef, atBottom, scrollToBottom, handleScroll };\n};\n\n/**\n * Preserves the reader's position when older messages are prepended.\n *\n * Without this, inserting a page above the viewport pushes the content the\n * user was reading down by the height of everything added, which reads as the\n * list jumping to a random place. Capturing `scrollHeight` before the DOM\n * mutation and restoring the delta in a *layout* effect corrects it before the\n * browser paints, so there is no visible shift.\n */\nexport const useScrollAnchor = (\n scrollRef: React.RefObject<HTMLDivElement | null>,\n messages: ConversationMessage[],\n): void => {\n const previous = useRef({ height: 0, firstId: '', count: 0 });\n\n useLayoutEffect(() => {\n const node = scrollRef.current;\n if (!node) return;\n\n const firstId = messages[0]?.id ?? '';\n const before = previous.current;\n // A prepend is the only case where the first id changes while the list\n // grows - an append leaves it untouched, and a replacement shrinks it.\n const prepended =\n before.firstId !== '' && before.firstId !== firstId && messages.length > before.count;\n\n if (prepended) {\n node.scrollTop += node.scrollHeight - before.height;\n }\n\n previous.current = { height: node.scrollHeight, firstId, count: messages.length };\n }, [messages, scrollRef]);\n};\n\n/**\n * Fires `onLoadMore` once the reader approaches the top of the history.\n *\n * Latched on the way in and released only after they scroll back out of the\n * trigger zone, so a single pause near the top can't queue a dozen fetches.\n */\nexport const useLoadMoreOnScrollTop = (\n scrollRef: React.RefObject<HTMLDivElement | null>,\n {\n hasMore,\n loadingMore,\n onLoadMore,\n }: {\n hasMore?: boolean;\n loadingMore?: boolean;\n onLoadMore?: () => void;\n },\n thresholdPx = 80,\n): (() => void) => {\n const armed = useRef(true);\n\n return useCallback(() => {\n const node = scrollRef.current;\n if (!node) return;\n\n if (node.scrollTop > thresholdPx) {\n armed.current = true;\n return;\n }\n\n if (!armed.current || !hasMore || loadingMore || !onLoadMore) return;\n armed.current = false;\n onLoadMore();\n }, [scrollRef, hasMore, loadingMore, onLoadMore, thresholdPx]);\n};\n\n/**\n * Grows a textarea to fit its content, up to `maxHeight`.\n *\n * Height is written to `style.height` directly rather than through a React\n * inline style: it is measured from the live DOM after every keystroke, so it\n * cannot be expressed as a class, and routing it through state would add a\n * render per character. Assigning the property imperatively also keeps it out\n * of the server-rendered `style` attribute that CSP's `style-src-attr` gates.\n */\nexport const useAutoGrow = (\n ref: React.RefObject<HTMLTextAreaElement | null>,\n value: string,\n maxHeight = 200,\n): void => {\n useLayoutEffect(() => {\n const node = ref.current;\n if (!node) return;\n\n node.style.height = 'auto';\n const next = Math.min(node.scrollHeight, maxHeight);\n node.style.height = `${next}px`;\n node.style.overflowY = node.scrollHeight > maxHeight ? 'auto' : 'hidden';\n }, [ref, value, maxHeight]);\n};\n","import type { ConversationMessage } from './Conversation.types';\n\n/**\n * Timestamp formatting goes through `Intl` rather than a date library on\n * purpose: `dayjs` is currently bundled only into the `DatePicker` chunk, and\n * pulling it in here would add it to every consumer importing a conversation.\n * `Intl` is built in and already locale-aware.\n */\n\nconst timeFormat = new Intl.DateTimeFormat(undefined, {\n hour: '2-digit',\n minute: '2-digit',\n});\n\nconst dateFormat = new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'long',\n year: 'numeric',\n});\n\nconst dateFormatSameYear = new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'long',\n});\n\n/** Parses an ISO string, returning null rather than an `Invalid Date`. */\nexport const parseDate = (iso: string): Date | null => {\n const date = new Date(iso);\n return Number.isNaN(date.getTime()) ? null : date;\n};\n\nexport const formatMessageTime = (iso: string): string => {\n const date = parseDate(iso);\n return date ? timeFormat.format(date) : iso;\n};\n\nconst startOfDay = (date: Date): number =>\n new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime();\n\nexport const isSameDay = (a: string, b: string): boolean => {\n const first = parseDate(a);\n const second = parseDate(b);\n if (!first || !second) return false;\n return startOfDay(first) === startOfDay(second);\n};\n\n/** `Today` / `Yesterday` / a full date, for the divider between days. */\nexport const formatDateSeparator = (iso: string): string => {\n const date = parseDate(iso);\n if (!date) return iso;\n\n const now = new Date();\n const dayDelta = Math.round((startOfDay(now) - startOfDay(date)) / 86_400_000);\n\n if (dayDelta === 0) return 'Today';\n if (dayDelta === 1) return 'Yesterday';\n return date.getFullYear() === now.getFullYear()\n ? dateFormatSameYear.format(date)\n : dateFormat.format(date);\n};\n\nconst FILE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];\n\nexport const formatFileSize = (bytes: number): string => {\n if (!Number.isFinite(bytes) || bytes < 0) return '';\n if (bytes < 1024) return `${bytes} B`;\n\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < FILE_UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n // One decimal below 10 (9.4 MB), none above it (24 MB) - the extra digit\n // stops carrying information once the integer part is two digits.\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${FILE_UNITS[unit]}`;\n};\n\n/**\n * Whether `message` continues a run started by `previous` - same author, same\n * day, and within the grouping window. A grouped message hides its avatar and\n * author line.\n */\nexport const continuesRun = (\n previous: ConversationMessage | undefined,\n message: ConversationMessage,\n windowMinutes: number,\n): boolean => {\n if (!previous) return false;\n if (previous.author.id !== message.author.id) return false;\n if (!isSameDay(previous.sentAt, message.sentAt)) return false;\n\n const before = parseDate(previous.sentAt);\n const after = parseDate(message.sentAt);\n if (!before || !after) return false;\n\n return Math.abs(after.getTime() - before.getTime()) <= windowMinutes * 60_000;\n};\n\nexport const sortBySentAt = (messages: ConversationMessage[]): ConversationMessage[] =>\n [...messages].sort((a, b) => {\n const first = parseDate(a.sentAt)?.getTime() ?? 0;\n const second = parseDate(b.sentAt)?.getTime() ?? 0;\n return first - second;\n });\n\nexport interface ThreadNode {\n message: ConversationMessage;\n replies: ConversationMessage[];\n}\n\n/**\n * Collapses a flat list into one level of nesting.\n *\n * Deeper chains are flattened onto the nearest **top-level** ancestor rather\n * than being dropped or nested further: indentation past one level is\n * unreadable on a phone, and silently discarding a reply-to-a-reply would lose\n * data the consumer stored. `replyingToName` on the rendered reply is what\n * preserves who was actually answered.\n *\n * A `parentId` pointing at a message that isn't present (paged out, deleted)\n * is treated as top-level, so nothing ever disappears from the list.\n */\nexport const buildThread = (messages: ConversationMessage[]): ThreadNode[] => {\n const byId = new Map(messages.map((message) => [message.id, message]));\n\n /** Walks up to the outermost ancestor, guarding against a parentId cycle. */\n const rootIdOf = (message: ConversationMessage): string => {\n const seen = new Set<string>([message.id]);\n let current = message;\n\n while (current.parentId) {\n const parent = byId.get(current.parentId);\n if (!parent || seen.has(parent.id)) break;\n seen.add(parent.id);\n current = parent;\n }\n\n return current.id;\n };\n\n const nodes = new Map<string, ThreadNode>();\n const roots: ThreadNode[] = [];\n\n for (const message of messages) {\n if (message.parentId && byId.has(message.parentId)) continue;\n const node: ThreadNode = { message, replies: [] };\n nodes.set(message.id, node);\n roots.push(node);\n }\n\n for (const message of messages) {\n if (!message.parentId || !byId.has(message.parentId)) continue;\n nodes.get(rootIdOf(message))?.replies.push(message);\n }\n\n for (const node of nodes.values()) {\n node.replies = sortBySentAt(node.replies);\n }\n\n return roots;\n};\n\n/** Name of the author a reply was addressed to, when it isn't its own parent. */\nexport const replyTargetName = (\n message: ConversationMessage,\n byId: Map<string, ConversationMessage>,\n rootId: string,\n): string | undefined => {\n if (!message.parentId || message.parentId === rootId) return undefined;\n return byId.get(message.parentId)?.author.name;\n};\n\n/** Trims a quoted excerpt so a long parent message can't dominate the reply. */\nexport const excerpt = (body: string, maxLength = 120): string => {\n const flat = body.replace(/\\s+/g, ' ').trim();\n return flat.length > maxLength ? `${flat.slice(0, maxLength - 1)}…` : flat;\n};\n\nexport const classNames = (...values: Array<string | false | null | undefined>): string =>\n values.filter(Boolean).join(' ');\n","import React from 'react';\nimport { AlertCircle, MoreHorizontal, Paperclip, RotateCw } from 'lucide-react';\nimport { Avatar } from '../Avatar';\nimport { Button } from '../Button';\nimport { Menu } from '../Menu';\nimport { Pill } from '../Pill';\nimport { Spinner } from '../Spinner';\nimport type { ConversationMessage, MessageAttachment } from './Conversation.types';\nimport type { MenuItemType } from '../Menu';\nimport { classNames, excerpt, formatFileSize, formatMessageTime } from './Conversation.utils';\n\nexport interface MessageItemProps {\n message: ConversationMessage;\n /** 'chat' right-aligns own messages into bubbles; 'comment' renders a flat card. */\n layout: 'chat' | 'comment';\n isOwn: boolean;\n /** Continues a run from the same author - hides the avatar and author line. */\n grouped: boolean;\n /** Plays the entrance animation. */\n isNew: boolean;\n actions?: MenuItemType[];\n footer?: React.ReactNode;\n body?: React.ReactNode;\n onRetry?: () => void;\n /** Author of the message being answered, for a flattened deep reply. */\n replyingToName?: string;\n /** Quoted parent, rendered above the body in chat layout. */\n quoted?: ConversationMessage;\n /** Reply affordance; omitted entirely when the viewer may not reply. */\n onReply?: () => void;\n formatTimestamp?: (iso: string) => string;\n}\n\nconst AttachmentList: React.FC<{ attachments: MessageAttachment[] }> = ({ attachments }) => (\n <ul className=\"eidos-message-attachments\">\n {attachments.map((attachment) => {\n const label = (\n <>\n <Paperclip aria-hidden=\"true\" />\n <span className=\"eidos-message-attachment-name\">{attachment.name}</span>\n {attachment.size !== undefined && (\n <span className=\"eidos-message-attachment-size\">{formatFileSize(attachment.size)}</span>\n )}\n </>\n );\n\n return (\n <li key={attachment.id} className=\"eidos-message-attachment\">\n {attachment.url ? (\n <a\n href={attachment.url}\n className=\"eidos-message-attachment-link\"\n target=\"_blank\"\n rel=\"noreferrer\"\n download={attachment.name}\n >\n {label}\n </a>\n ) : (\n <span className=\"eidos-message-attachment-link\">{label}</span>\n )}\n </li>\n );\n })}\n </ul>\n);\n\nexport const MessageItem: React.FC<MessageItemProps> = ({\n message,\n layout,\n isOwn,\n grouped,\n isNew,\n actions,\n footer,\n body,\n onRetry,\n replyingToName,\n quoted,\n onReply,\n formatTimestamp,\n}) => {\n const { author, status = 'sent' } = message;\n const timestamp = formatTimestamp\n ? formatTimestamp(message.sentAt)\n : formatMessageTime(message.sentAt);\n // An empty action list must not leave a dead trigger behind - see the\n // permissions note in Conversation.types.ts.\n const hasActions = Boolean(actions && actions.length > 0);\n\n return (\n <li\n className={classNames(\n 'eidos-message',\n `eidos-message--${layout}`,\n isOwn && 'eidos-message--own',\n grouped && 'eidos-message--grouped',\n status !== 'sent' && `eidos-message--${status}`,\n isNew && 'eidos-message--enter',\n )}\n data-message-id={message.id}\n >\n <div className=\"eidos-message-avatar\">\n {!grouped && (\n <Avatar\n src={author.avatarSrc}\n name={author.name}\n size=\"sm\"\n color={author.avatarColor ?? 'gray'}\n />\n )}\n </div>\n\n <div className=\"eidos-message-main\">\n {!grouped && (\n <div className=\"eidos-message-header\">\n <span className=\"eidos-message-author\">{author.name}</span>\n {author.role && (\n <Pill size=\"sm\" variant=\"outlined\" color=\"secondary\">\n {author.role}\n </Pill>\n )}\n <time className=\"eidos-message-time\" dateTime={message.sentAt}>\n {timestamp}\n </time>\n {message.edited && <span className=\"eidos-message-edited\">edited</span>}\n </div>\n )}\n\n <div className=\"eidos-message-bubble\">\n {quoted && (\n <blockquote className=\"eidos-message-quote\">\n <span className=\"eidos-message-quote-author\">{quoted.author.name}</span>\n <span className=\"eidos-message-quote-body\">{excerpt(quoted.body)}</span>\n </blockquote>\n )}\n\n {replyingToName && (\n <span className=\"eidos-message-reply-target\">Replying to {replyingToName}</span>\n )}\n\n <div className=\"eidos-message-body\">{body ?? message.body}</div>\n\n {message.attachments && message.attachments.length > 0 && (\n <AttachmentList attachments={message.attachments} />\n )}\n\n {grouped && (\n <time\n className=\"eidos-message-time eidos-message-time--inline\"\n dateTime={message.sentAt}\n >\n {timestamp}\n </time>\n )}\n </div>\n\n {status === 'sending' && (\n <span className=\"eidos-message-status\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Sending…\n </span>\n )}\n\n {status === 'failed' && (\n <span className=\"eidos-message-status eidos-message-status--failed\">\n <AlertCircle aria-hidden=\"true\" />\n Not sent\n {onRetry && (\n <Button variant=\"text\" color=\"danger\" size=\"sm\" preIcon={RotateCw} onClick={onRetry}>\n Retry\n </Button>\n )}\n </span>\n )}\n\n {footer && <div className=\"eidos-message-footer\">{footer}</div>}\n\n {onReply && (\n <div className=\"eidos-message-reply\">\n <Button variant=\"text\" color=\"secondary\" size=\"sm\" onClick={onReply}>\n Reply\n </Button>\n </div>\n )}\n </div>\n\n {hasActions && (\n <div className=\"eidos-message-actions\">\n <Menu\n items={actions ?? []}\n trigger={\n <Button\n variant=\"text\"\n color=\"secondary\"\n size=\"sm\"\n icon={MoreHorizontal}\n aria-label={`Actions for ${author.name}'s message`}\n />\n }\n />\n </div>\n )}\n </li>\n );\n};\n\nMessageItem.displayName = 'MessageItem';\n","import React, { useCallback, useMemo, useState } from 'react';\nimport { MessageSquare } from 'lucide-react';\nimport { Button } from '../Button';\nimport { EmptyState } from '../EmptyState';\nimport { Skeleton } from '../Skeleton';\nimport { Spinner } from '../Spinner';\nimport { MessageComposer } from './MessageComposer.component';\nimport { MessageItem } from './MessageItem.component';\nimport type { CommentThreadProps, ConversationMessage, MessageDraft } from './Conversation.types';\nimport { useNewMessageIds } from './Conversation.hooks';\nimport {\n buildThread,\n classNames,\n continuesRun,\n replyTargetName,\n sortBySentAt,\n} from './Conversation.utils';\n\nconst CommentSkeleton: React.FC = () => (\n <div className=\"eidos-comment-thread-skeleton\" aria-hidden=\"true\">\n {[0, 1, 2].map((row) => (\n <div key={row} className=\"eidos-comment-thread-skeleton-row\">\n <Skeleton variant=\"circular\" width={28} height={28} />\n <Skeleton variant=\"text\" lines={2} />\n </div>\n ))}\n </div>\n);\n\n/**\n * A comment section: document flow, no internal scroll container, one level of\n * nesting.\n *\n * Shares its data model and message rendering with `Chat`, so history captured\n * in one can be displayed in the other. Unlike `Chat` it has no scroll region\n * of its own - it grows with the page and pages *downward* via `onLoadMore`.\n */\nexport const CommentThread: React.FC<CommentThreadProps> = ({\n messages,\n currentUserId,\n onSend,\n onReply,\n onRetry,\n readOnly = false,\n readOnlyMessage,\n renderBody,\n messageActions,\n renderMessageFooter,\n loading = false,\n emptyContent,\n hasMore = false,\n loadingMore = false,\n onLoadMore,\n groupConsecutive = true,\n groupWindowMinutes = 5,\n formatTimestamp,\n animateNewMessages = true,\n allowAttachments = false,\n attachmentAccept,\n order = 'oldest-first',\n composerPosition = 'bottom',\n placeholder = 'Add a comment...',\n sendOnEnter = false,\n title,\n ariaLabel = 'Comments',\n className = '',\n}) => {\n const [replyingToId, setReplyingToId] = useState<string | null>(null);\n const newIds = useNewMessageIds(messages, animateNewMessages);\n\n const byId = useMemo(() => new Map(messages.map((message) => [message.id, message])), [messages]);\n\n const roots = useMemo(() => {\n const thread = buildThread(messages);\n const sorted = sortBySentAt(thread.map((node) => node.message));\n const ordered = order === 'newest-first' ? [...sorted].reverse() : sorted;\n const nodeById = new Map(thread.map((node) => [node.message.id, node]));\n // Replies always read oldest-first, even when roots are reversed - a\n // conversation under a single comment is chronological by nature.\n return ordered.map((message) => nodeById.get(message.id)!);\n }, [messages, order]);\n\n const canReply = !readOnly && Boolean(onReply);\n const showComposer = !readOnly && Boolean(onSend);\n const isEmpty = !loading && messages.length === 0;\n const total = messages.length;\n\n const handleReply = useCallback(\n async (draft: MessageDraft) => {\n await onReply?.(draft);\n setReplyingToId(null);\n },\n [onReply],\n );\n\n const renderComment = (\n message: ConversationMessage,\n options: { rootId: string; previous?: ConversationMessage; isReply: boolean },\n ) => {\n const actions = readOnly ? undefined : messageActions?.(message);\n\n return (\n <MessageItem\n key={message.id}\n message={message}\n layout=\"comment\"\n isOwn={Boolean(currentUserId) && message.author.id === currentUserId}\n grouped={groupConsecutive && continuesRun(options.previous, message, groupWindowMinutes)}\n isNew={newIds.has(message.id)}\n actions={actions}\n footer={renderMessageFooter?.(message)}\n body={renderBody?.(message)}\n onRetry={!readOnly && onRetry ? () => onRetry(message) : undefined}\n replyingToName={\n options.isReply ? replyTargetName(message, byId, options.rootId) : undefined\n }\n onReply={canReply ? () => setReplyingToId(message.id) : undefined}\n formatTimestamp={formatTimestamp}\n />\n );\n };\n\n const composer = showComposer && (\n <MessageComposer\n onSubmit={onSend as NonNullable<typeof onSend>}\n placeholder={placeholder}\n submitLabel=\"Comment\"\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n className=\"eidos-comment-thread-composer\"\n />\n );\n\n return (\n <section className={classNames('eidos-comment-thread', className)} aria-label={ariaLabel}>\n {(title || total > 0) && (\n <header className=\"eidos-comment-thread-header\">\n {title && <h3 className=\"eidos-comment-thread-title\">{title}</h3>}\n {total > 0 && (\n <span className=\"eidos-comment-thread-count\">\n {total} {total === 1 ? 'comment' : 'comments'}\n </span>\n )}\n </header>\n )}\n\n {composerPosition === 'top' && composer}\n\n {loading && <CommentSkeleton />}\n\n {isEmpty &&\n (emptyContent ?? (\n <EmptyState\n icon={<MessageSquare />}\n title=\"No comments yet\"\n description={showComposer ? 'Start the discussion by leaving a comment.' : undefined}\n size=\"sm\"\n />\n ))}\n\n {!loading && roots.length > 0 && (\n <ol className=\"eidos-conversation-list eidos-comment-thread-list\" aria-busy={loadingMore}>\n {roots.map((node) => (\n <li key={node.message.id} className=\"eidos-comment-thread-node\">\n <ol className=\"eidos-conversation-list\">\n {renderComment(node.message, { rootId: node.message.id, isReply: false })}\n </ol>\n\n {node.replies.length > 0 && (\n <ol className=\"eidos-conversation-list eidos-comment-thread-replies\">\n {node.replies.map((reply, index) =>\n renderComment(reply, {\n rootId: node.message.id,\n previous: node.replies[index - 1],\n isReply: true,\n }),\n )}\n </ol>\n )}\n\n {replyingToId &&\n (replyingToId === node.message.id ||\n node.replies.some((reply) => reply.id === replyingToId)) && (\n <div className=\"eidos-comment-thread-reply-composer\">\n <MessageComposer\n onSubmit={handleReply}\n onCancel={() => setReplyingToId(null)}\n parentId={replyingToId}\n replyingTo={byId.get(replyingToId)?.author.name}\n placeholder=\"Write a reply...\"\n submitLabel=\"Reply\"\n sendOnEnter={sendOnEnter}\n allowAttachments={allowAttachments}\n attachmentAccept={attachmentAccept}\n autoFocus\n />\n </div>\n )}\n </li>\n ))}\n </ol>\n )}\n\n {hasMore && (\n <div className=\"eidos-comment-thread-load-more\">\n {loadingMore ? (\n <span className=\"eidos-comment-thread-loading-more\">\n <Spinner size=\"sm\" color=\"secondary\" />\n Loading more comments…\n </span>\n ) : (\n <Button variant=\"outlined\" color=\"secondary\" size=\"sm\" onClick={onLoadMore}>\n Load more comments\n </Button>\n )}\n </div>\n )}\n\n {composerPosition === 'bottom' && composer}\n\n {readOnly && readOnlyMessage && (\n <p className=\"eidos-conversation-readonly\">{readOnlyMessage}</p>\n )}\n </section>\n );\n};\n\nCommentThread.displayName = 'CommentThread';\n"],"mappings":"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","names":["useCallback","useCallback","useRef","useState","useState","useRef","useCallback","Paperclip","Fragment","jsx","jsxs","Paperclip","jsx","jsxs","useCallback","useCallback","useMemo","useState","jsx","jsxs","useState","useMemo","useCallback"]}
|
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
} from "./chunk-XJBWFDDE.js";
|
|
4
4
|
import {
|
|
5
5
|
IconButton
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-44T4HMFU.js";
|
|
7
7
|
import {
|
|
8
8
|
Tooltip
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XO7WTE6O.js";
|
|
10
10
|
import {
|
|
11
11
|
renderIcon
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WT4WBHBR.js";
|
|
13
13
|
|
|
14
14
|
// src/components/Navigation/Navigation.component.tsx
|
|
15
15
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -126,4 +126,4 @@ Navigation.displayName = "Navigation";
|
|
|
126
126
|
export {
|
|
127
127
|
Navigation
|
|
128
128
|
};
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
129
|
+
//# sourceMappingURL=chunk-YIBPOLNN.js.map
|
|
@@ -20,6 +20,7 @@ var Checkbox = _react.forwardRef.call(void 0,
|
|
|
20
20
|
}, ref) => {
|
|
21
21
|
const generatedId = _react.useId.call(void 0, );
|
|
22
22
|
const checkboxId = id || `checkbox-${generatedId}`;
|
|
23
|
+
const errorId = `${checkboxId}-error`;
|
|
23
24
|
const internalRef = _react.useRef.call(void 0, null);
|
|
24
25
|
const isControlled = checked !== void 0;
|
|
25
26
|
const [localChecked, setLocalChecked] = _react.useState.call(void 0, _nullishCoalesce(defaultChecked, () => ( false)));
|
|
@@ -73,6 +74,8 @@ var Checkbox = _react.forwardRef.call(void 0,
|
|
|
73
74
|
type: "checkbox",
|
|
74
75
|
id: checkboxId,
|
|
75
76
|
className: "eidos-checkbox-input",
|
|
77
|
+
"aria-invalid": error ? true : void 0,
|
|
78
|
+
"aria-describedby": error ? errorId : void 0,
|
|
76
79
|
disabled,
|
|
77
80
|
onChange: handleChange,
|
|
78
81
|
onClick: handleClick,
|
|
@@ -86,7 +89,7 @@ var Checkbox = _react.forwardRef.call(void 0,
|
|
|
86
89
|
] }),
|
|
87
90
|
label && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "eidos-checkbox-label", children: label })
|
|
88
91
|
] }),
|
|
89
|
-
error && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "eidos-checkbox-error-message", children: [
|
|
92
|
+
error && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { id: errorId, className: "eidos-checkbox-error-message", children: [
|
|
90
93
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.CircleAlert, {}),
|
|
91
94
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { children: error })
|
|
92
95
|
] })
|
|
@@ -98,4 +101,4 @@ Checkbox.displayName = "Checkbox";
|
|
|
98
101
|
|
|
99
102
|
|
|
100
103
|
exports.Checkbox = Checkbox;
|
|
101
|
-
//# sourceMappingURL=chunk-
|
|
104
|
+
//# sourceMappingURL=chunk-YJNQLH2L.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/eidos-ui/eidos-ui/dist/chunk-YJNQLH2L.cjs","../src/components/Checkbox/Checkbox.component.tsx"],"names":[],"mappings":"AAAA;ACAA,8BAAmF;AACnF,2CAA0C;AA6GhC,+CAAA;AA1GH,IAAM,SAAA,EAAW,+BAAA;AAAA,EACtB,CACE;AAAA,IACE,KAAA;AAAA,IACA,KAAA;AAAA,IACA,cAAA,EAAgB,KAAA;AAAA,IAChB,MAAA,EAAQ,SAAA;AAAA,IACR,KAAA,EAAO,IAAA;AAAA,IACP,UAAA,EAAY,EAAA;AAAA,IACZ,SAAA,EAAW,KAAA;AAAA,IACX,EAAA;AAAA,IACA,OAAA;AAAA,IACA,cAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAG;AAAA,EACL,CAAA,EACA,GAAA,EAAA,GACG;AACH,IAAA,MAAM,YAAA,EAAc,0BAAA,CAAM;AAC1B,IAAA,MAAM,WAAA,EAAa,GAAA,GAAM,CAAA,SAAA,EAAY,WAAW,CAAA,CAAA;AAKnB,IAAA;AAGoB,IAAA;AAKhB,IAAA;AACyB,IAAA;AAG7B,IAAA;AAGb,IAAA;AACW,MAAA;AACa,QAAA;AACtC,MAAA;AACgB,IAAA;AAKA,IAAA;AACmB,MAAA;AACX,QAAA;AACS,QAAA;AACrB,UAAA;AACM,QAAA;AACqD,UAAA;AACrE,QAAA;AACF,MAAA;AACI,MAAA;AACN,IAAA;AAEiE,IAAA;AAC5C,MAAA;AACe,QAAA;AAClC,MAAA;AACY,sBAAA;AACd,IAAA;AAY+D,IAAA;AAClD,sBAAA;AACO,MAAA;AACpB,IAAA;AAEuB,IAAA;AACrB,MAAA;AACgC,MAAA;AACD,MAAA;AACnB,MAAA;AACH,MAAA;AAGA,IAAA;AAEY,IAAA;AACrB,MAAA;AACa,MAAA;AACI,MAAA;AACL,MAAA;AACU,MAAA;AAGb,IAAA;AAGQ,IAAA;AACG,sBAAA;AAChB,wBAAA;AAAC,UAAA;AAAA,UAAA;AACM,YAAA;AACA,YAAA;AACD,YAAA;AACM,YAAA;AACmB,YAAA;AACO,YAAA;AACpC,YAAA;AACU,YAAA;AACD,YAAA;AAC2B,YAAA;AAChC,YAAA;AAAA,UAAA;AACN,QAAA;AACiB,wBAAA;AACiB,UAAA;AACb,UAAA;AACrB,QAAA;AACgB,QAAA;AAClB,MAAA;AAGW,MAAA;AACM,wBAAA;AACN,wBAAA;AACT,MAAA;AAEJ,IAAA;AAEJ,EAAA;AACF;AAEuB;AD1C8B;AACA;AACA;AACA","file":"/home/runner/work/eidos-ui/eidos-ui/dist/chunk-YJNQLH2L.cjs","sourcesContent":[null,"import React, { forwardRef, useId, useRef, useState, useEffect, useCallback } from 'react';\nimport { Check, Minus, CircleAlert } from 'lucide-react';\nimport type { CheckboxProps } from './Checkbox.types';\n\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(\n (\n {\n label,\n error,\n indeterminate = false,\n color = 'primary',\n size = 'md',\n className = '',\n disabled = false,\n id,\n checked,\n defaultChecked,\n onChange,\n onClick,\n ...inputProps\n },\n ref,\n ) => {\n const generatedId = useId();\n const checkboxId = id || `checkbox-${generatedId}`;\n // Associates the visible error with the control. Without it the message\n // is just a red line of text sitting near the field - obvious to look\n // at, absent from the accessibility tree (SC 3.3.1). See the fuller\n // note in `Input`.\n const errorId = `${checkboxId}-error`;\n\n // Internal ref needed to imperatively set `indeterminate` (not a standard HTML attribute)\n const internalRef = useRef<HTMLInputElement>(null);\n\n // Track checked state for the visual control:\n // - controlled mode: `checked` prop drives everything\n // - uncontrolled mode: local state mirrors the native input\n const isControlled = checked !== undefined;\n const [localChecked, setLocalChecked] = useState<boolean>(defaultChecked ?? false);\n\n // `??` correctly handles `false` (falsy but defined): only falls back when `checked` is undefined\n const isChecked = checked ?? localChecked;\n\n // Imperatively set the indeterminate property – it has no HTML attribute equivalent\n useEffect(() => {\n if (internalRef.current) {\n internalRef.current.indeterminate = indeterminate;\n }\n }, [indeterminate]);\n\n // Merge the forwarded ref with our internal ref so callers can still access the input.\n // useCallback(_, [ref]) prevents the ref callback from being a new function identity\n // every render, which would cause React to detach/re-attach the ref unnecessarily.\n const mergedRef = useCallback(\n (node: HTMLInputElement | null) => {\n internalRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;\n }\n },\n [ref],\n );\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setLocalChecked(e.target.checked);\n }\n onChange?.(e);\n };\n\n // Clicking anywhere in the surrounding <label> (e.g. the custom\n // `.eidos-checkbox-control` box) fires a click on the clicked element\n // *and* a second, browser-synthesized click targeted at this input -\n // both bubble up. A consumer that wraps Checkbox in its own click\n // handler (e.g. DataGrid toggling a cell on click) would otherwise see\n // that handler fire twice per click and cancel itself out. Stopping\n // propagation here only cancels the synthesized click's bubbling; the\n // original click (whichever element the user actually clicked) still\n // bubbles normally. Still forwards to a caller-supplied `onClick`\n // first, so passing one through doesn't silently disable this fix.\n const handleClick = (e: React.MouseEvent<HTMLInputElement>) => {\n onClick?.(e);\n e.stopPropagation();\n };\n\n const wrapperClasses = [\n 'eidos-checkbox-wrapper',\n `eidos-checkbox-wrapper--${color}`,\n `eidos-checkbox-wrapper--${size}`,\n disabled && 'eidos-checkbox-wrapper--disabled',\n error && 'eidos-checkbox-wrapper--error',\n ]\n .filter(Boolean)\n .join(' ');\n\n const controlClasses = [\n 'eidos-checkbox-control',\n isChecked && 'eidos-checkbox-control--checked',\n indeterminate && 'eidos-checkbox-control--indeterminate',\n disabled && 'eidos-checkbox-control--disabled',\n error && !disabled && 'eidos-checkbox-control--error',\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <div className={['eidos-checkbox-container', className].filter(Boolean).join(' ')}>\n <label className={wrapperClasses}>\n <input\n ref={mergedRef}\n type=\"checkbox\"\n id={checkboxId}\n className=\"eidos-checkbox-input\"\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n disabled={disabled}\n onChange={handleChange}\n onClick={handleClick}\n {...(isControlled ? { checked } : { defaultChecked })}\n {...inputProps}\n />\n <span className={controlClasses}>\n {isChecked && !indeterminate && <Check className=\"eidos-checkbox-icon\" />}\n {indeterminate && <Minus className=\"eidos-checkbox-icon\" />}\n </span>\n {label && <span className=\"eidos-checkbox-label\">{label}</span>}\n </label>\n\n {error && (\n <div id={errorId} className=\"eidos-checkbox-error-message\">\n <CircleAlert />\n <span>{error}</span>\n </div>\n )}\n </div>\n );\n },\n);\n\nCheckbox.displayName = 'Checkbox';\n"]}
|