@persona/design-system 0.1.6 → 0.1.8
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/CHANGELOG.md +346 -202
- package/dist/{Action-DSg3yZck.d.mts → Action-CVtzREhU.d.mts} +3 -3
- package/dist/{Action-uduFOh2m.d.cts → Action-E90Wvqmc.d.cts} +3 -3
- package/dist/Action.d.cts +1 -1
- package/dist/Action.d.mts +1 -1
- package/dist/AdvancedFilterRuleRow.d.mts +37 -0
- package/dist/AdvancedFilterRulesPopover.d.mts +62 -0
- package/dist/ArrayValueControl.d.mts +38 -0
- package/dist/Attribute.d.mts +4 -4
- package/dist/AttributeGrid.d.mts +1 -1
- package/dist/{Badge-CvdUyXSH.cjs → Badge-ADPloFsd.cjs} +2 -5
- package/dist/{Badge-CvdUyXSH.cjs.map → Badge-ADPloFsd.cjs.map} +1 -1
- package/dist/{Badge-pq6B-3ue.mjs → Badge-DortWowZ.mjs} +2 -5
- package/dist/{Badge-pq6B-3ue.mjs.map → Badge-DortWowZ.mjs.map} +1 -1
- package/dist/Banner.d.mts +1 -1
- package/dist/BooleanValueControl.d.mts +20 -0
- package/dist/Breadcrumb-VH-Mwo-M.d.cts +44 -0
- package/dist/Breadcrumb-lziQ1FX9.d.mts +44 -0
- package/dist/Breadcrumb.d.cts +2 -0
- package/dist/Breadcrumb.d.mts +2 -0
- package/dist/Breadcrumbs-BBREROrw.d.cts +26 -0
- package/dist/Breadcrumbs-CDZ_Mrcg.mjs +288 -0
- package/dist/Breadcrumbs-CDZ_Mrcg.mjs.map +1 -0
- package/dist/Breadcrumbs-DJ5qqqRD.d.mts +26 -0
- package/dist/Breadcrumbs-tR49JqAS.cjs +295 -0
- package/dist/Breadcrumbs-tR49JqAS.cjs.map +1 -0
- package/dist/Breadcrumbs.d.cts +2 -0
- package/dist/Breadcrumbs.d.mts +2 -0
- package/dist/{Button-DcTUxRNI.d.mts → Button-9p469xA1.d.mts} +3 -3
- package/dist/Button.d.mts +1 -1
- package/dist/ButtonGroup.d.mts +1 -1
- package/dist/Calendar.d.mts +1 -1
- package/dist/CalendarBase.d.mts +1 -1
- package/dist/{Card-CLTkZLKr.mjs → Card-BMCB9eFC.mjs} +4 -4
- package/dist/{Card-CLTkZLKr.mjs.map → Card-BMCB9eFC.mjs.map} +1 -1
- package/dist/{Card-CrQL0hXm.d.cts → Card-CJPEmUvg.d.cts} +2 -2
- package/dist/{Card-YzGS3nQI.d.mts → Card-CqnG--CU.d.mts} +4 -4
- package/dist/{Card-CaOItkgL.cjs → Card-Csz-PxX0.cjs} +4 -4
- package/dist/{Card-CaOItkgL.cjs.map → Card-Csz-PxX0.cjs.map} +1 -1
- package/dist/Card.d.cts +1 -1
- package/dist/Card.d.mts +1 -1
- package/dist/Card2.d.cts +1 -1
- package/dist/Card2.d.mts +1 -1
- package/dist/CardV2-C9z0Hr-c.mjs +411 -0
- package/dist/CardV2-C9z0Hr-c.mjs.map +1 -0
- package/dist/CardV2-DJXgFomd.cjs +419 -0
- package/dist/CardV2-DJXgFomd.cjs.map +1 -0
- package/dist/{Checkbox-UWdk4pqb.mjs → Checkbox-B0bjZbji.mjs} +5 -5
- package/dist/{Checkbox-UWdk4pqb.mjs.map → Checkbox-B0bjZbji.mjs.map} +1 -1
- package/dist/{Checkbox-BmjK0Wac.d.cts → Checkbox-Bm8exUfH.d.mts} +1 -1
- package/dist/{Checkbox-B83WfsOZ.cjs → Checkbox-CmCujC1W.cjs} +5 -5
- package/dist/{Checkbox-B83WfsOZ.cjs.map → Checkbox-CmCujC1W.cjs.map} +1 -1
- package/dist/{Checkbox-Bc2kzl9q.d.mts → Checkbox-Cr8ifm10.d.cts} +1 -1
- package/dist/Checkbox.d.cts +1 -1
- package/dist/Checkbox.d.mts +1 -1
- package/dist/CheckboxGroupField.d.cts +1 -1
- package/dist/CheckboxGroupField.d.mts +2 -2
- package/dist/ChoicesValueControl.d.mts +35 -0
- package/dist/{Combobox-I44W5oX0.mjs → Combobox-BdqU5WLG.mjs} +261 -72
- package/dist/Combobox-BdqU5WLG.mjs.map +1 -0
- package/dist/{Combobox-Dx1u-L1A.d.cts → Combobox-C99xluaM.d.mts} +3 -3
- package/dist/{Combobox-R3_NDBi3.cjs → Combobox-COXvmxlM.cjs} +254 -65
- package/dist/Combobox-COXvmxlM.cjs.map +1 -0
- package/dist/{Combobox-DtD1XXaf.d.mts → Combobox-DBWMHwUt.d.cts} +3 -3
- package/dist/Combobox.d.cts +2 -2
- package/dist/Combobox.d.mts +2 -2
- package/dist/{ComboboxBase-sRVxlTJw.d.mts → ComboboxBase-BsY7CYoB.d.mts} +50 -9
- package/dist/{ComboboxBase-d98GJD-k.d.cts → ComboboxBase-zZ3tE-dH.d.cts} +47 -6
- package/dist/ComboboxBase.d.cts +2 -2
- package/dist/ComboboxBase.d.mts +2 -2
- package/dist/{ComboboxMultiSelect-CdLQDBMN.d.mts → ComboboxMultiSelect-dNuETspL.d.mts} +2 -2
- package/dist/{ComboboxMultiSelect-CRHf5TI-.d.cts → ComboboxMultiSelect-ioB4Jx7_.d.cts} +2 -2
- package/dist/ComboboxMultiSelect.d.cts +1 -1
- package/dist/ComboboxMultiSelect.d.mts +1 -1
- package/dist/{ComboboxSingleSelect-CxiQ2o6t.d.mts → ComboboxSingleSelect-C8KvIJ43.d.mts} +4 -4
- package/dist/{ComboboxSingleSelect-DvK8QioK.d.cts → ComboboxSingleSelect-Fn80tdhz.d.cts} +4 -4
- package/dist/ComboboxSingleSelect.d.cts +1 -1
- package/dist/ComboboxSingleSelect.d.mts +1 -1
- package/dist/{CompactBar-ODUl2Q6G.d.cts → CompactBar-BEAK55kh.d.cts} +3 -3
- package/dist/{CompactBar-k-rhZ5Z-.d.mts → CompactBar-CXqlVKy7.d.mts} +3 -3
- package/dist/CompactBar.d.cts +1 -1
- package/dist/CompactBar.d.mts +1 -1
- package/dist/{ConfigFormGroup-BhO9nm3P.cjs → ConfigFormGroup-m5YdVa9n.cjs} +3 -3
- package/dist/{ConfigFormGroup-BhO9nm3P.cjs.map → ConfigFormGroup-m5YdVa9n.cjs.map} +1 -1
- package/dist/{ConfigFormGroup-_yCksmZo.mjs → ConfigFormGroup-yhb2jldT.mjs} +3 -3
- package/dist/{ConfigFormGroup-_yCksmZo.mjs.map → ConfigFormGroup-yhb2jldT.mjs.map} +1 -1
- package/dist/{ControlBar-9m-DNImB.d.mts → ControlBar-D8CirAyZ.d.cts} +2 -2
- package/dist/{ControlBar-oT-I2y6E.d.cts → ControlBar-tOCQnbAl.d.mts} +2 -2
- package/dist/ControlBar.d.cts +1 -1
- package/dist/ControlBar.d.mts +1 -1
- package/dist/DataTable.d.cts +3 -3
- package/dist/DataTable.d.mts +4 -4
- package/dist/DateField.d.cts +1 -1
- package/dist/DateField.d.mts +1 -1
- package/dist/DatePicker-BlCgFb7g.cjs +948 -0
- package/dist/DatePicker-BlCgFb7g.cjs.map +1 -0
- package/dist/DatePicker-DY1YA_Fu.mjs +922 -0
- package/dist/DatePicker-DY1YA_Fu.mjs.map +1 -0
- package/dist/DatePicker.d.cts +1 -1
- package/dist/DatePicker.d.mts +1 -1
- package/dist/DateRangePicker.d.cts +6 -1
- package/dist/DateRangePicker.d.mts +6 -1
- package/dist/DateValueControl.d.mts +26 -0
- package/dist/{DropdownButton-BS1QfVX_.mjs → DropdownButton-DKLjJylQ.mjs} +3 -3
- package/dist/{DropdownButton-BS1QfVX_.mjs.map → DropdownButton-DKLjJylQ.mjs.map} +1 -1
- package/dist/{DropdownButton-Dpiy5v-R.cjs → DropdownButton-Dl7IEzAc.cjs} +3 -3
- package/dist/{DropdownButton-Dpiy5v-R.cjs.map → DropdownButton-Dl7IEzAc.cjs.map} +1 -1
- package/dist/DropdownButton.d.cts +1 -1
- package/dist/DropdownButton.d.mts +2 -2
- package/dist/{DropdownMenu-COkpUmIO.d.mts → DropdownMenu-CVPanvKA.d.mts} +3 -3
- package/dist/{DropdownMenu-cN_AnKAT.mjs → DropdownMenu-DGYexb7s.mjs} +3 -3
- package/dist/{DropdownMenu-cN_AnKAT.mjs.map → DropdownMenu-DGYexb7s.mjs.map} +1 -1
- package/dist/{DropdownMenu-CeqZAxjs.cjs → DropdownMenu-DnlQqTjs.cjs} +3 -3
- package/dist/{DropdownMenu-CeqZAxjs.cjs.map → DropdownMenu-DnlQqTjs.cjs.map} +1 -1
- package/dist/{DropdownMenu-D1E1jpZu.d.cts → DropdownMenu-FkiManPP.d.cts} +2 -2
- package/dist/DropdownMenu.d.cts +1 -1
- package/dist/DropdownMenu.d.mts +1 -1
- package/dist/{ErrorProps-DBw1_Z0a.d.mts → ErrorProps-CN2XjaOX.d.mts} +1 -1
- package/dist/{ErrorProps-DLdVsYlk.d.cts → ErrorProps-Dxqb4gw_.d.cts} +1 -1
- package/dist/ErrorProps.d.cts +1 -1
- package/dist/ErrorProps.d.mts +1 -1
- package/dist/FieldRegistry-OX60T3hr.d.cts +122 -0
- package/dist/FieldRegistry-rqSkjchY.d.mts +122 -0
- package/dist/FieldRegistry.d.cts +2 -0
- package/dist/FieldRegistry.d.mts +2 -0
- package/dist/FilterBar-D3LiIy1-.d.mts +103 -0
- package/dist/FilterBar-YzOh02f9.d.cts +103 -0
- package/dist/FilterBar.d.cts +2 -0
- package/dist/FilterBar.d.mts +2 -0
- package/dist/FilterPickerMenu.d.mts +84 -0
- package/dist/FilterPill.d.mts +60 -0
- package/dist/FilterPopover.d.mts +87 -0
- package/dist/FloatValueControl.d.mts +40 -0
- package/dist/{FormControl-fK4HA_yF.mjs → FormControl-B28qa7aQ.mjs} +6 -6
- package/dist/{FormControl-fK4HA_yF.mjs.map → FormControl-B28qa7aQ.mjs.map} +1 -1
- package/dist/{FormControl-OqBTbSwv.d.cts → FormControl-DFp_qcW1.d.cts} +2 -2
- package/dist/{FormControl-vfFzrYhv.d.mts → FormControl-DH3bgqaB.d.mts} +3 -3
- package/dist/{FormControl-DjxkL5Ft.cjs → FormControl-MkBXuUF6.cjs} +6 -6
- package/dist/{FormControl-DjxkL5Ft.cjs.map → FormControl-MkBXuUF6.cjs.map} +1 -1
- package/dist/FormControl.d.cts +1 -1
- package/dist/FormControl.d.mts +1 -1
- package/dist/FormControlLabel.d.mts +1 -1
- package/dist/{FormField-24UPuoL6.d.mts → FormField-BugkBvN4.d.mts} +2 -2
- package/dist/FormField-D3XhxTMg.cjs +550 -0
- package/dist/FormField-D3XhxTMg.cjs.map +1 -0
- package/dist/FormField-PeYYRO4s.mjs +471 -0
- package/dist/FormField-PeYYRO4s.mjs.map +1 -0
- package/dist/{FormField-Cqwi_PyR.d.cts → FormField-idNLw5PW.d.cts} +2 -2
- package/dist/FormField.d.cts +1 -1
- package/dist/FormField.d.mts +1 -1
- package/dist/{Header-D8oDtnIL.d.cts → Header-C-WhPOin.d.cts} +3 -3
- package/dist/{Header-WNGlG541.d.mts → Header-CDTYK1XJ.d.mts} +2 -2
- package/dist/Header.d.cts +1 -1
- package/dist/Header.d.mts +1 -1
- package/dist/{HelperTooltip-Bd5pQLqL.d.mts → HelperTooltip-B7Ysh8B3.d.mts} +3 -3
- package/dist/{HelperTooltip-oXdyk2At.d.cts → HelperTooltip-T-sTGGLD.d.cts} +2 -2
- package/dist/HelperTooltip.d.cts +1 -1
- package/dist/HelperTooltip.d.mts +1 -1
- package/dist/{HoverCopyableField-DyW2iZuo.cjs → HoverCopyableField-CRkM7o4B.cjs} +12 -12
- package/dist/HoverCopyableField-CRkM7o4B.cjs.map +1 -0
- package/dist/{HoverCopyableField-DBkYvwii.mjs → HoverCopyableField-ClXBg_ld.mjs} +8 -8
- package/dist/HoverCopyableField-ClXBg_ld.mjs.map +1 -0
- package/dist/HoverCopyableField.d.mts +1 -1
- package/dist/{IconButton-DggxojBH.d.mts → IconButton-BSES_-6g.d.mts} +2 -2
- package/dist/{IconButton-DC4rh6ow.mjs → IconButton-CIWiEsS-.mjs} +5 -5
- package/dist/{IconButton-DC4rh6ow.mjs.map → IconButton-CIWiEsS-.mjs.map} +1 -1
- package/dist/{IconButton-DNy7g5zh.cjs → IconButton-nlgjJQCS.cjs} +4 -4
- package/dist/{IconButton-DNy7g5zh.cjs.map → IconButton-nlgjJQCS.cjs.map} +1 -1
- package/dist/IconButton.d.mts +1 -1
- package/dist/ImageTile.d.cts +1 -1
- package/dist/ImageTile.d.mts +2 -2
- package/dist/InlineCombobox.d.mts +6 -3
- package/dist/InlineInput.d.mts +4 -1
- package/dist/{Input-Bl7633Ux.mjs → Input-BFI_e7aU.mjs} +5 -5
- package/dist/{Input-Bl7633Ux.mjs.map → Input-BFI_e7aU.mjs.map} +1 -1
- package/dist/{Input-XppkvfUf.d.cts → Input-BM0DzWEf.d.cts} +2 -2
- package/dist/{Input-DW7oacjc.d.mts → Input-BPClVzwD.d.mts} +4 -4
- package/dist/{Input-DQS-BEym.cjs → Input-OJPQ07TW.cjs} +5 -5
- package/dist/{Input-DQS-BEym.cjs.map → Input-OJPQ07TW.cjs.map} +1 -1
- package/dist/Input.d.cts +1 -1
- package/dist/Input.d.mts +1 -1
- package/dist/{InputProps-7rEMxBED.d.cts → InputProps-BnjxrdWQ.d.mts} +2 -2
- package/dist/{InputProps-DQpb2_E3.d.mts → InputProps-CoVNpQSj.d.cts} +2 -2
- package/dist/InputProps.d.cts +1 -1
- package/dist/InputProps.d.mts +1 -1
- package/dist/IntegerValueControl.d.mts +39 -0
- package/dist/LayoutLoadingShimmer.d.cts +20 -0
- package/dist/LayoutLoadingShimmer.d.mts +20 -0
- package/dist/Link.d.mts +1 -1
- package/dist/{ListBox-B4uoo_PT.cjs → ListBox-BP1eaG7L.cjs} +84 -15
- package/dist/ListBox-BP1eaG7L.cjs.map +1 -0
- package/dist/{ListBox-B7sbubm5.mjs → ListBox-BwID8jpk.mjs} +84 -15
- package/dist/ListBox-BwID8jpk.mjs.map +1 -0
- package/dist/{ListBox-DeVmbXeC.d.cts → ListBox-QaeRQBSC.d.cts} +2 -2
- package/dist/{ListBox-CVvKa5sc.d.mts → ListBox-_fcSPynx.d.mts} +5 -5
- package/dist/ListBox.d.cts +1 -1
- package/dist/ListBox.d.mts +1 -1
- package/dist/ListBoxField.d.cts +2 -2
- package/dist/ListBoxField.d.mts +3 -3
- package/dist/{LoadingShimmer-b430vgof.mjs → LoadingShimmer-Dsc_RCCf.mjs} +1 -1
- package/dist/{LoadingShimmer-b430vgof.mjs.map → LoadingShimmer-Dsc_RCCf.mjs.map} +1 -1
- package/dist/{LoadingShimmer-uG2UzYQd.cjs → LoadingShimmer-Dx6ncVUV.cjs} +1 -1
- package/dist/{LoadingShimmer-uG2UzYQd.cjs.map → LoadingShimmer-Dx6ncVUV.cjs.map} +1 -1
- package/dist/LoadingShimmer.d.mts +2 -2
- package/dist/LogicRow-BtCKGX_j.d.mts +40 -0
- package/dist/LogicRow-C6lrFIq0.d.cts +40 -0
- package/dist/LogicRow.d.cts +2 -0
- package/dist/LogicRow.d.mts +2 -0
- package/dist/MaskedInput.d.cts +1 -1
- package/dist/MaskedInput.d.mts +1 -1
- package/dist/{Menu-BbSyWxFD.mjs → Menu-CPYXo61N.mjs} +5 -5
- package/dist/{Menu-BbSyWxFD.mjs.map → Menu-CPYXo61N.mjs.map} +1 -1
- package/dist/{Menu-CSgCXX0k.d.mts → Menu-CUp_9HgC.d.mts} +4 -4
- package/dist/{Menu-C_NOsHAm.d.cts → Menu-fUnbIYr5.d.cts} +2 -2
- package/dist/{Menu-BLbEcodU.cjs → Menu-t2q8NrV0.cjs} +5 -5
- package/dist/{Menu-BLbEcodU.cjs.map → Menu-t2q8NrV0.cjs.map} +1 -1
- package/dist/Menu.d.cts +1 -1
- package/dist/Menu.d.mts +1 -1
- package/dist/{MiddleTruncatedText-BIzKECv9.cjs → MiddleTruncatedText-CO76xIqP.cjs} +2 -2
- package/dist/{MiddleTruncatedText-BIzKECv9.cjs.map → MiddleTruncatedText-CO76xIqP.cjs.map} +1 -1
- package/dist/{MiddleTruncatedText-Bzpg3zAU.mjs → MiddleTruncatedText-GXI-CPvs.mjs} +3 -3
- package/dist/{MiddleTruncatedText-Bzpg3zAU.mjs.map → MiddleTruncatedText-GXI-CPvs.mjs.map} +1 -1
- package/dist/Modal.d.cts +10 -5
- package/dist/Modal.d.mts +13 -8
- package/dist/PageHeader-CUDnarkt.cjs +77 -0
- package/dist/PageHeader-CUDnarkt.cjs.map +1 -0
- package/dist/PageHeader-D-6W61E_.mjs +64 -0
- package/dist/PageHeader-D-6W61E_.mjs.map +1 -0
- package/dist/PageHeader.d.cts +11 -2
- package/dist/PageHeader.d.mts +12 -3
- package/dist/{PageSelector-COqR1tdA.d.cts → PageSelector-CNnVp3Al.d.cts} +3 -3
- package/dist/{PageSelector-mn6q4SKY.d.mts → PageSelector-vay4jpMp.d.mts} +3 -3
- package/dist/PageSelector.d.cts +1 -1
- package/dist/PageSelector.d.mts +1 -1
- package/dist/PageSizeSelector.d.mts +7 -1
- package/dist/{Pagination-DEZFb552.cjs → Pagination-2Yo1vRJZ.cjs} +13 -9
- package/dist/Pagination-2Yo1vRJZ.cjs.map +1 -0
- package/dist/{Pagination-CeoHVtYD.d.cts → Pagination-D04tn-tf.d.cts} +9 -3
- package/dist/{Pagination-BUjpYIwq.d.mts → Pagination-ygLutOst.d.mts} +9 -3
- package/dist/{Pagination-F8ofIWnG.mjs → Pagination-ztqhywAP.mjs} +14 -10
- package/dist/Pagination-ztqhywAP.mjs.map +1 -0
- package/dist/Pagination.d.cts +1 -1
- package/dist/Pagination.d.mts +1 -1
- package/dist/{Pii--c8IxKI0.cjs → Pii-DHTb3ewO.cjs} +2 -2
- package/dist/{Pii--c8IxKI0.cjs.map → Pii-DHTb3ewO.cjs.map} +1 -1
- package/dist/{Pii-B_gBVAak.mjs → Pii-DrrtEz7U.mjs} +2 -2
- package/dist/{Pii-B_gBVAak.mjs.map → Pii-DrrtEz7U.mjs.map} +1 -1
- package/dist/Pii.d.mts +1 -1
- package/dist/PillShell.d.mts +85 -0
- package/dist/{Popover-D4_aqKlm.mjs → Popover-BkDpDdaO.mjs} +5 -5
- package/dist/{Popover-D4_aqKlm.mjs.map → Popover-BkDpDdaO.mjs.map} +1 -1
- package/dist/{Popover-jHZICnQt.cjs → Popover-BzgMAhsX.cjs} +4 -4
- package/dist/{Popover-jHZICnQt.cjs.map → Popover-BzgMAhsX.cjs.map} +1 -1
- package/dist/Popover.d.mts +1 -1
- package/dist/ProgressBar.d.mts +1 -1
- package/dist/ProgressIndicator.d.mts +1 -1
- package/dist/{PseudoClassProps--MiDp2l2.d.mts → PseudoClassProps-DLxPxo7T.d.mts} +1 -1
- package/dist/PseudoClassProps.d.mts +1 -1
- package/dist/{Radio-BN9FLRHy.cjs → Radio-BrTzy7-C.cjs} +3 -3
- package/dist/{Radio-BN9FLRHy.cjs.map → Radio-BrTzy7-C.cjs.map} +1 -1
- package/dist/{Radio-DnnJl87b.mjs → Radio-CSX2eO-O.mjs} +3 -3
- package/dist/{Radio-DnnJl87b.mjs.map → Radio-CSX2eO-O.mjs.map} +1 -1
- package/dist/{RadioGroup-BDHhFlmy.mjs → RadioGroup-CnK0opBd.mjs} +3 -3
- package/dist/{RadioGroup-BDHhFlmy.mjs.map → RadioGroup-CnK0opBd.mjs.map} +1 -1
- package/dist/{RadioGroup-D28hdlhz.cjs → RadioGroup-CriF2InQ.cjs} +3 -3
- package/dist/{RadioGroup-D28hdlhz.cjs.map → RadioGroup-CriF2InQ.cjs.map} +1 -1
- package/dist/{RadioGroup-BDO3pTgP.d.cts → RadioGroup-DKqbSfju.d.mts} +1 -1
- package/dist/{RadioGroup-DfhvXpi3.d.mts → RadioGroup-XYWjprsv.d.cts} +1 -1
- package/dist/RadioGroup.d.cts +1 -1
- package/dist/RadioGroup.d.mts +1 -1
- package/dist/RangeCalendar.d.mts +1 -1
- package/dist/RangeCalendarPresets.d.mts +1 -1
- package/dist/RuleEditorFields.d.mts +37 -0
- package/dist/RulesPill.d.mts +65 -0
- package/dist/{SearchInput-2lsv5_R6.d.mts → SearchInput-Bp2yjaE1.d.mts} +3 -3
- package/dist/{SearchInput-B6d2Qyzb.d.cts → SearchInput-CTI59-NW.d.cts} +2 -2
- package/dist/SearchInput.d.cts +1 -1
- package/dist/SearchInput.d.mts +1 -1
- package/dist/Section.d.mts +1 -1
- package/dist/{SectionHeading-BLDimf5d.d.mts → SectionHeading-C6hjGdK-.d.cts} +2 -2
- package/dist/{SectionHeading-DZyfNPn0.d.cts → SectionHeading-Q-rx2LEd.d.mts} +2 -2
- package/dist/SectionHeading.d.cts +1 -1
- package/dist/SectionHeading.d.mts +1 -1
- package/dist/{SegmentedControl-DTkFgjac.cjs → SegmentedControl-C2XZF0ys.cjs} +8 -7
- package/dist/SegmentedControl-C2XZF0ys.cjs.map +1 -0
- package/dist/{SegmentedControl-pRkMcMXe.mjs → SegmentedControl-CgDQIVll.mjs} +8 -7
- package/dist/SegmentedControl-CgDQIVll.mjs.map +1 -0
- package/dist/{SegmentedControl-1QD2zH3l.d.mts → SegmentedControl-DrjdUOU9.d.mts} +2 -2
- package/dist/{SegmentedControl-C51htGBp.d.cts → SegmentedControl-nHIK1ZvR.d.cts} +1 -1
- package/dist/SegmentedControl.d.cts +1 -1
- package/dist/SegmentedControl.d.mts +1 -1
- package/dist/{Select-XN23Ui_j.d.cts → Select-2KbtKawr.d.mts} +11 -5
- package/dist/{Select-BysJW9mH.d.mts → Select-BH3hXMxp.d.cts} +11 -5
- package/dist/{Select-CowngLd7.cjs → Select-BurtT6bN.cjs} +13 -10
- package/dist/Select-BurtT6bN.cjs.map +1 -0
- package/dist/{Select-Fg2z5J-p.mjs → Select-cPEORy70.mjs} +14 -11
- package/dist/Select-cPEORy70.mjs.map +1 -0
- package/dist/Select.d.cts +1 -1
- package/dist/Select.d.mts +1 -1
- package/dist/Slider.d.mts +1 -1
- package/dist/SliderRHF.d.mts +1 -1
- package/dist/SliderThumb.d.mts +1 -1
- package/dist/{Spacer-GYHbk-dS.cjs → Spacer-B2CerL6e.cjs} +1 -1
- package/dist/{Spacer-GYHbk-dS.cjs.map → Spacer-B2CerL6e.cjs.map} +1 -1
- package/dist/{Spacer-Ln3KChb5.mjs → Spacer-CJp0lwkL.mjs} +1 -1
- package/dist/{Spacer-Ln3KChb5.mjs.map → Spacer-CJp0lwkL.mjs.map} +1 -1
- package/dist/{Spinner-DaSlM4kU.cjs → Spinner-B6bx70yR.cjs} +1 -1
- package/dist/{Spinner-DaSlM4kU.cjs.map → Spinner-B6bx70yR.cjs.map} +1 -1
- package/dist/{Spinner-D9-u_5zS.mjs → Spinner-BEPF42RW.mjs} +1 -1
- package/dist/{Spinner-D9-u_5zS.mjs.map → Spinner-BEPF42RW.mjs.map} +1 -1
- package/dist/{Stack-ckRN7LrP.mjs → Stack-Bl54WmhY.mjs} +1 -1
- package/dist/{Stack-ckRN7LrP.mjs.map → Stack-Bl54WmhY.mjs.map} +1 -1
- package/dist/{Stack-B5VYkPBe.d.cts → Stack-BzVkf3cz.d.cts} +1 -1
- package/dist/{Stack-Du7tLSPC.d.mts → Stack-CkV7k7TE.d.mts} +2 -2
- package/dist/{Stack-BF-9YjA1.cjs → Stack-UeifHn3c.cjs} +1 -1
- package/dist/{Stack-BF-9YjA1.cjs.map → Stack-UeifHn3c.cjs.map} +1 -1
- package/dist/Stack.d.cts +1 -1
- package/dist/Stack.d.mts +1 -1
- package/dist/StatusIndicator.d.cts +31 -3
- package/dist/StatusIndicator.d.mts +32 -4
- package/dist/{Sticker-XfyoDEod.d.mts → Sticker-Bs25ISz7.d.mts} +5 -5
- package/dist/{Sticker-D9HjFQ6e.d.cts → Sticker-CI-XxUmM.d.cts} +3 -3
- package/dist/{Sticker-D0jIU2Dt.cjs → Sticker-CIoseh9f.cjs} +3 -3
- package/dist/{Sticker-D0jIU2Dt.cjs.map → Sticker-CIoseh9f.cjs.map} +1 -1
- package/dist/{Sticker-B0TbkV4o.mjs → Sticker-DbqKLe33.mjs} +3 -3
- package/dist/{Sticker-B0TbkV4o.mjs.map → Sticker-DbqKLe33.mjs.map} +1 -1
- package/dist/Sticker.d.cts +1 -1
- package/dist/Sticker.d.mts +1 -1
- package/dist/StringValueControl.d.mts +39 -0
- package/dist/SwatchPicker.d.mts +1 -1
- package/dist/Tab.d.mts +2 -2
- package/dist/{TabBar-D8WCGYd5.cjs → TabBar-PFaDNRw0.cjs} +6 -4
- package/dist/TabBar-PFaDNRw0.cjs.map +1 -0
- package/dist/{TabBar-CAv10XAF.mjs → TabBar-zSA8kyE6.mjs} +6 -4
- package/dist/TabBar-zSA8kyE6.mjs.map +1 -0
- package/dist/TabBarIconButton.d.mts +1 -1
- package/dist/{Table-D4SMuVIR.d.cts → Table-BNScLj9g.d.mts} +1 -1
- package/dist/{Table-CGtyWI6V.mjs → Table-D8IcpACv.mjs} +6 -6
- package/dist/{Table-CGtyWI6V.mjs.map → Table-D8IcpACv.mjs.map} +1 -1
- package/dist/{Table-C8D4lpSa.d.mts → Table-DXqyNohc.d.cts} +1 -1
- package/dist/{Table-B3FJsvXP.cjs → Table-DxLvJYWK.cjs} +6 -6
- package/dist/{Table-B3FJsvXP.cjs.map → Table-DxLvJYWK.cjs.map} +1 -1
- package/dist/Table.d.cts +1 -1
- package/dist/Table.d.mts +1 -1
- package/dist/{TableBody-0cd50RuX.d.mts → TableBody-CrDNMyT8.d.mts} +1 -1
- package/dist/{TableBody-BLmn5h3y.d.cts → TableBody-CxwHmdNb.d.cts} +1 -1
- package/dist/TableBody.d.cts +1 -1
- package/dist/TableBody.d.mts +1 -1
- package/dist/{TableBox-D_B9atn2.cjs → TableBox-CiuXCXyv.cjs} +229 -61
- package/dist/TableBox-CiuXCXyv.cjs.map +1 -0
- package/dist/{TableBox-CPCWzwSl.mjs → TableBox-luasMC73.mjs} +230 -62
- package/dist/TableBox-luasMC73.mjs.map +1 -0
- package/dist/TableBox.d.cts +2 -2
- package/dist/TableBox.d.mts +2 -2
- package/dist/{TableBoxCell-CEvOcGYa.d.mts → TableBoxCell-By1xE6_Q.d.mts} +28 -11
- package/dist/{TableBoxCell-DJz423uN.d.cts → TableBoxCell-DIMenSwX.d.cts} +27 -10
- package/dist/TableBoxCell.d.cts +1 -1
- package/dist/TableBoxCell.d.mts +1 -1
- package/dist/TableBoxField.d.cts +20 -5
- package/dist/TableBoxField.d.mts +20 -6
- package/dist/{TableBoxRow-LyHvITwZ.d.mts → TableBoxRow-CsRPRPQ-.d.mts} +13 -4
- package/dist/{TableBoxRow-CZwVgei6.d.cts → TableBoxRow-RNQ6wJLh.d.cts} +12 -3
- package/dist/TableBoxRow.d.cts +1 -1
- package/dist/TableBoxRow.d.mts +1 -1
- package/dist/{TableBoxRowActions-VDAOxNDG.d.cts → TableBoxRowActions-CYpoPhYZ.d.cts} +2 -2
- package/dist/{TableBoxRowActions-C8SAxrYG.d.mts → TableBoxRowActions-DMYOI8Do.d.mts} +2 -2
- package/dist/TableBoxRowActions.d.cts +1 -1
- package/dist/TableBoxRowActions.d.mts +1 -1
- package/dist/TableBoxRowContext.d.mts +5 -3
- package/dist/TableCell.d.cts +1 -1
- package/dist/TableCell.d.mts +1 -1
- package/dist/{TableControls-B4gpAvl7.d.mts → TableControls-BehR7CKS.d.mts} +4 -4
- package/dist/TableControls-BtXK7Ils.cjs +3995 -0
- package/dist/TableControls-BtXK7Ils.cjs.map +1 -0
- package/dist/{TableControls-BG0OKwjj.d.cts → TableControls-CLNF0AJG.d.cts} +3 -3
- package/dist/TableControls-DHMs7o58.mjs +3859 -0
- package/dist/TableControls-DHMs7o58.mjs.map +1 -0
- package/dist/TableControls.d.cts +1 -1
- package/dist/TableControls.d.mts +1 -1
- package/dist/{TableControlsContext-NdzzGevw.d.mts → TableControlsContext-DAZx6Rrj.d.cts} +2 -2
- package/dist/{TableControlsContext-CanIAaLW.d.cts → TableControlsContext-D_SBFZN1.d.mts} +2 -2
- package/dist/TableControlsContext.d.cts +1 -1
- package/dist/TableControlsContext.d.mts +1 -1
- package/dist/{TableHead-CuHNOeCj.d.mts → TableHead-Ch4Ho3LW.d.cts} +1 -1
- package/dist/{TableHead-roexW17v.d.cts → TableHead-LxvG0e9k.d.mts} +1 -1
- package/dist/TableHead.d.cts +1 -1
- package/dist/TableHead.d.mts +1 -1
- package/dist/{TableHeaderCell-Cflho9R2.d.mts → TableHeaderCell-Br8ZE3Xe.d.mts} +1 -1
- package/dist/{TableHeaderCell-BTGuqVXy.d.cts → TableHeaderCell-Crsa2R2g.d.cts} +1 -1
- package/dist/TableHeaderCell.d.cts +1 -1
- package/dist/TableHeaderCell.d.mts +1 -1
- package/dist/{TableLayout-BvVsuUVb.d.cts → TableLayout-Ct0kepcA.d.cts} +1 -1
- package/dist/{TableLayout-D9BGrjRs.d.mts → TableLayout-nwy094C_.d.mts} +1 -1
- package/dist/TableLayout.d.cts +1 -1
- package/dist/TableLayout.d.mts +1 -1
- package/dist/{TableRow-CjQmDmfx.d.mts → TableRow-BvS4yjo6.d.mts} +2 -2
- package/dist/{TableRow-BfGJSGZH.d.cts → TableRow-DVTPqT4w.d.cts} +1 -1
- package/dist/TableRow.d.cts +1 -1
- package/dist/TableRow.d.mts +1 -1
- package/dist/TableTH.d.cts +1 -1
- package/dist/TableTH.d.mts +1 -1
- package/dist/{TableViewConfigurationDropdown-DMeFkVUa.d.mts → TableViewConfigurationDropdown-BYPh4dIP.d.mts} +2 -2
- package/dist/{TableViewConfigurationDropdown-BaQ1IsYM.d.cts → TableViewConfigurationDropdown-CaiOGyrU.d.cts} +2 -2
- package/dist/TableViewConfigurationDropdown.d.cts +1 -1
- package/dist/TableViewConfigurationDropdown.d.mts +1 -1
- package/dist/TechnicalSticker.d.mts +1 -1
- package/dist/{Text-CPd0r0w2.d.mts → Text-DNPbAuUG.d.mts} +3 -3
- package/dist/{Text-CzN0DOXV.cjs → Text-G9QmYf0P.cjs} +1 -1
- package/dist/{Text-CzN0DOXV.cjs.map → Text-G9QmYf0P.cjs.map} +1 -1
- package/dist/{Text-hEoPeOc9.mjs → Text-RcEfNFLx.mjs} +1 -1
- package/dist/{Text-hEoPeOc9.mjs.map → Text-RcEfNFLx.mjs.map} +1 -1
- package/dist/Text.d.mts +1 -1
- package/dist/{TextArea-Dl9_Zizx.d.cts → TextArea-B89wtTsF.d.mts} +2 -2
- package/dist/{TextArea-p3oek3Zq.cjs → TextArea-BNiipQDb.cjs} +3 -3
- package/dist/{TextArea-p3oek3Zq.cjs.map → TextArea-BNiipQDb.cjs.map} +1 -1
- package/dist/{TextArea-J1MD_8rK.d.mts → TextArea-CHW9FKM4.d.cts} +2 -2
- package/dist/{TextArea-CJJlw3rV.mjs → TextArea-NCLsW-03.mjs} +3 -3
- package/dist/{TextArea-CJJlw3rV.mjs.map → TextArea-NCLsW-03.mjs.map} +1 -1
- package/dist/TextArea.d.cts +1 -1
- package/dist/TextArea.d.mts +1 -1
- package/dist/{TextUnderlineTooltip-D8GFsk5C.d.mts → TextUnderlineTooltip-5-0D8s4z.d.mts} +3 -3
- package/dist/{TextUnderlineTooltip-BDMs4iXn.d.cts → TextUnderlineTooltip-DMEyOu8C.d.cts} +2 -2
- package/dist/TextUnderlineTooltip.d.cts +1 -1
- package/dist/TextUnderlineTooltip.d.mts +1 -1
- package/dist/Tile.d.cts +1 -1
- package/dist/Tile.d.mts +2 -2
- package/dist/Toast.d.cts +1 -1
- package/dist/Toast.d.mts +1 -1
- package/dist/{Toggle-DOo0cLmI.mjs → Toggle-Bie9zCem.mjs} +5 -5
- package/dist/{Toggle-DOo0cLmI.mjs.map → Toggle-Bie9zCem.mjs.map} +1 -1
- package/dist/{Toggle-7o-n5vF6.cjs → Toggle-Ce-rF8Xp.cjs} +5 -5
- package/dist/{Toggle-7o-n5vF6.cjs.map → Toggle-Ce-rF8Xp.cjs.map} +1 -1
- package/dist/{Toggle-CY67yh4T.d.mts → Toggle-DzYLgxGk.d.mts} +1 -1
- package/dist/{Toggle-Dw4hisXm.d.cts → Toggle-tbIebQWC.d.cts} +1 -1
- package/dist/Toggle.d.cts +1 -1
- package/dist/Toggle.d.mts +1 -1
- package/dist/{Tooltip-CE1oH6Iz.d.cts → Tooltip--eDrrk2r.d.cts} +2 -2
- package/dist/{Tooltip-CDvOu1UN.mjs → Tooltip-C6AR1xT-.mjs} +3 -3
- package/dist/{Tooltip-CDvOu1UN.mjs.map → Tooltip-C6AR1xT-.mjs.map} +1 -1
- package/dist/{Tooltip-DtWJO_kU.d.mts → Tooltip-CUC-JuRL.d.mts} +3 -3
- package/dist/{Tooltip-vylydKVv.cjs → Tooltip-olP3rdUO.cjs} +3 -3
- package/dist/{Tooltip-vylydKVv.cjs.map → Tooltip-olP3rdUO.cjs.map} +1 -1
- package/dist/Tooltip.d.cts +1 -1
- package/dist/Tooltip.d.mts +1 -1
- package/dist/{TruncatedText-BBoWmFii.d.mts → TruncatedText-BJnowVTx.d.mts} +1 -1
- package/dist/{TruncatedText-DMpR-yaJ.d.cts → TruncatedText-Dit2NJI6.d.cts} +1 -1
- package/dist/components/Attribute/index.cjs +6 -6
- package/dist/components/Attribute/index.mjs +7 -7
- package/dist/components/Attribute/index.mjs.map +1 -1
- package/dist/components/Badge/index.cjs +1 -1
- package/dist/components/Badge/index.mjs +1 -1
- package/dist/components/Banner/index.cjs +1 -1
- package/dist/components/Banner/index.mjs +1 -1
- package/dist/components/Breadcrumbs/index.cjs +7 -0
- package/dist/components/Breadcrumbs/index.d.cts +2 -0
- package/dist/components/Breadcrumbs/index.d.mts +2 -0
- package/dist/components/Breadcrumbs/index.mjs +2 -0
- package/dist/components/Button/index.cjs +1 -1
- package/dist/components/Button/index.d.mts +2 -2
- package/dist/components/Button/index.mjs +1 -1
- package/dist/components/ButtonGroup/index.cjs +3 -3
- package/dist/components/ButtonGroup/index.mjs +3 -3
- package/dist/components/Card/index.cjs +1 -1
- package/dist/components/Card/index.d.cts +2 -2
- package/dist/components/Card/index.d.mts +2 -2
- package/dist/components/Card/index.mjs +1 -1
- package/dist/components/CardV2/index.cjs +3 -432
- package/dist/components/CardV2/index.d.cts +1 -1
- package/dist/components/CardV2/index.d.mts +1 -1
- package/dist/components/CardV2/index.mjs +1 -427
- package/dist/components/Checkbox/index.cjs +1 -1
- package/dist/components/Checkbox/index.d.cts +1 -1
- package/dist/components/Checkbox/index.d.mts +1 -1
- package/dist/components/Checkbox/index.mjs +1 -1
- package/dist/components/Combobox/index.cjs +1 -1
- package/dist/components/Combobox/index.d.cts +2 -2
- package/dist/components/Combobox/index.d.mts +2 -2
- package/dist/components/Combobox/index.mjs +1 -1
- package/dist/components/ConfigFormGroup/index.cjs +2 -2
- package/dist/components/ConfigFormGroup/index.mjs +2 -2
- package/dist/components/DataTable/TableControls/index.cjs +15 -1
- package/dist/components/DataTable/TableControls/index.d.cts +16 -6
- package/dist/components/DataTable/TableControls/index.d.mts +16 -6
- package/dist/components/DataTable/TableControls/index.mjs +2 -2
- package/dist/components/DataTable/index.cjs +15 -16
- package/dist/components/DataTable/index.cjs.map +1 -1
- package/dist/components/DataTable/index.mjs +25 -26
- package/dist/components/DataTable/index.mjs.map +1 -1
- package/dist/components/DataTable/types.d.cts +2 -2
- package/dist/components/DataTable/types.d.mts +2 -2
- package/dist/components/DatePicker/index.cjs +6 -898
- package/dist/components/DatePicker/index.mjs +1 -890
- package/dist/components/DropdownButton/index.cjs +1 -1
- package/dist/components/DropdownButton/index.mjs +1 -1
- package/dist/components/DropdownMenu/index.cjs +1 -1
- package/dist/components/DropdownMenu/index.d.cts +1 -1
- package/dist/components/DropdownMenu/index.d.mts +1 -1
- package/dist/components/DropdownMenu/index.mjs +1 -1
- package/dist/components/FormControl/index.cjs +2 -2
- package/dist/components/FormControl/index.d.cts +2 -2
- package/dist/components/FormControl/index.d.mts +2 -2
- package/dist/components/FormControl/index.mjs +2 -2
- package/dist/components/FormField/index.cjs +15 -468
- package/dist/components/FormField/index.d.cts +3 -3
- package/dist/components/FormField/index.d.mts +3 -3
- package/dist/components/FormField/index.mjs +1 -452
- package/dist/components/HoverCopyableField/index.cjs +1 -1
- package/dist/components/HoverCopyableField/index.mjs +1 -1
- package/dist/components/ImageTile/index.cjs +8 -8
- package/dist/components/ImageTile/index.mjs +8 -8
- package/dist/components/Input/index.cjs +1 -1
- package/dist/components/Input/index.d.cts +1 -1
- package/dist/components/Input/index.d.mts +1 -1
- package/dist/components/Input/index.mjs +1 -1
- package/dist/components/Link/index.cjs +1 -1
- package/dist/components/Link/index.mjs +1 -1
- package/dist/components/ListBox/index.cjs +1 -1
- package/dist/components/ListBox/index.d.cts +4 -2
- package/dist/components/ListBox/index.d.mts +4 -2
- package/dist/components/ListBox/index.mjs +1 -1
- package/dist/components/LoadingShimmer/index.cjs +1 -1
- package/dist/components/LoadingShimmer/index.mjs +1 -1
- package/dist/components/MaskedInput/index.cjs +2 -2
- package/dist/components/MaskedInput/index.mjs +2 -2
- package/dist/components/Menu/index.cjs +1 -1
- package/dist/components/Menu/index.d.cts +1 -1
- package/dist/components/Menu/index.d.mts +1 -1
- package/dist/components/Menu/index.mjs +1 -1
- package/dist/components/Modal/index.cjs +13 -9
- package/dist/components/Modal/index.cjs.map +1 -1
- package/dist/components/Modal/index.d.cts +2 -2
- package/dist/components/Modal/index.d.mts +2 -2
- package/dist/components/Modal/index.mjs +13 -9
- package/dist/components/Modal/index.mjs.map +1 -1
- package/dist/components/PageHeader/index.cjs +3 -67
- package/dist/components/PageHeader/index.mjs +1 -63
- package/dist/components/Pagination/index.cjs +1 -1
- package/dist/components/Pagination/index.d.cts +3 -3
- package/dist/components/Pagination/index.d.mts +3 -3
- package/dist/components/Pagination/index.mjs +1 -1
- package/dist/components/Pii/index.cjs +1 -1
- package/dist/components/Pii/index.mjs +1 -1
- package/dist/components/Popover/index.cjs +1 -1
- package/dist/components/Popover/index.mjs +1 -1
- package/dist/components/ProgressBar/index.cjs +1 -1
- package/dist/components/ProgressBar/index.mjs +1 -1
- package/dist/components/Radio/index.cjs +1 -1
- package/dist/components/Radio/index.mjs +1 -1
- package/dist/components/RadioGroup/index.cjs +1 -1
- package/dist/components/RadioGroup/index.d.cts +1 -1
- package/dist/components/RadioGroup/index.d.mts +1 -1
- package/dist/components/RadioGroup/index.mjs +1 -1
- package/dist/components/SegmentedControl/index.cjs +1 -1
- package/dist/components/SegmentedControl/index.d.cts +1 -1
- package/dist/components/SegmentedControl/index.d.mts +1 -1
- package/dist/components/SegmentedControl/index.mjs +1 -1
- package/dist/components/Select/index.cjs +1 -1
- package/dist/components/Select/index.d.cts +1 -1
- package/dist/components/Select/index.d.mts +1 -1
- package/dist/components/Select/index.mjs +1 -1
- package/dist/components/Slider/index.cjs +2 -2
- package/dist/components/Slider/index.mjs +2 -2
- package/dist/components/Spacer/index.cjs +1 -1
- package/dist/components/Spacer/index.mjs +1 -1
- package/dist/components/Spinner/index.cjs +1 -1
- package/dist/components/Spinner/index.mjs +1 -1
- package/dist/components/Stack/index.cjs +1 -1
- package/dist/components/Stack/index.d.cts +1 -1
- package/dist/components/Stack/index.d.mts +1 -1
- package/dist/components/Stack/index.mjs +1 -1
- package/dist/components/StatusIndicator/index.cjs +60 -10
- package/dist/components/StatusIndicator/index.cjs.map +1 -1
- package/dist/components/StatusIndicator/index.d.cts +2 -2
- package/dist/components/StatusIndicator/index.d.mts +2 -2
- package/dist/components/StatusIndicator/index.mjs +61 -12
- package/dist/components/StatusIndicator/index.mjs.map +1 -1
- package/dist/components/Sticker/index.cjs +1 -1
- package/dist/components/Sticker/index.d.cts +1 -1
- package/dist/components/Sticker/index.d.mts +1 -1
- package/dist/components/Sticker/index.mjs +1 -1
- package/dist/components/TabBar/index.cjs +1 -1
- package/dist/components/TabBar/index.mjs +1 -1
- package/dist/components/Table/index.cjs +1 -1
- package/dist/components/Table/index.d.cts +7 -7
- package/dist/components/Table/index.d.mts +7 -7
- package/dist/components/Table/index.mjs +1 -1
- package/dist/components/TableBox/index.cjs +1 -1
- package/dist/components/TableBox/index.d.cts +2 -2
- package/dist/components/TableBox/index.d.mts +2 -2
- package/dist/components/TableBox/index.mjs +1 -1
- package/dist/components/TechnicalSticker/index.cjs +4 -4
- package/dist/components/TechnicalSticker/index.mjs +4 -4
- package/dist/components/Text/index.cjs +1 -1
- package/dist/components/Text/index.d.mts +1 -1
- package/dist/components/Text/index.mjs +1 -1
- package/dist/components/TextArea/index.cjs +1 -1
- package/dist/components/TextArea/index.d.cts +1 -1
- package/dist/components/TextArea/index.d.mts +1 -1
- package/dist/components/TextArea/index.mjs +1 -1
- package/dist/components/Tile/index.cjs +4 -4
- package/dist/components/Tile/index.mjs +4 -4
- package/dist/components/Toast/index.cjs +1 -1
- package/dist/components/Toast/index.mjs +1 -1
- package/dist/components/Toggle/index.cjs +1 -1
- package/dist/components/Toggle/index.d.cts +1 -1
- package/dist/components/Toggle/index.d.mts +1 -1
- package/dist/components/Toggle/index.mjs +1 -1
- package/dist/components/Tooltip/index.cjs +1 -1
- package/dist/components/Tooltip/index.d.cts +3 -3
- package/dist/components/Tooltip/index.d.mts +3 -3
- package/dist/components/Tooltip/index.mjs +1 -1
- package/dist/compound-expression.d.mts +9 -0
- package/dist/{constants-D5CkrHza.d.cts → constants-B6c8S033.d.cts} +1 -1
- package/dist/{constants-CnkpDWTv.d.mts → constants-BASogfT_.d.mts} +1 -1
- package/dist/constants.d.cts +1 -1
- package/dist/constants.d.mts +1 -1
- package/dist/containers.d.mts +8 -0
- package/dist/display.d.mts +8 -0
- package/dist/fieldPickerOptions.d.mts +29 -0
- package/dist/filter-size-context-Blw6Sd69.d.mts +52 -0
- package/dist/filter-size-context-CIw0Zi6r.d.cts +52 -0
- package/dist/filter-size-context.d.cts +2 -0
- package/dist/filter-size-context.d.mts +2 -0
- package/dist/helpers.d.cts +1 -1
- package/dist/helpers.d.mts +1 -1
- package/dist/index-4_B2yRzD.d.mts +7 -0
- package/dist/{index-Bp3vEph6.d.cts → index-Bw8QVu_o.d.cts} +3 -3
- package/dist/index-C9EIpBh8.d.cts +7 -0
- package/dist/index-D34XP8vp.d.cts +54 -0
- package/dist/{index-BwWgmaXZ.d.mts → index-DDbjWdk0.d.mts} +4 -4
- package/dist/index-IlwGkCAe2.d.mts +54 -0
- package/dist/index-sQecLSpR.d.cts +1 -0
- package/dist/index-sViox9YW.d.mts +1 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index2.d.cts +1 -1
- package/dist/index2.d.mts +1 -1
- package/dist/index3.d.cts +4 -1
- package/dist/index3.d.mts +4 -1
- package/dist/index4.d.cts +11 -0
- package/dist/index4.d.mts +11 -0
- package/dist/index5.d.cts +1 -0
- package/dist/index5.d.mts +2 -0
- package/dist/index6.d.mts +1 -0
- package/dist/{input-FEaQ66an.d.mts → input-Bv5fKt5J.d.mts} +3 -3
- package/dist/{input-Bm_anD8X.d.cts → input-DPB0cX2m.d.cts} +2 -2
- package/dist/inputFields.d.cts +10 -10
- package/dist/inputFields.d.mts +11 -11
- package/dist/intersperse-_0nQM5Rb.cjs +26 -0
- package/dist/intersperse-_0nQM5Rb.cjs.map +1 -0
- package/dist/intersperse-vOYvFOfF.mjs +21 -0
- package/dist/intersperse-vOYvFOfF.mjs.map +1 -0
- package/dist/layouts/LayoutLoadingShimmer/index.cjs +139 -0
- package/dist/layouts/LayoutLoadingShimmer/index.cjs.map +1 -0
- package/dist/layouts/LayoutLoadingShimmer/index.d.cts +2 -0
- package/dist/layouts/LayoutLoadingShimmer/index.d.mts +2 -0
- package/dist/layouts/LayoutLoadingShimmer/index.mjs +133 -0
- package/dist/layouts/LayoutLoadingShimmer/index.mjs.map +1 -0
- package/dist/lib/MiddleTruncatedText.cjs +1 -1
- package/dist/lib/MiddleTruncatedText.mjs +1 -1
- package/dist/lib/PDSProvider.d.mts +1 -1
- package/dist/lib/TruncatedText.cjs +1 -1
- package/dist/lib/TruncatedText.d.cts +1 -1
- package/dist/lib/TruncatedText.d.mts +1 -1
- package/dist/lib/TruncatedText.mjs +1 -1
- package/dist/lib/TruncatedTextWithTooltip.cjs +2 -2
- package/dist/lib/TruncatedTextWithTooltip.d.cts +1 -1
- package/dist/lib/TruncatedTextWithTooltip.d.mts +1 -1
- package/dist/lib/TruncatedTextWithTooltip.mjs +2 -2
- package/dist/lib/message.d.cts +1 -1
- package/dist/lib/message.d.mts +1 -1
- package/dist/lib/pseudoClass.d.mts +1 -1
- package/dist/{message-SITKO6JZ.d.mts → message-BEgtEjQC.d.cts} +1 -1
- package/dist/{message-CWEGi3Xg.d.cts → message-CMHvlPGn.d.mts} +1 -1
- package/dist/mixins/focus.d.mts +1 -1
- package/dist/mixins/hoverable.d.mts +2 -2
- package/dist/mixins/input.d.cts +1 -1
- package/dist/mixins/input.d.mts +1 -1
- package/dist/mixins/menuItem.d.mts +2 -2
- package/dist/mixins/text.d.mts +1 -1
- package/dist/operators-1qE2GpC7.d.cts +43 -0
- package/dist/operators-Bo31xlc2.d.mts +43 -0
- package/dist/operators.d.cts +2 -0
- package/dist/operators.d.mts +2 -0
- package/dist/{text-BEKhEWTa.d.mts → text-B21s9mRZ.d.mts} +1 -1
- package/dist/{theme-q9fNLtcA.d.mts → theme-BzBpJXhO.d.mts} +1 -1
- package/dist/theme.d.mts +1 -1
- package/dist/types-B_HHNlia.d.cts +158 -0
- package/dist/types-CCvc590y.d.cts +45 -0
- package/dist/{types-CmnKGG5I.d.cts → types-CkiXVJeR.d.cts} +1 -1
- package/dist/types-D58IoRx6.d.mts +158 -0
- package/dist/types-D7ppqnNj.d.mts +45 -0
- package/dist/{types-BG21GgBe.d.cts → types-DEh_pXgS.d.mts} +1 -1
- package/dist/{types-ChCqSD9N.d.mts → types-SYSKcZKW.d.cts} +1 -1
- package/dist/{types-DMjr-u_o.d.mts → types-TV9nVfSe.d.mts} +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.mts +1 -1
- package/dist/types2.d.cts +2 -2
- package/dist/types2.d.mts +2 -2
- package/dist/types3.d.cts +2 -0
- package/dist/types3.d.mts +2 -0
- package/dist/types4.d.cts +2 -0
- package/dist/types4.d.mts +2 -0
- package/dist/use-rules-popover-selection.d.mts +19 -0
- package/dist/useColumnWidthLock.d.mts +2 -2
- package/dist/useComboboxFooterAction.d.mts +47 -0
- package/dist/useDelayedInlineEditError.d.mts +12 -0
- package/dist/useFilterStateV2-DCQghouq.d.mts +93 -0
- package/dist/useFilterStateV2-DbPwX5N1.d.cts +93 -0
- package/dist/useFilterStateV2.d.cts +2 -0
- package/dist/useFilterStateV2.d.mts +2 -0
- package/dist/{useFormField-DLGEZ1kD.d.cts → useFormField--Q8ZdpW5.d.cts} +2 -2
- package/dist/{useFormField-BpYf3CAW.cjs → useFormField-BZlArnsI.cjs} +2 -2
- package/dist/{useFormField-BpYf3CAW.cjs.map → useFormField-BZlArnsI.cjs.map} +1 -1
- package/dist/{useFormField-CVmt-ti2.d.mts → useFormField-D8mLsiQc.d.mts} +2 -2
- package/dist/{useFormField-D0RTrPld.mjs → useFormField-DhYd6bNn.mjs} +2 -2
- package/dist/{useFormField-D0RTrPld.mjs.map → useFormField-DhYd6bNn.mjs.map} +1 -1
- package/dist/useFormField.d.cts +1 -1
- package/dist/useFormField.d.mts +1 -1
- package/dist/{useFormGroup-DQJdKaL9.cjs → useFormGroup-Bmnzgq0S.cjs} +1 -1
- package/dist/{useFormGroup-DQJdKaL9.cjs.map → useFormGroup-Bmnzgq0S.cjs.map} +1 -1
- package/dist/{useFormGroup-z0epBxXn.mjs → useFormGroup-DWVW7A9f.mjs} +1 -1
- package/dist/{useFormGroup-z0epBxXn.mjs.map → useFormGroup-DWVW7A9f.mjs.map} +1 -1
- package/dist/{useTableBoxDragAndDrop-DXqy_AMP.d.mts → useTableBoxDragAndDrop-BMo-iaUF.d.mts} +1 -1
- package/dist/{useTableBoxDragAndDrop-hrESBhKb.d.cts → useTableBoxDragAndDrop-DnbUToce.d.cts} +1 -1
- package/dist/useTableBoxDragAndDrop.d.cts +1 -1
- package/dist/useTableBoxDragAndDrop.d.mts +1 -1
- package/dist/{useToggle-3k3EkV8V.mjs → useToggle-Bo3yd14Y.mjs} +1 -1
- package/dist/{useToggle-3k3EkV8V.mjs.map → useToggle-Bo3yd14Y.mjs.map} +1 -1
- package/dist/{useToggle-CSkZWXtQ.cjs → useToggle-DARO917w.cjs} +1 -1
- package/dist/{useToggle-CSkZWXtQ.cjs.map → useToggle-DARO917w.cjs.map} +1 -1
- package/dist/useTruncation.d.mts +1 -1
- package/dist/{utilities-C8Jf-xpi.d.cts → utilities-BO_95qFA.d.mts} +1 -1
- package/dist/{utilities-DT-Hh45R.d.mts → utilities-mSlaTrjB.d.cts} +1 -1
- package/dist/utilities.d.cts +1 -1
- package/dist/utilities.d.mts +1 -1
- package/dist/utils/storybook.cjs +1 -1
- package/dist/utils/storybook.mjs +1 -1
- package/dist/utils/testing.cjs +1 -1
- package/dist/utils/testing.d.mts +1 -1
- package/dist/utils/testing.mjs +1 -1
- package/dist/utils.d.mts +22 -5
- package/dist/utils2.d.mts +9 -0
- package/dist/validation-CJymv6JE.d.cts +51 -0
- package/dist/validation-vLh3t2dk.d.mts +51 -0
- package/dist/validation.d.cts +2 -0
- package/dist/validation.d.mts +2 -0
- package/dist/value-control-resolver-B9p25q3G.d.cts +46 -0
- package/dist/value-control-resolver-CE-bqdAl.d.mts +46 -0
- package/dist/value-control-resolver.d.cts +2 -0
- package/dist/value-control-resolver.d.mts +2 -0
- package/dist/variables/spacing.cjs +1 -1
- package/dist/variables/spacing.cjs.map +1 -1
- package/dist/variables/spacing.d.cts +1 -1
- package/dist/variables/spacing.d.mts +1 -1
- package/dist/variables/spacing.mjs +1 -1
- package/dist/variables/spacing.mjs.map +1 -1
- package/package.json +22 -4
- package/dist/Combobox-I44W5oX0.mjs.map +0 -1
- package/dist/Combobox-R3_NDBi3.cjs.map +0 -1
- package/dist/HoverCopyableField-DBkYvwii.mjs.map +0 -1
- package/dist/HoverCopyableField-DyW2iZuo.cjs.map +0 -1
- package/dist/ListBox-B4uoo_PT.cjs.map +0 -1
- package/dist/ListBox-B7sbubm5.mjs.map +0 -1
- package/dist/Pagination-DEZFb552.cjs.map +0 -1
- package/dist/Pagination-F8ofIWnG.mjs.map +0 -1
- package/dist/SegmentedControl-DTkFgjac.cjs.map +0 -1
- package/dist/SegmentedControl-pRkMcMXe.mjs.map +0 -1
- package/dist/Select-CowngLd7.cjs.map +0 -1
- package/dist/Select-Fg2z5J-p.mjs.map +0 -1
- package/dist/TabBar-CAv10XAF.mjs.map +0 -1
- package/dist/TabBar-D8WCGYd5.cjs.map +0 -1
- package/dist/TableBox-CPCWzwSl.mjs.map +0 -1
- package/dist/TableBox-D_B9atn2.cjs.map +0 -1
- package/dist/TableControls-B2ZsNlr6.mjs +0 -1029
- package/dist/TableControls-B2ZsNlr6.mjs.map +0 -1
- package/dist/TableControls-B7QEAzru.cjs +0 -1080
- package/dist/TableControls-B7QEAzru.cjs.map +0 -1
- package/dist/components/CardV2/index.cjs.map +0 -1
- package/dist/components/CardV2/index.mjs.map +0 -1
- package/dist/components/DatePicker/index.cjs.map +0 -1
- package/dist/components/DatePicker/index.mjs.map +0 -1
- package/dist/components/FormField/index.cjs.map +0 -1
- package/dist/components/FormField/index.mjs.map +0 -1
- package/dist/components/PageHeader/index.cjs.map +0 -1
- package/dist/components/PageHeader/index.mjs.map +0 -1
- /package/dist/{index-1-AkepUA.d.mts → index--p6oQT9I.d.mts} +0 -0
- /package/dist/{index-8SLBz2NH.d.mts → index-BL32-cvv.d.cts} +0 -0
- /package/dist/{index-BSVhZrWN.d.mts → index-BR1oNnaF.d.cts} +0 -0
- /package/dist/{index-C4zERU4O.d.cts → index-BXpMZvwA.d.cts} +0 -0
- /package/dist/{index-C6yvNuzO.d.cts → index-BYd3Btr1.d.mts} +0 -0
- /package/dist/{index-CIABef8t.d.mts → index-BhY1qu3m.d.cts} +0 -0
- /package/dist/{index-CP2eJYlK.d.mts → index-C0NRIDsj2.d.cts} +0 -0
- /package/dist/{index-CTBW75wG2.d.cts → index-C1ihAKnV.d.cts} +0 -0
- /package/dist/{index-CiBIKhMu.d.cts → index-CDGG7lCB.d.mts} +0 -0
- /package/dist/{index-Cii4Qb1X.d.mts → index-CFG0_wK4.d.cts} +0 -0
- /package/dist/{index-CspKFJM1.d.cts → index-CKKoxA9V.d.mts} +0 -0
- /package/dist/{index-CswGToRr2.d.mts → index-CNxS4Tlu.d.mts} +0 -0
- /package/dist/{index-D4ELpJXS.d.cts → index-CQ25UCod.d.cts} +0 -0
- /package/dist/{index-D8dPsry7.d.mts → index-CWB6exYm.d.mts} +0 -0
- /package/dist/{index-D9XihFV1.d.mts → index-Cv0jeUQf2.d.cts} +0 -0
- /package/dist/{index-DBQaKYAL2.d.cts → index-CwSIy_W-.d.cts} +0 -0
- /package/dist/{index-DLoAMD9J.d.cts → index-CzItj62a.d.mts} +0 -0
- /package/dist/{index-DZlqSwCF.d.mts → index-D-BciAtf.d.cts} +0 -0
- /package/dist/{index-Dflz2i1X.d.mts → index-D4DUvwPf.d.mts} +0 -0
- /package/dist/{index-Dpmi5zcN.d.cts → index-D5tSVhTd.d.mts} +0 -0
- /package/dist/{index-DwBYcw1r.d.cts → index-D9IqtUDs.d.cts} +0 -0
- /package/dist/{index-LhFpnU-f.d.mts → index-DGsJdI5l.d.mts} +0 -0
- /package/dist/{index-N-OgGgfF.d.mts → index-DM6vVDZD.d.mts} +0 -0
- /package/dist/{index-VmEFGBxN.d.cts → index-DXJd7-2Z.d.mts} +0 -0
- /package/dist/{index-_1bfRgi2.d.cts → index-DkrC-i1D.d.mts} +0 -0
- /package/dist/{index-c55Utzva.d.mts → index-DpEKQcoR.d.cts} +0 -0
- /package/dist/{index-gpCH5158.d.mts → index-DvuJXuAN.d.mts} +0 -0
- /package/dist/{index-kDNN3kX_2.d.mts → index-Iujpc9_6.d.mts} +0 -0
- /package/dist/{index-luywJTzJ.d.mts → index-Oio5ygqN.d.mts} +0 -0
- /package/dist/{index-qCd8f7Um.d.cts → index-_wQ5ClJU.d.cts} +0 -0
- /package/dist/{index-uTS5iDq_.d.cts → index-lIMqGGJz.d.mts} +0 -0
|
@@ -0,0 +1,3859 @@
|
|
|
1
|
+
import { isActive, isFocused, isHovered, isNotDisabled } from "./lib/pseudoClass.mjs";
|
|
2
|
+
import { focusOutline, focusWithinOutline } from "./mixins/focus.mjs";
|
|
3
|
+
import "./variables/transitions.mjs";
|
|
4
|
+
import { paragraphMixins } from "./mixins/text.mjs";
|
|
5
|
+
import { n as Stack, t as HStack } from "./Stack-Bl54WmhY.mjs";
|
|
6
|
+
import { TruncatedText } from "./lib/TruncatedText.mjs";
|
|
7
|
+
import { t as Card_default } from "./Card-BMCB9eFC.mjs";
|
|
8
|
+
import { t as Spacer_default } from "./Spacer-CJp0lwkL.mjs";
|
|
9
|
+
import { i as Paragraph, r as Label$1 } from "./Text-RcEfNFLx.mjs";
|
|
10
|
+
import { a as Tooltip } from "./Tooltip-C6AR1xT-.mjs";
|
|
11
|
+
import { useScrollOverflow } from "./lib/useScrollOverflow.mjs";
|
|
12
|
+
import { Z_INDICES } from "./lib/zindex.mjs";
|
|
13
|
+
import { n as Button, t as IconButton } from "./IconButton-CIWiEsS-.mjs";
|
|
14
|
+
import { c as MenuList, s as MenuItem } from "./Menu-CPYXo61N.mjs";
|
|
15
|
+
import { n as DropdownMenu_default } from "./DropdownMenu-DGYexb7s.mjs";
|
|
16
|
+
import { r as InputGroup } from "./Input-BFI_e7aU.mjs";
|
|
17
|
+
import Input_default from "./components/Input/index.mjs";
|
|
18
|
+
import { t as Combobox } from "./Combobox-BdqU5WLG.mjs";
|
|
19
|
+
import { s as Popover_default } from "./Popover-BkDpDdaO.mjs";
|
|
20
|
+
import { i as TabBar, n as StyledTab, r as Tab } from "./TabBar-zSA8kyE6.mjs";
|
|
21
|
+
import { t as Toggle } from "./Toggle-Bie9zCem.mjs";
|
|
22
|
+
import { t as ListBox } from "./ListBox-BwID8jpk.mjs";
|
|
23
|
+
import { a as ComboboxField, p as FormField } from "./FormField-PeYYRO4s.mjs";
|
|
24
|
+
import { n as DatePicker } from "./DatePicker-DY1YA_Fu.mjs";
|
|
25
|
+
import { BorderRadius, Color, Spacing, Typography } from "@persona/design-tokens";
|
|
26
|
+
import styled, { createGlobalStyle, css } from "styled-components";
|
|
27
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
28
|
+
import { Fragment as Fragment$1, createContext, createElement, forwardRef, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
29
|
+
import { CaretDownIcon, CheckIcon, CircleInfoIcon, CirclePlusIcon, CopyIcon, FilterIcon, MoreIcon, PlusIcon, RotateCcwIcon, SaveIcon, SearchIcon, SettingsIcon, TrashIcon, XIcon } from "@persona/icons";
|
|
30
|
+
import { CaretDownIcon as CaretDownIcon$1, CircleInfoIcon as CircleInfoIcon$1, LockedIcon as LockedIcon$1, MoreIcon as MoreIcon$1, PlusIcon as PlusIcon$1, SearchIcon as SearchIcon$1, XIcon as XIcon$1 } from "@persona/icons/12px";
|
|
31
|
+
import { debounce, union, uniqueId } from "lodash/fp.js";
|
|
32
|
+
import { useId } from "react-aria";
|
|
33
|
+
import { DragIcon as DragIcon$1 } from "@persona/icons/24px";
|
|
34
|
+
import { isEqual, noop, startCase, uniqueId as uniqueId$1 } from "lodash";
|
|
35
|
+
import Fuse from "fuse.js";
|
|
36
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
37
|
+
import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd";
|
|
38
|
+
import { parseDate } from "@internationalized/date";
|
|
39
|
+
import { v4 } from "uuid";
|
|
40
|
+
//#region components/DataTable/FormControlLabel/FormControlLabel.ts
|
|
41
|
+
const FormControlLabel = styled(Label$1).attrs(() => ({
|
|
42
|
+
size: "md",
|
|
43
|
+
weight: "bold"
|
|
44
|
+
}))`
|
|
45
|
+
margin-bottom: ${Spacing[2]};
|
|
46
|
+
transition: ${"200ms"};
|
|
47
|
+
display: inline-block;
|
|
48
|
+
|
|
49
|
+
${({ theme, isDisabled }) => css`
|
|
50
|
+
color: ${isDisabled ? theme.color.text.disabled : theme.color.text.primary};
|
|
51
|
+
`}
|
|
52
|
+
`;
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region components/DataTable/SearchInput/SearchInput.tsx
|
|
55
|
+
const SearchBar = styled.div``;
|
|
56
|
+
const StyledInput = styled(Input_default)`
|
|
57
|
+
${({ theme, flattenDisplay }) => flattenDisplay && css`
|
|
58
|
+
border-width: 0px;
|
|
59
|
+
|
|
60
|
+
&${isActive}, &${isFocused}, &${isHovered}&${isNotDisabled}, &${isHovered}&${isFocused} {
|
|
61
|
+
border-color: ${theme.color.border.input};
|
|
62
|
+
box-shadow: none;
|
|
63
|
+
}
|
|
64
|
+
`}
|
|
65
|
+
`;
|
|
66
|
+
const SearchInput = ({ className, title = "Search", handleInput, handleOnFocus = noop, onBlur = noop, id, value, placeholder, dataTest, autoFocus, useClearIcon = false, disabled = false, disabledTooltip, size = "md", leftIcon = size === "sm" ? /* @__PURE__ */ jsx(SearchIcon$1, {}) : /* @__PURE__ */ jsx(SearchIcon, {}), flattenDisplay = false, maxLength }) => {
|
|
67
|
+
const [innerValue, setInnerValue] = useState(value);
|
|
68
|
+
const handleChange = useCallback(({ target }) => {
|
|
69
|
+
setInnerValue(target.value);
|
|
70
|
+
handleInput(target.value);
|
|
71
|
+
}, [handleInput, setInnerValue]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
setInnerValue(value);
|
|
74
|
+
}, [value]);
|
|
75
|
+
const inputRef = useRef(null);
|
|
76
|
+
const handleClear = useCallback(() => {
|
|
77
|
+
setInnerValue("");
|
|
78
|
+
handleInput("");
|
|
79
|
+
handleOnFocus();
|
|
80
|
+
inputRef.current?.focus();
|
|
81
|
+
}, [
|
|
82
|
+
handleInput,
|
|
83
|
+
handleOnFocus,
|
|
84
|
+
inputRef,
|
|
85
|
+
setInnerValue
|
|
86
|
+
]);
|
|
87
|
+
const searchBar = /* @__PURE__ */ jsxs(SearchBar, {
|
|
88
|
+
className,
|
|
89
|
+
"data-test": dataTest,
|
|
90
|
+
children: [title != null && /* @__PURE__ */ jsx(FormControlLabel, {
|
|
91
|
+
htmlFor: id,
|
|
92
|
+
children: title
|
|
93
|
+
}), /* @__PURE__ */ jsx(InputGroup, {
|
|
94
|
+
leftIcon,
|
|
95
|
+
onClear: useClearIcon && !!innerValue ? (e) => {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
handleClear();
|
|
99
|
+
} : void 0,
|
|
100
|
+
size,
|
|
101
|
+
children: /* @__PURE__ */ jsx(StyledInput, {
|
|
102
|
+
id,
|
|
103
|
+
onChange: handleChange,
|
|
104
|
+
placeholder,
|
|
105
|
+
value: innerValue,
|
|
106
|
+
autoFocus,
|
|
107
|
+
ref: inputRef,
|
|
108
|
+
onFocus: handleOnFocus,
|
|
109
|
+
onBlur,
|
|
110
|
+
disabled,
|
|
111
|
+
maxLength,
|
|
112
|
+
flattenDisplay
|
|
113
|
+
})
|
|
114
|
+
})]
|
|
115
|
+
});
|
|
116
|
+
return disabled && disabledTooltip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
117
|
+
trigger: searchBar,
|
|
118
|
+
side: "top",
|
|
119
|
+
children: disabledTooltip
|
|
120
|
+
}) : searchBar;
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region components/DataTable/TableControls/CompactBar.tsx
|
|
124
|
+
const DataTableCompactBar = styled(HStack)`
|
|
125
|
+
border-bottom: 1px solid ${({ theme }) => theme.color.border.secondary};
|
|
126
|
+
padding-bottom: ${Spacing[1]};
|
|
127
|
+
`;
|
|
128
|
+
const CompactSearchInput = styled(SearchInput)`
|
|
129
|
+
width: ${({ $autoExpand }) => $autoExpand ? "100%" : "240px"};
|
|
130
|
+
margin-right: ${Spacing[1]};
|
|
131
|
+
`;
|
|
132
|
+
/**
|
|
133
|
+
* Compact variant of the ControlBar.
|
|
134
|
+
* Not intended to be used by itself.
|
|
135
|
+
*
|
|
136
|
+
* Tested using the ControlBar specs.
|
|
137
|
+
*
|
|
138
|
+
* @see ControlBar.tsx
|
|
139
|
+
* @see ControlBar.spec.tsx
|
|
140
|
+
*/
|
|
141
|
+
const CompactBar = ({ title, searchProps, showFilters, setShowFilters, filtersId, isLoading, propertiesButton, tableActions, "data-tour-target": dataTourTarget, filterPanel }) => {
|
|
142
|
+
const autoExpandSearch = !title;
|
|
143
|
+
const [showSearch, setShowSearch] = useState(autoExpandSearch);
|
|
144
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(DataTableCompactBar, {
|
|
145
|
+
"data-tour-target": dataTourTarget,
|
|
146
|
+
justify: "space-between",
|
|
147
|
+
align: "center",
|
|
148
|
+
children: [title && /* @__PURE__ */ jsx(Paragraph, {
|
|
149
|
+
size: "md",
|
|
150
|
+
weight: "bold",
|
|
151
|
+
color: "primary",
|
|
152
|
+
children: title
|
|
153
|
+
}), /* @__PURE__ */ jsxs(HStack, {
|
|
154
|
+
spacing: Spacing[1],
|
|
155
|
+
align: "center",
|
|
156
|
+
justify: "right",
|
|
157
|
+
style: autoExpandSearch ? { width: "100%" } : { marginLeft: "auto" },
|
|
158
|
+
children: [/* @__PURE__ */ jsxs(HStack, {
|
|
159
|
+
spacing: Spacing[1],
|
|
160
|
+
align: "center",
|
|
161
|
+
style: { width: "100%" },
|
|
162
|
+
children: [
|
|
163
|
+
searchProps && (showSearch ? /* @__PURE__ */ jsx(CompactSearchInput, {
|
|
164
|
+
title: null,
|
|
165
|
+
size: "sm",
|
|
166
|
+
$autoExpand: autoExpandSearch,
|
|
167
|
+
autoFocus: !autoExpandSearch,
|
|
168
|
+
onBlur: autoExpandSearch ? void 0 : () => {
|
|
169
|
+
if (!searchProps.value) setShowSearch(false);
|
|
170
|
+
},
|
|
171
|
+
useClearIcon: true,
|
|
172
|
+
...searchProps
|
|
173
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
174
|
+
id: "table-controls__compact-search-toggle",
|
|
175
|
+
variant: "text",
|
|
176
|
+
width: "icon",
|
|
177
|
+
size: "sm",
|
|
178
|
+
"aria-label": "Show search bar",
|
|
179
|
+
onClick: () => setShowSearch(true),
|
|
180
|
+
children: /* @__PURE__ */ jsx(SearchIcon, {})
|
|
181
|
+
})),
|
|
182
|
+
setShowFilters && /* @__PURE__ */ jsx(Button, {
|
|
183
|
+
id: "table-controls__view-tab-filters",
|
|
184
|
+
variant: "text",
|
|
185
|
+
onClick: () => setShowFilters?.(!showFilters),
|
|
186
|
+
"aria-label": showFilters ? "Hide filters" : "Show filters",
|
|
187
|
+
"aria-expanded": showFilters,
|
|
188
|
+
"aria-controls": filtersId,
|
|
189
|
+
disabled: isLoading,
|
|
190
|
+
active: showFilters,
|
|
191
|
+
width: "icon",
|
|
192
|
+
size: "sm",
|
|
193
|
+
children: /* @__PURE__ */ jsx(FilterIcon, {})
|
|
194
|
+
}),
|
|
195
|
+
propertiesButton
|
|
196
|
+
]
|
|
197
|
+
}), tableActions && Array.isArray(tableActions) ? tableActions.map((action) => /* @__PURE__ */ jsx(Button, {
|
|
198
|
+
id: `table-controls__compact-action-${action.label.toLowerCase().replace(/\s+/g, "-")}`,
|
|
199
|
+
variant: "basic",
|
|
200
|
+
size: "sm",
|
|
201
|
+
width: "auto",
|
|
202
|
+
onClick: action.onClick,
|
|
203
|
+
children: action.label
|
|
204
|
+
}, action.label)) : tableActions]
|
|
205
|
+
})]
|
|
206
|
+
}), showFilters && filterPanel] });
|
|
207
|
+
};
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region components/DataTable/TableControls/utils/positioning.ts
|
|
210
|
+
/**
|
|
211
|
+
* Check if a child rect is contained within a parent rect.
|
|
212
|
+
* @param parent - The parent rect.
|
|
213
|
+
* @param child - The child rect.
|
|
214
|
+
* @returns True if the child rect is contained within the parent rect, false otherwise.
|
|
215
|
+
*/
|
|
216
|
+
const rectContainsRect = (parent, child) => {
|
|
217
|
+
return child.top >= parent.top && child.bottom <= parent.bottom && child.left >= parent.left && child.right <= parent.right;
|
|
218
|
+
};
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region components/DataTable/TableControls/ControlBar.tsx
|
|
221
|
+
const TAB_ID_PREFIX = "table-controls__view-tab_";
|
|
222
|
+
const OVERFLOW_TAB_ID_PREFIX = "table-controls__view-tab-overflow_";
|
|
223
|
+
function enterProxiesClick(e) {
|
|
224
|
+
if (e.key === "Enter") e.target.dispatchEvent(new Event("click", { bubbles: true }));
|
|
225
|
+
}
|
|
226
|
+
const AddViewButton = styled(IconButton)`
|
|
227
|
+
color: ${({ theme }) => theme.color.text.secondary};
|
|
228
|
+
`;
|
|
229
|
+
const LockedTab = styled(Tab)`
|
|
230
|
+
svg {
|
|
231
|
+
height: 12px;
|
|
232
|
+
width: 12px;
|
|
233
|
+
}
|
|
234
|
+
`;
|
|
235
|
+
const OverflowMenuList = styled(MenuList)`
|
|
236
|
+
max-height: 300px;
|
|
237
|
+
overflow: auto;
|
|
238
|
+
`;
|
|
239
|
+
const ExpandedSearchInput = styled(SearchInput)`
|
|
240
|
+
flex-basis: 440px;
|
|
241
|
+
flex-grow: 1;
|
|
242
|
+
`;
|
|
243
|
+
const ViewsBar = styled(TabBar)`
|
|
244
|
+
${AddViewButton} {
|
|
245
|
+
/* Shows button when view is in progress of being added */
|
|
246
|
+
opacity: ${({ $isAddingView }) => $isAddingView ? 1 : 0};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
&:hover,
|
|
250
|
+
&:hover:focus,
|
|
251
|
+
&:focus-within {
|
|
252
|
+
${AddViewButton} {
|
|
253
|
+
opacity: 1;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
const TabOverflowContainer = styled.div`
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
gap: ${Spacing[2]};
|
|
261
|
+
position: absolute;
|
|
262
|
+
height: 32px;
|
|
263
|
+
`;
|
|
264
|
+
const SavedViewTab = ({ view, selected, tabIdPrefix = TAB_ID_PREFIX, canEditViews, handleNameChange, onDuplicateView, onDeleteView, onSelectView, onResetCurrentView, onSaveView, isViewDirty, showTabContextMenu, setShowTabContextMenu, className, ...rest }) => {
|
|
265
|
+
const inputRef = useRef(null);
|
|
266
|
+
const tab = /* @__PURE__ */ jsx(StyledTab, {
|
|
267
|
+
id: `${tabIdPrefix}${view.id}`,
|
|
268
|
+
selected,
|
|
269
|
+
"aria-selected": selected,
|
|
270
|
+
role: "tab",
|
|
271
|
+
onClick: !selected ? () => onSelectView?.(view.id) : void 0,
|
|
272
|
+
suffixIcon: selected && canEditViews ? /* @__PURE__ */ jsx(MoreIcon, {}) : void 0,
|
|
273
|
+
className,
|
|
274
|
+
...rest,
|
|
275
|
+
children: view.name
|
|
276
|
+
});
|
|
277
|
+
if (canEditViews && selected) return /* @__PURE__ */ jsx(Popover_default, {
|
|
278
|
+
trigger: tab,
|
|
279
|
+
open: showTabContextMenu,
|
|
280
|
+
onOpenChange: (open) => {
|
|
281
|
+
if (selected) setShowTabContextMenu(open);
|
|
282
|
+
if (!open && inputRef.current) handleNameChange?.(view, inputRef.current.value);
|
|
283
|
+
},
|
|
284
|
+
contentProps: {
|
|
285
|
+
align: "start",
|
|
286
|
+
padding: Spacing[2]
|
|
287
|
+
},
|
|
288
|
+
children: /* @__PURE__ */ jsxs(Stack, {
|
|
289
|
+
spacing: Spacing[3],
|
|
290
|
+
children: [/* @__PURE__ */ jsx(Input_default, {
|
|
291
|
+
defaultValue: view.name,
|
|
292
|
+
onBlur: (e) => handleNameChange?.(view, e.currentTarget.value),
|
|
293
|
+
onKeyDown: (e) => {
|
|
294
|
+
if (e.key === "Enter") handleNameChange?.(view, e.currentTarget.value);
|
|
295
|
+
},
|
|
296
|
+
disabled: !handleNameChange,
|
|
297
|
+
ref: inputRef
|
|
298
|
+
}), /* @__PURE__ */ jsxs(MenuList, {
|
|
299
|
+
padding: Spacing[0],
|
|
300
|
+
children: [
|
|
301
|
+
onDuplicateView && /* @__PURE__ */ jsx(MenuItem, {
|
|
302
|
+
id: "table-controls__view__menu-duplicate",
|
|
303
|
+
prefixIcon: /* @__PURE__ */ jsx(CopyIcon, {}),
|
|
304
|
+
onClick: async () => {
|
|
305
|
+
const newTableView = await onDuplicateView(view.id);
|
|
306
|
+
if (newTableView) {
|
|
307
|
+
const { id } = newTableView;
|
|
308
|
+
if (view.id !== id) {
|
|
309
|
+
onSelectView?.(id);
|
|
310
|
+
setShowTabContextMenu(true);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
onKeyDown: enterProxiesClick,
|
|
315
|
+
children: "Duplicate"
|
|
316
|
+
}),
|
|
317
|
+
canEditViews && onResetCurrentView && /* @__PURE__ */ jsx(MenuItem, {
|
|
318
|
+
id: "table-controls__view__menu-reset",
|
|
319
|
+
prefixIcon: /* @__PURE__ */ jsx(RotateCcwIcon, {}),
|
|
320
|
+
onClick: onResetCurrentView,
|
|
321
|
+
onKeyDown: enterProxiesClick,
|
|
322
|
+
children: "Reset"
|
|
323
|
+
}),
|
|
324
|
+
canEditViews && onSaveView && /* @__PURE__ */ jsx(MenuItem, {
|
|
325
|
+
id: "table-controls__view__menu-save",
|
|
326
|
+
prefixIcon: /* @__PURE__ */ jsx(SaveIcon, {}),
|
|
327
|
+
disabled: !isViewDirty,
|
|
328
|
+
onClick: () => {
|
|
329
|
+
onSaveView();
|
|
330
|
+
setShowTabContextMenu(false);
|
|
331
|
+
},
|
|
332
|
+
onKeyDown: enterProxiesClick,
|
|
333
|
+
children: "Save"
|
|
334
|
+
}),
|
|
335
|
+
onDeleteView && /* @__PURE__ */ jsx(MenuItem, {
|
|
336
|
+
id: "table-controls__view__menu-delete",
|
|
337
|
+
prefixIcon: /* @__PURE__ */ jsx(TrashIcon, {}),
|
|
338
|
+
onClick: async () => {
|
|
339
|
+
await onDeleteView(view.id);
|
|
340
|
+
setShowTabContextMenu(false);
|
|
341
|
+
},
|
|
342
|
+
onKeyDown: enterProxiesClick,
|
|
343
|
+
children: "Delete"
|
|
344
|
+
})
|
|
345
|
+
]
|
|
346
|
+
})]
|
|
347
|
+
})
|
|
348
|
+
});
|
|
349
|
+
return tab;
|
|
350
|
+
};
|
|
351
|
+
/**
|
|
352
|
+
* A tab that is locked and does not support editing or actions.
|
|
353
|
+
*/
|
|
354
|
+
const LockedViewTab = ({ id, selected, onClick, children, ...props }) => {
|
|
355
|
+
return /* @__PURE__ */ jsx(LockedTab, {
|
|
356
|
+
id,
|
|
357
|
+
selected,
|
|
358
|
+
onClick: selected ? void 0 : onClick,
|
|
359
|
+
suffixIcon: /* @__PURE__ */ jsx(LockedIcon$1, {}),
|
|
360
|
+
...props,
|
|
361
|
+
children
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
const OverflowSavedViewTab = styled(SavedViewTab)`
|
|
365
|
+
margin-left: ${Spacing[2]};
|
|
366
|
+
`;
|
|
367
|
+
const TabOverflow = forwardRef(function TabOverflow({ hiddenViews, currentView: currentViewId, handleAddView, onSelectView, canEditViews, lastVisibleViewElement, ...rest }, ref) {
|
|
368
|
+
const [filter, setFilter] = useState("");
|
|
369
|
+
const [openPopover, setOpenPopover] = useState(false);
|
|
370
|
+
const currentView = useMemo(() => {
|
|
371
|
+
return hiddenViews.find((view) => view.id === currentViewId);
|
|
372
|
+
}, [currentViewId, hiddenViews]);
|
|
373
|
+
const filteredViews = useMemo(() => hiddenViews.filter((view) => view.name.toLowerCase().includes(filter.toLowerCase()) && view.id !== currentView?.id), [
|
|
374
|
+
hiddenViews,
|
|
375
|
+
filter,
|
|
376
|
+
currentView
|
|
377
|
+
]);
|
|
378
|
+
return /* @__PURE__ */ jsxs(TabOverflowContainer, {
|
|
379
|
+
ref,
|
|
380
|
+
style: useMemo(() => {
|
|
381
|
+
if (!lastVisibleViewElement) return;
|
|
382
|
+
return { left: lastVisibleViewElement.offsetLeft + lastVisibleViewElement.offsetWidth + 8 };
|
|
383
|
+
}, [lastVisibleViewElement]),
|
|
384
|
+
children: [
|
|
385
|
+
currentView && /* @__PURE__ */ jsx(OverflowSavedViewTab, {
|
|
386
|
+
view: currentView,
|
|
387
|
+
canEditViews,
|
|
388
|
+
selected: true,
|
|
389
|
+
tabIdPrefix: OVERFLOW_TAB_ID_PREFIX,
|
|
390
|
+
...rest
|
|
391
|
+
}),
|
|
392
|
+
hiddenViews.length === 1 && currentView && hiddenViews[0].id !== currentView.id ? null : /* @__PURE__ */ jsxs(Popover_default, {
|
|
393
|
+
trigger: /* @__PURE__ */ jsxs(Button, {
|
|
394
|
+
variant: "basic",
|
|
395
|
+
size: "sm",
|
|
396
|
+
width: "auto",
|
|
397
|
+
children: [currentView ? hiddenViews.length - 1 : hiddenViews.length, " more…"]
|
|
398
|
+
}),
|
|
399
|
+
contentProps: {
|
|
400
|
+
align: "start",
|
|
401
|
+
padding: Spacing[2]
|
|
402
|
+
},
|
|
403
|
+
open: openPopover,
|
|
404
|
+
onOpenChange: (open) => {
|
|
405
|
+
if (!open) setFilter("");
|
|
406
|
+
setOpenPopover(open);
|
|
407
|
+
},
|
|
408
|
+
children: [/* @__PURE__ */ jsx(Input_default, {
|
|
409
|
+
value: filter,
|
|
410
|
+
onChange: (e) => setFilter(e.target.value),
|
|
411
|
+
placeholder: "Search for a view..."
|
|
412
|
+
}), /* @__PURE__ */ jsx(OverflowMenuList, {
|
|
413
|
+
padding: Spacing[0],
|
|
414
|
+
children: filteredViews.map((view) => {
|
|
415
|
+
return /* @__PURE__ */ jsx(MenuItem, {
|
|
416
|
+
id: `${OVERFLOW_TAB_ID_PREFIX}${view.id}`,
|
|
417
|
+
onClick: () => {
|
|
418
|
+
onSelectView?.(view.id);
|
|
419
|
+
setOpenPopover(false);
|
|
420
|
+
},
|
|
421
|
+
onKeyDown: enterProxiesClick,
|
|
422
|
+
children: view.name
|
|
423
|
+
}, view.id);
|
|
424
|
+
})
|
|
425
|
+
})]
|
|
426
|
+
}),
|
|
427
|
+
canEditViews && handleAddView && /* @__PURE__ */ jsx(AddViewButton, {
|
|
428
|
+
id: `${OVERFLOW_TAB_ID_PREFIX}overflow-new`,
|
|
429
|
+
onClick: handleAddView,
|
|
430
|
+
size: "md",
|
|
431
|
+
"aria-label": "Add new view",
|
|
432
|
+
children: /* @__PURE__ */ jsx(CirclePlusIcon, {})
|
|
433
|
+
})
|
|
434
|
+
]
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
/**
|
|
438
|
+
* Force the add button to be visible when the product tour is active
|
|
439
|
+
*/
|
|
440
|
+
const ProductTourStyles = createGlobalStyle`
|
|
441
|
+
body:has(#react-joyride-portal) {
|
|
442
|
+
[data-tour-target='saved-views-add-button'] {
|
|
443
|
+
opacity: 1;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
`;
|
|
447
|
+
const ControlBar = ({ presetViews, currentView, onSelectView, views, onAddView, onDuplicateView, onDeleteView, onViewNameChange, onSaveView, isViewDirty, canEditViews, showFilters, setShowFilters, searchProps, filtersId, propertiesButton, tableActions, isLoading, showViews, selectedRows = [], bulkActions, onResetCurrentView, onClearSelection, title, "data-tour-target": dataTourTarget, compact, onError, filterPanel }) => {
|
|
448
|
+
const tabListRef = useRef(null);
|
|
449
|
+
const tabOverflowRef = useRef(null);
|
|
450
|
+
const [hiddenViews, setHiddenViews] = useState([]);
|
|
451
|
+
const [showTabContextMenu, setShowTabContextMenu] = useState(false);
|
|
452
|
+
const [isAddingView, setIsAddingView] = useState(false);
|
|
453
|
+
const [lastVisibleViewElement, setLastVisibleViewElement] = useState(null);
|
|
454
|
+
const [tabOverflowWidth, setTabOverflowWidth] = useState(null);
|
|
455
|
+
const onResize = useCallback(() => {
|
|
456
|
+
if (!tabListRef.current) return;
|
|
457
|
+
const containerRect = tabListRef.current.getBoundingClientRect();
|
|
458
|
+
const tabListContainer = tabListRef.current;
|
|
459
|
+
const tabElements = Array.from(tabListContainer.querySelectorAll("[data-tab-view]"));
|
|
460
|
+
if (tabElements.length === 0 || rectContainsRect(containerRect, tabElements[tabElements.length - 1].getBoundingClientRect())) {
|
|
461
|
+
setHiddenViews([]);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const firstHiddenIndex = tabElements.findIndex((el) => !rectContainsRect(containerRect, el.getBoundingClientRect()));
|
|
465
|
+
if (firstHiddenIndex === -1) setLastVisibleViewElement(null);
|
|
466
|
+
else {
|
|
467
|
+
const lastVisibleViewElement = tabElements[firstHiddenIndex - 1];
|
|
468
|
+
if (lastVisibleViewElement) setLastVisibleViewElement(lastVisibleViewElement);
|
|
469
|
+
}
|
|
470
|
+
setHiddenViews((firstHiddenIndex === -1 ? [] : views?.slice(firstHiddenIndex)) || []);
|
|
471
|
+
const tabOverflowWidth = tabOverflowRef.current?.getBoundingClientRect().width;
|
|
472
|
+
if (tabOverflowWidth) setTabOverflowWidth(tabOverflowWidth);
|
|
473
|
+
}, [setHiddenViews, views]);
|
|
474
|
+
useLayoutEffect(() => {
|
|
475
|
+
if (!showViews || !tabListRef.current) return;
|
|
476
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
477
|
+
onResize();
|
|
478
|
+
});
|
|
479
|
+
resizeObserver.observe(tabListRef.current);
|
|
480
|
+
return () => {
|
|
481
|
+
resizeObserver.disconnect();
|
|
482
|
+
};
|
|
483
|
+
}, [onResize, showViews]);
|
|
484
|
+
useLayoutEffect(() => {
|
|
485
|
+
if (showViews) onResize();
|
|
486
|
+
}, [
|
|
487
|
+
onResize,
|
|
488
|
+
showViews,
|
|
489
|
+
views
|
|
490
|
+
]);
|
|
491
|
+
const currentViewHidden = useMemo(() => {
|
|
492
|
+
if (currentView) return !!hiddenViews.find((view) => view.id === currentView);
|
|
493
|
+
return false;
|
|
494
|
+
}, [currentView, hiddenViews]);
|
|
495
|
+
const handleAddView = async () => {
|
|
496
|
+
try {
|
|
497
|
+
setIsAddingView(true);
|
|
498
|
+
if (onAddView) {
|
|
499
|
+
const view = await onAddView();
|
|
500
|
+
if (!view || !view.id) return;
|
|
501
|
+
if (currentView !== view.id) onSelectView?.(view.id);
|
|
502
|
+
setShowTabContextMenu(true);
|
|
503
|
+
}
|
|
504
|
+
} catch {
|
|
505
|
+
onError?.(/* @__PURE__ */ new Error("Error adding view"));
|
|
506
|
+
} finally {
|
|
507
|
+
setIsAddingView(false);
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
const handleNameChange = (view, newName) => {
|
|
511
|
+
if (canEditViews && view.name !== newName) onViewNameChange?.(view.id, newName);
|
|
512
|
+
};
|
|
513
|
+
if (compact) return /* @__PURE__ */ jsx(CompactBar, {
|
|
514
|
+
"data-tour-target": dataTourTarget,
|
|
515
|
+
title,
|
|
516
|
+
searchProps,
|
|
517
|
+
showFilters,
|
|
518
|
+
setShowFilters,
|
|
519
|
+
filtersId,
|
|
520
|
+
isLoading,
|
|
521
|
+
propertiesButton,
|
|
522
|
+
tableActions,
|
|
523
|
+
filterPanel
|
|
524
|
+
});
|
|
525
|
+
const bulkActionButtons = bulkActions?.map((action, index) => {
|
|
526
|
+
const key = "label" in action ? action.label : `action-${index}`;
|
|
527
|
+
const isDisabled = selectedRows.length === 0 || action.disabled === true;
|
|
528
|
+
const selectedCount = selectedRows.length;
|
|
529
|
+
const clearSelection = () => {
|
|
530
|
+
onClearSelection?.();
|
|
531
|
+
};
|
|
532
|
+
if ("icon" in action && "aria-label" in action) return /* @__PURE__ */ jsx(IconButton, {
|
|
533
|
+
size: "sm",
|
|
534
|
+
"aria-label": action["aria-label"],
|
|
535
|
+
onClick: async () => {
|
|
536
|
+
await action.onClick(selectedRows, clearSelection);
|
|
537
|
+
},
|
|
538
|
+
disabled: isDisabled,
|
|
539
|
+
children: action.icon
|
|
540
|
+
}, key);
|
|
541
|
+
const displayLabel = "label" in action && selectedCount > 0 ? `${action.label} (${selectedCount})` : "label" in action ? action.label : "";
|
|
542
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
543
|
+
id: `table-controls__view-tab-bulk-action-${index}`,
|
|
544
|
+
variant: "basic",
|
|
545
|
+
size: "sm",
|
|
546
|
+
width: "auto",
|
|
547
|
+
onClick: async () => {
|
|
548
|
+
await action.onClick(selectedRows, clearSelection);
|
|
549
|
+
},
|
|
550
|
+
disabled: isDisabled,
|
|
551
|
+
children: displayLabel
|
|
552
|
+
}, key);
|
|
553
|
+
});
|
|
554
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
555
|
+
/* @__PURE__ */ jsx(ProductTourStyles, {}),
|
|
556
|
+
(title || showViews || canEditViews && onSaveView || tableActions || bulkActions) && /* @__PURE__ */ jsx(ViewsBar, {
|
|
557
|
+
ref: tabListRef,
|
|
558
|
+
title,
|
|
559
|
+
maxTabChars: 25,
|
|
560
|
+
$isAddingView: isAddingView,
|
|
561
|
+
"data-tour-target": dataTourTarget,
|
|
562
|
+
actions: /* @__PURE__ */ jsxs(HStack, {
|
|
563
|
+
spacing: Spacing[3],
|
|
564
|
+
align: "center",
|
|
565
|
+
style: tabOverflowWidth ? { paddingLeft: `${tabOverflowWidth + 16}px` } : void 0,
|
|
566
|
+
children: [
|
|
567
|
+
canEditViews && onSaveView && /* @__PURE__ */ jsx(Button, {
|
|
568
|
+
id: "table-controls__view-tab-save",
|
|
569
|
+
onClick: onSaveView,
|
|
570
|
+
"aria-label": "Save current view",
|
|
571
|
+
disabled: !isViewDirty || isLoading,
|
|
572
|
+
size: "sm",
|
|
573
|
+
variant: "basic",
|
|
574
|
+
width: "auto",
|
|
575
|
+
"data-tour-target": "saved-views-save-button",
|
|
576
|
+
children: "Save view"
|
|
577
|
+
}),
|
|
578
|
+
bulkActionButtons,
|
|
579
|
+
tableActions
|
|
580
|
+
]
|
|
581
|
+
}),
|
|
582
|
+
children: showViews && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
583
|
+
/* @__PURE__ */ jsx(LockedViewTab, {
|
|
584
|
+
id: `${TAB_ID_PREFIX}default`,
|
|
585
|
+
selected: !currentView,
|
|
586
|
+
onClick: () => onSelectView?.(null),
|
|
587
|
+
"data-tour-target": "saved-views-default-view",
|
|
588
|
+
children: "Default view"
|
|
589
|
+
}),
|
|
590
|
+
presetViews?.map((view) => {
|
|
591
|
+
const selected = view.id === currentView;
|
|
592
|
+
return /* @__PURE__ */ jsx(LockedViewTab, {
|
|
593
|
+
id: `${TAB_ID_PREFIX}preset-${view.id}`,
|
|
594
|
+
selected,
|
|
595
|
+
onClick: () => onSelectView?.(view.id),
|
|
596
|
+
children: view.name
|
|
597
|
+
}, view.id);
|
|
598
|
+
}),
|
|
599
|
+
views?.map((view) => {
|
|
600
|
+
return /* @__PURE__ */ jsx(SavedViewTab, {
|
|
601
|
+
view,
|
|
602
|
+
canEditViews: canEditViews ?? false,
|
|
603
|
+
handleNameChange,
|
|
604
|
+
onDuplicateView,
|
|
605
|
+
onDeleteView,
|
|
606
|
+
onSelectView,
|
|
607
|
+
onResetCurrentView,
|
|
608
|
+
onSaveView,
|
|
609
|
+
isViewDirty: isViewDirty ?? false,
|
|
610
|
+
selected: view.id === currentView,
|
|
611
|
+
showTabContextMenu: showTabContextMenu && !currentViewHidden,
|
|
612
|
+
setShowTabContextMenu,
|
|
613
|
+
"data-tab-view": true
|
|
614
|
+
}, view.id);
|
|
615
|
+
}),
|
|
616
|
+
canEditViews && onAddView && /* @__PURE__ */ jsx(AddViewButton, {
|
|
617
|
+
id: "table-controls__view-tab-new",
|
|
618
|
+
size: "md",
|
|
619
|
+
onClick: handleAddView,
|
|
620
|
+
"aria-label": "Add new view",
|
|
621
|
+
"data-tour-target": "saved-views-add-button",
|
|
622
|
+
children: /* @__PURE__ */ jsx(CirclePlusIcon, {})
|
|
623
|
+
}),
|
|
624
|
+
hiddenViews.length > 0 && /* @__PURE__ */ jsx(TabOverflow, {
|
|
625
|
+
ref: tabOverflowRef,
|
|
626
|
+
hiddenViews,
|
|
627
|
+
currentView,
|
|
628
|
+
onSelectView,
|
|
629
|
+
canEditViews: canEditViews ?? false,
|
|
630
|
+
handleAddView: onAddView ? handleAddView : null,
|
|
631
|
+
handleNameChange,
|
|
632
|
+
onDuplicateView,
|
|
633
|
+
onDeleteView,
|
|
634
|
+
onResetCurrentView,
|
|
635
|
+
onSaveView,
|
|
636
|
+
isViewDirty: isViewDirty ?? false,
|
|
637
|
+
showTabContextMenu: showTabContextMenu && currentViewHidden,
|
|
638
|
+
setShowTabContextMenu,
|
|
639
|
+
lastVisibleViewElement
|
|
640
|
+
})
|
|
641
|
+
] })
|
|
642
|
+
}),
|
|
643
|
+
/* @__PURE__ */ jsxs(HStack, {
|
|
644
|
+
align: "center",
|
|
645
|
+
spacing: Spacing[2],
|
|
646
|
+
justify: "right",
|
|
647
|
+
children: [
|
|
648
|
+
searchProps && /* @__PURE__ */ jsx(ExpandedSearchInput, {
|
|
649
|
+
title: null,
|
|
650
|
+
useClearIcon: true,
|
|
651
|
+
...searchProps,
|
|
652
|
+
size: "md"
|
|
653
|
+
}),
|
|
654
|
+
setShowFilters && /* @__PURE__ */ jsx(Button, {
|
|
655
|
+
id: "table-controls__view-tab-filters",
|
|
656
|
+
variant: "basic",
|
|
657
|
+
onClick: () => setShowFilters?.(!showFilters),
|
|
658
|
+
"aria-label": showFilters ? "Hide filters" : "Show filters",
|
|
659
|
+
"aria-expanded": showFilters,
|
|
660
|
+
"aria-controls": filtersId,
|
|
661
|
+
disabled: isLoading,
|
|
662
|
+
active: showFilters,
|
|
663
|
+
width: "icon",
|
|
664
|
+
size: "md",
|
|
665
|
+
"data-tour-target": "table-filters-toggle-button",
|
|
666
|
+
children: /* @__PURE__ */ jsx(FilterIcon, {})
|
|
667
|
+
}),
|
|
668
|
+
propertiesButton
|
|
669
|
+
]
|
|
670
|
+
}),
|
|
671
|
+
showFilters && filterPanel
|
|
672
|
+
] });
|
|
673
|
+
};
|
|
674
|
+
//#endregion
|
|
675
|
+
//#region components/DataTable/TableControls/TableControls.tsx
|
|
676
|
+
const TableControls = ({ filterPanel, searchProps, defaultShowFilters, showFilters: controlledShowFilters, setShowFilters: controlledSetShowFilters, className, tableActions, propertiesButton, isLoading, showViews, canEditViews, onSelectView, views, currentView, onAddView, onViewNameChange, onDuplicateView, onDeleteView, onSaveView, isViewDirty, selectedRows, bulkActions, onClearSelection, title, presetViews, compact, columnConfigurationProps, showFiltersAlways = false, onError, "data-tour-target": dataTourTarget }) => {
|
|
677
|
+
const filtersId = useId();
|
|
678
|
+
const [internalShowFilters, internalSetShowFilters] = useState(defaultShowFilters ?? false);
|
|
679
|
+
const showFilters = controlledShowFilters != null ? controlledShowFilters : internalShowFilters;
|
|
680
|
+
const setShowFilters = controlledSetShowFilters != null ? controlledSetShowFilters : internalSetShowFilters;
|
|
681
|
+
const enhancedPropertiesButton = useMemo(() => {
|
|
682
|
+
if (!columnConfigurationProps && !propertiesButton) return;
|
|
683
|
+
const elements = [];
|
|
684
|
+
if (propertiesButton) elements.push(propertiesButton);
|
|
685
|
+
if (columnConfigurationProps) elements.push(/* @__PURE__ */ jsx(TableViewConfigurationDropdown, {
|
|
686
|
+
disabled: false,
|
|
687
|
+
...columnConfigurationProps
|
|
688
|
+
}, "column-config"));
|
|
689
|
+
if (elements.length === 1) return elements[0];
|
|
690
|
+
return /* @__PURE__ */ jsx(StyledEnhancedPropertiesButton, { children: elements });
|
|
691
|
+
}, [columnConfigurationProps, propertiesButton]);
|
|
692
|
+
return /* @__PURE__ */ jsx(Container, {
|
|
693
|
+
className,
|
|
694
|
+
children: /* @__PURE__ */ jsx(ControlBar, {
|
|
695
|
+
showViews: compact ? false : showViews,
|
|
696
|
+
views: compact ? void 0 : views,
|
|
697
|
+
onSelectView: compact ? void 0 : onSelectView,
|
|
698
|
+
currentView: compact ? void 0 : currentView,
|
|
699
|
+
onAddView: compact ? void 0 : onAddView,
|
|
700
|
+
onDeleteView: compact ? void 0 : onDeleteView,
|
|
701
|
+
onDuplicateView: compact ? void 0 : onDuplicateView,
|
|
702
|
+
onViewNameChange: compact ? void 0 : onViewNameChange,
|
|
703
|
+
onSaveView: compact ? void 0 : onSaveView,
|
|
704
|
+
isViewDirty,
|
|
705
|
+
canEditViews: compact ? false : canEditViews,
|
|
706
|
+
showFilters,
|
|
707
|
+
setShowFilters: filterPanel != null && !showFiltersAlways ? setShowFilters : void 0,
|
|
708
|
+
filtersId,
|
|
709
|
+
searchProps,
|
|
710
|
+
propertiesButton: enhancedPropertiesButton,
|
|
711
|
+
tableActions,
|
|
712
|
+
isLoading,
|
|
713
|
+
selectedRows,
|
|
714
|
+
bulkActions,
|
|
715
|
+
onClearSelection,
|
|
716
|
+
title,
|
|
717
|
+
"data-tour-target": dataTourTarget,
|
|
718
|
+
presetViews: compact ? void 0 : presetViews,
|
|
719
|
+
compact,
|
|
720
|
+
onError,
|
|
721
|
+
filterPanel
|
|
722
|
+
})
|
|
723
|
+
});
|
|
724
|
+
};
|
|
725
|
+
const Container = styled(Stack)`
|
|
726
|
+
margin-bottom: ${Spacing[2]};
|
|
727
|
+
`;
|
|
728
|
+
const StyledEnhancedPropertiesButton = styled.div`
|
|
729
|
+
display: flex;
|
|
730
|
+
gap: ${Spacing[1]};
|
|
731
|
+
`;
|
|
732
|
+
//#endregion
|
|
733
|
+
//#region components/DataTable/dnd/StyledIconDrag.tsx
|
|
734
|
+
const StyledIconDrag = styled(DragIcon$1)`
|
|
735
|
+
display: ${({ $shouldHide = false }) => $shouldHide ? "none" : "inline"};
|
|
736
|
+
|
|
737
|
+
${({ $horizontal = false }) => $horizontal ? css`
|
|
738
|
+
transform: rotate(90deg);
|
|
739
|
+
` : ""}
|
|
740
|
+
|
|
741
|
+
${({ $active = false, $hover = false }) => $active ? css`
|
|
742
|
+
cursor: grabbing;
|
|
743
|
+
opacity: 1;
|
|
744
|
+
` : $hover ? css`
|
|
745
|
+
cursor: grab;
|
|
746
|
+
opacity: 1;
|
|
747
|
+
` : css`
|
|
748
|
+
cursor: normal;
|
|
749
|
+
opacity: 0.4;
|
|
750
|
+
`}
|
|
751
|
+
|
|
752
|
+
transition: opacity 0.2s ease-in-out;
|
|
753
|
+
|
|
754
|
+
path {
|
|
755
|
+
stroke: ${Color.CoreSlate1000};
|
|
756
|
+
stroke-width: 1px;
|
|
757
|
+
}
|
|
758
|
+
`;
|
|
759
|
+
//#endregion
|
|
760
|
+
//#region components/DataTable/dnd/utils.ts
|
|
761
|
+
/**
|
|
762
|
+
* reorder returns a reordered version of the inputed list by swapping the element
|
|
763
|
+
* at startIndex with the one at endIndex
|
|
764
|
+
*/
|
|
765
|
+
function reorder(list, startIndex, endIndex) {
|
|
766
|
+
const result = Array.from(list);
|
|
767
|
+
const [removed] = result.splice(startIndex, 1);
|
|
768
|
+
result.splice(endIndex, 0, removed);
|
|
769
|
+
return result;
|
|
770
|
+
}
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region components/DataTable/TableControls/TableViewConfigurationDropdown.tsx
|
|
773
|
+
const DndSection = styled("div")`
|
|
774
|
+
max-height: 400px;
|
|
775
|
+
|
|
776
|
+
overflow: scroll;
|
|
777
|
+
::-webkit-scrollbar {
|
|
778
|
+
display: none;
|
|
779
|
+
}
|
|
780
|
+
`;
|
|
781
|
+
const DndItemWrapper = styled("div")`
|
|
782
|
+
background: white;
|
|
783
|
+
width: 100%;
|
|
784
|
+
border-bottom: 1px solid ${({ theme }) => theme.color.border.secondary};
|
|
785
|
+
padding: ${Spacing[2]} ${Spacing[2]};
|
|
786
|
+
|
|
787
|
+
display: flex;
|
|
788
|
+
flex-direction: row;
|
|
789
|
+
align-items: center;
|
|
790
|
+
justify-content: left;
|
|
791
|
+
`;
|
|
792
|
+
const SuffixContainer = styled("div")`
|
|
793
|
+
padding-left: ${Spacing[2]};
|
|
794
|
+
margin-left: auto;
|
|
795
|
+
align-self: center;
|
|
796
|
+
`;
|
|
797
|
+
const SearchContainer = styled.div`
|
|
798
|
+
display: flex;
|
|
799
|
+
`;
|
|
800
|
+
const StyledSearchInput = styled(SearchInput)`
|
|
801
|
+
width: 100%;
|
|
802
|
+
`;
|
|
803
|
+
const PopoverCard$1 = styled(Card_default)`
|
|
804
|
+
width: 300px;
|
|
805
|
+
padding: ${Spacing[4]};
|
|
806
|
+
`;
|
|
807
|
+
const TriggerIconButton = forwardRef(function TriggerIconButton({ disabled, ...elemProps }, ref) {
|
|
808
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
809
|
+
disabled,
|
|
810
|
+
width: "icon",
|
|
811
|
+
variant: "basic",
|
|
812
|
+
size: "md",
|
|
813
|
+
...elemProps,
|
|
814
|
+
ref,
|
|
815
|
+
"data-tour-target": "table-properties-button",
|
|
816
|
+
"aria-label": "Table Properties",
|
|
817
|
+
children: /* @__PURE__ */ jsx(SettingsIcon, {})
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
const TooltipButtonWrapper = styled("div")`
|
|
821
|
+
display: inline-block;
|
|
822
|
+
`;
|
|
823
|
+
const TableViewConfigurationDropdown = ({ disabled = false, disabledTooltipMessage = "Your role does not have permission to configure the view.", size = "md", variant = "basic", ...props }) => {
|
|
824
|
+
return disabled ? /* @__PURE__ */ jsx(Tooltip, {
|
|
825
|
+
trigger: /* @__PURE__ */ jsx(TooltipButtonWrapper, { children: /* @__PURE__ */ jsx(TriggerIconButton, {
|
|
826
|
+
disabled: true,
|
|
827
|
+
size,
|
|
828
|
+
variant
|
|
829
|
+
}) }),
|
|
830
|
+
children: disabledTooltipMessage
|
|
831
|
+
}) : /* @__PURE__ */ jsxs(Popover_default, { children: [/* @__PURE__ */ jsx(Popover_default.Trigger, {
|
|
832
|
+
asChild: true,
|
|
833
|
+
children: /* @__PURE__ */ jsx(TriggerIconButton, {
|
|
834
|
+
disabled: false,
|
|
835
|
+
size,
|
|
836
|
+
variant
|
|
837
|
+
})
|
|
838
|
+
}), /* @__PURE__ */ jsx(Popover_default.Content, {
|
|
839
|
+
style: { zIndex: Z_INDICES.dropdown },
|
|
840
|
+
sideOffset: 8,
|
|
841
|
+
side: "bottom",
|
|
842
|
+
align: "end",
|
|
843
|
+
children: /* @__PURE__ */ jsx(PopoverCard$1, {
|
|
844
|
+
elevated: true,
|
|
845
|
+
children: /* @__PURE__ */ jsx(TableViewConfiguration, { ...props })
|
|
846
|
+
})
|
|
847
|
+
})] });
|
|
848
|
+
};
|
|
849
|
+
const TableViewConfiguration = ({ columnConfiguration, attributeOptions, updateColumnConfiguration, getColumnDisabled }) => {
|
|
850
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
851
|
+
const fuse = useMemo(() => {
|
|
852
|
+
return new Fuse(attributeOptions, {
|
|
853
|
+
shouldSort: false,
|
|
854
|
+
threshold: .4,
|
|
855
|
+
location: 0,
|
|
856
|
+
distance: 100,
|
|
857
|
+
minMatchCharLength: 1,
|
|
858
|
+
keys: [{
|
|
859
|
+
name: "label",
|
|
860
|
+
weight: 1
|
|
861
|
+
}]
|
|
862
|
+
});
|
|
863
|
+
}, [attributeOptions]);
|
|
864
|
+
const allOptions = useMemo(() => union(attributeOptions, columnConfiguration), [attributeOptions, columnConfiguration]);
|
|
865
|
+
const filteredSearchOptions = searchTerm === "" ? allOptions : fuse.search(searchTerm).map(({ item }) => item);
|
|
866
|
+
const hiddenAttributes = useMemo(() => {
|
|
867
|
+
return filteredSearchOptions.filter((attribute) => {
|
|
868
|
+
return !columnConfiguration.map((c) => c.key).includes(attribute.key);
|
|
869
|
+
});
|
|
870
|
+
}, [columnConfiguration, filteredSearchOptions]);
|
|
871
|
+
const filteredShownAttributes = useMemo(() => {
|
|
872
|
+
return columnConfiguration.filter((column) => filteredSearchOptions.map((o) => o.key).includes(column.key));
|
|
873
|
+
}, [columnConfiguration, filteredSearchOptions]);
|
|
874
|
+
const handleDragEnd = (result, _provided) => {
|
|
875
|
+
const { destination, source } = result;
|
|
876
|
+
if (!destination || destination.droppableId === source.droppableId && destination.index === source.index) return;
|
|
877
|
+
updateColumnConfiguration(reorder(columnConfiguration, source.index, destination.index));
|
|
878
|
+
};
|
|
879
|
+
const generateShownAttributesToggle = (column) => () => {
|
|
880
|
+
updateColumnConfiguration(columnConfiguration.filter((a) => a.key !== column.key));
|
|
881
|
+
};
|
|
882
|
+
const generateHiddenAttributesToggle = (column) => () => {
|
|
883
|
+
updateColumnConfiguration(columnConfiguration.concat(column));
|
|
884
|
+
};
|
|
885
|
+
const shownColumnIds = useMemo(() => {
|
|
886
|
+
return columnConfiguration.map((c) => c.key);
|
|
887
|
+
}, [columnConfiguration]);
|
|
888
|
+
const shownDndItemProps = filteredShownAttributes.map((column, index) => {
|
|
889
|
+
const isDisabledByDefault = columnConfiguration.length === 1;
|
|
890
|
+
const isDisabledByCustom = getColumnDisabled?.(column.key, shownColumnIds) ?? false;
|
|
891
|
+
const shouldDisableDragForColumn = isDisabledByCustom && index === filteredShownAttributes.length - 1;
|
|
892
|
+
return {
|
|
893
|
+
column,
|
|
894
|
+
index,
|
|
895
|
+
isDisabled: isDisabledByDefault || isDisabledByCustom,
|
|
896
|
+
isToggled: true,
|
|
897
|
+
shouldDisableDrag: shouldDisableDragForColumn,
|
|
898
|
+
handleToggle: generateShownAttributesToggle(column)
|
|
899
|
+
};
|
|
900
|
+
});
|
|
901
|
+
const hiddenDndItemProps = hiddenAttributes.map((column, index) => {
|
|
902
|
+
return {
|
|
903
|
+
column,
|
|
904
|
+
index,
|
|
905
|
+
isToggled: false,
|
|
906
|
+
shouldDisableDrag: true,
|
|
907
|
+
handleToggle: generateHiddenAttributesToggle(column)
|
|
908
|
+
};
|
|
909
|
+
});
|
|
910
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
911
|
+
/* @__PURE__ */ jsx(Label$1, {
|
|
912
|
+
size: "lg",
|
|
913
|
+
weight: "bold",
|
|
914
|
+
children: "Configure columns"
|
|
915
|
+
}),
|
|
916
|
+
/* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
|
|
917
|
+
/* @__PURE__ */ jsx(SearchContainer, { children: /* @__PURE__ */ jsx(StyledSearchInput, {
|
|
918
|
+
title: null,
|
|
919
|
+
value: searchTerm,
|
|
920
|
+
handleInput: setSearchTerm,
|
|
921
|
+
size: "md",
|
|
922
|
+
placeholder: "Search by item...",
|
|
923
|
+
dataTest: "checklist-item-panel-item-table__search"
|
|
924
|
+
}) }),
|
|
925
|
+
/* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
|
|
926
|
+
shownDndItemProps.length > 0 || hiddenDndItemProps.length > 0 ? /* @__PURE__ */ jsxs(DndSection, { children: [
|
|
927
|
+
shownDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: "Shown attributes" }), /* @__PURE__ */ jsx(DragDropContext, {
|
|
928
|
+
onDragEnd: handleDragEnd,
|
|
929
|
+
children: /* @__PURE__ */ jsx(Droppable, {
|
|
930
|
+
droppableId: "cases-view__configure-shown-columns",
|
|
931
|
+
renderClone: (provided, _snapshot, rubric) => {
|
|
932
|
+
return /* @__PURE__ */ jsx("div", {
|
|
933
|
+
...provided.draggableProps,
|
|
934
|
+
...provided.dragHandleProps,
|
|
935
|
+
ref: provided.innerRef,
|
|
936
|
+
children: /* @__PURE__ */ jsx(AttributeDnd, { ...shownDndItemProps[rubric.source.index] })
|
|
937
|
+
});
|
|
938
|
+
},
|
|
939
|
+
children: (provided) => {
|
|
940
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
941
|
+
ref: provided.innerRef,
|
|
942
|
+
...provided.droppableProps,
|
|
943
|
+
children: [shownDndItemProps.map((props, idx) => {
|
|
944
|
+
return /* @__PURE__ */ jsx(Draggable, {
|
|
945
|
+
draggableId: props.column.key,
|
|
946
|
+
index: idx,
|
|
947
|
+
isDragDisabled: props.shouldDisableDrag,
|
|
948
|
+
children: (provided) => {
|
|
949
|
+
return /* @__PURE__ */ jsx("div", {
|
|
950
|
+
ref: provided.innerRef,
|
|
951
|
+
...provided.draggableProps,
|
|
952
|
+
...provided.dragHandleProps,
|
|
953
|
+
children: /* @__PURE__ */ jsx(AttributeDnd, { ...props })
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
}, `${props.column.key}`);
|
|
957
|
+
}), provided.placeholder]
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
})
|
|
961
|
+
})] }),
|
|
962
|
+
/* @__PURE__ */ jsx(Spacer_default, { height: 2 }),
|
|
963
|
+
hiddenDndItemProps.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Paragraph, { children: "Hidden attributes" }), /* @__PURE__ */ jsx(DragDropContext, {
|
|
964
|
+
onDragEnd: handleDragEnd,
|
|
965
|
+
children: /* @__PURE__ */ jsx(Droppable, {
|
|
966
|
+
droppableId: "cases-view__configure-hidden-columns",
|
|
967
|
+
renderClone: (provided, _snapshot, rubric) => {
|
|
968
|
+
return /* @__PURE__ */ jsx("div", {
|
|
969
|
+
...provided.draggableProps,
|
|
970
|
+
...provided.dragHandleProps,
|
|
971
|
+
ref: provided.innerRef,
|
|
972
|
+
children: /* @__PURE__ */ jsx(AttributeDnd, { ...hiddenDndItemProps[rubric.source.index] })
|
|
973
|
+
});
|
|
974
|
+
},
|
|
975
|
+
children: (provided) => {
|
|
976
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
977
|
+
ref: provided.innerRef,
|
|
978
|
+
...provided.droppableProps,
|
|
979
|
+
children: [hiddenDndItemProps.map((props, idx) => {
|
|
980
|
+
return /* @__PURE__ */ jsx(Draggable, {
|
|
981
|
+
draggableId: props.column.key,
|
|
982
|
+
index: idx,
|
|
983
|
+
isDragDisabled: props.shouldDisableDrag,
|
|
984
|
+
children: (provided) => {
|
|
985
|
+
return /* @__PURE__ */ jsx("div", {
|
|
986
|
+
ref: provided.innerRef,
|
|
987
|
+
...provided.draggableProps,
|
|
988
|
+
...provided.dragHandleProps,
|
|
989
|
+
children: /* @__PURE__ */ jsx(AttributeDnd, { ...props })
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
}, `${props.column.key}`);
|
|
993
|
+
}), provided.placeholder]
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
})
|
|
997
|
+
})] })
|
|
998
|
+
] }) : /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Paragraph, { children: "No matching attributes" }) })
|
|
999
|
+
] });
|
|
1000
|
+
};
|
|
1001
|
+
const OverflowText = styled("div")`
|
|
1002
|
+
max-width: 160px;
|
|
1003
|
+
flex-wrap: wrap;
|
|
1004
|
+
display: flex;
|
|
1005
|
+
`;
|
|
1006
|
+
const AttributeDnd = ({ column, index, isDisabled = false, isToggled, shouldDisableDrag, handleToggle }) => {
|
|
1007
|
+
return /* @__PURE__ */ jsxs(DndItemWrapper, { children: [
|
|
1008
|
+
/* @__PURE__ */ jsx(StyledIconDrag, {
|
|
1009
|
+
$active: true,
|
|
1010
|
+
$shouldHide: shouldDisableDrag
|
|
1011
|
+
}),
|
|
1012
|
+
/* @__PURE__ */ jsx(OverflowText, { children: /* @__PURE__ */ jsx(Paragraph, {
|
|
1013
|
+
color: "primary",
|
|
1014
|
+
size: "sm",
|
|
1015
|
+
children: column.label
|
|
1016
|
+
}) }),
|
|
1017
|
+
/* @__PURE__ */ jsx(SuffixContainer, { children: /* @__PURE__ */ jsx(Toggle, {
|
|
1018
|
+
value: isToggled,
|
|
1019
|
+
disabled: isDisabled,
|
|
1020
|
+
onChange: handleToggle,
|
|
1021
|
+
"aria-label": isToggled ? `Hide ${column.label}` : `Show ${column.label}`
|
|
1022
|
+
}) })
|
|
1023
|
+
] }, `${column.key}-${index}`);
|
|
1024
|
+
};
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region components/DataTable/TableControls/TableControlsContext.ts
|
|
1027
|
+
const TableControlsContext = createContext({
|
|
1028
|
+
filters: [],
|
|
1029
|
+
activeFilters: [],
|
|
1030
|
+
setActiveFilters: () => {}
|
|
1031
|
+
});
|
|
1032
|
+
function useTableControls() {
|
|
1033
|
+
return useContext(TableControlsContext);
|
|
1034
|
+
}
|
|
1035
|
+
function TableControlsProvider({ children, ...value }) {
|
|
1036
|
+
return createElement(TableControlsContext.Provider, { value }, children);
|
|
1037
|
+
}
|
|
1038
|
+
//#endregion
|
|
1039
|
+
//#region components/DataTable/TableControls/FiltersV2/operators.ts
|
|
1040
|
+
/**
|
|
1041
|
+
* Operator vocabulary per field type. The UI uses this to populate the
|
|
1042
|
+
* operator dropdown in the Filter popover and to gate which operators are
|
|
1043
|
+
* reachable for a given field. `is_empty` / `is_not_empty` apply broadly;
|
|
1044
|
+
* everything else is type-specific.
|
|
1045
|
+
*
|
|
1046
|
+
* The first entry for each type is the default operator selected when a
|
|
1047
|
+
* pill is created from the picker.
|
|
1048
|
+
*/
|
|
1049
|
+
const OPERATORS_BY_TYPE = {
|
|
1050
|
+
string: [
|
|
1051
|
+
"is",
|
|
1052
|
+
"is_not",
|
|
1053
|
+
"contains",
|
|
1054
|
+
"does_not_contain",
|
|
1055
|
+
"is_empty",
|
|
1056
|
+
"is_not_empty"
|
|
1057
|
+
],
|
|
1058
|
+
email: [
|
|
1059
|
+
"is",
|
|
1060
|
+
"is_not",
|
|
1061
|
+
"contains",
|
|
1062
|
+
"does_not_contain",
|
|
1063
|
+
"is_empty",
|
|
1064
|
+
"is_not_empty"
|
|
1065
|
+
],
|
|
1066
|
+
choices: [
|
|
1067
|
+
"is",
|
|
1068
|
+
"is_not",
|
|
1069
|
+
"is_empty",
|
|
1070
|
+
"is_not_empty"
|
|
1071
|
+
],
|
|
1072
|
+
multi_choices: [
|
|
1073
|
+
"is",
|
|
1074
|
+
"is_not",
|
|
1075
|
+
"is_empty",
|
|
1076
|
+
"is_not_empty"
|
|
1077
|
+
],
|
|
1078
|
+
boolean: ["is"],
|
|
1079
|
+
integer: [
|
|
1080
|
+
"is",
|
|
1081
|
+
"is_not",
|
|
1082
|
+
"is_empty",
|
|
1083
|
+
"is_not_empty"
|
|
1084
|
+
],
|
|
1085
|
+
float: [
|
|
1086
|
+
"is",
|
|
1087
|
+
"is_not",
|
|
1088
|
+
"is_empty",
|
|
1089
|
+
"is_not_empty"
|
|
1090
|
+
],
|
|
1091
|
+
date: [
|
|
1092
|
+
"is",
|
|
1093
|
+
"before",
|
|
1094
|
+
"after",
|
|
1095
|
+
"is_empty",
|
|
1096
|
+
"is_not_empty"
|
|
1097
|
+
],
|
|
1098
|
+
array: [
|
|
1099
|
+
"contains",
|
|
1100
|
+
"does_not_contain",
|
|
1101
|
+
"is_empty",
|
|
1102
|
+
"is_not_empty"
|
|
1103
|
+
],
|
|
1104
|
+
tags_conditions: ["is"],
|
|
1105
|
+
date_range: ["is"]
|
|
1106
|
+
};
|
|
1107
|
+
/** Operators that have no value control (the operator itself is the predicate). */
|
|
1108
|
+
const VALUELESS_OPERATORS = new Set(["is_empty", "is_not_empty"]);
|
|
1109
|
+
function getOperatorsForType(type) {
|
|
1110
|
+
return OPERATORS_BY_TYPE[type] ?? [];
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Effective operator vocabulary for a concrete field: its `allowedOperators`
|
|
1114
|
+
* allow-list, or the field type's full vocabulary when the allow-list is
|
|
1115
|
+
* absent or empty. An empty allow-list means "unconstrained" rather than
|
|
1116
|
+
* "no operators" — the latter would leave the field selectable in the picker
|
|
1117
|
+
* but incapable of producing a rule that passes validation.
|
|
1118
|
+
*
|
|
1119
|
+
* Prefer this over {@link getOperatorsForType} anywhere a real field is in
|
|
1120
|
+
* hand — the popover's operator dropdown, `FieldRegistry`, validation — so a
|
|
1121
|
+
* field can never surface an operator the host application can't serialize.
|
|
1122
|
+
*/
|
|
1123
|
+
function getOperatorsForField(field) {
|
|
1124
|
+
const { allowedOperators } = field;
|
|
1125
|
+
if (allowedOperators == null || allowedOperators.length === 0) return getOperatorsForType(field.type);
|
|
1126
|
+
return allowedOperators;
|
|
1127
|
+
}
|
|
1128
|
+
function getDefaultOperatorForType(type) {
|
|
1129
|
+
const list = OPERATORS_BY_TYPE[type];
|
|
1130
|
+
if (list.length === 0) throw new Error(`No operators defined for field type "${type}"`);
|
|
1131
|
+
return list[0];
|
|
1132
|
+
}
|
|
1133
|
+
function isValuelessOperator(operator) {
|
|
1134
|
+
return VALUELESS_OPERATORS.has(operator);
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Whether a concrete field's operator selector should be shown. A field
|
|
1138
|
+
* pinned to one operator renders it as static text rather than a caret that
|
|
1139
|
+
* opens a one-item menu.
|
|
1140
|
+
*/
|
|
1141
|
+
function fieldHasMultipleOperators(field) {
|
|
1142
|
+
return getOperatorsForField(field).length > 1;
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Human-readable operator labels for the operator dropdown and filled pill.
|
|
1146
|
+
*/
|
|
1147
|
+
const OPERATOR_LABELS = {
|
|
1148
|
+
is: "is",
|
|
1149
|
+
is_not: "is not",
|
|
1150
|
+
contains: "contains",
|
|
1151
|
+
does_not_contain: "does not contain",
|
|
1152
|
+
is_empty: "is empty",
|
|
1153
|
+
is_not_empty: "is not empty",
|
|
1154
|
+
is_true: "is true",
|
|
1155
|
+
is_false: "is false",
|
|
1156
|
+
before: "before",
|
|
1157
|
+
after: "after"
|
|
1158
|
+
};
|
|
1159
|
+
/**
|
|
1160
|
+
* Field-type-specific label overrides. The operator vocabulary itself
|
|
1161
|
+
* stays unchanged (operator tokens are `is` / `is_not` across types), but
|
|
1162
|
+
* labels can be more specific where the generic label is ambiguous.
|
|
1163
|
+
*
|
|
1164
|
+
* Only present for `(fieldType, operator)` pairs that diverge from the
|
|
1165
|
+
* generic label; everything else falls through to `OPERATOR_LABELS`.
|
|
1166
|
+
*/
|
|
1167
|
+
const OPERATOR_LABELS_BY_TYPE = { multi_choices: {
|
|
1168
|
+
is: "is any of",
|
|
1169
|
+
is_not: "is none of"
|
|
1170
|
+
} };
|
|
1171
|
+
function getOperatorLabel(operator, fieldType) {
|
|
1172
|
+
if (fieldType) {
|
|
1173
|
+
const override = OPERATOR_LABELS_BY_TYPE[fieldType]?.[operator];
|
|
1174
|
+
if (override) return override;
|
|
1175
|
+
}
|
|
1176
|
+
return OPERATOR_LABELS[operator];
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Empty-but-present value seeded when a rule switches from a valueless operator
|
|
1180
|
+
* (`is_empty`) to one that requires a value. Non-null on purpose: a `null` value
|
|
1181
|
+
* reads as a removal and drops the pill mid-edit.
|
|
1182
|
+
*/
|
|
1183
|
+
function emptyValueForFieldType(type) {
|
|
1184
|
+
switch (type) {
|
|
1185
|
+
case "multi_choices":
|
|
1186
|
+
case "tags_conditions": return [];
|
|
1187
|
+
default: return "";
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
//#endregion
|
|
1191
|
+
//#region components/DataTable/TableControls/FiltersV2/AdvancedFilterRulesPopover/display.ts
|
|
1192
|
+
function valueDisplayString(value, optionLabels) {
|
|
1193
|
+
if (value == null) return;
|
|
1194
|
+
const display = (raw) => optionLabels?.[raw] ?? raw;
|
|
1195
|
+
if (Array.isArray(value)) {
|
|
1196
|
+
if (value.length === 0) return;
|
|
1197
|
+
if (value.length === 1) return display(String(value[0]));
|
|
1198
|
+
return `${display(String(value[0]))} +${value.length - 1}`;
|
|
1199
|
+
}
|
|
1200
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
1201
|
+
if (typeof value === "number") return String(value);
|
|
1202
|
+
if (typeof value === "string") return value.length > 0 ? display(value) : void 0;
|
|
1203
|
+
}
|
|
1204
|
+
function draftValueFor(field, operator) {
|
|
1205
|
+
if (isValuelessOperator(operator)) return null;
|
|
1206
|
+
if (field.type === "boolean") return true;
|
|
1207
|
+
if (field.type === "multi_choices") return [];
|
|
1208
|
+
return "";
|
|
1209
|
+
}
|
|
1210
|
+
//#endregion
|
|
1211
|
+
//#region components/DataTable/TableControls/FiltersV2/fieldPickerOptions.ts
|
|
1212
|
+
const FIELDS_SUBMENU_LABEL = "Fields";
|
|
1213
|
+
function newNode() {
|
|
1214
|
+
return {
|
|
1215
|
+
leaves: [],
|
|
1216
|
+
groups: /* @__PURE__ */ new Map(),
|
|
1217
|
+
parents: /* @__PURE__ */ new Set()
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* The leaf `value` stays the full field id even when nested, so selection,
|
|
1222
|
+
* serialization, and persisted filter state are unaffected by the grouping.
|
|
1223
|
+
*/
|
|
1224
|
+
function insertField(root, field, description) {
|
|
1225
|
+
const path = field.path && field.path.length > 1 ? field.path : null;
|
|
1226
|
+
if (!path) {
|
|
1227
|
+
root.leaves.push({
|
|
1228
|
+
label: field.label,
|
|
1229
|
+
value: field.id,
|
|
1230
|
+
description
|
|
1231
|
+
});
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
let node = root;
|
|
1235
|
+
for (const segment of path.slice(0, -1)) {
|
|
1236
|
+
let child = node.groups.get(segment);
|
|
1237
|
+
if (!child) {
|
|
1238
|
+
child = newNode();
|
|
1239
|
+
node.groups.set(segment, child);
|
|
1240
|
+
}
|
|
1241
|
+
if (description != null) child.parents.add(description);
|
|
1242
|
+
node = child;
|
|
1243
|
+
}
|
|
1244
|
+
node.leaves.push({
|
|
1245
|
+
label: startCase(path[path.length - 1]),
|
|
1246
|
+
value: field.id,
|
|
1247
|
+
description
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* A group gets a parent-record-type sublabel only when every field beneath it
|
|
1252
|
+
* shares one — a segment reused across templates has no single owner to name.
|
|
1253
|
+
*/
|
|
1254
|
+
function nodeToOptions(node) {
|
|
1255
|
+
const groups = [];
|
|
1256
|
+
for (const [segment, child] of node.groups) {
|
|
1257
|
+
const [onlyParent] = child.parents;
|
|
1258
|
+
groups.push({
|
|
1259
|
+
label: startCase(segment),
|
|
1260
|
+
description: child.parents.size === 1 ? onlyParent : void 0,
|
|
1261
|
+
nested: true,
|
|
1262
|
+
options: nodeToOptions(child)
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
return [...node.leaves, ...groups];
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Build the field-picker option tree shared by the `⊕ Filter` pill picker
|
|
1269
|
+
* (`FilterPickerMenu`) and the advanced-rules field dropdown
|
|
1270
|
+
* (`RuleEditorFields`) so both surface custom fields identically.
|
|
1271
|
+
*
|
|
1272
|
+
* System fields are top-level leaves; every custom field lives behind a
|
|
1273
|
+
* single `nested: true` "Fields" group, with its parent record type rendered
|
|
1274
|
+
* as the leaf's `description` sublabel. Hash subfields (fields whose backend
|
|
1275
|
+
* `path` has more than one segment, e.g. `address.city`) nest one level
|
|
1276
|
+
* deeper, behind their parent segment (`address`). The `nested`
|
|
1277
|
+
* flag is what makes the single-select Combobox render a group as an
|
|
1278
|
+
* expandable submenu — leaf selections commit via `onChange`, group descents
|
|
1279
|
+
* only navigate.
|
|
1280
|
+
*
|
|
1281
|
+
* `visibleCustomFieldParents`, when provided, narrows the Fields submenu to
|
|
1282
|
+
* only the listed parent record-type labels; passing an empty array hides the
|
|
1283
|
+
* submenu entirely. Omit it (or pass `undefined`) to show every group.
|
|
1284
|
+
*
|
|
1285
|
+
* `excludeFieldIds`, when provided, drops those fields from the tree.
|
|
1286
|
+
*/
|
|
1287
|
+
function buildFieldPickerOptions(registry, visibleCustomFieldParents, excludeFieldIds) {
|
|
1288
|
+
const systemFields = registry.systemFieldsList();
|
|
1289
|
+
const customGroups = registry.customFieldGroups();
|
|
1290
|
+
const excluded = excludeFieldIds == null ? null : new Set(excludeFieldIds);
|
|
1291
|
+
const tree = systemFields.filter((field) => excluded == null || !excluded.has(field.id)).map((field) => ({
|
|
1292
|
+
label: field.label,
|
|
1293
|
+
value: field.id
|
|
1294
|
+
}));
|
|
1295
|
+
const allowedParents = visibleCustomFieldParents == null ? null : new Set(visibleCustomFieldParents);
|
|
1296
|
+
const customRoot = newNode();
|
|
1297
|
+
for (const group of customGroups) {
|
|
1298
|
+
if (allowedParents != null && !allowedParents.has(group.parent)) continue;
|
|
1299
|
+
const description = group.parent !== "" ? group.parent : void 0;
|
|
1300
|
+
for (const field of group.fields) {
|
|
1301
|
+
if (excluded != null && excluded.has(field.id)) continue;
|
|
1302
|
+
insertField(customRoot, field, description);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
const customOptions = nodeToOptions(customRoot);
|
|
1306
|
+
if (customOptions.length > 0) tree.push({
|
|
1307
|
+
label: FIELDS_SUBMENU_LABEL,
|
|
1308
|
+
nested: true,
|
|
1309
|
+
options: customOptions
|
|
1310
|
+
});
|
|
1311
|
+
return tree;
|
|
1312
|
+
}
|
|
1313
|
+
//#endregion
|
|
1314
|
+
//#region components/DataTable/TableControls/FiltersV2/AdvancedFilterRulesPopover/RuleEditorFields.tsx
|
|
1315
|
+
function RuleEditorFields({ registry, field, groupIndex, ruleIndex, rule, size, operators, operatorConfigurable, onFieldChange, onOperatorChange, onValueChange, renderValueControl }) {
|
|
1316
|
+
const fieldOptions = useMemo(() => buildFieldPickerOptions(registry), [registry]);
|
|
1317
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1318
|
+
/* @__PURE__ */ jsx(ComboboxField, {
|
|
1319
|
+
label: "Value",
|
|
1320
|
+
labelProps: { size },
|
|
1321
|
+
"aria-label": `Field for rule ${ruleIndex + 1} in group ${groupIndex + 1}`,
|
|
1322
|
+
size,
|
|
1323
|
+
options: fieldOptions,
|
|
1324
|
+
value: rule.fieldId,
|
|
1325
|
+
onChange: (next) => {
|
|
1326
|
+
if (typeof next === "string") onFieldChange(groupIndex, ruleIndex, next);
|
|
1327
|
+
},
|
|
1328
|
+
placeholder: "Select field",
|
|
1329
|
+
portal: false
|
|
1330
|
+
}),
|
|
1331
|
+
operatorConfigurable ? /* @__PURE__ */ jsx(ComboboxField, {
|
|
1332
|
+
label: "Operator",
|
|
1333
|
+
labelProps: { size },
|
|
1334
|
+
"aria-label": `Operator for rule ${ruleIndex + 1} in group ${groupIndex + 1}`,
|
|
1335
|
+
size,
|
|
1336
|
+
options: operators.map((op) => ({
|
|
1337
|
+
label: getOperatorLabel(op, field?.type),
|
|
1338
|
+
value: op
|
|
1339
|
+
})),
|
|
1340
|
+
value: rule.operator,
|
|
1341
|
+
onChange: (next) => {
|
|
1342
|
+
if (typeof next === "string") onOperatorChange(groupIndex, ruleIndex, next);
|
|
1343
|
+
},
|
|
1344
|
+
portal: false
|
|
1345
|
+
}) : /* @__PURE__ */ jsx(FormField, {
|
|
1346
|
+
label: "Operator",
|
|
1347
|
+
labelProps: { size },
|
|
1348
|
+
children: /* @__PURE__ */ jsx(Input_default, {
|
|
1349
|
+
size,
|
|
1350
|
+
"aria-label": `Operator for rule ${ruleIndex + 1} in group ${groupIndex + 1}`,
|
|
1351
|
+
value: getOperatorLabel(rule.operator, field?.type),
|
|
1352
|
+
disabled: true,
|
|
1353
|
+
readOnly: true
|
|
1354
|
+
})
|
|
1355
|
+
}),
|
|
1356
|
+
field && !isValuelessOperator(rule.operator) ? /* @__PURE__ */ jsx(FormField, {
|
|
1357
|
+
label: "Value",
|
|
1358
|
+
labelProps: { size },
|
|
1359
|
+
children: /* @__PURE__ */ jsx(Fragment, { children: renderValueControl(field, rule.operator, rule.value, (next) => onValueChange(groupIndex, ruleIndex, next)) })
|
|
1360
|
+
}) : null
|
|
1361
|
+
] });
|
|
1362
|
+
}
|
|
1363
|
+
//#endregion
|
|
1364
|
+
//#region components/DataTable/TableControls/FiltersV2/AdvancedFilterRulesPopover/AdvancedFilterRuleRow.tsx
|
|
1365
|
+
function AdvancedFilterRuleRow({ registry, groupIndex, ruleIndex, rule, size, isOpen, onClose, onDelete, onFieldChange, onOperatorChange, onValueChange, renderValueControl }) {
|
|
1366
|
+
const field = registry.get(rule.fieldId);
|
|
1367
|
+
const operators = field ? getOperatorsForField(field) : [];
|
|
1368
|
+
const operatorConfigurable = field ? fieldHasMultipleOperators(field) : true;
|
|
1369
|
+
const condition = ruleIndex === 0 ? "If" : "And";
|
|
1370
|
+
const rowId = `${groupIndex}-${ruleIndex}`;
|
|
1371
|
+
const valueDisplay = isValuelessOperator(rule.operator) ? void 0 : valueDisplayString(rule.value, registry.optionLabelsFor(rule.fieldId));
|
|
1372
|
+
const textValue = [
|
|
1373
|
+
condition,
|
|
1374
|
+
field?.label ?? rule.fieldId,
|
|
1375
|
+
getOperatorLabel(rule.operator, field?.type),
|
|
1376
|
+
valueDisplay
|
|
1377
|
+
].filter(Boolean).join(" ");
|
|
1378
|
+
return /* @__PURE__ */ jsx(ListBox.LogicRow, {
|
|
1379
|
+
id: rowId,
|
|
1380
|
+
condition,
|
|
1381
|
+
hideCondition: true,
|
|
1382
|
+
attributeName: field?.label ?? rule.fieldId,
|
|
1383
|
+
operator: getOperatorLabel(rule.operator, field?.type),
|
|
1384
|
+
matchLevelName: valueDisplay,
|
|
1385
|
+
textValue,
|
|
1386
|
+
size,
|
|
1387
|
+
onClick: () => {
|
|
1388
|
+
if (isOpen) onClose();
|
|
1389
|
+
},
|
|
1390
|
+
onDelete,
|
|
1391
|
+
dropdownHeadingText: `Edit \u2018${condition}\u2019 statement`,
|
|
1392
|
+
dropdownFormFields: /* @__PURE__ */ jsx(RuleEditorFields, {
|
|
1393
|
+
registry,
|
|
1394
|
+
field,
|
|
1395
|
+
groupIndex,
|
|
1396
|
+
ruleIndex,
|
|
1397
|
+
rule,
|
|
1398
|
+
size,
|
|
1399
|
+
operators,
|
|
1400
|
+
operatorConfigurable,
|
|
1401
|
+
onFieldChange,
|
|
1402
|
+
onOperatorChange,
|
|
1403
|
+
onValueChange,
|
|
1404
|
+
renderValueControl
|
|
1405
|
+
})
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
//#endregion
|
|
1409
|
+
//#region components/DataTable/TableControls/FiltersV2/AdvancedFilterRulesPopover/compound-expression.ts
|
|
1410
|
+
function withGroup(compound, index, group) {
|
|
1411
|
+
const groups = [...compound.groups];
|
|
1412
|
+
groups[index] = group;
|
|
1413
|
+
return { groups };
|
|
1414
|
+
}
|
|
1415
|
+
function addRuleToGroup(compound, groupIndex, rule) {
|
|
1416
|
+
const groups = [...compound.groups];
|
|
1417
|
+
while (groups.length <= groupIndex) groups.push({ rules: [] });
|
|
1418
|
+
groups[groupIndex] = { rules: [...groups[groupIndex].rules, rule] };
|
|
1419
|
+
return { groups };
|
|
1420
|
+
}
|
|
1421
|
+
function setRuleInGroup(compound, groupIndex, ruleIndex, rule) {
|
|
1422
|
+
const group = compound.groups[groupIndex];
|
|
1423
|
+
if (!group || ruleIndex < 0 || ruleIndex >= group.rules.length) return compound;
|
|
1424
|
+
const rules = [...group.rules];
|
|
1425
|
+
rules[ruleIndex] = rule;
|
|
1426
|
+
return withGroup(compound, groupIndex, { rules });
|
|
1427
|
+
}
|
|
1428
|
+
function removeRuleFromGroup(compound, groupIndex, ruleIndex) {
|
|
1429
|
+
const group = compound.groups[groupIndex];
|
|
1430
|
+
if (!group || ruleIndex < 0 || ruleIndex >= group.rules.length) return compound;
|
|
1431
|
+
const rules = group.rules.filter((_, i) => i !== ruleIndex);
|
|
1432
|
+
if (rules.length === 0) return { groups: compound.groups.filter((_, i) => i !== groupIndex) };
|
|
1433
|
+
return withGroup(compound, groupIndex, { rules });
|
|
1434
|
+
}
|
|
1435
|
+
//#endregion
|
|
1436
|
+
//#region components/DataTable/TableControls/FiltersV2/AdvancedFilterRulesPopover/use-rules-popover-selection.ts
|
|
1437
|
+
function useRulesPopoverSelection({ isOpen }) {
|
|
1438
|
+
const [selectedRowKey, setSelectedRowKey] = useState(null);
|
|
1439
|
+
const popoverContentRef = useRef(null);
|
|
1440
|
+
const lastPointerDownTargetRef = useRef(null);
|
|
1441
|
+
useEffect(() => {
|
|
1442
|
+
if (!isOpen && selectedRowKey != null) setSelectedRowKey(null);
|
|
1443
|
+
}, [isOpen, selectedRowKey]);
|
|
1444
|
+
useEffect(() => {
|
|
1445
|
+
const onPointerDown = (event) => {
|
|
1446
|
+
lastPointerDownTargetRef.current = event.target;
|
|
1447
|
+
};
|
|
1448
|
+
document.addEventListener("mousedown", onPointerDown, true);
|
|
1449
|
+
return () => document.removeEventListener("mousedown", onPointerDown, true);
|
|
1450
|
+
}, []);
|
|
1451
|
+
const closeSelectedRow = () => setSelectedRowKey(null);
|
|
1452
|
+
const handleGroupSelectionChange = (keys) => {
|
|
1453
|
+
const next = keys instanceof Set ? Array.from(keys)[0] ?? null : null;
|
|
1454
|
+
if (next == null) {
|
|
1455
|
+
const target = lastPointerDownTargetRef.current;
|
|
1456
|
+
if (target && popoverContentRef.current?.contains(target)) closeSelectedRow();
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
setSelectedRowKey(next);
|
|
1460
|
+
};
|
|
1461
|
+
const selectedKeysForGroup = (groupIndex) => {
|
|
1462
|
+
return selectedRowKey != null && selectedRowKey.startsWith(`${groupIndex}-`) ? new Set([selectedRowKey]) : /* @__PURE__ */ new Set();
|
|
1463
|
+
};
|
|
1464
|
+
return {
|
|
1465
|
+
selectedRowKey,
|
|
1466
|
+
setSelectedRowKey,
|
|
1467
|
+
closeSelectedRow,
|
|
1468
|
+
popoverContentRef,
|
|
1469
|
+
handleGroupSelectionChange,
|
|
1470
|
+
selectedKeysForGroup
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
//#endregion
|
|
1474
|
+
//#region components/DataTable/TableControls/FiltersV2/filter-size-context.ts
|
|
1475
|
+
/**
|
|
1476
|
+
* Context for FilterBar-wide rendering settings. It currently carries
|
|
1477
|
+
* only `size`, but the object shape leaves room for future bar-level
|
|
1478
|
+
* settings without introducing another provider.
|
|
1479
|
+
*/
|
|
1480
|
+
const FilterBarContext = createContext({ size: "sm" });
|
|
1481
|
+
/**
|
|
1482
|
+
* Resolve the active filter-bar size, honoring an explicit prop
|
|
1483
|
+
* override before falling back to the surrounding `FilterBarContext`.
|
|
1484
|
+
* Pattern is the same as the design-system FormControl size hook —
|
|
1485
|
+
* components accept an optional `size` prop that wins, otherwise
|
|
1486
|
+
* follow the ambient context.
|
|
1487
|
+
*/
|
|
1488
|
+
function useFilterBarSize(override) {
|
|
1489
|
+
const { size } = useContext(FilterBarContext);
|
|
1490
|
+
return override ?? size;
|
|
1491
|
+
}
|
|
1492
|
+
const FILTER_SIZE_TOKENS = {
|
|
1493
|
+
sm: {
|
|
1494
|
+
controlHeight: "24px",
|
|
1495
|
+
chipHeight: "18px",
|
|
1496
|
+
fontSize: "12px",
|
|
1497
|
+
lineHeight: "18px",
|
|
1498
|
+
iconSize: "12px"
|
|
1499
|
+
},
|
|
1500
|
+
md: {
|
|
1501
|
+
controlHeight: "32px",
|
|
1502
|
+
chipHeight: "24px",
|
|
1503
|
+
fontSize: "14px",
|
|
1504
|
+
lineHeight: "22px",
|
|
1505
|
+
iconSize: "16px"
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region components/DataTable/TableControls/FiltersV2/AdvancedFilterRulesPopover/AdvancedFilterRulesPopover.tsx
|
|
1510
|
+
const PopoverContent = styled(PopoverPrimitive.Content)`
|
|
1511
|
+
z-index: ${Z_INDICES.modalDropdown};
|
|
1512
|
+
`;
|
|
1513
|
+
const PopoverCard = styled(Card_default)`
|
|
1514
|
+
width: 384px;
|
|
1515
|
+
max-width: calc(100vw - ${Spacing[4]});
|
|
1516
|
+
margin-bottom: 0;
|
|
1517
|
+
`;
|
|
1518
|
+
const Header = styled.div`
|
|
1519
|
+
display: flex;
|
|
1520
|
+
align-items: center;
|
|
1521
|
+
justify-content: space-between;
|
|
1522
|
+
padding: ${Spacing[3]};
|
|
1523
|
+
`;
|
|
1524
|
+
const Body = styled.div`
|
|
1525
|
+
display: flex;
|
|
1526
|
+
flex-direction: column;
|
|
1527
|
+
gap: ${Spacing[2]};
|
|
1528
|
+
padding: 0 ${Spacing[3]} ${Spacing[3]};
|
|
1529
|
+
max-height: 60vh;
|
|
1530
|
+
overflow-y: auto;
|
|
1531
|
+
`;
|
|
1532
|
+
/**
|
|
1533
|
+
* Bordered container that wraps a single AndGroup. Visually mirrors the
|
|
1534
|
+
* `TableBox` shell (1px BorderDivider + BorderRadius.Md) but is built
|
|
1535
|
+
* standalone here because the rows inside are `LogicRow`s, which depend
|
|
1536
|
+
* on a parent `ListBox` rather than `TableBox`. Each group owns its
|
|
1537
|
+
* "If" / "Or" title and rule actions in the card header.
|
|
1538
|
+
*/
|
|
1539
|
+
const GroupCard = styled.div`
|
|
1540
|
+
display: flex;
|
|
1541
|
+
flex-direction: column;
|
|
1542
|
+
border: 1px solid ${Color.BorderDivider};
|
|
1543
|
+
border-radius: ${BorderRadius.Md};
|
|
1544
|
+
background-color: ${Color.BackgroundPrimary};
|
|
1545
|
+
overflow: hidden;
|
|
1546
|
+
`;
|
|
1547
|
+
const GroupHeader = styled.div`
|
|
1548
|
+
display: flex;
|
|
1549
|
+
align-items: center;
|
|
1550
|
+
justify-content: space-between;
|
|
1551
|
+
padding: ${Spacing[1]} ${Spacing[2]};
|
|
1552
|
+
border-bottom: 1px solid ${Color.BorderDivider};
|
|
1553
|
+
background-color: ${Color.BackgroundSecondary};
|
|
1554
|
+
`;
|
|
1555
|
+
const GroupTitle = styled.span`
|
|
1556
|
+
font-size: ${({ $size }) => FILTER_SIZE_TOKENS[$size].fontSize};
|
|
1557
|
+
line-height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
1558
|
+
color: ${Color.TextPrimary};
|
|
1559
|
+
`;
|
|
1560
|
+
const GroupHeaderActions = styled.div`
|
|
1561
|
+
display: inline-flex;
|
|
1562
|
+
align-items: center;
|
|
1563
|
+
gap: ${Spacing[1]};
|
|
1564
|
+
`;
|
|
1565
|
+
/**
|
|
1566
|
+
* A focused-down `ListBox` for the rules in one AndGroup. Strips the
|
|
1567
|
+
* default 204px width AND the standalone border (the parent `GroupCard`
|
|
1568
|
+
* supplies the border now). Removes the per-listbox padding so the row
|
|
1569
|
+
* cells sit flush against the card.
|
|
1570
|
+
*/
|
|
1571
|
+
const RulesListBox = styled(ListBox)`
|
|
1572
|
+
width: 100%;
|
|
1573
|
+
border: 0;
|
|
1574
|
+
border-radius: 0;
|
|
1575
|
+
padding: 0;
|
|
1576
|
+
`;
|
|
1577
|
+
/**
|
|
1578
|
+
* "Or group" affordance that appears between consecutive groups and at
|
|
1579
|
+
* the bottom of the popover. Adds a new empty `AndGroup` at the end of
|
|
1580
|
+
* `compound.groups` when clicked.
|
|
1581
|
+
*/
|
|
1582
|
+
const OrGroupRow = styled.div`
|
|
1583
|
+
display: flex;
|
|
1584
|
+
`;
|
|
1585
|
+
function groupsForRender(compound) {
|
|
1586
|
+
if (compound.groups.length === 0) return [{
|
|
1587
|
+
rules: [],
|
|
1588
|
+
synthetic: true
|
|
1589
|
+
}];
|
|
1590
|
+
return compound.groups.map((g) => ({
|
|
1591
|
+
rules: g.rules,
|
|
1592
|
+
synthetic: false
|
|
1593
|
+
}));
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* DNF (OR-of-AND) editor popover for the compound expression owned by the
|
|
1597
|
+
* Rules pill.
|
|
1598
|
+
*
|
|
1599
|
+
* The popover is fully controlled by `compound` + `onChange` — it never
|
|
1600
|
+
* holds compound state internally so parents can persist edits incrementally
|
|
1601
|
+
* (URL state, saved-view dual-write, etc.). `open` / `onOpenChange` are
|
|
1602
|
+
* optional; when omitted the popover manages its own open state and is
|
|
1603
|
+
* opened by clicking the parent-supplied trigger.
|
|
1604
|
+
*
|
|
1605
|
+
* Each AndGroup's rules are rendered as a `<ListBox>` of `<LogicRow>` items.
|
|
1606
|
+
* The collapsed row reads `[If/And] [field-sticker] [operator] [value-sticker]`
|
|
1607
|
+
* inline; clicking a row reveals the field/operator/value editor inside the
|
|
1608
|
+
* row's dropdown popover.
|
|
1609
|
+
*/
|
|
1610
|
+
const AdvancedFilterRulesPopover = ({ registry, compound, onChange, trigger, open, onOpenChange, renderValueControl, size }) => {
|
|
1611
|
+
const resolvedSize = useFilterBarSize(size);
|
|
1612
|
+
const subtleIconSize = resolvedSize === "sm" ? "xs" : "sm";
|
|
1613
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
1614
|
+
const isControlled = open != null;
|
|
1615
|
+
const isOpen = isControlled ? open : internalOpen;
|
|
1616
|
+
const { selectedRowKey, setSelectedRowKey, closeSelectedRow, popoverContentRef, handleGroupSelectionChange, selectedKeysForGroup } = useRulesPopoverSelection({ isOpen });
|
|
1617
|
+
const handleOpenChange = useCallback((next) => {
|
|
1618
|
+
if (!isControlled) setInternalOpen(next);
|
|
1619
|
+
onOpenChange?.(next);
|
|
1620
|
+
}, [isControlled, onOpenChange]);
|
|
1621
|
+
const baseId = useMemo(() => uniqueId$1("advanced-filter-rules__"), []);
|
|
1622
|
+
const fields = registry.all();
|
|
1623
|
+
const handleAddOrGroup = useCallback(() => {
|
|
1624
|
+
const firstField = fields[0];
|
|
1625
|
+
if (!firstField) return;
|
|
1626
|
+
const operator = registry.reconcileOperator(firstField.id, "is");
|
|
1627
|
+
const groupIndex = compound.groups.length;
|
|
1628
|
+
onChange(addRuleToGroup(compound, groupIndex, {
|
|
1629
|
+
fieldId: firstField.id,
|
|
1630
|
+
operator,
|
|
1631
|
+
value: draftValueFor(firstField, operator)
|
|
1632
|
+
}));
|
|
1633
|
+
setSelectedRowKey(`${groupIndex}-0`);
|
|
1634
|
+
}, [
|
|
1635
|
+
compound,
|
|
1636
|
+
fields,
|
|
1637
|
+
onChange,
|
|
1638
|
+
registry
|
|
1639
|
+
]);
|
|
1640
|
+
const handleAddRuleToGroup = useCallback((groupIndex) => {
|
|
1641
|
+
const firstField = fields[0];
|
|
1642
|
+
if (!firstField) return;
|
|
1643
|
+
const operator = registry.reconcileOperator(firstField.id, "is");
|
|
1644
|
+
const newRowId = `${groupIndex}-${compound.groups[groupIndex]?.rules.length ?? 0}`;
|
|
1645
|
+
onChange(addRuleToGroup(compound, groupIndex, {
|
|
1646
|
+
fieldId: firstField.id,
|
|
1647
|
+
operator,
|
|
1648
|
+
value: draftValueFor(firstField, operator)
|
|
1649
|
+
}));
|
|
1650
|
+
setSelectedRowKey(newRowId);
|
|
1651
|
+
}, [
|
|
1652
|
+
compound,
|
|
1653
|
+
fields,
|
|
1654
|
+
onChange,
|
|
1655
|
+
registry
|
|
1656
|
+
]);
|
|
1657
|
+
const applyCompoundChange = useCallback((next) => {
|
|
1658
|
+
onChange(next);
|
|
1659
|
+
}, [onChange]);
|
|
1660
|
+
const handleRemoveRule = useCallback((groupIndex, ruleIndex) => {
|
|
1661
|
+
applyCompoundChange(removeRuleFromGroup(compound, groupIndex, ruleIndex));
|
|
1662
|
+
}, [compound, applyCompoundChange]);
|
|
1663
|
+
const handleFieldChange = useCallback((groupIndex, ruleIndex, nextFieldId) => {
|
|
1664
|
+
const field = registry.get(nextFieldId);
|
|
1665
|
+
if (!field) return;
|
|
1666
|
+
const prev = compound.groups[groupIndex]?.rules[ruleIndex];
|
|
1667
|
+
if (!prev) return;
|
|
1668
|
+
const operator = registry.reconcileOperator(field.id, prev.operator);
|
|
1669
|
+
onChange(setRuleInGroup(compound, groupIndex, ruleIndex, {
|
|
1670
|
+
fieldId: nextFieldId,
|
|
1671
|
+
operator,
|
|
1672
|
+
value: draftValueFor(field, operator)
|
|
1673
|
+
}));
|
|
1674
|
+
}, [
|
|
1675
|
+
compound,
|
|
1676
|
+
onChange,
|
|
1677
|
+
registry
|
|
1678
|
+
]);
|
|
1679
|
+
const handleOperatorChange = useCallback((groupIndex, ruleIndex, nextOperator) => {
|
|
1680
|
+
const prev = compound.groups[groupIndex]?.rules[ruleIndex];
|
|
1681
|
+
if (!prev) return;
|
|
1682
|
+
onChange(setRuleInGroup(compound, groupIndex, ruleIndex, {
|
|
1683
|
+
fieldId: prev.fieldId,
|
|
1684
|
+
operator: nextOperator,
|
|
1685
|
+
value: isValuelessOperator(nextOperator) ? null : prev.value
|
|
1686
|
+
}));
|
|
1687
|
+
}, [compound, onChange]);
|
|
1688
|
+
const handleValueChange = useCallback((groupIndex, ruleIndex, nextValue) => {
|
|
1689
|
+
const prev = compound.groups[groupIndex]?.rules[ruleIndex];
|
|
1690
|
+
if (!prev) return;
|
|
1691
|
+
onChange(setRuleInGroup(compound, groupIndex, ruleIndex, {
|
|
1692
|
+
fieldId: prev.fieldId,
|
|
1693
|
+
operator: prev.operator,
|
|
1694
|
+
value: nextValue
|
|
1695
|
+
}));
|
|
1696
|
+
}, [compound, onChange]);
|
|
1697
|
+
return /* @__PURE__ */ jsxs(PopoverPrimitive.Root, {
|
|
1698
|
+
open: isOpen,
|
|
1699
|
+
onOpenChange: handleOpenChange,
|
|
1700
|
+
children: [/* @__PURE__ */ jsx(PopoverPrimitive.Trigger, {
|
|
1701
|
+
asChild: true,
|
|
1702
|
+
children: trigger
|
|
1703
|
+
}), /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(PopoverContent, {
|
|
1704
|
+
align: "start",
|
|
1705
|
+
sideOffset: 8,
|
|
1706
|
+
collisionPadding: 8,
|
|
1707
|
+
role: "dialog",
|
|
1708
|
+
"aria-label": "Advanced filter rules",
|
|
1709
|
+
id: baseId,
|
|
1710
|
+
ref: popoverContentRef,
|
|
1711
|
+
tabIndex: -1,
|
|
1712
|
+
onKeyDown: (event) => {
|
|
1713
|
+
if (event.key !== "Enter") return;
|
|
1714
|
+
if (selectedRowKey != null) {
|
|
1715
|
+
event.preventDefault();
|
|
1716
|
+
closeSelectedRow();
|
|
1717
|
+
requestAnimationFrame(() => {
|
|
1718
|
+
popoverContentRef.current?.focus();
|
|
1719
|
+
});
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
if (event.target !== popoverContentRef.current) return;
|
|
1723
|
+
event.preventDefault();
|
|
1724
|
+
handleOpenChange(false);
|
|
1725
|
+
},
|
|
1726
|
+
children: /* @__PURE__ */ jsxs(PopoverCard, {
|
|
1727
|
+
elevated: true,
|
|
1728
|
+
children: [/* @__PURE__ */ jsx(Header, { children: /* @__PURE__ */ jsx(Paragraph, {
|
|
1729
|
+
size: resolvedSize,
|
|
1730
|
+
color: "secondary",
|
|
1731
|
+
children: "Advanced filter rules"
|
|
1732
|
+
}) }), /* @__PURE__ */ jsxs(Body, { children: [groupsForRender(compound).map((group, groupIndex, renderableGroups) => {
|
|
1733
|
+
const groupTitle = groupIndex === 0 ? "If" : "Or";
|
|
1734
|
+
const isSynthetic = group.synthetic;
|
|
1735
|
+
const isOnlyGroup = renderableGroups.length === 1;
|
|
1736
|
+
const isEmptyGroup = group.rules.length === 0;
|
|
1737
|
+
const hideGroupMenu = isOnlyGroup && isEmptyGroup;
|
|
1738
|
+
const deleteGroupLabel = isOnlyGroup && !isEmptyGroup ? "Clear all" : "Delete group";
|
|
1739
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsxs(GroupCard, {
|
|
1740
|
+
"data-test": `advanced-filter-rules__group-${groupIndex}`,
|
|
1741
|
+
role: "group",
|
|
1742
|
+
"aria-label": `Filter group ${groupIndex + 1}`,
|
|
1743
|
+
children: [/* @__PURE__ */ jsxs(GroupHeader, { children: [/* @__PURE__ */ jsx(GroupTitle, {
|
|
1744
|
+
$size: resolvedSize,
|
|
1745
|
+
children: groupTitle
|
|
1746
|
+
}), /* @__PURE__ */ jsxs(GroupHeaderActions, { children: [hideGroupMenu ? null : /* @__PURE__ */ jsx(DropdownMenu_default, {
|
|
1747
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
1748
|
+
size: subtleIconSize,
|
|
1749
|
+
"aria-label": `More actions for filter group ${groupIndex + 1}`,
|
|
1750
|
+
"data-test": `advanced-filter-rules__group-more-${groupIndex}`,
|
|
1751
|
+
children: /* @__PURE__ */ jsx(MoreIcon$1, {})
|
|
1752
|
+
}),
|
|
1753
|
+
align: "end",
|
|
1754
|
+
portal: true,
|
|
1755
|
+
zIndex: Z_INDICES.modalDropdown,
|
|
1756
|
+
children: /* @__PURE__ */ jsx(DropdownMenu_default.Item, {
|
|
1757
|
+
size: resolvedSize,
|
|
1758
|
+
onClick: () => {
|
|
1759
|
+
if (isSynthetic) {
|
|
1760
|
+
handleOpenChange(false);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
applyCompoundChange({ groups: compound.groups.filter((_, i) => i !== groupIndex) });
|
|
1764
|
+
},
|
|
1765
|
+
"data-test": `advanced-filter-rules__delete-group-${groupIndex}`,
|
|
1766
|
+
children: deleteGroupLabel
|
|
1767
|
+
})
|
|
1768
|
+
}), /* @__PURE__ */ jsx(IconButton, {
|
|
1769
|
+
size: subtleIconSize,
|
|
1770
|
+
onClick: () => handleAddRuleToGroup(groupIndex),
|
|
1771
|
+
"aria-label": `Add rule to filter group ${groupIndex + 1}`,
|
|
1772
|
+
"data-test": `advanced-filter-rules__add-rule-${groupIndex}`,
|
|
1773
|
+
children: /* @__PURE__ */ jsx(PlusIcon$1, {})
|
|
1774
|
+
})] })] }), group.rules.length === 0 ? /* @__PURE__ */ jsx(Paragraph, {
|
|
1775
|
+
size: resolvedSize,
|
|
1776
|
+
color: "secondary",
|
|
1777
|
+
style: { padding: Spacing[2] },
|
|
1778
|
+
children: "No rules yet"
|
|
1779
|
+
}) : /* @__PURE__ */ jsx(RulesListBox, {
|
|
1780
|
+
"aria-label": `Rules in group ${groupIndex + 1}`,
|
|
1781
|
+
selectionMode: "single",
|
|
1782
|
+
size: resolvedSize,
|
|
1783
|
+
fullWidth: true,
|
|
1784
|
+
selectedKeys: selectedKeysForGroup(groupIndex),
|
|
1785
|
+
onSelectionChange: handleGroupSelectionChange,
|
|
1786
|
+
children: group.rules.map((rule, ruleIndex) => {
|
|
1787
|
+
const rowId = `${groupIndex}-${ruleIndex}`;
|
|
1788
|
+
return /* @__PURE__ */ jsx(AdvancedFilterRuleRow, {
|
|
1789
|
+
registry,
|
|
1790
|
+
groupIndex,
|
|
1791
|
+
ruleIndex,
|
|
1792
|
+
rule,
|
|
1793
|
+
size: resolvedSize,
|
|
1794
|
+
isOpen: selectedRowKey === rowId,
|
|
1795
|
+
onClose: closeSelectedRow,
|
|
1796
|
+
onDelete: () => handleRemoveRule(groupIndex, ruleIndex),
|
|
1797
|
+
onFieldChange: handleFieldChange,
|
|
1798
|
+
onOperatorChange: handleOperatorChange,
|
|
1799
|
+
onValueChange: handleValueChange,
|
|
1800
|
+
renderValueControl
|
|
1801
|
+
}, rowId);
|
|
1802
|
+
})
|
|
1803
|
+
})]
|
|
1804
|
+
}) }, `${baseId}__group-${groupIndex}`);
|
|
1805
|
+
}), /* @__PURE__ */ jsx(OrGroupRow, { children: /* @__PURE__ */ jsx(Button, {
|
|
1806
|
+
variant: "basic",
|
|
1807
|
+
size: resolvedSize,
|
|
1808
|
+
width: "auto",
|
|
1809
|
+
prefixIcon: /* @__PURE__ */ jsx(PlusIcon, {}),
|
|
1810
|
+
onClick: handleAddOrGroup,
|
|
1811
|
+
"data-test": "advanced-filter-rules__add-or-group",
|
|
1812
|
+
children: "Or group"
|
|
1813
|
+
}) })] })]
|
|
1814
|
+
})
|
|
1815
|
+
}) })]
|
|
1816
|
+
});
|
|
1817
|
+
};
|
|
1818
|
+
//#endregion
|
|
1819
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/ArrayValueControl/ArrayValueControl.tsx
|
|
1820
|
+
const stringDisplayValue = (value) => {
|
|
1821
|
+
if (typeof value === "string") return value;
|
|
1822
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1823
|
+
return "";
|
|
1824
|
+
};
|
|
1825
|
+
const stringValue = (value) => {
|
|
1826
|
+
if (typeof value === "string" && value !== "") return value;
|
|
1827
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1828
|
+
return null;
|
|
1829
|
+
};
|
|
1830
|
+
/**
|
|
1831
|
+
* Array-typed value control for the Filter UI v2 popover.
|
|
1832
|
+
*
|
|
1833
|
+
* Used for `array` fields with operators `contains` / `does_not_contain`.
|
|
1834
|
+
* The popover renders nothing when the operator is valueless
|
|
1835
|
+
* (`is_empty` / `is_not_empty`).
|
|
1836
|
+
*
|
|
1837
|
+
* The rendered control depends on the inner item type advertised by the
|
|
1838
|
+
* field's `itemType`:
|
|
1839
|
+
*
|
|
1840
|
+
* - `'choices'` → single-select Combobox sourced from `field.options`.
|
|
1841
|
+
* - `'string'` or unset → free-text input (a single value to test for
|
|
1842
|
+
* membership against the array column).
|
|
1843
|
+
*
|
|
1844
|
+
* Output value is always a single string; an empty text input emits `''`, not
|
|
1845
|
+
* `null`, because the pill lifecycle reads a `null` value on a value-requiring
|
|
1846
|
+
* operator as a removal and drops the pill. `''` keeps the pill as a visible
|
|
1847
|
+
* draft; the serialize boundary is what filters incomplete values out of the
|
|
1848
|
+
* request. The predicate is "does this array contain (or not contain) this
|
|
1849
|
+
* scalar?", so we never produce an array-of-values from this control.
|
|
1850
|
+
*/
|
|
1851
|
+
const ArrayValueControl = ({ field, operator, value, onChange, onCommit, ariaLabel, layout = "popover" }) => {
|
|
1852
|
+
const size = useFilterBarSize();
|
|
1853
|
+
const [localValue, setLocalValue] = useState(() => stringDisplayValue(value));
|
|
1854
|
+
useEffect(() => {
|
|
1855
|
+
setLocalValue(stringDisplayValue(value));
|
|
1856
|
+
}, [value]);
|
|
1857
|
+
const handleTextChange = useCallback((raw) => {
|
|
1858
|
+
setLocalValue(raw);
|
|
1859
|
+
onChange(raw);
|
|
1860
|
+
}, [onChange]);
|
|
1861
|
+
const handleComboboxChange = useCallback((next) => {
|
|
1862
|
+
onChange(next ?? null);
|
|
1863
|
+
onCommit?.();
|
|
1864
|
+
}, [onChange, onCommit]);
|
|
1865
|
+
if (isValuelessOperator(operator)) return null;
|
|
1866
|
+
const label = ariaLabel ?? `${field.label} value`;
|
|
1867
|
+
if ((field.itemType ?? "string") === "choices") return /* @__PURE__ */ jsx(Combobox, {
|
|
1868
|
+
"aria-label": label,
|
|
1869
|
+
options: (field.options ?? []).map((opt) => ({
|
|
1870
|
+
label: opt.label,
|
|
1871
|
+
value: opt.value
|
|
1872
|
+
})),
|
|
1873
|
+
value: stringValue(value),
|
|
1874
|
+
onChange: handleComboboxChange,
|
|
1875
|
+
placeholder: "Select an option",
|
|
1876
|
+
size,
|
|
1877
|
+
embedded: layout === "popover"
|
|
1878
|
+
});
|
|
1879
|
+
return /* @__PURE__ */ jsx(Input_default, {
|
|
1880
|
+
type: "text",
|
|
1881
|
+
size,
|
|
1882
|
+
placeholder: "Enter a value",
|
|
1883
|
+
"aria-label": label,
|
|
1884
|
+
value: localValue,
|
|
1885
|
+
onChange: (e) => handleTextChange(e.target.value)
|
|
1886
|
+
});
|
|
1887
|
+
};
|
|
1888
|
+
//#endregion
|
|
1889
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/BooleanValueControl/BooleanValueControl.tsx
|
|
1890
|
+
const OPTIONS = [{
|
|
1891
|
+
label: "True",
|
|
1892
|
+
value: true
|
|
1893
|
+
}, {
|
|
1894
|
+
label: "False",
|
|
1895
|
+
value: false
|
|
1896
|
+
}];
|
|
1897
|
+
const Row = styled.button`
|
|
1898
|
+
${Typography.TextSmall};
|
|
1899
|
+
display: flex;
|
|
1900
|
+
align-items: center;
|
|
1901
|
+
justify-content: space-between;
|
|
1902
|
+
box-sizing: border-box;
|
|
1903
|
+
width: 100%;
|
|
1904
|
+
margin: 0;
|
|
1905
|
+
padding: ${Spacing[1]} ${Spacing[2]};
|
|
1906
|
+
border: none;
|
|
1907
|
+
appearance: none;
|
|
1908
|
+
border-radius: ${BorderRadius.Sm};
|
|
1909
|
+
background: ${({ $selected }) => $selected ? Color.CorePurple100 : "transparent"};
|
|
1910
|
+
color: ${({ $selected }) => $selected ? Color.TextActive : Color.TextPrimary};
|
|
1911
|
+
font-family: inherit;
|
|
1912
|
+
text-align: left;
|
|
1913
|
+
cursor: pointer;
|
|
1914
|
+
|
|
1915
|
+
&:hover {
|
|
1916
|
+
background: ${({ $selected }) => $selected ? Color.CorePurple100 : Color.BackgroundSecondary};
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
&:focus-visible {
|
|
1920
|
+
outline: 2px solid ${Color.BorderFocus};
|
|
1921
|
+
outline-offset: 1px;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
svg {
|
|
1925
|
+
flex-shrink: 0;
|
|
1926
|
+
color: inherit;
|
|
1927
|
+
}
|
|
1928
|
+
`;
|
|
1929
|
+
const Root = styled.div`
|
|
1930
|
+
display: flex;
|
|
1931
|
+
flex-direction: column;
|
|
1932
|
+
gap: ${Spacing[0]};
|
|
1933
|
+
`;
|
|
1934
|
+
/**
|
|
1935
|
+
* Filter UI v2 — boolean value picker for `boolean`-typed fields with `is` /
|
|
1936
|
+
* `is_not` operators. Renders two rows (True / False); the selected row uses
|
|
1937
|
+
* the same purple treatment as other filter value selections. Renders nothing
|
|
1938
|
+
* for valueless operators (`is_empty`, `is_not_empty`).
|
|
1939
|
+
*/
|
|
1940
|
+
function BooleanValueControl({ operator, value, onChange, ariaLabel }) {
|
|
1941
|
+
if (isValuelessOperator(operator)) return null;
|
|
1942
|
+
const selected = typeof value === "boolean" ? value : null;
|
|
1943
|
+
return /* @__PURE__ */ jsx(Root, {
|
|
1944
|
+
role: "radiogroup",
|
|
1945
|
+
"aria-label": ariaLabel ?? "Boolean value",
|
|
1946
|
+
children: OPTIONS.map((opt) => {
|
|
1947
|
+
const isSelected = selected === opt.value;
|
|
1948
|
+
return /* @__PURE__ */ jsxs(Row, {
|
|
1949
|
+
type: "button",
|
|
1950
|
+
role: "radio",
|
|
1951
|
+
"aria-checked": isSelected,
|
|
1952
|
+
$selected: isSelected,
|
|
1953
|
+
onClick: () => onChange(opt.value),
|
|
1954
|
+
children: [opt.label, isSelected ? /* @__PURE__ */ jsx(CheckIcon, { "aria-hidden": true }) : null]
|
|
1955
|
+
}, String(opt.value));
|
|
1956
|
+
})
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
//#endregion
|
|
1960
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/ChoicesValueControl/ChoicesValueControl.tsx
|
|
1961
|
+
const EMPTY_OPTIONS = [];
|
|
1962
|
+
const EMPTY_VALUES = [];
|
|
1963
|
+
/**
|
|
1964
|
+
* Filter UI v2 — value control for `choices` / `multi_choices`-typed fields.
|
|
1965
|
+
*
|
|
1966
|
+
* Backed by the design-system `Combobox`. In single-select mode the value is a
|
|
1967
|
+
* string; in `multiple` mode it is an array of strings. Options come from
|
|
1968
|
+
* `field.options` (populated by `FieldRegistry` from the backend's
|
|
1969
|
+
* `field_key_to_info` payload). Renders nothing when the active operator is
|
|
1970
|
+
* valueless (e.g. `is_empty`).
|
|
1971
|
+
*/
|
|
1972
|
+
function ChoicesValueControl({ field, operator, value, onChange, onCommit, ariaLabel, trigger, layout = "popover", multiple = false }) {
|
|
1973
|
+
const generatedId = useMemo(() => uniqueId$1(multiple ? "filter_v2_multi_choices_" : "filter_v2_choices_"), [multiple]);
|
|
1974
|
+
const size = useFilterBarSize();
|
|
1975
|
+
const options = field.options ?? EMPTY_OPTIONS;
|
|
1976
|
+
const values = useMemo(() => Array.isArray(value) ? [...value] : EMPTY_VALUES, [value]);
|
|
1977
|
+
if (isValuelessOperator(operator)) return null;
|
|
1978
|
+
const useEmbeddedCombobox = layout === "popover" && trigger == null;
|
|
1979
|
+
if (multiple) return /* @__PURE__ */ jsx(Combobox, {
|
|
1980
|
+
id: generatedId,
|
|
1981
|
+
size,
|
|
1982
|
+
"aria-label": ariaLabel ?? field.label,
|
|
1983
|
+
placeholder: field.label,
|
|
1984
|
+
searchPlaceholder: "Search",
|
|
1985
|
+
emptyStateMessage: "No matches",
|
|
1986
|
+
options,
|
|
1987
|
+
values,
|
|
1988
|
+
trigger,
|
|
1989
|
+
onValuesChange: onChange,
|
|
1990
|
+
embedded: useEmbeddedCombobox,
|
|
1991
|
+
multiple: true,
|
|
1992
|
+
truncate: true,
|
|
1993
|
+
stableOptionOrder: true
|
|
1994
|
+
});
|
|
1995
|
+
return /* @__PURE__ */ jsx(Combobox, {
|
|
1996
|
+
id: generatedId,
|
|
1997
|
+
size,
|
|
1998
|
+
"aria-label": ariaLabel ?? field.label,
|
|
1999
|
+
placeholder: field.label,
|
|
2000
|
+
options,
|
|
2001
|
+
value: typeof value === "string" ? value : null,
|
|
2002
|
+
trigger,
|
|
2003
|
+
onChange: (next) => {
|
|
2004
|
+
onChange(next ?? null);
|
|
2005
|
+
onCommit?.();
|
|
2006
|
+
},
|
|
2007
|
+
embedded: useEmbeddedCombobox
|
|
2008
|
+
});
|
|
2009
|
+
}
|
|
2010
|
+
//#endregion
|
|
2011
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/DateValueControl/DateValueControl.tsx
|
|
2012
|
+
const parseIsoDate = (value) => {
|
|
2013
|
+
if (typeof value !== "string") return null;
|
|
2014
|
+
try {
|
|
2015
|
+
return parseDate(value);
|
|
2016
|
+
} catch {
|
|
2017
|
+
return null;
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
/**
|
|
2021
|
+
* Single-date value control for the Filter UI v2 popover.
|
|
2022
|
+
*
|
|
2023
|
+
* Used for `date` fields with operators `is` / `before` / `after`. The
|
|
2024
|
+
* popover renders nothing when the selected operator is valueless
|
|
2025
|
+
* (`is_empty` / `is_not_empty`) — those operators are themselves the
|
|
2026
|
+
* predicate and need no companion value.
|
|
2027
|
+
*
|
|
2028
|
+
* Output value is always a `YYYY-MM-DD` ISO date string (or `null` when
|
|
2029
|
+
* cleared).
|
|
2030
|
+
*/
|
|
2031
|
+
const DateValueControl = ({ field, operator, value, onChange, ariaLabel }) => {
|
|
2032
|
+
const size = useFilterBarSize();
|
|
2033
|
+
const handleChange = useCallback((nextDate) => {
|
|
2034
|
+
onChange(nextDate ? nextDate.toString() : null);
|
|
2035
|
+
}, [onChange]);
|
|
2036
|
+
if (isValuelessOperator(operator)) return null;
|
|
2037
|
+
return /* @__PURE__ */ jsx(DatePicker, {
|
|
2038
|
+
value: parseIsoDate(value),
|
|
2039
|
+
size,
|
|
2040
|
+
"aria-label": ariaLabel ?? `${field.label} value`,
|
|
2041
|
+
onChange: handleChange,
|
|
2042
|
+
useModalZIndex: true
|
|
2043
|
+
});
|
|
2044
|
+
};
|
|
2045
|
+
//#endregion
|
|
2046
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/utils.ts
|
|
2047
|
+
/**
|
|
2048
|
+
* Coerce a `FilterRuleValue` into a string suitable for an `<input>`'s
|
|
2049
|
+
* `value` prop. Used by `IntegerValueControl` and `FloatValueControl` so
|
|
2050
|
+
* both share identical display-coercion semantics — without this, drift
|
|
2051
|
+
* (e.g. different handling of `''`) would let the two controls disagree
|
|
2052
|
+
* about how to render the same hydrated rule.
|
|
2053
|
+
*
|
|
2054
|
+
* `Number.isFinite` rules out `NaN` / `±Infinity` (a hydrated state that
|
|
2055
|
+
* round-tripped through JSON should never carry those, but the guard is
|
|
2056
|
+
* cheap defense). Empty strings collapse to `''` rather than falling
|
|
2057
|
+
* through to `'undefined'` / `'null'` if the rule arrives in a malformed
|
|
2058
|
+
* shape.
|
|
2059
|
+
*/
|
|
2060
|
+
function numericDisplayValue(value) {
|
|
2061
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
2062
|
+
if (typeof value === "string" && value !== "") return value;
|
|
2063
|
+
return "";
|
|
2064
|
+
}
|
|
2065
|
+
//#endregion
|
|
2066
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/FloatValueControl/FloatValueControl.tsx
|
|
2067
|
+
/**
|
|
2068
|
+
* Float-typed value control for the Filter UI v2 popover.
|
|
2069
|
+
*
|
|
2070
|
+
* Used for `float` fields with operators `is` / `is_not`. The popover
|
|
2071
|
+
* renders nothing when the operator is valueless (`is_empty` /
|
|
2072
|
+
* `is_not_empty`). Range operators are not part of the current numeric
|
|
2073
|
+
* field vocabulary.
|
|
2074
|
+
*
|
|
2075
|
+
* The control accepts decimal input and emits a `number` via `onChange`.
|
|
2076
|
+
* We keep local display state as a string so partial input like `"3."` is
|
|
2077
|
+
* preserved while the user types; the emitted numeric value is only updated
|
|
2078
|
+
* once the input parses to a finite number.
|
|
2079
|
+
*
|
|
2080
|
+
* `isFocused` ref guards the external-value sync effect: typing a
|
|
2081
|
+
* mid-decimal value like `"3."` parses to `3` and emits it, which
|
|
2082
|
+
* round-trips back through the controlled `value` prop. Without the
|
|
2083
|
+
* guard, the sync effect would rewrite the display from `"3."` to
|
|
2084
|
+
* `"3"` and the next keystroke (`"7"`) would land at the wrong cursor
|
|
2085
|
+
* position. The blur handler resyncs the display so any half-typed
|
|
2086
|
+
* string that never parsed (`"-"`, `"."`) renders as whatever the
|
|
2087
|
+
* rule actually carries when focus leaves.
|
|
2088
|
+
*
|
|
2089
|
+
* `onChange` is debounced (300ms) to avoid committing on every keystroke;
|
|
2090
|
+
* the debounce flushes on blur and on unmount so no pending value is lost.
|
|
2091
|
+
*/
|
|
2092
|
+
const FloatValueControl = ({ field, operator, value, onChange, ariaLabel }) => {
|
|
2093
|
+
const size = useFilterBarSize();
|
|
2094
|
+
const [localValue, setLocalValue] = useState(() => numericDisplayValue(value));
|
|
2095
|
+
const isFocusedRef = useRef(false);
|
|
2096
|
+
const debouncedOnChange = useMemo(() => debounce(300, onChange), [onChange]);
|
|
2097
|
+
useEffect(() => {
|
|
2098
|
+
const cancelPendingChange = () => {
|
|
2099
|
+
debouncedOnChange.cancel();
|
|
2100
|
+
};
|
|
2101
|
+
return cancelPendingChange;
|
|
2102
|
+
}, [debouncedOnChange]);
|
|
2103
|
+
useEffect(() => {
|
|
2104
|
+
if (isFocusedRef.current) return;
|
|
2105
|
+
setLocalValue(numericDisplayValue(value));
|
|
2106
|
+
}, [value]);
|
|
2107
|
+
const handleChange = useCallback((raw) => {
|
|
2108
|
+
setLocalValue(raw);
|
|
2109
|
+
if (raw === "" || raw === "-" || raw === ".") {
|
|
2110
|
+
debouncedOnChange(null);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
const parsed = Number(raw);
|
|
2114
|
+
if (!Number.isFinite(parsed)) return;
|
|
2115
|
+
debouncedOnChange(parsed);
|
|
2116
|
+
}, [debouncedOnChange]);
|
|
2117
|
+
if (isValuelessOperator(operator)) return null;
|
|
2118
|
+
return /* @__PURE__ */ jsx(Input_default, {
|
|
2119
|
+
type: "number",
|
|
2120
|
+
size,
|
|
2121
|
+
step: "any",
|
|
2122
|
+
inputMode: "decimal",
|
|
2123
|
+
placeholder: "Enter a number",
|
|
2124
|
+
"aria-label": ariaLabel ?? `${field.label} value`,
|
|
2125
|
+
value: localValue,
|
|
2126
|
+
onChange: (e) => handleChange(e.target.value),
|
|
2127
|
+
onFocus: () => {
|
|
2128
|
+
isFocusedRef.current = true;
|
|
2129
|
+
},
|
|
2130
|
+
onBlur: () => {
|
|
2131
|
+
isFocusedRef.current = false;
|
|
2132
|
+
debouncedOnChange.flush();
|
|
2133
|
+
setLocalValue(numericDisplayValue(value));
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
};
|
|
2137
|
+
//#endregion
|
|
2138
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/IntegerValueControl/IntegerValueControl.tsx
|
|
2139
|
+
/**
|
|
2140
|
+
* Integer-typed value control for the Filter UI v2 popover.
|
|
2141
|
+
*
|
|
2142
|
+
* Used for `integer` fields with operators `is` / `is_not`. The popover
|
|
2143
|
+
* renders nothing when the operator is valueless (`is_empty` /
|
|
2144
|
+
* `is_not_empty`). Range operators are not part of the current numeric
|
|
2145
|
+
* field vocabulary.
|
|
2146
|
+
*
|
|
2147
|
+
* The control accepts any numeric input but always emits an integer via
|
|
2148
|
+
* `onChange`. We truncate (rather than round) so that typing `3` -> `3.` -> `3.7`
|
|
2149
|
+
* does not jump to `4` mid-typing; the wire-format value follows `parseInt`
|
|
2150
|
+
* semantics, matching common backend integer-coercion behavior.
|
|
2151
|
+
*
|
|
2152
|
+
* `isFocused` ref guards the external-value sync effect: while the user
|
|
2153
|
+
* is actively typing, an external value change (which we ourselves
|
|
2154
|
+
* triggered by emitting `onChange`) must NOT clobber the local display
|
|
2155
|
+
* string. Without this guard, typing `3` -> emits `3` -> external value
|
|
2156
|
+
* becomes `3` -> the sync effect resets local back to `'3'`, which
|
|
2157
|
+
* looks fine until you type `-` to clear and the sync races against
|
|
2158
|
+
* the user's next keystroke.
|
|
2159
|
+
*
|
|
2160
|
+
* `onChange` is debounced (300ms) to avoid committing on every keystroke;
|
|
2161
|
+
* the debounce flushes on blur and on unmount so no pending value is lost.
|
|
2162
|
+
*/
|
|
2163
|
+
const IntegerValueControl = ({ field, operator, value, onChange, ariaLabel }) => {
|
|
2164
|
+
const size = useFilterBarSize();
|
|
2165
|
+
const [localValue, setLocalValue] = useState(() => numericDisplayValue(value));
|
|
2166
|
+
const isFocusedRef = useRef(false);
|
|
2167
|
+
const debouncedOnChange = useMemo(() => debounce(300, onChange), [onChange]);
|
|
2168
|
+
useEffect(() => {
|
|
2169
|
+
const cancelPendingChange = () => {
|
|
2170
|
+
debouncedOnChange.cancel();
|
|
2171
|
+
};
|
|
2172
|
+
return cancelPendingChange;
|
|
2173
|
+
}, [debouncedOnChange]);
|
|
2174
|
+
useEffect(() => {
|
|
2175
|
+
if (isFocusedRef.current) return;
|
|
2176
|
+
setLocalValue(numericDisplayValue(value));
|
|
2177
|
+
}, [value]);
|
|
2178
|
+
const handleChange = useCallback((raw) => {
|
|
2179
|
+
setLocalValue(raw);
|
|
2180
|
+
if (raw === "" || raw === "-") {
|
|
2181
|
+
debouncedOnChange(null);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
const parsed = Number(raw);
|
|
2185
|
+
if (!Number.isFinite(parsed)) return;
|
|
2186
|
+
debouncedOnChange(Math.trunc(parsed));
|
|
2187
|
+
}, [debouncedOnChange]);
|
|
2188
|
+
if (isValuelessOperator(operator)) return null;
|
|
2189
|
+
return /* @__PURE__ */ jsx(Input_default, {
|
|
2190
|
+
type: "number",
|
|
2191
|
+
size,
|
|
2192
|
+
step: 1,
|
|
2193
|
+
inputMode: "numeric",
|
|
2194
|
+
placeholder: "Enter a number",
|
|
2195
|
+
"aria-label": ariaLabel ?? `${field.label} value`,
|
|
2196
|
+
value: localValue,
|
|
2197
|
+
onChange: (e) => handleChange(e.target.value),
|
|
2198
|
+
onFocus: () => {
|
|
2199
|
+
isFocusedRef.current = true;
|
|
2200
|
+
},
|
|
2201
|
+
onBlur: () => {
|
|
2202
|
+
isFocusedRef.current = false;
|
|
2203
|
+
debouncedOnChange.flush();
|
|
2204
|
+
setLocalValue(numericDisplayValue(value));
|
|
2205
|
+
}
|
|
2206
|
+
});
|
|
2207
|
+
};
|
|
2208
|
+
//#endregion
|
|
2209
|
+
//#region components/DataTable/TableControls/FiltersV2/value-controls/StringValueControl/StringValueControl.tsx
|
|
2210
|
+
const DEBOUNCE_MS = 300;
|
|
2211
|
+
function StringValueControl({ field, operator, value, onChange, ariaLabel, inputType, placeholder, id }) {
|
|
2212
|
+
const generatedId = useMemo(() => uniqueId$1("filter_v2_string_"), []);
|
|
2213
|
+
const inputId = id ?? generatedId;
|
|
2214
|
+
const resolvedInputType = inputType ?? (field.type === "email" ? "email" : "text");
|
|
2215
|
+
const size = useFilterBarSize();
|
|
2216
|
+
const [localValue, setLocalValue] = useState(typeof value === "string" ? value : "");
|
|
2217
|
+
const debouncedOnChange = useMemo(() => debounce(DEBOUNCE_MS, onChange), [onChange]);
|
|
2218
|
+
useEffect(() => () => {
|
|
2219
|
+
debouncedOnChange.cancel();
|
|
2220
|
+
}, [debouncedOnChange]);
|
|
2221
|
+
useEffect(() => {
|
|
2222
|
+
setLocalValue(typeof value === "string" ? value : "");
|
|
2223
|
+
}, [value]);
|
|
2224
|
+
if (isValuelessOperator(operator)) return null;
|
|
2225
|
+
return /* @__PURE__ */ jsx(Input_default, {
|
|
2226
|
+
id: inputId,
|
|
2227
|
+
type: resolvedInputType,
|
|
2228
|
+
size,
|
|
2229
|
+
placeholder: placeholder ?? field.label,
|
|
2230
|
+
"aria-label": ariaLabel ?? field.label,
|
|
2231
|
+
value: localValue,
|
|
2232
|
+
onChange: ({ target }) => {
|
|
2233
|
+
setLocalValue(target.value);
|
|
2234
|
+
debouncedOnChange(target.value);
|
|
2235
|
+
},
|
|
2236
|
+
onBlur: () => {
|
|
2237
|
+
debouncedOnChange.flush();
|
|
2238
|
+
},
|
|
2239
|
+
onKeyDown: (event) => {
|
|
2240
|
+
if (event.key === "Enter") debouncedOnChange.flush();
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
//#endregion
|
|
2245
|
+
//#region components/DataTable/TableControls/FiltersV2/FilterBar/value-control-resolver.tsx
|
|
2246
|
+
const RESOLVERS = {
|
|
2247
|
+
string: (opts) => /* @__PURE__ */ jsx(StringValueControl, { ...opts }),
|
|
2248
|
+
email: (opts) => /* @__PURE__ */ jsx(StringValueControl, { ...opts }),
|
|
2249
|
+
choices: (opts) => /* @__PURE__ */ jsx(ChoicesValueControl, { ...opts }),
|
|
2250
|
+
multi_choices: (opts) => /* @__PURE__ */ jsx(ChoicesValueControl, {
|
|
2251
|
+
multiple: true,
|
|
2252
|
+
...opts
|
|
2253
|
+
}),
|
|
2254
|
+
boolean: (opts) => /* @__PURE__ */ jsx(BooleanValueControl, { ...opts }),
|
|
2255
|
+
date: (opts) => /* @__PURE__ */ jsx(DateValueControl, { ...opts }),
|
|
2256
|
+
integer: (opts) => /* @__PURE__ */ jsx(IntegerValueControl, { ...opts }),
|
|
2257
|
+
float: (opts) => /* @__PURE__ */ jsx(FloatValueControl, { ...opts }),
|
|
2258
|
+
array: (opts) => /* @__PURE__ */ jsx(ArrayValueControl, { ...opts })
|
|
2259
|
+
};
|
|
2260
|
+
function resolveValueControl(opts, hostResolver) {
|
|
2261
|
+
const hostControl = hostResolver?.(opts);
|
|
2262
|
+
if (hostControl != null) return hostControl;
|
|
2263
|
+
const renderer = RESOLVERS[opts.field.type];
|
|
2264
|
+
if (!renderer) return null;
|
|
2265
|
+
return renderer(opts);
|
|
2266
|
+
}
|
|
2267
|
+
//#endregion
|
|
2268
|
+
//#region components/DataTable/TableControls/FiltersV2/FilterPickerMenu/FilterPickerMenu.tsx
|
|
2269
|
+
const TriggerWrapper = styled.span`
|
|
2270
|
+
display: inline-flex;
|
|
2271
|
+
|
|
2272
|
+
& button {
|
|
2273
|
+
background-color: transparent;
|
|
2274
|
+
color: ${({ theme }) => theme.color.text.secondary};
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
& button:hover,
|
|
2278
|
+
& button:active {
|
|
2279
|
+
background-color: transparent;
|
|
2280
|
+
color: ${({ theme }) => theme.color.text.primary};
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
${({ $isOpen, theme }) => $isOpen && css`
|
|
2284
|
+
& button {
|
|
2285
|
+
background-color: transparent;
|
|
2286
|
+
color: ${theme.color.text.primary};
|
|
2287
|
+
}
|
|
2288
|
+
`}
|
|
2289
|
+
`;
|
|
2290
|
+
const FilterPickerMenu = ({ registry, onPickField, triggerLabel = "Filter", compact = false, disabled = false, visibleCustomFieldParents, excludeFieldIds, size, ...rest }) => {
|
|
2291
|
+
const resolvedSize = useFilterBarSize(size);
|
|
2292
|
+
const unfilteredOptions = useMemo(() => buildFieldPickerOptions(registry, visibleCustomFieldParents), [registry, visibleCustomFieldParents]);
|
|
2293
|
+
const options = useMemo(() => excludeFieldIds == null || excludeFieldIds.length === 0 ? unfilteredOptions : buildFieldPickerOptions(registry, visibleCustomFieldParents, excludeFieldIds), [
|
|
2294
|
+
registry,
|
|
2295
|
+
visibleCustomFieldParents,
|
|
2296
|
+
excludeFieldIds,
|
|
2297
|
+
unfilteredOptions
|
|
2298
|
+
]);
|
|
2299
|
+
const handleChange = useCallback((value) => {
|
|
2300
|
+
if (value == null) return;
|
|
2301
|
+
const field = registry.get(value);
|
|
2302
|
+
if (!field) return;
|
|
2303
|
+
onPickField(field);
|
|
2304
|
+
}, [registry, onPickField]);
|
|
2305
|
+
if (options.length === 0 && unfilteredOptions.length > 0) return null;
|
|
2306
|
+
const trigger = compact ? /* @__PURE__ */ jsx(IconButton, {
|
|
2307
|
+
size: resolvedSize,
|
|
2308
|
+
disabled,
|
|
2309
|
+
"aria-label": triggerLabel,
|
|
2310
|
+
children: /* @__PURE__ */ jsx(CirclePlusIcon, {})
|
|
2311
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
2312
|
+
variant: "text",
|
|
2313
|
+
size: resolvedSize,
|
|
2314
|
+
width: "auto",
|
|
2315
|
+
prefixIcon: /* @__PURE__ */ jsx(CirclePlusIcon, {}),
|
|
2316
|
+
disabled,
|
|
2317
|
+
children: triggerLabel
|
|
2318
|
+
});
|
|
2319
|
+
return /* @__PURE__ */ jsx(TriggerWrapper, {
|
|
2320
|
+
...rest,
|
|
2321
|
+
$isOpen: false,
|
|
2322
|
+
children: /* @__PURE__ */ jsx(Combobox, {
|
|
2323
|
+
options,
|
|
2324
|
+
value: null,
|
|
2325
|
+
onChange: handleChange,
|
|
2326
|
+
size: resolvedSize,
|
|
2327
|
+
"aria-label": "Filter field picker",
|
|
2328
|
+
trigger
|
|
2329
|
+
})
|
|
2330
|
+
});
|
|
2331
|
+
};
|
|
2332
|
+
//#endregion
|
|
2333
|
+
//#region components/DataTable/TableControls/FiltersV2/PillShell/PillShell.tsx
|
|
2334
|
+
const PillRoot = styled.span`
|
|
2335
|
+
display: inline-flex;
|
|
2336
|
+
align-items: center;
|
|
2337
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].controlHeight};
|
|
2338
|
+
padding: 0 ${Spacing[1]} 0 ${Spacing[2]};
|
|
2339
|
+
gap: 2px;
|
|
2340
|
+
border-radius: ${BorderRadius.Md};
|
|
2341
|
+
${({ $size }) => $size === "sm" ? Typography.TextSmall : Typography.TextMedium};
|
|
2342
|
+
line-height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2343
|
+
max-width: 100%;
|
|
2344
|
+
cursor: pointer;
|
|
2345
|
+
user-select: none;
|
|
2346
|
+
transition: background-color ${"150ms"} ease-out;
|
|
2347
|
+
|
|
2348
|
+
/* Keyboard focus lands on the inner body button, so the visible focus
|
|
2349
|
+
ring is drawn on the whole pill via :focus-within. */
|
|
2350
|
+
${focusWithinOutline};
|
|
2351
|
+
|
|
2352
|
+
${({ $variant }) => $variant === "active" ? css`
|
|
2353
|
+
background-color: ${Color.CorePurple100};
|
|
2354
|
+
color: ${Color.TextActive};
|
|
2355
|
+
` : css`
|
|
2356
|
+
background-color: ${Color.BackgroundSecondary};
|
|
2357
|
+
color: ${Color.TextPrimary};
|
|
2358
|
+
|
|
2359
|
+
&:hover {
|
|
2360
|
+
background-color: ${Color.BackgroundLabelGrey};
|
|
2361
|
+
}
|
|
2362
|
+
`}
|
|
2363
|
+
`;
|
|
2364
|
+
/**
|
|
2365
|
+
* The clickable body of the pill. Suppresses its own focus outline
|
|
2366
|
+
* (the parent `PillRoot` carries the visible focus ring via
|
|
2367
|
+
* `:focus-within`) and is otherwise a transparent wrapper that
|
|
2368
|
+
* inherits the pill's typography + color.
|
|
2369
|
+
*/
|
|
2370
|
+
const PillBody = styled.button`
|
|
2371
|
+
display: inline-flex;
|
|
2372
|
+
align-items: center;
|
|
2373
|
+
gap: ${Spacing[1]};
|
|
2374
|
+
height: 100%;
|
|
2375
|
+
background: transparent;
|
|
2376
|
+
border: 0;
|
|
2377
|
+
padding: 0;
|
|
2378
|
+
margin: 0;
|
|
2379
|
+
appearance: none;
|
|
2380
|
+
color: inherit;
|
|
2381
|
+
font: inherit;
|
|
2382
|
+
cursor: pointer;
|
|
2383
|
+
min-width: 0;
|
|
2384
|
+
max-width: 100%;
|
|
2385
|
+
|
|
2386
|
+
&:focus-visible {
|
|
2387
|
+
outline: none;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
${PillRoot}:focus-within & {
|
|
2391
|
+
outline: none;
|
|
2392
|
+
}
|
|
2393
|
+
`;
|
|
2394
|
+
/**
|
|
2395
|
+
* Square slot containing the trailing caret or dismiss glyph. Slot
|
|
2396
|
+
* size scales with the pill (`sm` = 16x16 / 12px glyph; `md` = 20x20
|
|
2397
|
+
* / 16px glyph) so the visual weight stays proportional to the pill
|
|
2398
|
+
* height.
|
|
2399
|
+
*/
|
|
2400
|
+
const actionSlotStyles = css`
|
|
2401
|
+
display: inline-flex;
|
|
2402
|
+
align-items: center;
|
|
2403
|
+
justify-content: center;
|
|
2404
|
+
width: ${({ $size }) => $size === "sm" ? "16px" : "20px"};
|
|
2405
|
+
height: ${({ $size }) => $size === "sm" ? "16px" : "20px"};
|
|
2406
|
+
flex: 0 0 auto;
|
|
2407
|
+
border-radius: ${BorderRadius.Sm};
|
|
2408
|
+
color: inherit;
|
|
2409
|
+
|
|
2410
|
+
svg {
|
|
2411
|
+
width: ${({ $size }) => FILTER_SIZE_TOKENS[$size].iconSize};
|
|
2412
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].iconSize};
|
|
2413
|
+
}
|
|
2414
|
+
`;
|
|
2415
|
+
const ActionSlot = styled.span`
|
|
2416
|
+
${actionSlotStyles};
|
|
2417
|
+
`;
|
|
2418
|
+
const DismissibleCaretWrapper = styled(ActionSlot)`
|
|
2419
|
+
${PillRoot}:hover &,
|
|
2420
|
+
${PillRoot}[data-variant='active'] & {
|
|
2421
|
+
display: none;
|
|
2422
|
+
}
|
|
2423
|
+
`;
|
|
2424
|
+
const DismissButton$1 = styled.button`
|
|
2425
|
+
${actionSlotStyles};
|
|
2426
|
+
display: none; /* see :hover / [data-variant='active'] overrides below */
|
|
2427
|
+
padding: 0;
|
|
2428
|
+
margin: 0;
|
|
2429
|
+
border: 0;
|
|
2430
|
+
appearance: none;
|
|
2431
|
+
background: transparent;
|
|
2432
|
+
color: inherit;
|
|
2433
|
+
font: inherit;
|
|
2434
|
+
cursor: pointer;
|
|
2435
|
+
|
|
2436
|
+
&:hover {
|
|
2437
|
+
background-color: ${Color.BackgroundButtonTextHover};
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
${focusOutline};
|
|
2441
|
+
|
|
2442
|
+
${PillRoot}:hover &,
|
|
2443
|
+
${PillRoot}[data-variant='active'] & {
|
|
2444
|
+
display: inline-flex;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
${PillRoot}[data-variant='active'] &:hover {
|
|
2448
|
+
background-color: ${Color.CorePurple200};
|
|
2449
|
+
}
|
|
2450
|
+
`;
|
|
2451
|
+
const PillShell = forwardRef(function PillShell({ variant, children, bodyAriaLabel, onBodyClick, onDismiss, dismissAriaLabel, size, ...rest }, ref) {
|
|
2452
|
+
const ariaExpanded = rest["aria-expanded"];
|
|
2453
|
+
const resolvedSize = useFilterBarSize(size);
|
|
2454
|
+
const CaretIcon = resolvedSize === "sm" ? CaretDownIcon$1 : CaretDownIcon;
|
|
2455
|
+
const DismissIcon = resolvedSize === "sm" ? XIcon$1 : XIcon;
|
|
2456
|
+
const handleDismissClick = (event) => {
|
|
2457
|
+
event.stopPropagation();
|
|
2458
|
+
onDismiss?.();
|
|
2459
|
+
};
|
|
2460
|
+
const handleBodyKeyDown = (event) => {
|
|
2461
|
+
if (event.key === "Enter" || event.key === " ") event.stopPropagation();
|
|
2462
|
+
};
|
|
2463
|
+
return /* @__PURE__ */ jsxs(PillRoot, {
|
|
2464
|
+
ref,
|
|
2465
|
+
$variant: variant,
|
|
2466
|
+
$size: resolvedSize,
|
|
2467
|
+
"data-variant": variant,
|
|
2468
|
+
onClick: onBodyClick,
|
|
2469
|
+
...rest,
|
|
2470
|
+
children: [/* @__PURE__ */ jsx(PillBody, {
|
|
2471
|
+
type: "button",
|
|
2472
|
+
onKeyDown: handleBodyKeyDown,
|
|
2473
|
+
"aria-label": bodyAriaLabel,
|
|
2474
|
+
"aria-haspopup": "dialog",
|
|
2475
|
+
"aria-expanded": ariaExpanded,
|
|
2476
|
+
children
|
|
2477
|
+
}), onDismiss ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DismissibleCaretWrapper, {
|
|
2478
|
+
"aria-hidden": "true",
|
|
2479
|
+
$size: resolvedSize,
|
|
2480
|
+
children: /* @__PURE__ */ jsx(CaretIcon, {})
|
|
2481
|
+
}), /* @__PURE__ */ jsx(DismissButton$1, {
|
|
2482
|
+
$size: resolvedSize,
|
|
2483
|
+
type: "button",
|
|
2484
|
+
onClick: handleDismissClick,
|
|
2485
|
+
"aria-label": dismissAriaLabel,
|
|
2486
|
+
children: /* @__PURE__ */ jsx(DismissIcon, {})
|
|
2487
|
+
})] }) : /* @__PURE__ */ jsx(ActionSlot, {
|
|
2488
|
+
"aria-hidden": "true",
|
|
2489
|
+
$size: resolvedSize,
|
|
2490
|
+
children: /* @__PURE__ */ jsx(CaretIcon, {})
|
|
2491
|
+
})]
|
|
2492
|
+
});
|
|
2493
|
+
});
|
|
2494
|
+
//#endregion
|
|
2495
|
+
//#region components/DataTable/TableControls/FiltersV2/FilterPill/FilterPill.tsx
|
|
2496
|
+
const FieldLabel = styled.span`
|
|
2497
|
+
max-width: 120px;
|
|
2498
|
+
flex: 0 1 auto;
|
|
2499
|
+
`;
|
|
2500
|
+
const OperatorLabel = styled.span`
|
|
2501
|
+
color: inherit;
|
|
2502
|
+
white-space: nowrap;
|
|
2503
|
+
flex: 0 0 auto;
|
|
2504
|
+
`;
|
|
2505
|
+
const ValueChipGroup = styled.span`
|
|
2506
|
+
display: inline-flex;
|
|
2507
|
+
align-items: center;
|
|
2508
|
+
gap: 2px;
|
|
2509
|
+
flex: 0 1 auto;
|
|
2510
|
+
`;
|
|
2511
|
+
const ValueChip = styled.span`
|
|
2512
|
+
display: inline-flex;
|
|
2513
|
+
align-items: center;
|
|
2514
|
+
justify-content: center;
|
|
2515
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].chipHeight};
|
|
2516
|
+
min-width: ${({ $size }) => FILTER_SIZE_TOKENS[$size].chipHeight};
|
|
2517
|
+
max-width: 120px;
|
|
2518
|
+
padding: 0 ${Spacing[1]};
|
|
2519
|
+
border-radius: 3px;
|
|
2520
|
+
font-size: ${({ $size }) => FILTER_SIZE_TOKENS[$size].fontSize};
|
|
2521
|
+
line-height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2522
|
+
|
|
2523
|
+
background-color: ${({ $variant }) => $variant === "active" ? Color.CorePurple200 : Color.CoreSlate200};
|
|
2524
|
+
color: ${({ $variant }) => $variant === "active" ? Color.TextActive : Color.TextPrimary};
|
|
2525
|
+
`;
|
|
2526
|
+
const OverflowChip = styled(ValueChip)`
|
|
2527
|
+
flex: 0 0 auto;
|
|
2528
|
+
`;
|
|
2529
|
+
const OverflowTooltipList = styled(Stack).attrs({
|
|
2530
|
+
as: "ul",
|
|
2531
|
+
spacing: Spacing[1]
|
|
2532
|
+
})`
|
|
2533
|
+
margin: 0;
|
|
2534
|
+
padding: 0;
|
|
2535
|
+
list-style: none;
|
|
2536
|
+
|
|
2537
|
+
& > li {
|
|
2538
|
+
margin: 0;
|
|
2539
|
+
padding: 0;
|
|
2540
|
+
}
|
|
2541
|
+
`;
|
|
2542
|
+
/**
|
|
2543
|
+
* Normalize a `FilterRuleValue` to a list of display strings, applying the
|
|
2544
|
+
* `optionLabels` lookup so the pill renders human-readable option labels
|
|
2545
|
+
* instead of raw option ids. Null / undefined collapses to an empty list.
|
|
2546
|
+
*/
|
|
2547
|
+
function normalizeValueToDisplay(value, optionLabels) {
|
|
2548
|
+
if (value == null) return [];
|
|
2549
|
+
const lookup = (raw) => optionLabels?.[raw] ?? raw;
|
|
2550
|
+
if (Array.isArray(value)) {
|
|
2551
|
+
if (value.length > 0 && Array.isArray(value[0])) return [`${value.length} group${value.length === 1 ? "" : "s"}`];
|
|
2552
|
+
return value.map((entry) => lookup(String(entry)));
|
|
2553
|
+
}
|
|
2554
|
+
if (typeof value === "object") {
|
|
2555
|
+
const record = value;
|
|
2556
|
+
if (record.startDate && record.endDate) return [`${record.startDate} – ${record.endDate}`];
|
|
2557
|
+
if (record.startDate || record.endDate) return [record.startDate ?? record.endDate ?? ""];
|
|
2558
|
+
}
|
|
2559
|
+
if (typeof value === "boolean") return [value ? "True" : "False"];
|
|
2560
|
+
if (typeof value === "number") return [String(value)];
|
|
2561
|
+
return [lookup(value)];
|
|
2562
|
+
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Accessible name builder used both for the pill body's `aria-label` and the
|
|
2565
|
+
* dismiss button's `aria-label`. We assemble a human sentence rather than
|
|
2566
|
+
* relying on child text nodes because the chip components don't always read
|
|
2567
|
+
* cleanly when concatenated by the AT.
|
|
2568
|
+
*/
|
|
2569
|
+
function buildAccessibleName(field, rule, displayedValues) {
|
|
2570
|
+
if (rule == null) return field.label;
|
|
2571
|
+
if (!isValuelessOperator(rule.operator) && displayedValues.length === 0) return field.label;
|
|
2572
|
+
const operatorLabel = getOperatorLabel(rule.operator, field.type);
|
|
2573
|
+
if (isValuelessOperator(rule.operator)) return `${field.label} ${operatorLabel}`;
|
|
2574
|
+
return `${field.label} ${operatorLabel} ${displayedValues.join(", ")}`;
|
|
2575
|
+
}
|
|
2576
|
+
const FilterPill = forwardRef(function FilterPill({ field, rule, onClick, onDismiss, variant = "default", optionLabels, size, ...rest }, ref) {
|
|
2577
|
+
const reactId = useMemo(() => uniqueId("filter-pill-"), []);
|
|
2578
|
+
const resolvedSize = useFilterBarSize(size);
|
|
2579
|
+
const displayedValues = useMemo(() => rule && !isValuelessOperator(rule.operator) ? normalizeValueToDisplay(rule.value, optionLabels) : [], [rule, optionLabels]);
|
|
2580
|
+
const accessibleName = buildAccessibleName(field, rule, displayedValues);
|
|
2581
|
+
const hasRule = rule != null;
|
|
2582
|
+
const isComplete = hasRule && (isValuelessOperator(rule.operator) || displayedValues.length > 0);
|
|
2583
|
+
const firstValue = displayedValues[0];
|
|
2584
|
+
const overflowValues = displayedValues.slice(1);
|
|
2585
|
+
const overflowTooltipId = `${reactId}-overflow`;
|
|
2586
|
+
return /* @__PURE__ */ jsxs(PillShell, {
|
|
2587
|
+
ref,
|
|
2588
|
+
variant,
|
|
2589
|
+
size: resolvedSize,
|
|
2590
|
+
bodyAriaLabel: `Edit filter: ${accessibleName}`,
|
|
2591
|
+
onBodyClick: onClick,
|
|
2592
|
+
onDismiss,
|
|
2593
|
+
dismissAriaLabel: `Remove filter: ${accessibleName}`,
|
|
2594
|
+
...rest,
|
|
2595
|
+
children: [
|
|
2596
|
+
/* @__PURE__ */ jsx(FieldLabel, { children: /* @__PURE__ */ jsx(TruncatedText, { children: field.label }) }),
|
|
2597
|
+
isComplete && hasRule && /* @__PURE__ */ jsx(OperatorLabel, { children: getOperatorLabel(rule.operator, field.type) }),
|
|
2598
|
+
isComplete && hasRule && !isValuelessOperator(rule.operator) && firstValue != null && /* @__PURE__ */ jsxs(ValueChipGroup, { children: [/* @__PURE__ */ jsx(ValueChip, {
|
|
2599
|
+
$variant: variant,
|
|
2600
|
+
$size: resolvedSize,
|
|
2601
|
+
children: /* @__PURE__ */ jsx(TruncatedText, { children: firstValue })
|
|
2602
|
+
}), overflowValues.length > 0 && /* @__PURE__ */ jsx(Tooltip, {
|
|
2603
|
+
trigger: /* @__PURE__ */ jsx(OverflowChip, {
|
|
2604
|
+
$variant: variant,
|
|
2605
|
+
$size: resolvedSize,
|
|
2606
|
+
"aria-describedby": overflowTooltipId,
|
|
2607
|
+
children: `+${overflowValues.length}`
|
|
2608
|
+
}),
|
|
2609
|
+
side: "bottom",
|
|
2610
|
+
hideArrow: true,
|
|
2611
|
+
children: /* @__PURE__ */ jsx(OverflowTooltipList, {
|
|
2612
|
+
id: overflowTooltipId,
|
|
2613
|
+
"aria-label": "Additional values",
|
|
2614
|
+
children: overflowValues.map((entry, index) => /* @__PURE__ */ jsx("li", { children: entry }, `${entry}-${index}`))
|
|
2615
|
+
})
|
|
2616
|
+
})] })
|
|
2617
|
+
]
|
|
2618
|
+
});
|
|
2619
|
+
});
|
|
2620
|
+
//#endregion
|
|
2621
|
+
//#region components/DataTable/TableControls/FiltersV2/FilterPopover/FilterPopover.tsx
|
|
2622
|
+
const PopoverPanel = styled(Stack).attrs({ spacing: Spacing[0] })`
|
|
2623
|
+
min-width: ${"196px"};
|
|
2624
|
+
max-width: ${"320px"};
|
|
2625
|
+
/* The design-system Popover.Card already supplies bg, border, shadow,
|
|
2626
|
+
and 6px corner radius — we just constrain the inner content. */
|
|
2627
|
+
`;
|
|
2628
|
+
const Main = styled(Stack).attrs({ spacing: Spacing[1] })`
|
|
2629
|
+
/* Valueless operators have no input below the header — symmetric padding
|
|
2630
|
+
keeps the popover tight. Valued operators keep extra bottom padding so
|
|
2631
|
+
the control's baseline has breathing room before the edge or footer. */
|
|
2632
|
+
padding: ${({ $valueless }) => $valueless ? Spacing[2] : `${Spacing[2]} ${Spacing[2]} ${Spacing[3]}`};
|
|
2633
|
+
`;
|
|
2634
|
+
const HeaderRow = styled(HStack).attrs({
|
|
2635
|
+
align: "center",
|
|
2636
|
+
spacing: Spacing[1]
|
|
2637
|
+
})`
|
|
2638
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2639
|
+
padding: 0 ${Spacing[1]};
|
|
2640
|
+
font-size: ${({ $size }) => FILTER_SIZE_TOKENS[$size].fontSize};
|
|
2641
|
+
line-height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2642
|
+
color: ${Color.TextSecondary};
|
|
2643
|
+
white-space: nowrap;
|
|
2644
|
+
`;
|
|
2645
|
+
const FieldTypeLabel = styled.span`
|
|
2646
|
+
font-size: ${({ $size }) => FILTER_SIZE_TOKENS[$size].fontSize};
|
|
2647
|
+
line-height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2648
|
+
color: ${Color.TextSecondary};
|
|
2649
|
+
white-space: nowrap;
|
|
2650
|
+
flex: 0 1 auto;
|
|
2651
|
+
min-width: 0;
|
|
2652
|
+
`;
|
|
2653
|
+
/**
|
|
2654
|
+
* Inline operator picker — a text button (sized to the resolved
|
|
2655
|
+
* filter-bar size) with a trailing caret that opens a `DropdownMenu`
|
|
2656
|
+
* of operator options. Uses `DropdownMenu` rather than the design-system
|
|
2657
|
+
* `Select` so the menu auto-sizes to the longest operator label
|
|
2658
|
+
* (`is_not_empty` etc.) instead of inheriting the trigger's narrow
|
|
2659
|
+
* width, which clipped labels to "is e..." in the previous styling.
|
|
2660
|
+
*/
|
|
2661
|
+
const InlineOperatorTrigger = styled.button`
|
|
2662
|
+
display: inline-flex;
|
|
2663
|
+
align-items: center;
|
|
2664
|
+
gap: 2px;
|
|
2665
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2666
|
+
padding: 0 ${Spacing[1]};
|
|
2667
|
+
margin: 0;
|
|
2668
|
+
border: 0;
|
|
2669
|
+
border-radius: ${BorderRadius.Sm};
|
|
2670
|
+
appearance: none;
|
|
2671
|
+
background: transparent;
|
|
2672
|
+
color: ${Color.TextPrimary};
|
|
2673
|
+
font-family: inherit;
|
|
2674
|
+
font-size: ${({ $size }) => FILTER_SIZE_TOKENS[$size].fontSize};
|
|
2675
|
+
line-height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2676
|
+
cursor: pointer;
|
|
2677
|
+
|
|
2678
|
+
&:hover {
|
|
2679
|
+
background: ${Color.BackgroundSecondary};
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
&[data-state='open'] {
|
|
2683
|
+
background: ${Color.BackgroundLabelGrey};
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
&:focus-visible {
|
|
2687
|
+
outline: 2px solid ${Color.BorderFocus};
|
|
2688
|
+
outline-offset: 1px;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
svg {
|
|
2692
|
+
width: ${({ $size }) => FILTER_SIZE_TOKENS[$size].iconSize};
|
|
2693
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].iconSize};
|
|
2694
|
+
flex-shrink: 0;
|
|
2695
|
+
}
|
|
2696
|
+
`;
|
|
2697
|
+
const ValueSlot = styled.div`
|
|
2698
|
+
padding: 0 ${Spacing[1]};
|
|
2699
|
+
`;
|
|
2700
|
+
const Footer = styled(HStack).attrs({
|
|
2701
|
+
align: "center",
|
|
2702
|
+
spacing: Spacing[1]
|
|
2703
|
+
})`
|
|
2704
|
+
padding: 6px ${Spacing[2]};
|
|
2705
|
+
border-top: 1px solid ${Color.BorderDivider};
|
|
2706
|
+
color: ${Color.TextSecondary};
|
|
2707
|
+
${paragraphMixins.xs};
|
|
2708
|
+
|
|
2709
|
+
svg {
|
|
2710
|
+
flex-shrink: 0;
|
|
2711
|
+
}
|
|
2712
|
+
`;
|
|
2713
|
+
/**
|
|
2714
|
+
* The single-rule editor popover. Renders a Radix `Popover` anchored on
|
|
2715
|
+
* `trigger`, with a `Popover.Card` body that stacks:
|
|
2716
|
+
*
|
|
2717
|
+
* 1. Inline header — "[Field name] [operator ▾]" on one line.
|
|
2718
|
+
* 2. Parent-supplied value control.
|
|
2719
|
+
* 3. Custom-field footer (`ⓘ [label] is a field on [parentRecordType]`).
|
|
2720
|
+
*
|
|
2721
|
+
* The popover stays purely presentational. Rule state lives in the parent's
|
|
2722
|
+
* `useFilterStateV2()` hook; this component just routes operator changes
|
|
2723
|
+
* back through `onChangeOperator` and forwards open/close transitions
|
|
2724
|
+
* through Radix's controlled props.
|
|
2725
|
+
*/
|
|
2726
|
+
const FilterPopover = ({ field, operator, onChangeOperator, valueControlSlot, onClose, trigger, open, onOpenChange, size }) => {
|
|
2727
|
+
const resolvedSize = useFilterBarSize(size);
|
|
2728
|
+
const valueless = isValuelessOperator(operator);
|
|
2729
|
+
const CaretIcon = resolvedSize === "sm" ? CaretDownIcon$1 : CaretDownIcon;
|
|
2730
|
+
const InfoIcon = resolvedSize === "sm" ? CircleInfoIcon$1 : CircleInfoIcon;
|
|
2731
|
+
const showOperatorSelector = fieldHasMultipleOperators(field);
|
|
2732
|
+
const operatorOptions = useMemo(() => getOperatorsForField(field), [field]);
|
|
2733
|
+
const handleOpenChange = useCallback((next) => {
|
|
2734
|
+
onOpenChange?.(next);
|
|
2735
|
+
if (!next) onClose();
|
|
2736
|
+
}, [onOpenChange, onClose]);
|
|
2737
|
+
const valueSlotRef = useRef(null);
|
|
2738
|
+
useEffect(() => {
|
|
2739
|
+
if (open !== true || valueless) return;
|
|
2740
|
+
const handle = requestAnimationFrame(() => {
|
|
2741
|
+
const root = valueSlotRef.current;
|
|
2742
|
+
if (!root) return;
|
|
2743
|
+
root.querySelector("input:not([disabled]), [contenteditable=\"true\"], [tabindex]:not([tabindex=\"-1\"])")?.focus();
|
|
2744
|
+
});
|
|
2745
|
+
return () => cancelAnimationFrame(handle);
|
|
2746
|
+
}, [
|
|
2747
|
+
open,
|
|
2748
|
+
field.id,
|
|
2749
|
+
valueless
|
|
2750
|
+
]);
|
|
2751
|
+
const handleKeyDown = (event) => {
|
|
2752
|
+
if (event.key !== "Enter") return;
|
|
2753
|
+
const target = event.target;
|
|
2754
|
+
if (!target || target.tagName !== "INPUT") return;
|
|
2755
|
+
const input = target;
|
|
2756
|
+
if (input.type === "checkbox" || input.type === "radio") return;
|
|
2757
|
+
if (input.getAttribute("role") === "combobox") return;
|
|
2758
|
+
event.preventDefault();
|
|
2759
|
+
handleOpenChange(false);
|
|
2760
|
+
};
|
|
2761
|
+
return /* @__PURE__ */ jsxs(Popover_default, {
|
|
2762
|
+
open,
|
|
2763
|
+
onOpenChange: handleOpenChange,
|
|
2764
|
+
children: [/* @__PURE__ */ jsx(Popover_default.Trigger, {
|
|
2765
|
+
asChild: true,
|
|
2766
|
+
children: trigger
|
|
2767
|
+
}), /* @__PURE__ */ jsx(Popover_default.Card, {
|
|
2768
|
+
align: "start",
|
|
2769
|
+
sideOffset: 4,
|
|
2770
|
+
"aria-label": `Edit filter for ${field.label}`,
|
|
2771
|
+
noPadding: true,
|
|
2772
|
+
children: /* @__PURE__ */ jsxs(PopoverPanel, {
|
|
2773
|
+
onKeyDown: handleKeyDown,
|
|
2774
|
+
children: [/* @__PURE__ */ jsxs(Main, {
|
|
2775
|
+
$valueless: valueless,
|
|
2776
|
+
"data-test": "filter-popover__main",
|
|
2777
|
+
children: [/* @__PURE__ */ jsxs(HeaderRow, {
|
|
2778
|
+
$size: resolvedSize,
|
|
2779
|
+
children: [/* @__PURE__ */ jsx(FieldTypeLabel, {
|
|
2780
|
+
$size: resolvedSize,
|
|
2781
|
+
children: /* @__PURE__ */ jsx(TruncatedText, { children: field.label })
|
|
2782
|
+
}), showOperatorSelector ? /* @__PURE__ */ jsx(DropdownMenu_default, {
|
|
2783
|
+
trigger: /* @__PURE__ */ jsxs(InlineOperatorTrigger, {
|
|
2784
|
+
type: "button",
|
|
2785
|
+
"aria-label": "Operator",
|
|
2786
|
+
$size: resolvedSize,
|
|
2787
|
+
children: [getOperatorLabel(operator, field.type), /* @__PURE__ */ jsx(CaretIcon, { "aria-hidden": "true" })]
|
|
2788
|
+
}),
|
|
2789
|
+
align: "start",
|
|
2790
|
+
portal: true,
|
|
2791
|
+
children: operatorOptions.map((op) => /* @__PURE__ */ jsx(DropdownMenu_default.Item, {
|
|
2792
|
+
size: resolvedSize,
|
|
2793
|
+
onClick: () => onChangeOperator(op),
|
|
2794
|
+
children: getOperatorLabel(op, field.type)
|
|
2795
|
+
}, op))
|
|
2796
|
+
}) : /* @__PURE__ */ jsx("span", { children: getOperatorLabel(operator, field.type) })]
|
|
2797
|
+
}), !valueless ? /* @__PURE__ */ jsx(ValueSlot, {
|
|
2798
|
+
ref: valueSlotRef,
|
|
2799
|
+
children: valueControlSlot
|
|
2800
|
+
}) : null]
|
|
2801
|
+
}), field.source === "custom" && field.parentRecordType != null && /* @__PURE__ */ jsxs(Footer, { children: [/* @__PURE__ */ jsx(InfoIcon, { "aria-hidden": "true" }), /* @__PURE__ */ jsxs(TruncatedText, { children: [
|
|
2802
|
+
field.label,
|
|
2803
|
+
" is a field on ",
|
|
2804
|
+
field.parentRecordType
|
|
2805
|
+
] })] })]
|
|
2806
|
+
})
|
|
2807
|
+
})]
|
|
2808
|
+
});
|
|
2809
|
+
};
|
|
2810
|
+
//#endregion
|
|
2811
|
+
//#region components/DataTable/TableControls/FiltersV2/RulesPill/RulesPill.tsx
|
|
2812
|
+
/**
|
|
2813
|
+
* Total rule count across every AndGroup in the compound expression.
|
|
2814
|
+
*
|
|
2815
|
+
* Empty groups contribute zero (they're pruned at adapter serialization time
|
|
2816
|
+
* anyway). The number drives the count chip's visibility.
|
|
2817
|
+
*/
|
|
2818
|
+
function countRules(compound) {
|
|
2819
|
+
let n = 0;
|
|
2820
|
+
for (const group of compound.groups) n += group.rules.length;
|
|
2821
|
+
return n;
|
|
2822
|
+
}
|
|
2823
|
+
const Label = styled.span`
|
|
2824
|
+
flex: 0 0 auto;
|
|
2825
|
+
`;
|
|
2826
|
+
const CountChip = styled.span`
|
|
2827
|
+
display: inline-flex;
|
|
2828
|
+
align-items: center;
|
|
2829
|
+
justify-content: center;
|
|
2830
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].chipHeight};
|
|
2831
|
+
min-width: ${({ $size }) => FILTER_SIZE_TOKENS[$size].chipHeight};
|
|
2832
|
+
padding: 0 ${Spacing[1]};
|
|
2833
|
+
border-radius: 3px;
|
|
2834
|
+
font-size: ${({ $size }) => FILTER_SIZE_TOKENS[$size].fontSize};
|
|
2835
|
+
line-height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].lineHeight};
|
|
2836
|
+
flex: 0 0 auto;
|
|
2837
|
+
|
|
2838
|
+
background-color: ${({ $variant }) => $variant === "active" ? Color.CorePurple200 : Color.CoreSlate200};
|
|
2839
|
+
color: ${({ $variant }) => $variant === "active" ? Color.TextActive : Color.TextPrimary};
|
|
2840
|
+
`;
|
|
2841
|
+
/**
|
|
2842
|
+
* Always-visible filter pill that owns the entire compound (DNF) expression.
|
|
2843
|
+
*
|
|
2844
|
+
* Composes `PillShell` for the rounded variant-aware container, the
|
|
2845
|
+
* `:focus-within` outline, and the trailing caret↔dismiss swap that's
|
|
2846
|
+
* identical to `FilterPill`. The pill-specific body content is just
|
|
2847
|
+
* the "Rules" label + an optional count chip.
|
|
2848
|
+
*
|
|
2849
|
+
* Visual states:
|
|
2850
|
+
* - `enabled === false` → renders `null` (the owning view has opted out of
|
|
2851
|
+
* Advanced Filter Rules).
|
|
2852
|
+
* - resting (no popover, no rules) → gray pill, label `Rules`, caret `▾`.
|
|
2853
|
+
* - resting (no popover, N rules) → gray pill, label `Rules` + `[N]` chip,
|
|
2854
|
+
* caret `▾`.
|
|
2855
|
+
* - hover (no popover) → gray-hover bg, caret stays visible.
|
|
2856
|
+
* - active (popover open) → "ube" purple pill, `TextActive` color, purple
|
|
2857
|
+
* `[N]` chip when N > 0, caret stays visible.
|
|
2858
|
+
*
|
|
2859
|
+
* The pill itself owns no popover state — parents place it inside an
|
|
2860
|
+
* `AdvancedFilterRulesPopover` (or any other popover container) and wire
|
|
2861
|
+
* `onClick` / `isOpen` themselves.
|
|
2862
|
+
*
|
|
2863
|
+
* RulesPill does not expose a dismiss action; clicking the pill toggles
|
|
2864
|
+
* the popover.
|
|
2865
|
+
*/
|
|
2866
|
+
const RulesPill = forwardRef(function RulesPill({ compound, enabled, onClick, isOpen = false, "aria-label": ariaLabel, "data-test": dataTest, size, ...rest }, ref) {
|
|
2867
|
+
const resolvedSize = useFilterBarSize(size);
|
|
2868
|
+
if (!enabled) return null;
|
|
2869
|
+
const count = countRules(compound);
|
|
2870
|
+
const hasCount = count > 0;
|
|
2871
|
+
const variant = isOpen ? "active" : "default";
|
|
2872
|
+
const accessibleName = hasCount ? `Rules ${count}` : "Rules";
|
|
2873
|
+
return /* @__PURE__ */ jsxs(PillShell, {
|
|
2874
|
+
ref,
|
|
2875
|
+
variant,
|
|
2876
|
+
size: resolvedSize,
|
|
2877
|
+
bodyAriaLabel: ariaLabel ?? accessibleName,
|
|
2878
|
+
onBodyClick: onClick,
|
|
2879
|
+
"data-test": dataTest ?? "rules-pill__trigger",
|
|
2880
|
+
"aria-expanded": isOpen,
|
|
2881
|
+
...rest,
|
|
2882
|
+
children: [/* @__PURE__ */ jsx(Label, { children: "Rules" }), hasCount && /* @__PURE__ */ jsx(CountChip, {
|
|
2883
|
+
$variant: variant,
|
|
2884
|
+
$size: resolvedSize,
|
|
2885
|
+
"data-test": "rules-pill__count",
|
|
2886
|
+
children: count
|
|
2887
|
+
})]
|
|
2888
|
+
});
|
|
2889
|
+
});
|
|
2890
|
+
//#endregion
|
|
2891
|
+
//#region components/DataTable/TableControls/FiltersV2/types.ts
|
|
2892
|
+
/** The empty state — no filters applied. */
|
|
2893
|
+
const EMPTY_FILTER_STATE = Object.freeze({
|
|
2894
|
+
topLevelRules: [],
|
|
2895
|
+
compound: Object.freeze({ groups: [] })
|
|
2896
|
+
});
|
|
2897
|
+
//#endregion
|
|
2898
|
+
//#region components/DataTable/TableControls/FiltersV2/validation.ts
|
|
2899
|
+
/**
|
|
2900
|
+
* Shared runtime validators for `FilterStateV2`, consumed by the URL
|
|
2901
|
+
* codec, the saved-view dual-write helpers, and `useFilterStateV2`.
|
|
2902
|
+
*
|
|
2903
|
+
* Validation is defense-in-depth: the type system prevents bad shapes
|
|
2904
|
+
* in code we control, but URL state and saved-view blobs are external
|
|
2905
|
+
* input — corrupted bookmarks, bad clipboard pastes, future-build
|
|
2906
|
+
* rules rolled back to an older build, attacker-crafted params — and
|
|
2907
|
+
* the validators are the last line of defense before anything reaches
|
|
2908
|
+
* the hook, the bar, or the adapter.
|
|
2909
|
+
*/
|
|
2910
|
+
const OPERATOR_SET = new Set([
|
|
2911
|
+
"is",
|
|
2912
|
+
"is_not",
|
|
2913
|
+
"contains",
|
|
2914
|
+
"does_not_contain",
|
|
2915
|
+
"is_empty",
|
|
2916
|
+
"is_not_empty",
|
|
2917
|
+
"is_true",
|
|
2918
|
+
"is_false",
|
|
2919
|
+
"before",
|
|
2920
|
+
"after"
|
|
2921
|
+
]);
|
|
2922
|
+
/**
|
|
2923
|
+
* Returns `true` when `value` is a structurally-valid `FilterStateV2`,
|
|
2924
|
+
* including per-rule content validation. A `topLevelRules` element
|
|
2925
|
+
* with a non-string `fieldId`, an operator not in the known
|
|
2926
|
+
* vocabulary, or a value of an unsupported type is rejected — a
|
|
2927
|
+
* shallow shape sniff would let those reach the adapter.
|
|
2928
|
+
*/
|
|
2929
|
+
function isFilterStateV2(value) {
|
|
2930
|
+
if (typeof value !== "object" || value == null) return false;
|
|
2931
|
+
const v = value;
|
|
2932
|
+
if (!Array.isArray(v.topLevelRules)) return false;
|
|
2933
|
+
if (!v.topLevelRules.every(isTopLevelFilterRule)) return false;
|
|
2934
|
+
if (typeof v.compound !== "object" || v.compound == null) return false;
|
|
2935
|
+
const compound = v.compound;
|
|
2936
|
+
if (!Array.isArray(compound.groups)) return false;
|
|
2937
|
+
if (!compound.groups.every(isAndGroup)) return false;
|
|
2938
|
+
return true;
|
|
2939
|
+
}
|
|
2940
|
+
function isAndGroup(value) {
|
|
2941
|
+
if (typeof value !== "object" || value == null) return false;
|
|
2942
|
+
const v = value;
|
|
2943
|
+
if (!Array.isArray(v.rules)) return false;
|
|
2944
|
+
return v.rules.every(isFilterRule);
|
|
2945
|
+
}
|
|
2946
|
+
function isFilterRule(value) {
|
|
2947
|
+
if (typeof value !== "object" || value == null) return false;
|
|
2948
|
+
const v = value;
|
|
2949
|
+
if (typeof v.fieldId !== "string" || v.fieldId.length === 0) return false;
|
|
2950
|
+
if (typeof v.operator !== "string" || !OPERATOR_SET.has(v.operator)) return false;
|
|
2951
|
+
if (!isFilterRuleValue(v.value)) return false;
|
|
2952
|
+
return true;
|
|
2953
|
+
}
|
|
2954
|
+
function isTopLevelFilterRule(value) {
|
|
2955
|
+
if (!isFilterRule(value)) return false;
|
|
2956
|
+
const v = value;
|
|
2957
|
+
return typeof v.id === "string" && v.id.length > 0;
|
|
2958
|
+
}
|
|
2959
|
+
function isFilterRuleValue(value) {
|
|
2960
|
+
if (value == null) return true;
|
|
2961
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return true;
|
|
2962
|
+
if (Array.isArray(value)) {
|
|
2963
|
+
if (value.length === 0) return true;
|
|
2964
|
+
if (value.every((entry) => typeof entry === "string")) return true;
|
|
2965
|
+
return value.every((group) => Array.isArray(group) && group.every((entry) => typeof entry === "object" && entry != null && !Array.isArray(entry)));
|
|
2966
|
+
}
|
|
2967
|
+
if (typeof value === "object") {
|
|
2968
|
+
const record = value;
|
|
2969
|
+
if ("startDate" in record || "endDate" in record) {
|
|
2970
|
+
const { startDate, endDate } = record;
|
|
2971
|
+
return (startDate == null || typeof startDate === "string") && (endDate == null || typeof endDate === "string");
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
return false;
|
|
2975
|
+
}
|
|
2976
|
+
/**
|
|
2977
|
+
* Strip rules referencing unregistered `fieldId`s from a decoded state.
|
|
2978
|
+
* Pairs with `isFilterStateV2` for defense-in-depth: even after the
|
|
2979
|
+
* shape guard passes, a rule with a `fieldId` that doesn't exist in the
|
|
2980
|
+
* current resource's registry would silently render nothing in the UI
|
|
2981
|
+
* but still reach the adapter and ship to the backend. Pruning closes
|
|
2982
|
+
* that gap.
|
|
2983
|
+
*
|
|
2984
|
+
* Empty AND-groups created by pruning are also dropped, since the
|
|
2985
|
+
* adapter prunes them on serialize anyway and the editor UI hides
|
|
2986
|
+
* empty groups too.
|
|
2987
|
+
*
|
|
2988
|
+
* Skips its own work and returns the input unchanged when `registry`
|
|
2989
|
+
* is `undefined` — useful for callers that hydrate before the registry
|
|
2990
|
+
* is built.
|
|
2991
|
+
*
|
|
2992
|
+
* For valueless operators (`is_empty`, etc.) the value contract is
|
|
2993
|
+
* `null`. Non-valueless operators keep in-progress draft values (an empty
|
|
2994
|
+
* string / empty array) so a pill the user is mid-editing survives the
|
|
2995
|
+
* report/hydrate round-trip and stays visible — top-level and compound
|
|
2996
|
+
* rules are treated alike here, and the serialize boundary
|
|
2997
|
+
* (`hasCompleteValue`) is what drops incomplete rules from the request.
|
|
2998
|
+
*/
|
|
2999
|
+
function pruneToRegistry(state, registry) {
|
|
3000
|
+
if (!registry) return state;
|
|
3001
|
+
const coerced = coerceBooleanRules(state, registry);
|
|
3002
|
+
return {
|
|
3003
|
+
topLevelRules: coerced.topLevelRules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })),
|
|
3004
|
+
compound: { groups: coerced.compound.groups.map((group) => ({ rules: group.rules.filter((rule) => isRuleAllowed(rule, registry, { allowDraftValues: true })) })).filter((group) => group.rules.length > 0) }
|
|
3005
|
+
};
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Normalizes boolean-field rules into the in-memory contract the new UI
|
|
3009
|
+
* and adapter expect — `is` / `is_not` + a real boolean `value`. Two
|
|
3010
|
+
* legacy shapes are coerced, both only for rules whose registered field
|
|
3011
|
+
* type is `boolean`:
|
|
3012
|
+
*
|
|
3013
|
+
* - Pre–value-control rules using the `is_true` / `is_false` operators
|
|
3014
|
+
* (from saved views) become `is` + boolean `value` so they survive
|
|
3015
|
+
* operator-vocabulary pruning and render in the new UI.
|
|
3016
|
+
* - v1-URL bookmarks hydrate query params as strings, so
|
|
3017
|
+
* `filter[custom][active]=true` arrives as `{ operator: 'is', value:
|
|
3018
|
+
* 'true' }`. The string is coerced back to a boolean so the adapter
|
|
3019
|
+
* emits the legacy `is_true` / `is_false` wire operator instead of
|
|
3020
|
+
* shipping the raw string (which would break old boolean bookmarks).
|
|
3021
|
+
* Non-`'true'`/`'false'` strings are left as-is and dropped later by
|
|
3022
|
+
* `isRuleAllowed`.
|
|
3023
|
+
*/
|
|
3024
|
+
function coerceBooleanRule(rule, registry) {
|
|
3025
|
+
const field = registry.get(rule.fieldId);
|
|
3026
|
+
if (!field || field.type !== "boolean") return rule;
|
|
3027
|
+
if (rule.operator === "is_true") return {
|
|
3028
|
+
...rule,
|
|
3029
|
+
operator: "is",
|
|
3030
|
+
value: true
|
|
3031
|
+
};
|
|
3032
|
+
if (rule.operator === "is_false") return {
|
|
3033
|
+
...rule,
|
|
3034
|
+
operator: "is",
|
|
3035
|
+
value: false
|
|
3036
|
+
};
|
|
3037
|
+
if (rule.operator === "is" || rule.operator === "is_not") {
|
|
3038
|
+
if (rule.value === "true") return {
|
|
3039
|
+
...rule,
|
|
3040
|
+
value: true
|
|
3041
|
+
};
|
|
3042
|
+
if (rule.value === "false") return {
|
|
3043
|
+
...rule,
|
|
3044
|
+
value: false
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
return rule;
|
|
3048
|
+
}
|
|
3049
|
+
function coerceBooleanRules(state, registry) {
|
|
3050
|
+
return {
|
|
3051
|
+
topLevelRules: state.topLevelRules.map((r) => coerceBooleanRule(r, registry)),
|
|
3052
|
+
compound: { groups: state.compound.groups.map((g) => ({ rules: g.rules.map((r) => coerceBooleanRule(r, registry)) })) }
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
function isDraftRuleValue(rule, registry) {
|
|
3056
|
+
if (isValuelessOperator(rule.operator)) return false;
|
|
3057
|
+
const field = registry.get(rule.fieldId);
|
|
3058
|
+
if (!field) return false;
|
|
3059
|
+
switch (field.type) {
|
|
3060
|
+
case "multi_choices":
|
|
3061
|
+
case "tags_conditions": return Array.isArray(rule.value) && rule.value.length === 0;
|
|
3062
|
+
case "date": return rule.value === "" || rule.value == null;
|
|
3063
|
+
case "date_range": return false;
|
|
3064
|
+
case "boolean": return false;
|
|
3065
|
+
default: return rule.value === "" || rule.value == null;
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
function isRuleAllowed(rule, registry, options = { allowDraftValues: false }) {
|
|
3069
|
+
if (!registry.has(rule.fieldId)) return false;
|
|
3070
|
+
if (!registry.operatorsFor(rule.fieldId).includes(rule.operator)) return false;
|
|
3071
|
+
if (isValuelessOperator(rule.operator)) return rule.value == null;
|
|
3072
|
+
if (options.allowDraftValues && isDraftRuleValue(rule, registry)) return true;
|
|
3073
|
+
if (registry.get(rule.fieldId)?.type === "boolean") return typeof rule.value === "boolean";
|
|
3074
|
+
if (registry.get(rule.fieldId)?.type === "date") return typeof rule.value === "string" && rule.value.length > 0;
|
|
3075
|
+
if (registry.get(rule.fieldId)?.type === "multi_choices") {
|
|
3076
|
+
if (Array.isArray(rule.value)) return rule.value.length > 0;
|
|
3077
|
+
return typeof rule.value === "string" && rule.value.length > 0;
|
|
3078
|
+
}
|
|
3079
|
+
if (registry.get(rule.fieldId)?.type === "date_range") {
|
|
3080
|
+
const value = rule.value;
|
|
3081
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
3082
|
+
const { startDate, endDate } = value;
|
|
3083
|
+
return typeof startDate === "string" && startDate.length > 0 && typeof endDate === "string" && endDate.length > 0;
|
|
3084
|
+
}
|
|
3085
|
+
if (registry.get(rule.fieldId)?.type === "tags_conditions") return Array.isArray(rule.value) && rule.value.length > 0;
|
|
3086
|
+
return rule.value != null && typeof rule.value !== "boolean";
|
|
3087
|
+
}
|
|
3088
|
+
/**
|
|
3089
|
+
* Whether `rule` carries a complete value for its field type — the
|
|
3090
|
+
* serialize-boundary predicate `pruneEmptyRulesForSerialize` uses to drop
|
|
3091
|
+
* half-edited rules from the request (a value-requiring operator like
|
|
3092
|
+
* `is_not` with no value yet makes the backend reject the whole request).
|
|
3093
|
+
*
|
|
3094
|
+
* Deliberately stricter than `isRuleAllowed`'s hydration policy, which keeps
|
|
3095
|
+
* an empty string so an in-progress pill survives the state round-trip and
|
|
3096
|
+
* stays visible for the user to finish. Checks value completeness only — it
|
|
3097
|
+
* does not gate operator vocabulary or registry membership.
|
|
3098
|
+
*/
|
|
3099
|
+
function hasCompleteValue(rule, registry) {
|
|
3100
|
+
if (isValuelessOperator(rule.operator)) return rule.value == null;
|
|
3101
|
+
const { value } = rule;
|
|
3102
|
+
if (value == null) return false;
|
|
3103
|
+
switch (registry.get(rule.fieldId)?.type) {
|
|
3104
|
+
case "boolean": return typeof value === "boolean";
|
|
3105
|
+
case "date": return typeof value === "string" && value.length > 0;
|
|
3106
|
+
case "multi_choices": return Array.isArray(value) ? value.length > 0 : typeof value === "string" && value.length > 0;
|
|
3107
|
+
case "tags_conditions": return Array.isArray(value) && value.length > 0;
|
|
3108
|
+
case "date_range": {
|
|
3109
|
+
if (typeof value !== "object" || Array.isArray(value)) return false;
|
|
3110
|
+
const { startDate, endDate } = value;
|
|
3111
|
+
return typeof startDate === "string" && startDate.length > 0 && typeof endDate === "string" && endDate.length > 0;
|
|
3112
|
+
}
|
|
3113
|
+
default:
|
|
3114
|
+
if (typeof value === "string") return value.length > 0;
|
|
3115
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
3116
|
+
return typeof value !== "boolean";
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
//#endregion
|
|
3120
|
+
//#region components/DataTable/TableControls/FiltersV2/useFilterStateV2.ts
|
|
3121
|
+
const nextRuleId = () => `top-rule-${v4()}`;
|
|
3122
|
+
/**
|
|
3123
|
+
* Owns `FilterStateV2` for a filter surface and exposes a small mutator API.
|
|
3124
|
+
*
|
|
3125
|
+
* When `advancedRulesEnabled` is false, `compound.groups` is forced to
|
|
3126
|
+
* `[]`.
|
|
3127
|
+
*
|
|
3128
|
+
* Putting that invariant here (rather than in the components) keeps
|
|
3129
|
+
* `TableControls/FiltersV2` callers from accidentally leaking compound state when
|
|
3130
|
+
* the owning view has opted out.
|
|
3131
|
+
*
|
|
3132
|
+
* The mutator surface is deliberately small: top-level rules get
|
|
3133
|
+
* upsert/remove, the compound expression gets whole-value writes. That
|
|
3134
|
+
* matches how the UI emits changes — simple pills mutate one rule at a
|
|
3135
|
+
* time; the rules popover always emits a full compound expression as
|
|
3136
|
+
* one `onChange`.
|
|
3137
|
+
*
|
|
3138
|
+
* A pill the user has picked but not yet valued is held by the bar rather
|
|
3139
|
+
* than committed. Which pills those are is recorded when the picker mints
|
|
3140
|
+
* them rather than inferred: they are never reported, so incoming state never
|
|
3141
|
+
* describes one and hydration has nothing to reconcile against them. They
|
|
3142
|
+
* stay in the same row as the committed pills, so giving one a value doesn't
|
|
3143
|
+
* move it.
|
|
3144
|
+
*/
|
|
3145
|
+
function useFilterStateV2(options) {
|
|
3146
|
+
const { initialState = EMPTY_FILTER_STATE, advancedRulesEnabled = true, registry } = options;
|
|
3147
|
+
const [internal, setInternal] = useState(() => ({
|
|
3148
|
+
all: pruneToRegistry(initialState, registry),
|
|
3149
|
+
heldIds: /* @__PURE__ */ new Set()
|
|
3150
|
+
}));
|
|
3151
|
+
const { all, heldIds } = internal;
|
|
3152
|
+
const lastHydratedState = useRef(all);
|
|
3153
|
+
useEffect(() => {
|
|
3154
|
+
const nextHydratedState = pruneToRegistry(initialState, registry);
|
|
3155
|
+
const hydrate = !isEqual(nextHydratedState, lastHydratedState.current);
|
|
3156
|
+
if (hydrate) lastHydratedState.current = nextHydratedState;
|
|
3157
|
+
setInternal((prev) => {
|
|
3158
|
+
const pruned = pruneHeldRules(prev, registry);
|
|
3159
|
+
return hydrate ? withHeldRules(nextHydratedState, pruned) : pruned;
|
|
3160
|
+
});
|
|
3161
|
+
}, [initialState, registry]);
|
|
3162
|
+
const state = useMemo(() => enforceAdvancedRulesEnabled(all, advancedRulesEnabled), [all, advancedRulesEnabled]);
|
|
3163
|
+
return {
|
|
3164
|
+
state,
|
|
3165
|
+
committedState: useMemo(() => heldIds.size === 0 ? state : {
|
|
3166
|
+
...state,
|
|
3167
|
+
topLevelRules: state.topLevelRules.filter((rule) => !heldIds.has(rule.id))
|
|
3168
|
+
}, [state, heldIds]),
|
|
3169
|
+
advancedRulesEnabled,
|
|
3170
|
+
setState: useCallback((next) => {
|
|
3171
|
+
setInternal({
|
|
3172
|
+
all: pruneToRegistry(next, registry),
|
|
3173
|
+
heldIds: /* @__PURE__ */ new Set()
|
|
3174
|
+
});
|
|
3175
|
+
}, [registry]),
|
|
3176
|
+
setCompound: useCallback((compound) => {
|
|
3177
|
+
setInternal((prev) => ({
|
|
3178
|
+
...prev,
|
|
3179
|
+
all: {
|
|
3180
|
+
...prev.all,
|
|
3181
|
+
compound
|
|
3182
|
+
}
|
|
3183
|
+
}));
|
|
3184
|
+
}, []),
|
|
3185
|
+
reset: useCallback(() => {
|
|
3186
|
+
setInternal({
|
|
3187
|
+
all: {
|
|
3188
|
+
topLevelRules: [],
|
|
3189
|
+
compound: { groups: [] }
|
|
3190
|
+
},
|
|
3191
|
+
heldIds: /* @__PURE__ */ new Set()
|
|
3192
|
+
});
|
|
3193
|
+
}, []),
|
|
3194
|
+
addTopLevelRule: useCallback((rule) => {
|
|
3195
|
+
const id = nextRuleId();
|
|
3196
|
+
const added = {
|
|
3197
|
+
...rule,
|
|
3198
|
+
id
|
|
3199
|
+
};
|
|
3200
|
+
setInternal((prev) => ({
|
|
3201
|
+
all: {
|
|
3202
|
+
...prev.all,
|
|
3203
|
+
topLevelRules: [...prev.all.topLevelRules, added]
|
|
3204
|
+
},
|
|
3205
|
+
heldIds: isHeldRule(added) ? withId(prev.heldIds, id) : prev.heldIds
|
|
3206
|
+
}));
|
|
3207
|
+
return id;
|
|
3208
|
+
}, []),
|
|
3209
|
+
updateTopLevelRule: useCallback((id, rule) => {
|
|
3210
|
+
setInternal((prev) => {
|
|
3211
|
+
const idx = prev.all.topLevelRules.findIndex((r) => r.id === id);
|
|
3212
|
+
if (idx < 0) return prev;
|
|
3213
|
+
const topLevelRules = [...prev.all.topLevelRules];
|
|
3214
|
+
topLevelRules[idx] = {
|
|
3215
|
+
...rule,
|
|
3216
|
+
id
|
|
3217
|
+
};
|
|
3218
|
+
const heldIds = isHeldRule(rule) ? withId(prev.heldIds, id) : withoutId(prev.heldIds, id);
|
|
3219
|
+
return {
|
|
3220
|
+
all: {
|
|
3221
|
+
...prev.all,
|
|
3222
|
+
topLevelRules
|
|
3223
|
+
},
|
|
3224
|
+
heldIds
|
|
3225
|
+
};
|
|
3226
|
+
});
|
|
3227
|
+
}, []),
|
|
3228
|
+
removeTopLevelRule: useCallback((id) => {
|
|
3229
|
+
setInternal((prev) => {
|
|
3230
|
+
const topLevelRules = prev.all.topLevelRules.filter((r) => r.id !== id);
|
|
3231
|
+
if (topLevelRules.length === prev.all.topLevelRules.length) return prev;
|
|
3232
|
+
return {
|
|
3233
|
+
all: {
|
|
3234
|
+
...prev.all,
|
|
3235
|
+
topLevelRules
|
|
3236
|
+
},
|
|
3237
|
+
heldIds: prev.heldIds.has(id) ? withoutId(prev.heldIds, id) : prev.heldIds
|
|
3238
|
+
};
|
|
3239
|
+
});
|
|
3240
|
+
}, [])
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* A pill the picker minted that has no value yet. `null` and not "empty":
|
|
3245
|
+
* `''` and `[]` are values a user can choose, and a filter cleared to one is
|
|
3246
|
+
* still a filter.
|
|
3247
|
+
*/
|
|
3248
|
+
function isHeldRule(rule) {
|
|
3249
|
+
return !isValuelessOperator(rule.operator) && rule.value == null;
|
|
3250
|
+
}
|
|
3251
|
+
function heldRulesOf(state) {
|
|
3252
|
+
return state.heldIds.size === 0 ? [] : state.all.topLevelRules.filter((rule) => state.heldIds.has(rule.id));
|
|
3253
|
+
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Lays incoming committed rules into the row, keeping the held pills where the
|
|
3256
|
+
* user left them: a held pill goes back after however many committed rules
|
|
3257
|
+
* preceded it before. An id the incoming state now uses is the parent's, so the
|
|
3258
|
+
* held pill of that id gives way rather than duplicating it.
|
|
3259
|
+
*/
|
|
3260
|
+
function withHeldRules(committed, prev) {
|
|
3261
|
+
if (prev.heldIds.size === 0) return {
|
|
3262
|
+
all: committed,
|
|
3263
|
+
heldIds: prev.heldIds
|
|
3264
|
+
};
|
|
3265
|
+
const incoming = committed.topLevelRules;
|
|
3266
|
+
const incomingIds = new Set(incoming.map((rule) => rule.id));
|
|
3267
|
+
const topLevelRules = [];
|
|
3268
|
+
const heldIds = /* @__PURE__ */ new Set();
|
|
3269
|
+
let taken = 0;
|
|
3270
|
+
let committedBefore = 0;
|
|
3271
|
+
prev.all.topLevelRules.forEach((rule) => {
|
|
3272
|
+
if (!prev.heldIds.has(rule.id)) {
|
|
3273
|
+
committedBefore += 1;
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
if (incomingIds.has(rule.id)) return;
|
|
3277
|
+
while (taken < committedBefore && taken < incoming.length) {
|
|
3278
|
+
topLevelRules.push(incoming[taken]);
|
|
3279
|
+
taken += 1;
|
|
3280
|
+
}
|
|
3281
|
+
topLevelRules.push(rule);
|
|
3282
|
+
heldIds.add(rule.id);
|
|
3283
|
+
});
|
|
3284
|
+
if (heldIds.size === 0) return {
|
|
3285
|
+
all: committed,
|
|
3286
|
+
heldIds
|
|
3287
|
+
};
|
|
3288
|
+
while (taken < incoming.length) {
|
|
3289
|
+
topLevelRules.push(incoming[taken]);
|
|
3290
|
+
taken += 1;
|
|
3291
|
+
}
|
|
3292
|
+
return {
|
|
3293
|
+
all: {
|
|
3294
|
+
...committed,
|
|
3295
|
+
topLevelRules
|
|
3296
|
+
},
|
|
3297
|
+
heldIds
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
/**
|
|
3301
|
+
* Drops held pills whose field or operator the registry no longer supports,
|
|
3302
|
+
* preserving the state's identity when there are none so an unchanged registry
|
|
3303
|
+
* can't loop the effect.
|
|
3304
|
+
*/
|
|
3305
|
+
function pruneHeldRules(state, registry) {
|
|
3306
|
+
const droppedIds = new Set(heldRulesOf(state).filter((rule) => !registry.has(rule.fieldId) || !registry.operatorsFor(rule.fieldId).includes(rule.operator)).map((rule) => rule.id));
|
|
3307
|
+
if (droppedIds.size === 0) return state;
|
|
3308
|
+
const heldIds = new Set(state.heldIds);
|
|
3309
|
+
droppedIds.forEach((id) => heldIds.delete(id));
|
|
3310
|
+
return {
|
|
3311
|
+
all: {
|
|
3312
|
+
...state.all,
|
|
3313
|
+
topLevelRules: state.all.topLevelRules.filter((rule) => !droppedIds.has(rule.id))
|
|
3314
|
+
},
|
|
3315
|
+
heldIds
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
function withId(ids, id) {
|
|
3319
|
+
return ids.has(id) ? ids : new Set(ids).add(id);
|
|
3320
|
+
}
|
|
3321
|
+
function withoutId(ids, id) {
|
|
3322
|
+
if (!ids.has(id)) return ids;
|
|
3323
|
+
const next = new Set(ids);
|
|
3324
|
+
next.delete(id);
|
|
3325
|
+
return next;
|
|
3326
|
+
}
|
|
3327
|
+
function enforceAdvancedRulesEnabled(state, advancedRulesEnabled) {
|
|
3328
|
+
if (advancedRulesEnabled) return state;
|
|
3329
|
+
if (state.compound.groups.length === 0) return state;
|
|
3330
|
+
return {
|
|
3331
|
+
...state,
|
|
3332
|
+
compound: { groups: [] }
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
//#endregion
|
|
3336
|
+
//#region components/DataTable/TableControls/FiltersV2/FilterBar/FilterBar.tsx
|
|
3337
|
+
const Bar = styled.div`
|
|
3338
|
+
display: flex;
|
|
3339
|
+
align-items: center;
|
|
3340
|
+
gap: ${Spacing[2]};
|
|
3341
|
+
flex-wrap: nowrap;
|
|
3342
|
+
min-width: 0;
|
|
3343
|
+
padding: ${Spacing[2]} 0;
|
|
3344
|
+
`;
|
|
3345
|
+
const SCROLL_FADE_WIDTH = 48;
|
|
3346
|
+
const SCROLL_SETTLE_FALLBACK_MS = 400;
|
|
3347
|
+
const POPOVER_CLOSE_BUFFER_PX = 8;
|
|
3348
|
+
const PILL_FOCUS_RING_SPACE = 3;
|
|
3349
|
+
/**
|
|
3350
|
+
* Builds the `mask-image` gradient that fades the scroll track's edges.
|
|
3351
|
+
*
|
|
3352
|
+
* A `mask-image` (rather than a same-colored overlay painted on top) fades
|
|
3353
|
+
* the track's *entire rendered output* — pill backgrounds, borders, and any
|
|
3354
|
+
* focus ring that bleeds into the padding above — because masking
|
|
3355
|
+
* composites against the real pixels the element paints, instead of
|
|
3356
|
+
* relying on an opaque sibling layer to visually cover them. An overlay
|
|
3357
|
+
* approach only hides whatever happens to sit below it in paint order;
|
|
3358
|
+
* a mask fades everything, including outlines.
|
|
3359
|
+
*/
|
|
3360
|
+
const buildEdgeMaskImage = (showLeftFade, showRightFade) => {
|
|
3361
|
+
return `linear-gradient(to right, ${showLeftFade ? `transparent 0, black ${SCROLL_FADE_WIDTH}px` : "black 0"}, ${showRightFade ? `black calc(100% - ${SCROLL_FADE_WIDTH}px), transparent 100%` : "black 100%"})`;
|
|
3362
|
+
};
|
|
3363
|
+
const PillsScrollRegion = styled.div`
|
|
3364
|
+
display: flex;
|
|
3365
|
+
align-items: center;
|
|
3366
|
+
gap: ${Spacing[1]};
|
|
3367
|
+
flex: 0 1 auto;
|
|
3368
|
+
min-width: 0;
|
|
3369
|
+
width: max-content;
|
|
3370
|
+
max-width: 100%;
|
|
3371
|
+
`;
|
|
3372
|
+
const PillsScrollTrack = styled.div`
|
|
3373
|
+
display: flex;
|
|
3374
|
+
align-items: center;
|
|
3375
|
+
flex: 1 1 auto;
|
|
3376
|
+
min-width: 0;
|
|
3377
|
+
flex-wrap: nowrap;
|
|
3378
|
+
gap: ${Spacing[1]};
|
|
3379
|
+
overflow-x: auto;
|
|
3380
|
+
overflow-y: hidden;
|
|
3381
|
+
-webkit-overflow-scrolling: touch;
|
|
3382
|
+
scrollbar-width: none;
|
|
3383
|
+
padding: ${PILL_FOCUS_RING_SPACE}px;
|
|
3384
|
+
margin: -${PILL_FOCUS_RING_SPACE}px;
|
|
3385
|
+
-webkit-mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
|
|
3386
|
+
mask-image: ${({ $showLeftFade, $showRightFade }) => buildEdgeMaskImage($showLeftFade, $showRightFade)};
|
|
3387
|
+
transition:
|
|
3388
|
+
-webkit-mask-image ${"200ms"} ease-out,
|
|
3389
|
+
mask-image ${"200ms"} ease-out;
|
|
3390
|
+
|
|
3391
|
+
& > * {
|
|
3392
|
+
flex: 0 0 auto;
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
&::-webkit-scrollbar {
|
|
3396
|
+
display: none;
|
|
3397
|
+
}
|
|
3398
|
+
`;
|
|
3399
|
+
const Divider = styled.div`
|
|
3400
|
+
width: 1px;
|
|
3401
|
+
height: ${({ $size }) => FILTER_SIZE_TOKENS[$size].controlHeight};
|
|
3402
|
+
background: ${Color.BorderDivider};
|
|
3403
|
+
flex: 0 0 auto;
|
|
3404
|
+
margin: 0 ${({ $flush }) => $flush ? 0 : Spacing[1]};
|
|
3405
|
+
`;
|
|
3406
|
+
const PickerSlot = styled.div`
|
|
3407
|
+
flex: 0 0 auto;
|
|
3408
|
+
`;
|
|
3409
|
+
const EmptyTrailingGroup = styled.div`
|
|
3410
|
+
display: flex;
|
|
3411
|
+
align-items: center;
|
|
3412
|
+
gap: ${Spacing[1]};
|
|
3413
|
+
flex: 0 0 auto;
|
|
3414
|
+
`;
|
|
3415
|
+
/**
|
|
3416
|
+
* Returns the previous array whenever its contents still match, so the field
|
|
3417
|
+
* ids can be derived on every render without giving the picker a new array to
|
|
3418
|
+
* rebuild its whole option tree from. Editing a pill's value rewrites the
|
|
3419
|
+
* rules while leaving the fields in use unchanged.
|
|
3420
|
+
*/
|
|
3421
|
+
function useStableFieldIds(fieldIds) {
|
|
3422
|
+
const stable = useRef(fieldIds);
|
|
3423
|
+
const previous = stable.current;
|
|
3424
|
+
if (previous == null || fieldIds == null || previous.length !== fieldIds.length || fieldIds.some((id, index) => previous[index] !== id)) stable.current = fieldIds;
|
|
3425
|
+
return stable.current;
|
|
3426
|
+
}
|
|
3427
|
+
function FilterBar({ registry, initialState, onChange, advancedRulesEnabled = true, singleUseFields = false, size = "md", valueControlResolver }) {
|
|
3428
|
+
const filterState = useFilterStateV2({
|
|
3429
|
+
registry,
|
|
3430
|
+
initialState,
|
|
3431
|
+
advancedRulesEnabled
|
|
3432
|
+
});
|
|
3433
|
+
const [editingRuleId, setEditingRuleId] = useState(null);
|
|
3434
|
+
const [rulesPopoverOpen, setRulesPopoverOpen] = useState(false);
|
|
3435
|
+
const pillNodesRef = useRef(/* @__PURE__ */ new Map());
|
|
3436
|
+
const onChangeRef = useRef(onChange);
|
|
3437
|
+
useEffect(() => {
|
|
3438
|
+
onChangeRef.current = onChange;
|
|
3439
|
+
}, [onChange]);
|
|
3440
|
+
const { committedState } = filterState;
|
|
3441
|
+
const lastReportedState = useRef(committedState);
|
|
3442
|
+
useEffect(() => {
|
|
3443
|
+
if (isEqual(lastReportedState.current, committedState)) return;
|
|
3444
|
+
lastReportedState.current = committedState;
|
|
3445
|
+
onChangeRef.current?.(committedState);
|
|
3446
|
+
}, [committedState]);
|
|
3447
|
+
const pendingEditRuleIdRef = useRef(null);
|
|
3448
|
+
const openedViaScrollIntoViewRef = useRef(false);
|
|
3449
|
+
const handlePickField = (field) => {
|
|
3450
|
+
pendingEditRuleIdRef.current = filterState.addTopLevelRule({
|
|
3451
|
+
fieldId: field.id,
|
|
3452
|
+
operator: registry.defaultOperatorFor(field.id),
|
|
3453
|
+
value: null
|
|
3454
|
+
});
|
|
3455
|
+
};
|
|
3456
|
+
const hasPills = filterState.state.topLevelRules.length > 0;
|
|
3457
|
+
const excludeFieldIds = useStableFieldIds(singleUseFields ? filterState.state.topLevelRules.map((rule) => rule.fieldId) : void 0);
|
|
3458
|
+
const pillsScrollRef = useRef(null);
|
|
3459
|
+
const { hasHorizontalOverflow, hasMoreScrollLeft, hasMoreScrollRight } = useScrollOverflow(pillsScrollRef, { enabled: hasPills });
|
|
3460
|
+
const ruleCount = filterState.state.topLevelRules.length;
|
|
3461
|
+
const previousRuleCountRef = useRef(ruleCount);
|
|
3462
|
+
useEffect(() => {
|
|
3463
|
+
const grew = ruleCount > previousRuleCountRef.current;
|
|
3464
|
+
previousRuleCountRef.current = ruleCount;
|
|
3465
|
+
if (!grew || pendingEditRuleIdRef.current == null) return;
|
|
3466
|
+
const openPendingRule = () => {
|
|
3467
|
+
openedViaScrollIntoViewRef.current = true;
|
|
3468
|
+
setEditingRuleId(pendingEditRuleIdRef.current);
|
|
3469
|
+
pendingEditRuleIdRef.current = null;
|
|
3470
|
+
};
|
|
3471
|
+
const track = pillsScrollRef.current;
|
|
3472
|
+
if (!track || !track.scrollTo) {
|
|
3473
|
+
openPendingRule();
|
|
3474
|
+
return;
|
|
3475
|
+
}
|
|
3476
|
+
const maxScrollLeft = Math.max(track.scrollWidth - track.clientWidth, 0);
|
|
3477
|
+
if (Math.abs(track.scrollLeft - maxScrollLeft) <= 1) {
|
|
3478
|
+
openPendingRule();
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
let settled = false;
|
|
3482
|
+
const finish = () => {
|
|
3483
|
+
if (settled) return;
|
|
3484
|
+
settled = true;
|
|
3485
|
+
track.removeEventListener("scrollend", finish);
|
|
3486
|
+
window.clearTimeout(fallbackTimer);
|
|
3487
|
+
openPendingRule();
|
|
3488
|
+
};
|
|
3489
|
+
track.addEventListener("scrollend", finish, { once: true });
|
|
3490
|
+
const fallbackTimer = window.setTimeout(finish, SCROLL_SETTLE_FALLBACK_MS);
|
|
3491
|
+
track.scrollTo({
|
|
3492
|
+
left: track.scrollWidth,
|
|
3493
|
+
behavior: "smooth"
|
|
3494
|
+
});
|
|
3495
|
+
return () => {
|
|
3496
|
+
track.removeEventListener("scrollend", finish);
|
|
3497
|
+
window.clearTimeout(fallbackTimer);
|
|
3498
|
+
};
|
|
3499
|
+
}, [ruleCount]);
|
|
3500
|
+
useEffect(() => {
|
|
3501
|
+
if (editingRuleId == null) return;
|
|
3502
|
+
const root = pillsScrollRef.current;
|
|
3503
|
+
const target = pillNodesRef.current.get(editingRuleId);
|
|
3504
|
+
if (!root || !target || typeof IntersectionObserver === "undefined") return;
|
|
3505
|
+
let skipInitialReport = openedViaScrollIntoViewRef.current;
|
|
3506
|
+
openedViaScrollIntoViewRef.current = false;
|
|
3507
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
3508
|
+
if (skipInitialReport) {
|
|
3509
|
+
skipInitialReport = false;
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
if (entry && entry.intersectionRatio < 1) setEditingRuleId(null);
|
|
3513
|
+
}, {
|
|
3514
|
+
root,
|
|
3515
|
+
threshold: 1,
|
|
3516
|
+
rootMargin: `0px ${POPOVER_CLOSE_BUFFER_PX}px 0px ${POPOVER_CLOSE_BUFFER_PX}px`
|
|
3517
|
+
});
|
|
3518
|
+
observer.observe(target);
|
|
3519
|
+
return () => observer.disconnect();
|
|
3520
|
+
}, [editingRuleId]);
|
|
3521
|
+
useLayoutEffect(() => {
|
|
3522
|
+
if (editingRuleId == null) return;
|
|
3523
|
+
pillNodesRef.current.get(editingRuleId)?.scrollIntoView?.({
|
|
3524
|
+
block: "nearest",
|
|
3525
|
+
inline: "nearest"
|
|
3526
|
+
});
|
|
3527
|
+
}, [editingRuleId, useMemo(() => editingRuleId == null ? null : filterState.state.topLevelRules.find((rule) => rule.id === editingRuleId)?.value ?? null, [editingRuleId, filterState.state.topLevelRules])]);
|
|
3528
|
+
const visibleCustomFieldParents = useMemo(() => deriveVisibleCustomFieldParents(registry, filterState.state.topLevelRules), [registry, filterState.state.topLevelRules]);
|
|
3529
|
+
return /* @__PURE__ */ jsx(FilterBarContext.Provider, {
|
|
3530
|
+
value: { size },
|
|
3531
|
+
children: /* @__PURE__ */ jsxs(Bar, {
|
|
3532
|
+
role: "toolbar",
|
|
3533
|
+
"aria-label": "Filter bar",
|
|
3534
|
+
children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(AdvancedFilterRulesPopover, {
|
|
3535
|
+
registry,
|
|
3536
|
+
compound: filterState.state.compound,
|
|
3537
|
+
open: rulesPopoverOpen,
|
|
3538
|
+
onOpenChange: setRulesPopoverOpen,
|
|
3539
|
+
onChange: (compound) => {
|
|
3540
|
+
filterState.setCompound(compound);
|
|
3541
|
+
},
|
|
3542
|
+
renderValueControl: (field, operator, value, onChange) => resolveValueControl({
|
|
3543
|
+
field,
|
|
3544
|
+
operator,
|
|
3545
|
+
value,
|
|
3546
|
+
onChange,
|
|
3547
|
+
layout: "inline"
|
|
3548
|
+
}, valueControlResolver),
|
|
3549
|
+
trigger: /* @__PURE__ */ jsx(RulesPill, {
|
|
3550
|
+
compound: filterState.state.compound,
|
|
3551
|
+
enabled: true,
|
|
3552
|
+
isOpen: rulesPopoverOpen
|
|
3553
|
+
})
|
|
3554
|
+
}) : null, hasPills ? /* @__PURE__ */ jsxs(Fragment, { children: [filterState.advancedRulesEnabled ? /* @__PURE__ */ jsx(Divider, {
|
|
3555
|
+
"aria-hidden": "true",
|
|
3556
|
+
$size: size
|
|
3557
|
+
}) : null, /* @__PURE__ */ jsxs(PillsScrollRegion, {
|
|
3558
|
+
"data-test": "filter-bar-pills-scroll-region",
|
|
3559
|
+
children: [/* @__PURE__ */ jsx(PillsScrollTrack, {
|
|
3560
|
+
ref: pillsScrollRef,
|
|
3561
|
+
"data-test": "filter-bar-pills-scroll-track",
|
|
3562
|
+
$showLeftFade: hasMoreScrollLeft,
|
|
3563
|
+
$showRightFade: hasHorizontalOverflow && hasMoreScrollRight,
|
|
3564
|
+
children: filterState.state.topLevelRules.map((rule) => {
|
|
3565
|
+
const field = registry.get(rule.fieldId);
|
|
3566
|
+
if (!field) return null;
|
|
3567
|
+
const optionLabels = registry.optionLabelsFor(field.id);
|
|
3568
|
+
const isOpen = editingRuleId === rule.id;
|
|
3569
|
+
return /* @__PURE__ */ jsx(FilterPopover, {
|
|
3570
|
+
field,
|
|
3571
|
+
operator: rule.operator,
|
|
3572
|
+
onChangeOperator: (op) => {
|
|
3573
|
+
filterState.updateTopLevelRule(rule.id, {
|
|
3574
|
+
...rule,
|
|
3575
|
+
operator: op,
|
|
3576
|
+
value: isValuelessOperator(op) ? null : rule.value ?? emptyValueForFieldType(field.type)
|
|
3577
|
+
});
|
|
3578
|
+
},
|
|
3579
|
+
valueControlSlot: resolveValueControl({
|
|
3580
|
+
field,
|
|
3581
|
+
operator: rule.operator,
|
|
3582
|
+
value: rule.value,
|
|
3583
|
+
onChange: (value) => filterState.updateTopLevelRule(rule.id, {
|
|
3584
|
+
...rule,
|
|
3585
|
+
value
|
|
3586
|
+
}),
|
|
3587
|
+
onCommit: () => setEditingRuleId(null)
|
|
3588
|
+
}, valueControlResolver),
|
|
3589
|
+
onClose: () => setEditingRuleId(null),
|
|
3590
|
+
open: isOpen,
|
|
3591
|
+
onOpenChange: (open) => setEditingRuleId(open ? rule.id : null),
|
|
3592
|
+
trigger: /* @__PURE__ */ jsx(FilterPill, {
|
|
3593
|
+
ref: (node) => {
|
|
3594
|
+
if (node) pillNodesRef.current.set(rule.id, node);
|
|
3595
|
+
else pillNodesRef.current.delete(rule.id);
|
|
3596
|
+
},
|
|
3597
|
+
field,
|
|
3598
|
+
rule,
|
|
3599
|
+
variant: isOpen ? "active" : "default",
|
|
3600
|
+
optionLabels,
|
|
3601
|
+
onDismiss: () => filterState.removeTopLevelRule(rule.id)
|
|
3602
|
+
})
|
|
3603
|
+
}, rule.id);
|
|
3604
|
+
})
|
|
3605
|
+
}), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
|
|
3606
|
+
registry,
|
|
3607
|
+
onPickField: handlePickField,
|
|
3608
|
+
compact: true,
|
|
3609
|
+
visibleCustomFieldParents,
|
|
3610
|
+
excludeFieldIds
|
|
3611
|
+
}) })]
|
|
3612
|
+
})] }) : filterState.advancedRulesEnabled ? /* @__PURE__ */ jsxs(EmptyTrailingGroup, { children: [/* @__PURE__ */ jsx(Divider, {
|
|
3613
|
+
"aria-hidden": "true",
|
|
3614
|
+
$size: size,
|
|
3615
|
+
$flush: true
|
|
3616
|
+
}), /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
|
|
3617
|
+
registry,
|
|
3618
|
+
onPickField: handlePickField,
|
|
3619
|
+
visibleCustomFieldParents,
|
|
3620
|
+
excludeFieldIds
|
|
3621
|
+
}) })] }) : /* @__PURE__ */ jsx(PickerSlot, { children: /* @__PURE__ */ jsx(FilterPickerMenu, {
|
|
3622
|
+
registry,
|
|
3623
|
+
onPickField: handlePickField,
|
|
3624
|
+
visibleCustomFieldParents,
|
|
3625
|
+
excludeFieldIds
|
|
3626
|
+
}) })]
|
|
3627
|
+
})
|
|
3628
|
+
});
|
|
3629
|
+
}
|
|
3630
|
+
/**
|
|
3631
|
+
* The parent labels the picker's Fields submenu should keep visible,
|
|
3632
|
+
* derived from active `is` pills on scoping selector fields
|
|
3633
|
+
* ({@link FieldRegistry.narrowingSelectorFields}). Each selected option
|
|
3634
|
+
* value is a backend record-type/template id, resolved to its parent
|
|
3635
|
+
* label via {@link FieldRegistry.parentRecordLabelForId}.
|
|
3636
|
+
*
|
|
3637
|
+
* Returns `undefined` only when no `is` selector pill is active (show
|
|
3638
|
+
* everything). An active pill always returns an array, possibly empty:
|
|
3639
|
+
* selecting a scope with no custom fields narrows to *nothing*, not to
|
|
3640
|
+
* everything — conflating the two would leak other scopes' fields in.
|
|
3641
|
+
*
|
|
3642
|
+
* Only `is` narrows; multiple pills union their parents.
|
|
3643
|
+
*/
|
|
3644
|
+
function deriveVisibleCustomFieldParents(registry, topLevelRules) {
|
|
3645
|
+
const selectorFields = registry.narrowingSelectorFields();
|
|
3646
|
+
if (selectorFields.length === 0 || topLevelRules.length === 0) return;
|
|
3647
|
+
const selectorIds = new Set(selectorFields.map((f) => f.id));
|
|
3648
|
+
const allowedParents = /* @__PURE__ */ new Set();
|
|
3649
|
+
let hasActiveSelectorRule = false;
|
|
3650
|
+
for (const rule of topLevelRules) {
|
|
3651
|
+
if (!selectorIds.has(rule.fieldId)) continue;
|
|
3652
|
+
if (rule.operator !== "is") continue;
|
|
3653
|
+
hasActiveSelectorRule = true;
|
|
3654
|
+
const values = Array.isArray(rule.value) ? rule.value : [rule.value];
|
|
3655
|
+
for (const value of values) {
|
|
3656
|
+
if (typeof value !== "string") continue;
|
|
3657
|
+
const parentLabel = registry.parentRecordLabelForId(value);
|
|
3658
|
+
if (parentLabel != null) allowedParents.add(parentLabel);
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
return hasActiveSelectorRule ? Array.from(allowedParents) : void 0;
|
|
3662
|
+
}
|
|
3663
|
+
//#endregion
|
|
3664
|
+
//#region components/DataTable/TableControls/FiltersV2/FilterBar/index.ts
|
|
3665
|
+
var FilterBar_default = FilterBar;
|
|
3666
|
+
//#endregion
|
|
3667
|
+
//#region components/DataTable/TableControls/FiltersV2/FieldRegistry.ts
|
|
3668
|
+
/**
|
|
3669
|
+
* Per-resource field registry.
|
|
3670
|
+
*
|
|
3671
|
+
* Built from the backend's `field_key_to_info` payload (extended in v2 to
|
|
3672
|
+
* include `type`, `options`, `item_type`, and a `source`/`parent_record_type`
|
|
3673
|
+
* pair for custom fields). The registry is the single place the UI looks up
|
|
3674
|
+
* "what field is this and what can the user do with it" — value control
|
|
3675
|
+
* rendering, operator vocabulary, picker grouping, and popover footer text
|
|
3676
|
+
* all route through here.
|
|
3677
|
+
*
|
|
3678
|
+
* Construction is tolerant of partial metadata: if `type` is missing the
|
|
3679
|
+
* field falls back to `'string'`.
|
|
3680
|
+
*/
|
|
3681
|
+
var FieldRegistry = class {
|
|
3682
|
+
constructor(resource, fields) {
|
|
3683
|
+
this.optionLabelCache = /* @__PURE__ */ new Map();
|
|
3684
|
+
this.resource = resource;
|
|
3685
|
+
this.byId = /* @__PURE__ */ new Map();
|
|
3686
|
+
this.customByParent = /* @__PURE__ */ new Map();
|
|
3687
|
+
this.parentLabelByRecordId = /* @__PURE__ */ new Map();
|
|
3688
|
+
this.systemFields = [];
|
|
3689
|
+
const parentRecordIds = /* @__PURE__ */ new Set();
|
|
3690
|
+
for (const field of fields) {
|
|
3691
|
+
this.byId.set(field.id, field);
|
|
3692
|
+
if (field.source === "custom") {
|
|
3693
|
+
const parent = field.parentRecordType ?? "";
|
|
3694
|
+
const bucket = this.customByParent.get(parent) ?? [];
|
|
3695
|
+
bucket.push(field);
|
|
3696
|
+
this.customByParent.set(parent, bucket);
|
|
3697
|
+
if (field.parentRecordId) {
|
|
3698
|
+
parentRecordIds.add(field.parentRecordId);
|
|
3699
|
+
if (field.parentRecordType) this.parentLabelByRecordId.set(field.parentRecordId, field.parentRecordType);
|
|
3700
|
+
}
|
|
3701
|
+
} else this.systemFields.push(field);
|
|
3702
|
+
}
|
|
3703
|
+
this.narrowingSelectors = [];
|
|
3704
|
+
if (parentRecordIds.size > 0) for (const field of fields) {
|
|
3705
|
+
if (!field.options || field.options.length === 0) continue;
|
|
3706
|
+
if (field.options.some((opt) => parentRecordIds.has(opt.value))) this.narrowingSelectors.push(field);
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
parentRecordLabelForId(recordId) {
|
|
3710
|
+
return this.parentLabelByRecordId.get(recordId);
|
|
3711
|
+
}
|
|
3712
|
+
has(fieldId) {
|
|
3713
|
+
return this.byId.has(fieldId);
|
|
3714
|
+
}
|
|
3715
|
+
get(fieldId) {
|
|
3716
|
+
return this.byId.get(fieldId);
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* Get a field by id, throwing if it isn't registered. Use this when the
|
|
3720
|
+
* fieldId came from state we own (e.g. an existing `FilterRule`) and a
|
|
3721
|
+
* missing field is a programming error rather than user input.
|
|
3722
|
+
*/
|
|
3723
|
+
getOrThrow(fieldId) {
|
|
3724
|
+
const field = this.byId.get(fieldId);
|
|
3725
|
+
if (!field) throw new Error(`FieldRegistry(${this.resource}): unknown fieldId "${fieldId}"`);
|
|
3726
|
+
return field;
|
|
3727
|
+
}
|
|
3728
|
+
/** All registered fields, in insertion order. */
|
|
3729
|
+
all() {
|
|
3730
|
+
return Array.from(this.byId.values());
|
|
3731
|
+
}
|
|
3732
|
+
/** System / built-in fields, used as the top section of the `⊕ Filter` menu. */
|
|
3733
|
+
systemFieldsList() {
|
|
3734
|
+
return this.systemFields;
|
|
3735
|
+
}
|
|
3736
|
+
/** Custom fields grouped by parent record type for the picker submenu. */
|
|
3737
|
+
customFieldGroups() {
|
|
3738
|
+
return Array.from(this.customByParent.entries()).map(([parent, fields]) => ({
|
|
3739
|
+
parent,
|
|
3740
|
+
fields
|
|
3741
|
+
}));
|
|
3742
|
+
}
|
|
3743
|
+
/**
|
|
3744
|
+
* Fields that drive the picker's field-narrowing. A field is inferred
|
|
3745
|
+
* as a scoping selector (e.g. `account_type`, `case_template`) when its
|
|
3746
|
+
* `options[].value` set intersects the registry's custom-field
|
|
3747
|
+
* `parentRecordId`s — matching on the backend id, not the display label,
|
|
3748
|
+
* so a suffixed or colliding label (`"KYC Standard (CAS)"`) still
|
|
3749
|
+
* narrows correctly. `FilterBar` turns an active `is` pill on such a
|
|
3750
|
+
* field into the visible parent labels via {@link parentRecordLabelForId}.
|
|
3751
|
+
*
|
|
3752
|
+
* A coincidental id collision would falsely tag a field as a selector —
|
|
3753
|
+
* benign (it just narrows too); layer on a backend flag if ever needed.
|
|
3754
|
+
*/
|
|
3755
|
+
narrowingSelectorFields() {
|
|
3756
|
+
return this.narrowingSelectors;
|
|
3757
|
+
}
|
|
3758
|
+
/** Operator vocabulary for a registered field, gated by its `allowedOperators` then type. */
|
|
3759
|
+
operatorsFor(fieldId) {
|
|
3760
|
+
return getOperatorsForField(this.getOrThrow(fieldId));
|
|
3761
|
+
}
|
|
3762
|
+
/** Default operator for a registered field. Used when creating a new pill. */
|
|
3763
|
+
defaultOperatorFor(fieldId) {
|
|
3764
|
+
const [defaultOperator] = getOperatorsForField(this.getOrThrow(fieldId));
|
|
3765
|
+
if (defaultOperator == null) throw new Error(`FieldRegistry: no operators available for field "${fieldId}"`);
|
|
3766
|
+
return defaultOperator;
|
|
3767
|
+
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Pick the operator to use for a rule after a field swap. If the
|
|
3770
|
+
* previous operator is still in the new field's vocabulary, keep it
|
|
3771
|
+
* (the user's intent likely carries over — e.g. `is_empty` stays
|
|
3772
|
+
* `is_empty` regardless of field type). Otherwise fall back to the
|
|
3773
|
+
* field's default operator.
|
|
3774
|
+
*
|
|
3775
|
+
* Lives on the registry rather than as a free helper because the
|
|
3776
|
+
* answer is entirely a function of registry-owned data (operator
|
|
3777
|
+
* vocabulary per type) — and centralizing it keeps the field-swap
|
|
3778
|
+
* semantics consistent across `FilterBar.handlePickField`,
|
|
3779
|
+
* `AdvancedFilterRulesPopover.handleFieldChange`, and any future
|
|
3780
|
+
* caller.
|
|
3781
|
+
*/
|
|
3782
|
+
reconcileOperator(fieldId, prevOperator) {
|
|
3783
|
+
return this.operatorsFor(fieldId).includes(prevOperator) ? prevOperator : this.defaultOperatorFor(fieldId);
|
|
3784
|
+
}
|
|
3785
|
+
/**
|
|
3786
|
+
* Build a `value → label` lookup for a `choices` / `multi_choices`
|
|
3787
|
+
* field, suitable for translating raw option ids into the human
|
|
3788
|
+
* labels rendered by `FilterPill` chips and value-control headers.
|
|
3789
|
+
* Returns `undefined` when the field has no options so callers can
|
|
3790
|
+
* skip the lookup entirely.
|
|
3791
|
+
*
|
|
3792
|
+
* Memoized per-field so repeated reads from render loops don't
|
|
3793
|
+
* rebuild the same map. Field options are immutable for the lifetime
|
|
3794
|
+
* of a `FieldRegistry`, so the cache never needs invalidation.
|
|
3795
|
+
*/
|
|
3796
|
+
optionLabelsFor(fieldId) {
|
|
3797
|
+
const cached = this.optionLabelCache.get(fieldId);
|
|
3798
|
+
if (cached != null) return cached === EMPTY_OPTION_LABELS ? void 0 : cached;
|
|
3799
|
+
const field = this.byId.get(fieldId);
|
|
3800
|
+
if (!field?.options || field.options.length === 0) {
|
|
3801
|
+
this.optionLabelCache.set(fieldId, EMPTY_OPTION_LABELS);
|
|
3802
|
+
return;
|
|
3803
|
+
}
|
|
3804
|
+
const map = {};
|
|
3805
|
+
for (const opt of field.options) map[opt.value] = opt.label;
|
|
3806
|
+
this.optionLabelCache.set(fieldId, map);
|
|
3807
|
+
return map;
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
const EMPTY_OPTION_LABELS = Object.freeze({});
|
|
3811
|
+
/**
|
|
3812
|
+
* Build a registry from a `field_key_to_info` map. Forgiving by design — any
|
|
3813
|
+
* key whose `type` is missing or unrecognized falls back to `'string'`, and
|
|
3814
|
+
* `source` defaults to `'custom'` (current `customFilters` are all custom).
|
|
3815
|
+
*/
|
|
3816
|
+
function buildFieldRegistry(resource, fieldKeyToInfo, options) {
|
|
3817
|
+
const apiFields = Object.entries(fieldKeyToInfo).map(([id, raw]) => rawFieldInfoToFilterField(id, raw));
|
|
3818
|
+
return new FieldRegistry(resource, [...options?.systemFields ?? [], ...apiFields]);
|
|
3819
|
+
}
|
|
3820
|
+
function rawFieldInfoToFilterField(id, raw) {
|
|
3821
|
+
const type = normalizeFieldType(raw.type);
|
|
3822
|
+
const itemType = raw.item_type ? normalizeFieldType(raw.item_type) : void 0;
|
|
3823
|
+
const source = normalizeFieldSource(raw.source);
|
|
3824
|
+
return {
|
|
3825
|
+
id,
|
|
3826
|
+
label: raw.label,
|
|
3827
|
+
type,
|
|
3828
|
+
source,
|
|
3829
|
+
options: raw.options,
|
|
3830
|
+
itemType,
|
|
3831
|
+
parentRecordType: raw.parent_record_type,
|
|
3832
|
+
parentRecordId: raw.parent_record_id,
|
|
3833
|
+
path: raw.path
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
const KNOWN_TYPES = new Set([
|
|
3837
|
+
"string",
|
|
3838
|
+
"email",
|
|
3839
|
+
"choices",
|
|
3840
|
+
"multi_choices",
|
|
3841
|
+
"boolean",
|
|
3842
|
+
"integer",
|
|
3843
|
+
"float",
|
|
3844
|
+
"date",
|
|
3845
|
+
"array",
|
|
3846
|
+
"tags_conditions",
|
|
3847
|
+
"date_range"
|
|
3848
|
+
]);
|
|
3849
|
+
function normalizeFieldType(raw) {
|
|
3850
|
+
if (raw && KNOWN_TYPES.has(raw)) return raw;
|
|
3851
|
+
return "string";
|
|
3852
|
+
}
|
|
3853
|
+
function normalizeFieldSource(raw) {
|
|
3854
|
+
return raw === "system" ? "system" : "custom";
|
|
3855
|
+
}
|
|
3856
|
+
//#endregion
|
|
3857
|
+
export { DataTableCompactBar as S, useTableControls as _, hasCompleteValue as a, TableControls as b, EMPTY_FILTER_STATE as c, getDefaultOperatorForType as d, getOperatorsForField as f, TableControlsProvider as g, TableControlsContext as h, useFilterStateV2 as i, resolveValueControl as l, isValuelessOperator as m, buildFieldRegistry as n, isFilterStateV2 as o, getOperatorsForType as p, FilterBar_default as r, pruneToRegistry as s, FieldRegistry as t, useFilterBarSize as u, TableViewConfiguration as v, ControlBar as x, TableViewConfigurationDropdown as y };
|
|
3858
|
+
|
|
3859
|
+
//# sourceMappingURL=TableControls-DHMs7o58.mjs.map
|