@leavepulse/ui 0.77.2 → 0.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -0
- package/dist/assets/{LpCommandPalette-DOIZHw-g.css → LpCommandPalette-DDJKICKh.css} +19 -19
- package/dist/assets/{LpDock-Cbh0wQUv.css → LpDock-Dw82tWVD.css} +3 -3
- package/dist/assets/{LpDropzone-B_qRq1-R.css → LpDropzone-DNbh_NFJ.css} +10 -10
- package/dist/assets/{LpImageEditor-XM5bAziW.css → LpImageEditor-Bgu20XLs.css} +6 -6
- package/dist/assets/{LpTable-Bv-EcQs7.css → LpTable-CCk0ZHo7.css} +19 -19
- package/dist/assets/LpTabs-Bh7a-Bdl.css +18 -0
- package/dist/chunks/AppShellContextNav-C6Ho2mof.js +49 -0
- package/dist/chunks/ChartRangeSelector-HO0sMMz6.js +129 -0
- package/dist/chunks/{ContextMenuItems-BXumicbh.js → ContextMenuItems-DmaKSJVv.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-C-9BCGJn.js → DropzoneLinkField-DXIr0eFz.js} +2 -2
- package/dist/chunks/{FieldWrapper-CORPzo9s.js → FieldWrapper-DnuMTrqW.js} +1 -1
- package/dist/chunks/IconCloseButton-COZEsa9u.js +29 -0
- package/dist/chunks/{LayoutCanvas-CNOAUk7D.js → LayoutCanvas-CZKOltfA.js} +1 -1
- package/dist/chunks/{LayoutNode-DF5BMN0J.js → LayoutNode-D2f7v683.js} +45 -45
- package/dist/chunks/{LpActionBar-BP1ld7-S.js → LpActionBar-BGMk5KoG.js} +3 -3
- package/dist/chunks/{LpActivityLine-CJvzrNpj.js → LpActivityLine-Ea-t5Vza.js} +4 -2
- package/dist/chunks/{LpAddressInput-sYMvYzcY.js → LpAddressInput-ZqoZ-N2Y.js} +12 -11
- package/dist/chunks/{LpAlert-n5XPAZfv.js → LpAlert-CjvTZIKF.js} +3 -3
- package/dist/chunks/LpAppShell-BNAVTQsY.js +582 -0
- package/dist/chunks/{LpAutocomplete-DOu7FN5P.js → LpAutocomplete-DkY6CSyD.js} +26 -42
- package/dist/chunks/{LpAvatar-Cg6Az3wz.js → LpAvatar-cQVsNk6F.js} +2 -2
- package/dist/chunks/{LpBadge-wUYLN4U9.js → LpBadge-D5QQdDQ_.js} +2 -2
- package/dist/chunks/{LpBottomNav-CbNQQPuL.js → LpBottomNav-zxLGGbjm.js} +9 -2
- package/dist/chunks/{LpBreadcrumbs-DPdbth-c.js → LpBreadcrumbs-CIIBsygI.js} +4 -3
- package/dist/chunks/{LpButton-CFb44IjC.js → LpButton-DSHioIfD.js} +5 -4
- package/dist/chunks/{LpCalendar-CuoWks-0.js → LpCalendar-D4-LVjfF.js} +1 -1
- package/dist/chunks/{LpCard-r13WRqox.js → LpCard-BPtYXwnT.js} +1 -1
- package/dist/chunks/{LpCheckbox-Cs4SHaHX.js → LpCheckbox-D6RuDW4X.js} +38 -16
- package/dist/chunks/{LpCodeBlock-DRC8VbXW.js → LpCodeBlock-DuzmnIHl.js} +19 -41
- package/dist/chunks/{LpCodeDisplay-CE9TxCA4.js → LpCodeDisplay-CO8fngxa.js} +5 -5
- package/dist/chunks/{LpCollapse-CWS9M_lm.js → LpCollapse-DrAw5DBZ.js} +5 -4
- package/dist/chunks/{LpColorPicker-1y1QJHZR.js → LpColorPicker-BxAHEnR5.js} +11 -12
- package/dist/chunks/{LpCommandScope-CtUhJe8T.js → LpCommandScope-DRUvHiX7.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-DlD45Bi_.js → LpConfirmDialog-6oCIJTDa.js} +3 -3
- package/dist/chunks/{LpConfirmHost-BIWjBLmI.js → LpConfirmHost-DgHvfT6s.js} +1 -1
- package/dist/chunks/{LpContextMenu-rx2tCjwk.js → LpContextMenu--eMGIql6.js} +13 -15
- package/dist/chunks/{LpContextSwitcher-Dwh6dyID.js → LpContextSwitcher-1C1tExdl.js} +2 -2
- package/dist/chunks/{LpCopyButton-xF7wFVoU.js → LpCopyButton-7EOSIuwa.js} +3 -3
- package/dist/chunks/{LpCountdown-DgeY3cLG.js → LpCountdown-DVl7vXI1.js} +4 -3
- package/dist/chunks/{LpDataList-Jxyl9H5w.js → LpDataList-DgBeM01y.js} +13 -4
- package/dist/chunks/{LpDatePicker-CA0QTfBC.js → LpDatePicker-DwSDY0mt.js} +12 -18
- package/dist/chunks/{LpDateRangePicker-u3J51xXk.js → LpDateRangePicker-CNQXdTVv.js} +10 -16
- package/dist/chunks/{LpDisclosure-Cpa7Hc5T.js → LpDisclosure-DG7JCo-d.js} +2 -2
- package/dist/chunks/{LpDrawer-CrG8t_KP.js → LpDrawer-Bt9UUqt_.js} +61 -61
- package/dist/chunks/{LpDropdownMenu-Dl7Z4ZhS.js → LpDropdownMenu-CLzxIOgM.js} +9 -17
- package/dist/chunks/{LpEditable-Cy6GLbFx.js → LpEditable-DVT0Rh8F.js} +9 -6
- package/dist/chunks/{LpEmptyState-EQHiRYhG.js → LpEmptyState-DurPF001.js} +1 -1
- package/dist/chunks/{LpFieldNotice-CzQ2_5rQ.js → LpFieldNotice-DJ10cYIo.js} +1 -1
- package/dist/chunks/LpFileTree-BSx1kBoV.js +735 -0
- package/dist/chunks/{LpFileTreeNode-cT6BIWvz.js → LpFileTreeNode-4dVmFHb3.js} +116 -34
- package/dist/chunks/{LpFormField-Ch69Jd-X.js → LpFormField-BKYn9dyP.js} +3 -3
- package/dist/chunks/{LpFrame-BVQCQLDI.js → LpFrame-Bv5Fphte.js} +25 -11
- package/dist/chunks/{LpHintExample-9Py0wc-L.js → LpHintExample-YzCXiSUp.js} +1 -1
- package/dist/chunks/{LpHintLayer-CgObdUsM.js → LpHintLayer-BeKRsRon.js} +2 -2
- package/dist/chunks/{LpHints-Bz-BlaSl.js → LpHints-qf3xYsza.js} +3 -3
- package/dist/chunks/{LpHoverCard-spGxyw7T.js → LpHoverCard-DanH3Nz1.js} +23 -5
- package/dist/chunks/{LpIcon--5WSb3jJ.js → LpIcon-C6JpQ5td.js} +6 -3
- package/dist/chunks/{LpIdenticon-BzdC_qz9.js → LpIdenticon-Dbbr4fvt.js} +2 -1
- package/dist/chunks/{LpInfraNode-46XOCkfq.js → LpInfraNode-BAU40Km2.js} +2 -2
- package/dist/chunks/{LpInput-Tw-yxT4I.js → LpInput-DL6HEJdi.js} +16 -28
- package/dist/chunks/LpInspector-CuoMAhiw.js +209 -0
- package/dist/chunks/{LpLightbox-B95yu22B.js → LpLightbox-xvpQ64MM.js} +57 -32
- package/dist/chunks/{LpLoader-DVwkOmt9.js → LpLoader-Bf1yZwaU.js} +1 -1
- package/dist/chunks/LpLogInspector-6cNnXqp4.js +119 -0
- package/dist/chunks/{LpLogViewer-ngqkns4m.js → LpLogViewer-Dvb1E9_G.js} +234 -53
- package/dist/chunks/{LpMapPicker-DRGgh89B.js → LpMapPicker-D5_YLLBC.js} +4 -3
- package/dist/chunks/{LpMasonry--k0571qG.js → LpMasonry-DPYekFxs.js} +8 -5
- package/dist/chunks/{LpMediaGallery-Bkram-MO.js → LpMediaGallery-Df-tiWeq.js} +3 -3
- package/dist/chunks/{LpModal-CoGhLTyH.js → LpModal-DrtQ_1A2.js} +11 -13
- package/dist/chunks/{LpModalNav-BGvs2y5Q.js → LpModalNav-DkmGCnZE.js} +11 -37
- package/dist/chunks/{LpNotificationBell-B0034Lk-.js → LpNotificationBell-D_wklO1m.js} +9 -9
- package/dist/chunks/{LpNumberField-e0IBkWU1.js → LpNumberField-CNHsmSDZ.js} +7 -16
- package/dist/chunks/{LpOtpInput-CeBPsM7D.js → LpOtpInput-BRpLBbOD.js} +2 -2
- package/dist/chunks/{LpPagination-BIIiYHXR.js → LpPagination-snQ020xQ.js} +6 -4
- package/dist/chunks/{LpPaletteTheme-DiLYKQiZ.js → LpPaletteTheme-dVtxHx-F.js} +4 -4
- package/dist/chunks/{LpPasswordInput-BkHKQDhM.js → LpPasswordInput-B-zD6tUS.js} +2 -2
- package/dist/chunks/{LpPhoneInput-CETEp5W7.js → LpPhoneInput-BSL7NX4z.js} +15 -26
- package/dist/chunks/{LpPie-DrZepi0p.js → LpPie-9t_guO49.js} +22 -21
- package/dist/chunks/{LpPipeline-B3aOVOkV.js → LpPipeline-rjXvEDRJ.js} +16 -7
- package/dist/chunks/{LpPopover-DmZ3wUtP.js → LpPopover-BQSuxtai.js} +10 -1
- package/dist/chunks/{LpProgress-Yr6ONG5t.js → LpProgress-D_MSjowb.js} +2 -1
- package/dist/chunks/{LpQuota-Dk2MyI2Z.js → LpQuota-CgWPgdVt.js} +1 -1
- package/dist/chunks/{LpRadioGroup-ggaPL92T.js → LpRadioGroup-BrazjLY_.js} +4 -4
- package/dist/chunks/{LpRangeCalendar-CYYUYqJ7.js → LpRangeCalendar-BrqlGZzE.js} +1 -1
- package/dist/chunks/{LpRangeSlider-CM-0ZITZ.js → LpRangeSlider-fz61tzc-.js} +5 -9
- package/dist/chunks/{LpRelativeTime-DwgCb5-8.js → LpRelativeTime-BovUk8MP.js} +1 -1
- package/dist/chunks/{LpRepeater-D7ZmnIYd.js → LpRepeater-BkJHSPI1.js} +3 -3
- package/dist/chunks/{LpRevealOnScroll-DcQjexLy.js → LpRevealOnScroll-CPwyiQAr.js} +1 -1
- package/dist/chunks/{LpScramble-Bn9OoOtC.js → LpScramble-DsE77iVp.js} +1 -1
- package/dist/chunks/{LpScrollArea-MyEnVB_5.js → LpScrollArea-ay4Xo_hg.js} +22 -10
- package/dist/chunks/{LpSecretField-BbmqM9ye.js → LpSecretField-DBvU6iAw.js} +10 -9
- package/dist/chunks/{LpSection-DFK_uoJE.js → LpSection-BfteipxU.js} +1 -1
- package/dist/chunks/{LpSegmented-CeACsMMA.js → LpSegmented-DbEJhbv-.js} +8 -10
- package/dist/chunks/{LpSelect-Cxq4yBGd.js → LpSelect-Y-iuSymW.js} +47 -30
- package/dist/chunks/{LpShaderBackdrop-Bote0iuy.js → LpShaderBackdrop-wQygYb5h.js} +13 -10
- package/dist/chunks/{LpShift-CCq7dofl.js → LpShift-DAgS_d2x.js} +18 -15
- package/dist/chunks/{LpSidebar-DzE1783C.js → LpSidebar-kSkpPh3-.js} +74 -87
- package/dist/chunks/{LpSidebarNav-DE3PhXVP.js → LpSidebarNav-C2mvqrY2.js} +8 -6
- package/dist/chunks/{LpSidebarRow-BRYhNDSo.js → LpSidebarRow-eRP4Q31q.js} +6 -5
- package/dist/chunks/{LpSlider-DxYoCJJO.js → LpSlider-A4ixovgS.js} +5 -9
- package/dist/chunks/{LpSpotlight-4F3NeUMY.js → LpSpotlight-DwxkhFgR.js} +2 -1
- package/dist/chunks/{LpStat-f2LfDCJ4.js → LpStat-pMNZgFcq.js} +2 -2
- package/dist/chunks/{LpStatus-DglxttQ7.js → LpStatus-DGJFwk5F.js} +2 -2
- package/dist/chunks/{LpStepMark-DpzKVyh8.js → LpStepMark-Dw2MRTrt.js} +2 -2
- package/dist/chunks/{LpStepper-DpsocBNJ.js → LpStepper-DNnh7lCu.js} +1 -1
- package/dist/chunks/{LpSwap-C96c0LWh.js → LpSwap-M7nFeW3C.js} +1 -1
- package/dist/chunks/{LpSwitch-BnXd1llc.js → LpSwitch-DnC2-0aV.js} +3 -3
- package/dist/chunks/{LpTabFace-DPCwKkH2.js → LpTabFace-DQoV8mQG.js} +2 -2
- package/dist/chunks/{LpTableFilter-CiVBpaq0.js → LpTableFilter-DgARTsA1.js} +4 -4
- package/dist/chunks/{LpTableOfContents-BN5zvpnx.js → LpTableOfContents-CKkWEK6R.js} +14 -9
- package/dist/chunks/{LpTagsInput-C-mDG648.js → LpTagsInput-Dt0mteSj.js} +19 -27
- package/dist/chunks/{LpTextarea-TE-yZv2U.js → LpTextarea-Co0b9PVx.js} +8 -16
- package/dist/chunks/LpThemeProvider-XK6ZeWAE.js +81 -0
- package/dist/chunks/{LpThemeStudio-BpORDvH6.js → LpThemeStudio-DzYfE-5k.js} +12 -195
- package/dist/chunks/{LpThemeSwitcher-BnEn_mvf.js → LpThemeSwitcher-CFLkdbBL.js} +8 -6
- package/dist/chunks/{LpTimeField-BawSVsmt.js → LpTimeField-DeOcKWJq.js} +6 -9
- package/dist/chunks/{LpToaster-IwTXjbbU.js → LpToaster-QFj0P-5m.js} +10 -8
- package/dist/chunks/{LpTooltip-BcBvIx4z.js → LpTooltip-BJf3lZMq.js} +51 -19
- package/dist/chunks/LpTopologyCanvas-BOHHHZ_6.js +673 -0
- package/dist/chunks/{LpTour-BZq-mUMv.js → LpTour-ClKYhx4h.js} +7 -5
- package/dist/chunks/{LpTruncate-CflAIwiJ.js → LpTruncate-C6u3gjc6.js} +6 -5
- package/dist/chunks/{LpTypewriter-BCL_a6wE.js → LpTypewriter-CZIzpdbe.js} +2 -1
- package/dist/chunks/{LpUptimeBar-DqNvvMBK.js → LpUptimeBar-BnYmR2rj.js} +3 -3
- package/dist/chunks/{LpVideoBackdrop-DS1wGv-X.js → LpVideoBackdrop-Ct4St4ZL.js} +10 -13
- package/dist/chunks/LpWindow-B8YSRVTH.js +297 -0
- package/dist/chunks/LpWorkspaceSwitcher-D0e9upAy.js +139 -0
- package/dist/chunks/{MenuTitle-BmCNNTKH.js → MenuTitle-Bh5dAdV1.js} +1 -1
- package/dist/chunks/{OptionContent-22UT8zLn.js → OptionContent-CGb8QlHA.js} +1 -1
- package/dist/chunks/{PickerField-Bfh8id3d.js → PickerField-BM1dyafh.js} +4 -4
- package/dist/chunks/{RevealToggle-BEit1n6Z.js → RevealToggle-CCPDE8Mv.js} +3 -3
- package/dist/chunks/SliderRail-DhPREa_R.js +18 -0
- package/dist/chunks/{TourCard-DNclH3u_.js → TourCard-DAzTYfav.js} +4 -4
- package/dist/chunks/{icons-DMITlfw5.js → icons-CYfcpSDS.js} +26 -10
- package/dist/component-names.d.ts +1 -1
- package/dist/components/AppShellContextNav.vue.d.ts +13 -0
- package/dist/components/AppShellContextNav.vue.js +4 -0
- package/dist/components/ChartRangeSelector.vue.d.ts +21 -0
- package/dist/components/ChartRangeSelector.vue.js +4 -0
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.d.ts +2 -2
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/IconCloseButton.vue.d.ts +7 -0
- package/dist/components/IconCloseButton.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpActivityLine.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +110 -2
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.js +1 -1
- package/dist/components/LpBottomNav.vue.d.ts +5 -0
- package/dist/components/LpBottomNav.vue.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +14 -2
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +74 -2
- package/dist/components/LpChart.vue.js +326 -85
- package/dist/components/LpCheckbox.vue.d.ts +6 -2
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpCollapse.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +8 -0
- package/dist/components/LpCommandPalette.vue.js +107 -56
- package/dist/components/LpCommandScope.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpContextSwitcher.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/LpCountdown.vue.js +1 -1
- package/dist/components/LpDataList.vue.d.ts +1 -1
- package/dist/components/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +3 -15
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +3 -13
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.js +21 -18
- package/dist/components/LpDrawer.vue.d.ts +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +14 -11
- package/dist/components/LpEditable.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.js +1 -1
- package/dist/components/LpFileTree.vue.d.ts +96 -6
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.d.ts +48 -0
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpFrame.vue.js +1 -1
- package/dist/components/LpGauge.vue.d.ts +1 -1
- package/dist/components/LpHintExample.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpHoverCard.vue.js +1 -1
- package/dist/components/LpIcon.vue.d.ts +2 -0
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpIdenticon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +7 -6
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.d.ts +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpInspector.vue.d.ts +87 -0
- package/dist/components/LpInspector.vue.js +4 -0
- package/dist/components/LpLightbox.vue.d.ts +8 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLoader.vue.d.ts +1 -1
- package/dist/components/LpLoader.vue.js +1 -1
- package/dist/components/LpLogInspector.vue.d.ts +18 -0
- package/dist/components/LpLogInspector.vue.js +4 -0
- package/dist/components/LpLogViewer.vue.d.ts +93 -0
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMasonry.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +4 -2
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +5 -5
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +2 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +1 -1
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPaletteTheme.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +2 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPipeline.vue.d.ts +4 -0
- package/dist/components/LpPipeline.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +7 -3
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.d.ts +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpQuota.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpRevealOnScroll.vue.js +1 -1
- package/dist/components/LpScramble.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShaderBackdrop.vue.js +1 -1
- package/dist/components/LpShift.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.d.ts +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -1
- package/dist/components/LpSpotlight.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStatus.vue.js +1 -1
- package/dist/components/LpStepMark.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwap.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTabFace.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +132 -0
- package/dist/components/LpTable.vue.js +547 -135
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.js +6 -5
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +2 -0
- package/dist/components/LpThemeProvider.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.d.ts +131 -5
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpTypewriter.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/LpVideoBackdrop.vue.js +1 -1
- package/dist/components/LpWindow.vue.d.ts +99 -0
- package/dist/components/LpWindow.vue.js +4 -0
- package/dist/components/LpWorkspaceSwitcher.vue.d.ts +18 -0
- package/dist/components/LpWorkspaceSwitcher.vue.js +4 -0
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.js +1 -1
- package/dist/components/PickerField.vue.d.ts +2 -2
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/SliderRail.vue.d.ts +3 -0
- package/dist/components/SliderRail.vue.js +4 -0
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/address.js +2 -1
- package/dist/components/appShell.d.ts +36 -0
- package/dist/components/appShell.js +30 -0
- package/dist/components/calendar.d.ts +12 -3
- package/dist/components/calendar.js +2 -5
- package/dist/components/chart.d.ts +21 -0
- package/dist/components/chart.js +4 -3
- package/dist/components/chartWindow.d.ts +57 -0
- package/dist/components/chartWindow.js +136 -0
- package/dist/components/color.js +1 -1
- package/dist/components/commandPalette.d.ts +16 -2
- package/dist/components/commandPalette.js +39 -4
- package/dist/components/controlSize.d.ts +5 -0
- package/dist/components/controlSize.js +8 -0
- package/dist/components/dropdown.d.ts +5 -0
- package/dist/components/dropdown.js +2 -0
- package/dist/components/exportData.d.ts +6 -1
- package/dist/components/exportData.js +25 -18
- package/dist/components/fileTree.d.ts +67 -0
- package/dist/components/fileTree.js +141 -1
- package/dist/components/identicon.js +3 -2
- package/dist/components/logTools.d.ts +93 -0
- package/dist/components/logTools.js +210 -0
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/meter.js +2 -1
- package/dist/components/pipeline.d.ts +2 -0
- package/dist/components/pipeline.js +5 -0
- package/dist/components/relativeTime.js +2 -1
- package/dist/components/reveal.js +2 -1
- package/dist/components/select.d.ts +5 -0
- package/dist/components/select.js +10 -0
- package/dist/components/sidebar.d.ts +1 -0
- package/dist/components/tableFeatures.d.ts +58 -0
- package/dist/components/tableFeatures.js +87 -0
- package/dist/components/topologyGraph.d.ts +164 -0
- package/dist/components/topologyGraph.js +416 -0
- package/dist/components/tour.js +5 -3
- package/dist/components/videoBackdrop.js +7 -6
- package/dist/composables/clamp.d.ts +2 -0
- package/dist/composables/clamp.js +4 -0
- package/dist/composables/devWarn.d.ts +3 -0
- package/dist/composables/devWarn.js +14 -0
- package/dist/composables/easing.d.ts +13 -4
- package/dist/composables/easing.js +16 -10
- package/dist/composables/overlayHistory.js +14 -1
- package/dist/composables/pointerDrag.d.ts +15 -0
- package/dist/composables/pointerDrag.js +40 -0
- package/dist/composables/propCheck.d.ts +10 -0
- package/dist/composables/propCheck.js +78 -0
- package/dist/composables/realm.d.ts +6 -0
- package/dist/composables/realm.js +23 -0
- package/dist/composables/scroll.js +4 -2
- package/dist/composables/scrollMemory.d.ts +12 -1
- package/dist/composables/scrollMemory.js +36 -12
- package/dist/composables/spatialNav.js +2 -1
- package/dist/composables/textEntry.d.ts +17 -0
- package/dist/composables/textEntry.js +19 -0
- package/dist/composables/useAsyncTask.d.ts +6 -0
- package/dist/composables/useChartSync.d.ts +5 -0
- package/dist/composables/useChartSync.js +33 -0
- package/dist/composables/useCloseGuard.d.ts +3 -1
- package/dist/composables/useCloseGuard.js +6 -3
- package/dist/composables/useCommands.d.ts +50 -0
- package/dist/composables/useCommands.js +66 -0
- package/dist/composables/useField.d.ts +37 -0
- package/dist/composables/useField.js +27 -2
- package/dist/composables/useFitScale.js +4 -1
- package/dist/composables/useFormDraft.d.ts +13 -2
- package/dist/composables/useFormDraft.js +17 -9
- package/dist/composables/useHintSystem.js +2 -2
- package/dist/composables/useHotkeys.js +11 -32
- package/dist/composables/useInView.js +2 -1
- package/dist/composables/useInputFilter.js +7 -5
- package/dist/composables/useInputMask.js +5 -3
- package/dist/composables/useLinkedView.d.ts +97 -0
- package/dist/composables/useLinkedView.js +139 -0
- package/dist/composables/useOptimisticToggle.d.ts +3 -1
- package/dist/composables/useOptimisticToggle.js +2 -1
- package/dist/composables/useOverlayHistory.d.ts +7 -0
- package/dist/composables/useOverlayHistory.js +16 -2
- package/dist/composables/usePaintSelect.js +18 -14
- package/dist/composables/usePaletteCommands.d.ts +7 -0
- package/dist/composables/usePaletteCommands.js +12 -1
- package/dist/composables/usePersisted.d.ts +10 -0
- package/dist/composables/usePersisted.js +1 -1
- package/dist/composables/usePillTransition.d.ts +2 -2
- package/dist/composables/usePillTransition.js +5 -5
- package/dist/composables/useResizeObserver.js +2 -1
- package/dist/composables/useReveal.js +2 -1
- package/dist/composables/useRovingFocus.d.ts +2 -2
- package/dist/composables/useRovingFocus.js +9 -11
- package/dist/composables/useScramble.js +7 -1
- package/dist/composables/useShift.js +8 -6
- package/dist/composables/useTilt.js +2 -1
- package/dist/composables/useTween.js +9 -5
- package/dist/composables/useView.d.ts +1 -0
- package/dist/composables/useView.js +14 -2
- package/dist/composables/useZoomPan.js +2 -4
- package/dist/composables/windowing.d.ts +40 -0
- package/dist/composables/windowing.js +118 -0
- package/dist/icons-all.js +1 -1
- package/dist/icons.d.ts +4 -0
- package/dist/index.d.ts +30 -5
- package/dist/index.js +310 -264
- package/dist/layout/tree.js +19 -15
- package/dist/layout/useLayout.d.ts +2 -0
- package/dist/layout/useLayout.js +5 -1
- package/dist/layout/useWorkspaces.d.ts +52 -0
- package/dist/layout/useWorkspaces.js +140 -0
- package/dist/{chunks/backdrop-Dkf2vM81.js → theme/backdrop.js} +5 -5
- package/dist/theme/config.js +25 -0
- package/dist/theme/define.js +61 -0
- package/dist/{chunks/palette-Co8tSASo.js → theme/palette.js} +2 -2
- package/dist/theme/presets.js +239 -0
- package/dist/theme/studio.js +134 -0
- package/dist/theme/tokens.js +63 -0
- package/dist/theme/useTheme.d.ts +3 -2
- package/dist/theme/useTheme.js +325 -0
- package/package.json +8 -1
- package/src/component-names.ts +5 -1
- package/src/components/AppShellContextNav.vue +44 -0
- package/src/components/ChartRangeSelector.vue +120 -0
- package/src/components/IconCloseButton.vue +21 -0
- package/src/components/LayoutCanvas.vue +3 -0
- package/src/components/LayoutNode.vue +37 -38
- package/src/components/LpActivityLine.vue +4 -2
- package/src/components/LpAddressInput.vue +3 -3
- package/src/components/LpAppShell.vue +362 -24
- package/src/components/LpAutocomplete.vue +18 -30
- package/src/components/LpBottomNav.vue +9 -1
- package/src/components/LpBreadcrumbs.vue +2 -1
- package/src/components/LpButton.vue +11 -4
- package/src/components/LpChart.vue +336 -37
- package/src/components/LpCheckbox.vue +43 -9
- package/src/components/LpCodeBlock.vue +17 -23
- package/src/components/LpCollapse.vue +5 -4
- package/src/components/LpColorPicker.vue +8 -9
- package/src/components/LpCommandPalette.vue +133 -50
- package/src/components/LpContextMenu.vue +15 -13
- package/src/components/LpCountdown.vue +2 -1
- package/src/components/LpDataList.vue +13 -1
- package/src/components/LpDatePicker.vue +5 -17
- package/src/components/LpDateRangePicker.vue +5 -16
- package/src/components/LpDock.vue +25 -16
- package/src/components/LpDrawer.vue +70 -61
- package/src/components/LpDropdownMenu.vue +6 -10
- package/src/components/LpDropzone.vue +12 -7
- package/src/components/LpEditable.vue +6 -2
- package/src/components/LpFileTree.vue +507 -66
- package/src/components/LpFileTreeNode.vue +106 -5
- package/src/components/LpFormField.vue +1 -1
- package/src/components/LpFrame.vue +33 -13
- package/src/components/LpHoverCard.vue +30 -5
- package/src/components/LpIcon.vue +6 -2
- package/src/components/LpIdenticon.vue +2 -1
- package/src/components/LpImageEditor.vue +5 -2
- package/src/components/LpInput.vue +15 -23
- package/src/components/LpInspector.vue +197 -0
- package/src/components/LpLightbox.vue +74 -29
- package/src/components/LpLogInspector.vue +85 -0
- package/src/components/LpLogViewer.vue +239 -29
- package/src/components/LpMapPicker.vue +2 -1
- package/src/components/LpMasonry.vue +8 -5
- package/src/components/LpModal.vue +12 -12
- package/src/components/LpModalNav.vue +2 -21
- package/src/components/LpNumberField.vue +6 -15
- package/src/components/LpOtpInput.vue +2 -2
- package/src/components/LpPagination.vue +5 -3
- package/src/components/LpPhoneInput.vue +8 -19
- package/src/components/LpPie.vue +25 -23
- package/src/components/LpPipeline.vue +14 -1
- package/src/components/LpPopover.vue +23 -2
- package/src/components/LpProgress.vue +2 -1
- package/src/components/LpRadioGroup.vue +2 -2
- package/src/components/LpRangeSlider.vue +5 -6
- package/src/components/LpRevealOnScroll.vue +1 -1
- package/src/components/LpScrollArea.vue +28 -11
- package/src/components/LpSecretField.vue +2 -1
- package/src/components/LpSegmented.vue +6 -8
- package/src/components/LpSelect.vue +54 -24
- package/src/components/LpShaderBackdrop.vue +12 -9
- package/src/components/LpShift.vue +18 -15
- package/src/components/LpSidebar.vue +77 -74
- package/src/components/LpSidebarNav.vue +3 -2
- package/src/components/LpSidebarRow.vue +2 -1
- package/src/components/LpSlider.vue +5 -6
- package/src/components/LpSpotlight.vue +2 -1
- package/src/components/LpSwitch.vue +5 -3
- package/src/components/LpTable.vue +609 -93
- package/src/components/LpTableFilter.vue +1 -1
- package/src/components/LpTableOfContents.vue +16 -9
- package/src/components/LpTabs.vue +2 -1
- package/src/components/LpTagsInput.vue +12 -19
- package/src/components/LpTextarea.vue +5 -11
- package/src/components/LpThemeProvider.vue +29 -13
- package/src/components/LpTimeField.vue +4 -7
- package/src/components/LpToaster.vue +7 -6
- package/src/components/LpTooltip.vue +62 -18
- package/src/components/LpTopologyCanvas.vue +567 -42
- package/src/components/LpTour.vue +7 -5
- package/src/components/LpTruncate.vue +4 -3
- package/src/components/LpTypewriter.vue +2 -1
- package/src/components/LpVideoBackdrop.vue +11 -13
- package/src/components/LpWindow.vue +373 -0
- package/src/components/LpWorkspaceSwitcher.vue +116 -0
- package/src/components/PickerField.vue +2 -2
- package/src/components/SliderRail.vue +10 -0
- package/src/components/address.ts +3 -1
- package/src/components/appShell.ts +74 -0
- package/src/components/calendar.ts +12 -5
- package/src/components/chart.ts +27 -3
- package/src/components/chartWindow.ts +198 -0
- package/src/components/color.ts +2 -1
- package/src/components/commandPalette.ts +66 -5
- package/src/components/controlSize.ts +6 -0
- package/src/components/dropdown.ts +3 -0
- package/src/components/exportData.ts +33 -18
- package/src/components/fileTree.ts +215 -0
- package/src/components/identicon.ts +4 -2
- package/src/components/logTools.ts +298 -0
- package/src/components/meter.ts +2 -1
- package/src/components/pipeline.ts +6 -0
- package/src/components/relativeTime.ts +2 -1
- package/src/components/reveal.ts +3 -1
- package/src/components/select.ts +14 -0
- package/src/components/sidebar.ts +1 -0
- package/src/components/tableFeatures.ts +149 -0
- package/src/components/topologyGraph.ts +655 -0
- package/src/components/tour.ts +5 -3
- package/src/components/videoBackdrop.ts +7 -6
- package/src/composables/clamp.ts +2 -0
- package/src/composables/devWarn.ts +19 -0
- package/src/composables/easing.ts +37 -12
- package/src/composables/overlayHistory.ts +24 -0
- package/src/composables/pointerDrag.ts +54 -0
- package/src/composables/propCheck.ts +90 -0
- package/src/composables/realm.ts +25 -0
- package/src/composables/scroll.ts +4 -2
- package/src/composables/scrollMemory.ts +56 -14
- package/src/composables/spatialNav.ts +2 -1
- package/src/composables/textEntry.ts +33 -0
- package/src/composables/useAsyncTask.ts +6 -0
- package/src/composables/useChartSync.ts +52 -0
- package/src/composables/useCloseGuard.ts +22 -10
- package/src/composables/useCommands.ts +123 -0
- package/src/composables/useField.ts +41 -2
- package/src/composables/useFitScale.ts +4 -1
- package/src/composables/useFormDraft.ts +38 -14
- package/src/composables/useHintSystem.ts +2 -2
- package/src/composables/useHotkeys.ts +19 -48
- package/src/composables/useInView.ts +2 -1
- package/src/composables/useInputFilter.ts +8 -5
- package/src/composables/useInputMask.ts +8 -3
- package/src/composables/useLinkedView.ts +222 -0
- package/src/composables/useOptimisticToggle.ts +7 -1
- package/src/composables/useOverlayHistory.ts +20 -0
- package/src/composables/usePaintSelect.ts +20 -14
- package/src/composables/usePaletteCommands.ts +13 -0
- package/src/composables/usePersisted.ts +10 -0
- package/src/composables/usePillTransition.ts +9 -6
- package/src/composables/useResizeObserver.ts +2 -1
- package/src/composables/useReveal.ts +2 -1
- package/src/composables/useRovingFocus.ts +11 -14
- package/src/composables/useScramble.ts +12 -1
- package/src/composables/useShift.ts +8 -6
- package/src/composables/useTilt.ts +2 -1
- package/src/composables/useTween.ts +10 -5
- package/src/composables/useView.ts +17 -3
- package/src/composables/useZoomPan.ts +2 -5
- package/src/composables/windowing.ts +190 -0
- package/src/icons.ts +27 -6
- package/src/index.ts +41 -4
- package/src/kit-icons.ts +1 -1
- package/src/layout/tree.ts +31 -16
- package/src/layout/useLayout.ts +6 -0
- package/src/layout/useWorkspaces.ts +234 -0
- package/src/nuxt.ts +9 -1
- package/src/runtime/propCheck.plugin.ts +10 -0
- package/src/theme/studio.ts +7 -5
- package/src/theme/useTheme.ts +89 -15
- package/src/tokens/tokens.css +8 -1
- package/dist/assets/LpTabs-CJ0rYQbx.css +0 -18
- package/dist/chunks/LpAppShell-Dgejipzz.js +0 -319
- package/dist/chunks/LpFileTree-CooHinsC.js +0 -425
- package/dist/chunks/LpThemeProvider-8MG6yqU9.js +0 -640
- package/dist/chunks/LpTopologyCanvas-B1gIbS0n.js +0 -298
|
@@ -11,6 +11,7 @@ import { menuPaletteCommands } from "./commandPalette"
|
|
|
11
11
|
import LpDataList from "./LpDataList.vue"
|
|
12
12
|
import LpCheckbox from "./LpCheckbox.vue"
|
|
13
13
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
14
|
+
import LpEditable from "./LpEditable.vue"
|
|
14
15
|
import LpEmptyState from "./LpEmptyState.vue"
|
|
15
16
|
import LpBadge from "./LpBadge.vue"
|
|
16
17
|
import LpIcon from "./LpIcon.vue"
|
|
@@ -29,7 +30,21 @@ import { usePaintSelect } from "../composables/usePaintSelect"
|
|
|
29
30
|
import { useRovingFocus } from "../composables/useRovingFocus"
|
|
30
31
|
import { useLabels } from "../composables/useLpConfig"
|
|
31
32
|
import { useResizeObserver } from "../composables/useResizeObserver"
|
|
32
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
clampColumnWidth,
|
|
35
|
+
groupRows,
|
|
36
|
+
nextSorts,
|
|
37
|
+
orderPinned,
|
|
38
|
+
pinOffsets,
|
|
39
|
+
presetHidden,
|
|
40
|
+
rowWindow,
|
|
41
|
+
sortByKeys,
|
|
42
|
+
type ColumnPin,
|
|
43
|
+
type ColumnPreset,
|
|
44
|
+
} from "./tableFeatures"
|
|
45
|
+
import { devWarn } from "../composables/devWarn"
|
|
46
|
+
import { startPointerDrag } from "../composables/pointerDrag"
|
|
47
|
+
import { isHTMLElement, nodeWindow } from "../composables/useView"
|
|
33
48
|
import LpButton from "./LpButton.vue"
|
|
34
49
|
import { downloadFile, fileStem, toCsv, type CsvOptions } from "./exportData"
|
|
35
50
|
import {
|
|
@@ -100,6 +115,41 @@ export interface TableColumn<Row> {
|
|
|
100
115
|
* user's own show/hide applies as usual.
|
|
101
116
|
*/
|
|
102
117
|
hideBelow?: number
|
|
118
|
+
/** Stick to this edge while the table scrolls sideways. Pinned columns move to that edge. */
|
|
119
|
+
pin?: ColumnPin
|
|
120
|
+
/** Bounds in px for `resizableColumns`. */
|
|
121
|
+
minWidth?: number
|
|
122
|
+
maxWidth?: number
|
|
123
|
+
/** Set false to keep this column out of `resizableColumns`. */
|
|
124
|
+
resizable?: boolean
|
|
125
|
+
/**
|
|
126
|
+
* The cell edits in place (LpEditable), and a save emits `cellEdit`. A cell
|
|
127
|
+
* slot for the column wins: it draws the cell itself. Values are edited as
|
|
128
|
+
* text; `parseEdit` turns the text back into the value the row holds.
|
|
129
|
+
*/
|
|
130
|
+
editable?: boolean | ((row: Row) => boolean)
|
|
131
|
+
/** A message rejects the text and keeps the cell open. */
|
|
132
|
+
validateEdit?: (value: string, row: Row) => string | true | void
|
|
133
|
+
parseEdit?: (value: string, row: Row) => unknown
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A saved inline edit. The table holds no rows of its own to change: apply it to yours. */
|
|
137
|
+
export interface TableCellEdit<Row> {
|
|
138
|
+
row: Row
|
|
139
|
+
key: string
|
|
140
|
+
value: unknown
|
|
141
|
+
previous: unknown
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* "Everything this query matches, except these": what a bulk action sends to
|
|
146
|
+
* the server, in place of ids the client never loaded. See `selectionToken`.
|
|
147
|
+
*/
|
|
148
|
+
export interface TableSelectionToken {
|
|
149
|
+
sorts: SortState[]
|
|
150
|
+
hidden: Record<string, unknown[]>
|
|
151
|
+
excluded: (string | number)[]
|
|
152
|
+
total: number
|
|
103
153
|
}
|
|
104
154
|
|
|
105
155
|
export interface TableLabels {
|
|
@@ -137,6 +187,13 @@ export interface TableLabels {
|
|
|
137
187
|
pageSelected: string
|
|
138
188
|
selectAllMatching: string
|
|
139
189
|
allMatchingSelected: string
|
|
190
|
+
columnPresets: string
|
|
191
|
+
resizeColumn: string
|
|
192
|
+
expandRow: string
|
|
193
|
+
collapseRow: string
|
|
194
|
+
groupCount: string
|
|
195
|
+
expandGroup: string
|
|
196
|
+
collapseGroup: string
|
|
140
197
|
}
|
|
141
198
|
|
|
142
199
|
export interface TableExportOptions extends CsvOptions {
|
|
@@ -165,6 +222,8 @@ export interface TableQuery {
|
|
|
165
222
|
page: number
|
|
166
223
|
pageSize: number
|
|
167
224
|
sort: SortState | null
|
|
225
|
+
/** Every sort key in order, with `multiSort`. `sort` is the first of them. */
|
|
226
|
+
sorts?: SortState[]
|
|
168
227
|
hidden: Record<string, unknown[]>
|
|
169
228
|
}
|
|
170
229
|
|
|
@@ -288,8 +347,70 @@ const props = withDefaults(
|
|
|
288
347
|
* With `load`: the user chose "select all N" beyond the loaded page
|
|
289
348
|
* (v-model:allMatching). The keys in `selected` are then only the loaded
|
|
290
349
|
* ones; acting on everything that matches is the consumer's to do.
|
|
350
|
+
*
|
|
351
|
+
* It holds only while every loaded row is ticked: a new page, sort or
|
|
352
|
+
* filter brings unticked rows and clears it, so a bulk action never runs
|
|
353
|
+
* on a set the user can no longer see.
|
|
291
354
|
*/
|
|
292
355
|
allMatching?: boolean
|
|
356
|
+
/**
|
|
357
|
+
* Keep "select all N" across pages and sorts, and let the user untick rows
|
|
358
|
+
* out of it: the selection is then the query and its exceptions, emitted
|
|
359
|
+
* as `update:selectionToken` for a bulk action to send, with no need for
|
|
360
|
+
* ids the client never loaded. `selected` stays the loaded keys. A change
|
|
361
|
+
* of filter means a different set of rows, so it clears the selection.
|
|
362
|
+
*/
|
|
363
|
+
selectionToken?: boolean
|
|
364
|
+
/** Sort by several columns: Shift-click adds a column (v-model:sorts). */
|
|
365
|
+
multiSort?: boolean
|
|
366
|
+
/** Sort keys in order (v-model:sorts), with `multiSort`. Left unset the table keeps them. */
|
|
367
|
+
sorts?: SortState[]
|
|
368
|
+
/**
|
|
369
|
+
* Drag a column's edge, or focus it and use the arrows, to set its width;
|
|
370
|
+
* double-click clears it. Sets a fixed table layout once a width is set.
|
|
371
|
+
*/
|
|
372
|
+
resizableColumns?: boolean
|
|
373
|
+
/** Widths in px by column key (v-model:columnWidths). */
|
|
374
|
+
columnWidths?: Record<string, number>
|
|
375
|
+
/** Named sets of columns, offered in the table's menu. */
|
|
376
|
+
columnPresets?: readonly ColumnPreset[]
|
|
377
|
+
/** The chosen preset's id (v-model:columnPreset). */
|
|
378
|
+
columnPreset?: string | null
|
|
379
|
+
/**
|
|
380
|
+
* Group rows under a heading: a column key or a function. Client side, so
|
|
381
|
+
* with `load` it groups the loaded page. Groups follow the row order, so
|
|
382
|
+
* sort by the same column to keep them put.
|
|
383
|
+
*/
|
|
384
|
+
groupBy?: string | ((row: T) => unknown)
|
|
385
|
+
groupLabel?: (value: unknown, rows: T[]) => string
|
|
386
|
+
/** Collapsed group ids, `String(value)` (v-model:collapsedGroups). */
|
|
387
|
+
collapsedGroups?: string[]
|
|
388
|
+
/**
|
|
389
|
+
* Rows open a panel beneath them, drawn by the `expanded` slot. A function
|
|
390
|
+
* says which rows can. Ignored with `virtualRowHeight`.
|
|
391
|
+
*/
|
|
392
|
+
expandable?: boolean | ((row: T) => boolean)
|
|
393
|
+
/** Open row keys (v-model:expanded). */
|
|
394
|
+
expanded?: RowKey[]
|
|
395
|
+
/**
|
|
396
|
+
* Draw only the rows in view, for thousands of them, at this height in px.
|
|
397
|
+
* Give the table a `max-height`, or a limited parent: with room for every
|
|
398
|
+
* row, every row is in view and all of them are drawn.
|
|
399
|
+
*
|
|
400
|
+
* The price of drawing a window:
|
|
401
|
+
* - Every row, group heading included, must be exactly this tall: keep cells
|
|
402
|
+
* on one line (`nowrap`). A taller row drifts the scroll position.
|
|
403
|
+
* - Row enter, leave and move animations are off.
|
|
404
|
+
* - `expandable` is ignored: a panel has no fixed height.
|
|
405
|
+
* - Arrow keys and typeahead reach the rows drawn, not the whole list.
|
|
406
|
+
* - Sweeping ticks reaches the rows drawn; Ctrl+A and the header tick do
|
|
407
|
+
* cover all of them.
|
|
408
|
+
* - Find in page sees the rows drawn.
|
|
409
|
+
* `stickyHeader`, pins and the row menu work as usual.
|
|
410
|
+
*/
|
|
411
|
+
virtualRowHeight?: number
|
|
412
|
+
/** Rows drawn beyond each edge of the view. */
|
|
413
|
+
virtualOverscan?: number
|
|
293
414
|
}>(),
|
|
294
415
|
{ palette: undefined,
|
|
295
416
|
rows: () => [],
|
|
@@ -307,6 +428,14 @@ const props = withDefaults(
|
|
|
307
428
|
sort: undefined,
|
|
308
429
|
filters: undefined,
|
|
309
430
|
filtered: false,
|
|
431
|
+
sorts: undefined,
|
|
432
|
+
columnWidths: undefined,
|
|
433
|
+
columnPreset: undefined,
|
|
434
|
+
groupBy: undefined,
|
|
435
|
+
collapsedGroups: undefined,
|
|
436
|
+
expanded: undefined,
|
|
437
|
+
virtualRowHeight: undefined,
|
|
438
|
+
virtualOverscan: 6,
|
|
310
439
|
},
|
|
311
440
|
)
|
|
312
441
|
|
|
@@ -346,6 +475,13 @@ const labelSet = useLabels<TableLabels>(
|
|
|
346
475
|
pageSelected: "All {count} on this page are selected.",
|
|
347
476
|
selectAllMatching: "Select all {total}",
|
|
348
477
|
allMatchingSelected: "All {total} are selected.",
|
|
478
|
+
columnPresets: "Column presets",
|
|
479
|
+
resizeColumn: "Resize {column}",
|
|
480
|
+
expandRow: "Expand row",
|
|
481
|
+
collapseRow: "Collapse row",
|
|
482
|
+
groupCount: "{label} ({count})",
|
|
483
|
+
expandGroup: "Expand {label}",
|
|
484
|
+
collapseGroup: "Collapse {label}",
|
|
349
485
|
},
|
|
350
486
|
() => props.labels,
|
|
351
487
|
)
|
|
@@ -384,6 +520,13 @@ const emit = defineEmits<{
|
|
|
384
520
|
/** The empty table's reset was pressed: clear the filters kept outside it. */
|
|
385
521
|
(e: "resetFilters"): void
|
|
386
522
|
(e: "update:allMatching", value: boolean): void
|
|
523
|
+
(e: "update:selectionToken", value: TableSelectionToken | null): void
|
|
524
|
+
(e: "update:sorts", value: SortState[]): void
|
|
525
|
+
(e: "update:columnWidths", value: Record<string, number>): void
|
|
526
|
+
(e: "update:columnPreset", value: string | null): void
|
|
527
|
+
(e: "update:collapsedGroups", value: string[]): void
|
|
528
|
+
(e: "update:expanded", value: RowKey[]): void
|
|
529
|
+
(e: "cellEdit", edit: TableCellEdit<T>): void
|
|
387
530
|
(e: "rowClick", row: T): void
|
|
388
531
|
}>()
|
|
389
532
|
|
|
@@ -435,10 +578,11 @@ function onRowContextMenu(row: T) {
|
|
|
435
578
|
*/
|
|
436
579
|
function openRowMenu(event: Event) {
|
|
437
580
|
const button = event.currentTarget
|
|
438
|
-
if (!(button
|
|
581
|
+
if (!isHTMLElement(button)) return
|
|
439
582
|
const rect = button.getBoundingClientRect()
|
|
583
|
+
const win = nodeWindow(button)
|
|
440
584
|
button.dispatchEvent(
|
|
441
|
-
new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.bottom }),
|
|
585
|
+
new win.MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: rect.left, clientY: rect.bottom }),
|
|
442
586
|
)
|
|
443
587
|
}
|
|
444
588
|
|
|
@@ -478,7 +622,16 @@ const contextMenu = computed<ContextMenuItemDef[]>(() => {
|
|
|
478
622
|
// emitted an event nobody was listening for, and left the rows exactly as they
|
|
479
623
|
// were: a control that answers a click by doing nothing.
|
|
480
624
|
const ownSort = ref<SortState | null>(null)
|
|
481
|
-
const
|
|
625
|
+
const ownSorts = ref<SortState[]>([])
|
|
626
|
+
const activeSort = computed(() => {
|
|
627
|
+
if (props.multiSort) return (props.sorts ?? ownSorts.value)[0] ?? null
|
|
628
|
+
return props.sort !== undefined ? props.sort : ownSort.value
|
|
629
|
+
})
|
|
630
|
+
const sortList = computed<SortState[]>(() => {
|
|
631
|
+
if (props.multiSort) return props.sorts ?? ownSorts.value
|
|
632
|
+
return activeSort.value ? [activeSort.value] : []
|
|
633
|
+
})
|
|
634
|
+
const sortOf = (col: TableColumn<T>) => sortList.value.find((sort) => sort.key === col.key)
|
|
482
635
|
|
|
483
636
|
// ── server-backed rows ───────────────────────────────────────
|
|
484
637
|
const loaded = shallowRef<T[]>([])
|
|
@@ -492,6 +645,14 @@ const busy = computed(() => props.loading || task.pending.value)
|
|
|
492
645
|
const sourceRows = computed<T[]>(() => (props.load ? loaded.value : props.rows))
|
|
493
646
|
const sourceIndex = computed(() => new Map(sourceRows.value.map((row, i) => [row, i])))
|
|
494
647
|
|
|
648
|
+
// New objects drop what position-keyed selection pointed at, without a word.
|
|
649
|
+
let warnedRowKey = false
|
|
650
|
+
watch(sourceRows, () => {
|
|
651
|
+
if (warnedRowKey || props.rowKey || !props.selected.length) return
|
|
652
|
+
warnedRowKey = true
|
|
653
|
+
devWarn("LpTable: rows were replaced while some are selected, and without `rowKey` the selection now points at positions, not rows. Set `rowKey`.")
|
|
654
|
+
})
|
|
655
|
+
|
|
495
656
|
// The tallest page shown so far, held as a floor: the last page is usually
|
|
496
657
|
// short, and without it the pager below jumps up the moment you reach it.
|
|
497
658
|
/*
|
|
@@ -547,12 +708,14 @@ function writeHidden(next: Record<string, Set<unknown>>) {
|
|
|
547
708
|
* out beside the new one, and the table would show both lists for a moment.
|
|
548
709
|
*/
|
|
549
710
|
const animating = ref(false)
|
|
711
|
+
/* Longer than any row transition: a fallback off switch, not a measure of one. */
|
|
712
|
+
const MOTION_WINDOW_MS = 600
|
|
550
713
|
let motionTimer: ReturnType<typeof setTimeout> | undefined
|
|
551
714
|
function withMotion(change: () => void) {
|
|
552
715
|
if (!props.load) {
|
|
553
716
|
animating.value = true
|
|
554
717
|
if (motionTimer) clearTimeout(motionTimer)
|
|
555
|
-
motionTimer = setTimeout(() => (animating.value = false),
|
|
718
|
+
motionTimer = setTimeout(() => (animating.value = false), MOTION_WINDOW_MS)
|
|
556
719
|
}
|
|
557
720
|
change()
|
|
558
721
|
}
|
|
@@ -576,6 +739,113 @@ const visibleColumns = computed(() =>
|
|
|
576
739
|
),
|
|
577
740
|
)
|
|
578
741
|
|
|
742
|
+
// Pinned columns at their edges; with no pins, the columns as given.
|
|
743
|
+
const shownColumns = computed(() => orderPinned(visibleColumns.value))
|
|
744
|
+
|
|
745
|
+
// ── column widths and pins ───────────────────────────────────
|
|
746
|
+
const RESIZE_STEP_PX = 16
|
|
747
|
+
/* Room counted for a column with no width of its own, once the layout is fixed. */
|
|
748
|
+
const UNSET_COLUMN_PX = 120
|
|
749
|
+
const TICK_COLUMN_WIDTH = "3.25rem"
|
|
750
|
+
const EXPAND_COLUMN_WIDTH = "2.5rem"
|
|
751
|
+
|
|
752
|
+
const ownWidths = ref<Record<string, number>>({})
|
|
753
|
+
const widths = computed(() => props.columnWidths ?? ownWidths.value)
|
|
754
|
+
const fixedLayout = computed(() => !!props.resizableColumns && Object.keys(widths.value).length > 0)
|
|
755
|
+
const tableStyle = computed(() => {
|
|
756
|
+
if (!fixedLayout.value) return undefined
|
|
757
|
+
const sum = shownColumns.value.reduce((room, col) => room + (widths.value[col.key] ?? UNSET_COLUMN_PX), 0)
|
|
758
|
+
return { minWidth: `${sum}px` }
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
function writeWidths(next: Record<string, number>) {
|
|
762
|
+
ownWidths.value = next
|
|
763
|
+
emit("update:columnWidths", next)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const canResize = (col: TableColumn<T>) => !!props.resizableColumns && col.resizable !== false && !!col.label
|
|
767
|
+
/* Marks the header cells something measures, and only those. */
|
|
768
|
+
const measuredCol = (col: TableColumn<T>) => (props.resizableColumns || col.pin ? col.key : undefined)
|
|
769
|
+
|
|
770
|
+
function headerWidths(): Record<string, number> {
|
|
771
|
+
const measured: Record<string, number> = {}
|
|
772
|
+
for (const cell of frameEl.value?.querySelectorAll<HTMLElement>("th[data-lp-col]") ?? []) {
|
|
773
|
+
measured[cell.dataset.lpCol!] = cell.offsetWidth
|
|
774
|
+
}
|
|
775
|
+
return measured
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function setWidth(col: TableColumn<T>, width: number) {
|
|
779
|
+
writeWidths({ ...widths.value, [col.key]: clampColumnWidth(width, col.minWidth, col.maxWidth) })
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function clearWidth(col: TableColumn<T>) {
|
|
783
|
+
const { [col.key]: _gone, ...rest } = widths.value
|
|
784
|
+
writeWidths(rest)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/*
|
|
788
|
+
* The first drag fixes the layout, which would hand the free room out anew, so
|
|
789
|
+
* every column starts from the width it has on screen.
|
|
790
|
+
*/
|
|
791
|
+
function startResize(event: PointerEvent, col: TableColumn<T>) {
|
|
792
|
+
const handle = event.currentTarget
|
|
793
|
+
if (!isHTMLElement(handle) || event.button !== 0) return
|
|
794
|
+
event.stopPropagation()
|
|
795
|
+
const measured = headerWidths()
|
|
796
|
+
if (!fixedLayout.value) writeWidths(measured)
|
|
797
|
+
const from = measured[col.key] ?? handle.parentElement?.offsetWidth ?? 0
|
|
798
|
+
const startX = event.clientX
|
|
799
|
+
startPointerDrag(event, { move: (e) => setWidth(col, from + e.clientX - startX), end: () => {} })
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function onResizeKey(event: KeyboardEvent, col: TableColumn<T>) {
|
|
803
|
+
const step = event.key === "ArrowRight" ? RESIZE_STEP_PX : event.key === "ArrowLeft" ? -RESIZE_STEP_PX : 0
|
|
804
|
+
if (!step) return
|
|
805
|
+
event.preventDefault()
|
|
806
|
+
event.stopPropagation()
|
|
807
|
+
const measured = headerWidths()
|
|
808
|
+
if (!fixedLayout.value) writeWidths(measured)
|
|
809
|
+
setWidth(col, (widths.value[col.key] ?? measured[col.key] ?? UNSET_COLUMN_PX) + step)
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const pinWidths = ref<Record<string, number>>({})
|
|
813
|
+
const pinOffset = computed(() => pinOffsets(shownColumns.value, pinWidths.value))
|
|
814
|
+
function measurePins() {
|
|
815
|
+
if (shownColumns.value.some((col) => col.pin)) pinWidths.value = headerWidths()
|
|
816
|
+
}
|
|
817
|
+
watch([shownColumns, widths, tableWidth], measurePins, { flush: "post" })
|
|
818
|
+
onMounted(measurePins)
|
|
819
|
+
|
|
820
|
+
const PIN_HEAD = "sticky z-[11] bg-surface-soft"
|
|
821
|
+
const PIN_CELL = "sticky z-[1] bg-surface"
|
|
822
|
+
const pinStyle = (col: TableColumn<T>) => (col.pin ? { [col.pin]: `${pinOffset.value[col.key] ?? 0}px` } : undefined)
|
|
823
|
+
|
|
824
|
+
function headStyle(col: TableColumn<T>) {
|
|
825
|
+
const width = (props.resizableColumns ? widths.value[col.key] : undefined)
|
|
826
|
+
const size = width != null ? `${width}px` : col.width
|
|
827
|
+
if (!size && !col.pin) return undefined
|
|
828
|
+
return { ...(size ? { width: size } : {}), ...pinStyle(col) }
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// ── column presets ───────────────────────────────────────────
|
|
832
|
+
const ownPreset = ref<string | null>(null)
|
|
833
|
+
const activePreset = computed(() => (props.columnPreset !== undefined ? props.columnPreset : ownPreset.value))
|
|
834
|
+
|
|
835
|
+
function showPreset(id: string | null | undefined) {
|
|
836
|
+
const preset = props.columnPresets?.find((p) => p.id === id)
|
|
837
|
+
if (!preset) return
|
|
838
|
+
hiddenColumns.value = presetHidden(preset, props.columns)
|
|
839
|
+
if (preset.widths && props.resizableColumns) writeWidths({ ...widths.value, ...preset.widths })
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function choosePreset(id: string | null) {
|
|
843
|
+
ownPreset.value = id
|
|
844
|
+
emit("update:columnPreset", id)
|
|
845
|
+
showPreset(id)
|
|
846
|
+
}
|
|
847
|
+
watch(() => props.columnPreset, showPreset, { immediate: true })
|
|
848
|
+
|
|
579
849
|
const facetColumns = computed(() => props.columns.filter((col) => col.filterable).map((col) => col.key))
|
|
580
850
|
const localFacets = useTableFacets({
|
|
581
851
|
rows: () => props.load ? [] : props.rows,
|
|
@@ -665,6 +935,11 @@ function toggleColumn(key: string) {
|
|
|
665
935
|
if (next.has(key)) next.delete(key)
|
|
666
936
|
else next.add(key)
|
|
667
937
|
hiddenColumns.value = next
|
|
938
|
+
// The set on screen is no longer the preset's.
|
|
939
|
+
if (activePreset.value != null) {
|
|
940
|
+
ownPreset.value = null
|
|
941
|
+
emit("update:columnPreset", null)
|
|
942
|
+
}
|
|
668
943
|
}
|
|
669
944
|
|
|
670
945
|
/** Rows left once the per-column value filters have been applied. */
|
|
@@ -677,42 +952,43 @@ const filteredRows = computed<T[]>(() => {
|
|
|
677
952
|
)
|
|
678
953
|
})
|
|
679
954
|
|
|
680
|
-
// We compare by raw cell value with a stable-ish coerce.
|
|
681
955
|
function sortRows(rows: T[]): T[] {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
const col = props.columns.find((c) => c.key === s.key)
|
|
685
|
-
if (!col?.sortable) return rows
|
|
686
|
-
const factor = s.dir === "asc" ? 1 : -1
|
|
687
|
-
const own = col.compare
|
|
688
|
-
const value = col.sortValue ?? ((row: T) => row[s.key])
|
|
689
|
-
return [...rows].sort((a, b) => (own ? own(a, b) : compareValues(value(a), value(b))) * factor)
|
|
956
|
+
if (!sortList.value.length || props.load) return rows
|
|
957
|
+
return sortByKeys(rows, sortList.value, props.columns)
|
|
690
958
|
}
|
|
691
959
|
|
|
692
960
|
const displayRows = computed<T[]>(() => sortRows(filteredRows.value))
|
|
693
961
|
|
|
694
|
-
function
|
|
962
|
+
function writeSorts(next: SortState[]) {
|
|
963
|
+
withMotion(() => {
|
|
964
|
+
ownSorts.value = next
|
|
965
|
+
ownSort.value = next[0] ?? null
|
|
966
|
+
if (props.multiSort) emit("update:sorts", next)
|
|
967
|
+
emit("update:sort", next[0] ?? null)
|
|
968
|
+
})
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function toggleSort(col: TableColumn<T>, event?: MouseEvent) {
|
|
695
972
|
if (!col.sortable) return
|
|
696
|
-
const s = activeSort.value
|
|
697
973
|
// Cycle: none → asc → desc → none, scoped to the clicked column.
|
|
698
|
-
let next: SortState | null
|
|
699
|
-
if (!s || s.key !== col.key) next = { key: col.key, dir: "asc" }
|
|
700
|
-
else if (s.dir === "asc") next = { key: col.key, dir: "desc" }
|
|
701
|
-
else next = null
|
|
702
974
|
// Kept locally as well as emitted: a parent that binds v-model overwrites
|
|
703
975
|
// this on the way back, and one that does not still gets a table that sorts.
|
|
704
|
-
|
|
705
|
-
ownSort.value = next
|
|
706
|
-
emit("update:sort", next)
|
|
707
|
-
})
|
|
976
|
+
writeSorts(nextSorts(sortList.value, col.key, !!(props.multiSort && event?.shiftKey)))
|
|
708
977
|
}
|
|
709
978
|
|
|
710
979
|
function sortIcon(col: TableColumn<T>): string {
|
|
711
|
-
const s =
|
|
712
|
-
if (s
|
|
980
|
+
const s = sortOf(col)
|
|
981
|
+
if (!s) return "lucide:chevrons-up-down"
|
|
713
982
|
return s.dir === "asc" ? "lucide:arrow-up" : "lucide:arrow-down"
|
|
714
983
|
}
|
|
715
984
|
|
|
985
|
+
/* The key's place in a sort by several columns; nothing to say with just one. */
|
|
986
|
+
function sortPosition(col: TableColumn<T>): number | undefined {
|
|
987
|
+
if (sortList.value.length < 2) return undefined
|
|
988
|
+
const at = sortList.value.findIndex((sort) => sort.key === col.key)
|
|
989
|
+
return at < 0 ? undefined : at + 1
|
|
990
|
+
}
|
|
991
|
+
|
|
716
992
|
/**
|
|
717
993
|
* What right-clicking a header offers: the directions by name.
|
|
718
994
|
*
|
|
@@ -727,11 +1003,16 @@ function sortIcon(col: TableColumn<T>): string {
|
|
|
727
1003
|
// back before the popover can take it, or the popover reads that focus move as
|
|
728
1004
|
// an outside interaction and dismisses itself. So the trigger is clicked once
|
|
729
1005
|
// the menu is gone.
|
|
1006
|
+
const MENU_CLOSE_LIMIT_MS = 600
|
|
1007
|
+
const FOCUS_RETURN_MS = 30
|
|
730
1008
|
function openFilter(key: string) {
|
|
731
1009
|
const open = () => frameEl.value?.querySelector<HTMLElement>(`[data-lp-filter="${CSS.escape(key)}"]`)?.click()
|
|
732
1010
|
const started = performance.now()
|
|
1011
|
+
// The menu this was picked from, in the table's own document; a menu that
|
|
1012
|
+
// opens elsewhere in the meantime is no reason to wait.
|
|
1013
|
+
const closing = [...(frameEl.value?.ownerDocument ?? document).querySelectorAll("[data-reka-menu-content]")]
|
|
733
1014
|
const wait = () => {
|
|
734
|
-
if (!
|
|
1015
|
+
if (!closing.some((menu) => menu.isConnected) || performance.now() - started > MENU_CLOSE_LIMIT_MS) setTimeout(open, FOCUS_RETURN_MS)
|
|
735
1016
|
else requestAnimationFrame(wait)
|
|
736
1017
|
}
|
|
737
1018
|
requestAnimationFrame(wait)
|
|
@@ -739,11 +1020,7 @@ function openFilter(key: string) {
|
|
|
739
1020
|
|
|
740
1021
|
function headerMenu(col: TableColumn<T>): ContextMenuItemDef[] {
|
|
741
1022
|
const s = activeSort.value
|
|
742
|
-
const set = (next: SortState | null) => () =>
|
|
743
|
-
withMotion(() => {
|
|
744
|
-
ownSort.value = next
|
|
745
|
-
emit("update:sort", next)
|
|
746
|
-
})
|
|
1023
|
+
const set = (next: SortState | null) => () => writeSorts(next ? [next] : [])
|
|
747
1024
|
|
|
748
1025
|
const items: ContextMenuItemDef[] = []
|
|
749
1026
|
|
|
@@ -806,8 +1083,20 @@ function tableMenu(): ContextMenuItemDef[] {
|
|
|
806
1083
|
? [{ label: text.value.exportCsv, icon: "lucide:download", onSelect: () => exportCsv() }]
|
|
807
1084
|
: []
|
|
808
1085
|
if (!offered.length) return exportItem
|
|
1086
|
+
const presets: ContextMenuItemDef[] = props.columnPresets?.length
|
|
1087
|
+
? [{
|
|
1088
|
+
label: text.value.columnPresets,
|
|
1089
|
+
icon: "lucide:layout-template",
|
|
1090
|
+
children: props.columnPresets.map((preset) => ({
|
|
1091
|
+
label: preset.label,
|
|
1092
|
+
checked: activePreset.value === preset.id,
|
|
1093
|
+
onSelect: () => choosePreset(preset.id),
|
|
1094
|
+
})),
|
|
1095
|
+
}]
|
|
1096
|
+
: []
|
|
809
1097
|
return [
|
|
810
1098
|
...exportItem,
|
|
1099
|
+
...presets,
|
|
811
1100
|
{
|
|
812
1101
|
label: text.value.columns,
|
|
813
1102
|
icon: "lucide:columns-3",
|
|
@@ -833,6 +1122,7 @@ function fetchPage(delay = 0) {
|
|
|
833
1122
|
.map(([key, set]) => [key, [...set]]),
|
|
834
1123
|
)
|
|
835
1124
|
const query: TableQuery = { page: page.value, pageSize: props.pageSize, sort: activeSort.value, hidden }
|
|
1125
|
+
if (props.multiSort) query.sorts = sortList.value
|
|
836
1126
|
task.run((signal) => load(query, signal), {
|
|
837
1127
|
delay,
|
|
838
1128
|
apply: (result) => {
|
|
@@ -858,7 +1148,7 @@ function fetchPage(delay = 0) {
|
|
|
858
1148
|
// A new sort, filter or page size starts from the first page: page 4 of a
|
|
859
1149
|
// different question, or of a different cut of the rows, is a page of nothing
|
|
860
1150
|
// in particular.
|
|
861
|
-
watch([
|
|
1151
|
+
watch([sortList, hiddenValues, () => props.pageSize], () => {
|
|
862
1152
|
if (!props.load) return
|
|
863
1153
|
if (page.value !== 1) page.value = 1
|
|
864
1154
|
else fetchPage(props.debounce)
|
|
@@ -878,6 +1168,7 @@ onMounted(() => {
|
|
|
878
1168
|
if (state) {
|
|
879
1169
|
if (state.sort !== undefined) {
|
|
880
1170
|
ownSort.value = state.sort
|
|
1171
|
+
ownSorts.value = state.sort ? [state.sort] : []
|
|
881
1172
|
emit("update:sort", state.sort)
|
|
882
1173
|
}
|
|
883
1174
|
if (state.hidden) writeHidden(mergeRestored(hiddenValues.value, state.hidden))
|
|
@@ -916,7 +1207,7 @@ function exportCsv(options: TableExportOptions = {}): string {
|
|
|
916
1207
|
}))
|
|
917
1208
|
const csv = toCsv(rows, columns, options)
|
|
918
1209
|
if (options.download !== false) {
|
|
919
|
-
downloadFile(`${fileStem(options.filename ?? props.exportFilename, "table")}.csv`, csv, "text/csv;charset=utf-8")
|
|
1210
|
+
downloadFile(`${fileStem(options.filename ?? props.exportFilename, "table")}.csv`, csv, "text/csv;charset=utf-8", tableEl.value?.ownerDocument)
|
|
920
1211
|
}
|
|
921
1212
|
return csv
|
|
922
1213
|
}
|
|
@@ -925,16 +1216,28 @@ defineExpose({ reload: () => fetchPage(), exportCsv })
|
|
|
925
1216
|
|
|
926
1217
|
// ── selection ────────────────────────────────────────────────
|
|
927
1218
|
const selectedSet = computed(() => new Set(props.selected))
|
|
1219
|
+
/*
|
|
1220
|
+
* With `selectionToken`, "all N" is the query and its exceptions rather than
|
|
1221
|
+
* keys: the rows the client never loaded have no key to hold.
|
|
1222
|
+
*/
|
|
1223
|
+
const excluded = ref<Set<RowKey>>(new Set())
|
|
1224
|
+
const tokenActive = computed(() => !!props.selectionToken && !!props.load && props.allMatching)
|
|
1225
|
+
const isSelected = (key: RowKey) => (tokenActive.value ? !excluded.value.has(key) : selectedSet.value.has(key))
|
|
928
1226
|
// "All" is what the user can see: rows a header filter hides are left alone.
|
|
929
1227
|
const allKeys = computed(() => filteredRows.value.map((r, i) => keyFor(r, i)))
|
|
930
1228
|
const allChecked = computed(
|
|
931
|
-
() => allKeys.value.length > 0 && allKeys.value.every(
|
|
1229
|
+
() => allKeys.value.length > 0 && allKeys.value.every(isSelected),
|
|
932
1230
|
)
|
|
933
1231
|
const someChecked = computed(
|
|
934
|
-
() => !allChecked.value && allKeys.value.some(
|
|
1232
|
+
() => !allChecked.value && allKeys.value.some(isSelected),
|
|
935
1233
|
)
|
|
936
1234
|
|
|
937
1235
|
function toggleAll(checked: boolean) {
|
|
1236
|
+
if (tokenActive.value) {
|
|
1237
|
+
if (checked) excluded.value = new Set()
|
|
1238
|
+
else clearSelection()
|
|
1239
|
+
return
|
|
1240
|
+
}
|
|
938
1241
|
const next = new Set(selectedSet.value)
|
|
939
1242
|
for (const key of allKeys.value) {
|
|
940
1243
|
if (checked) next.add(key)
|
|
@@ -944,6 +1247,14 @@ function toggleAll(checked: boolean) {
|
|
|
944
1247
|
}
|
|
945
1248
|
|
|
946
1249
|
function toggleRow(key: RowKey, checked: boolean) {
|
|
1250
|
+
if (tokenActive.value) {
|
|
1251
|
+
const out = new Set(excluded.value)
|
|
1252
|
+
if (checked) out.delete(key)
|
|
1253
|
+
else out.add(key)
|
|
1254
|
+
if (out.size >= total.value) clearSelection()
|
|
1255
|
+
else excluded.value = out
|
|
1256
|
+
return
|
|
1257
|
+
}
|
|
947
1258
|
const next = new Set(selectedSet.value)
|
|
948
1259
|
if (checked) next.add(key)
|
|
949
1260
|
else next.delete(key)
|
|
@@ -957,7 +1268,7 @@ const keyByText = computed(
|
|
|
957
1268
|
)
|
|
958
1269
|
const paint = usePaintSelect<RowKey>({
|
|
959
1270
|
root: frameEl,
|
|
960
|
-
enabled: () => !!props.selectable,
|
|
1271
|
+
enabled: () => !!props.selectable && !tokenActive.value,
|
|
961
1272
|
selection: () => props.selected,
|
|
962
1273
|
set: (keys) => emit("update:selected", keys),
|
|
963
1274
|
resolve: (raw) => keyByText.value.get(raw),
|
|
@@ -978,8 +1289,29 @@ const ticksShown = computed(
|
|
|
978
1289
|
function clearSelection() {
|
|
979
1290
|
emit("update:selected", [])
|
|
980
1291
|
if (props.allMatching) emit("update:allMatching", false)
|
|
1292
|
+
excluded.value = new Set()
|
|
981
1293
|
}
|
|
982
1294
|
|
|
1295
|
+
watch([tokenActive, sortList, excluded, total], () => {
|
|
1296
|
+
if (!tokenActive.value) {
|
|
1297
|
+
if (excluded.value.size) excluded.value = new Set()
|
|
1298
|
+
emit("update:selectionToken", null)
|
|
1299
|
+
return
|
|
1300
|
+
}
|
|
1301
|
+
emit("update:selectionToken", {
|
|
1302
|
+
sorts: sortList.value,
|
|
1303
|
+
hidden: Object.fromEntries(
|
|
1304
|
+
Object.entries(hiddenValues.value).filter(([, set]) => set.size).map(([key, set]) => [key, [...set]]),
|
|
1305
|
+
),
|
|
1306
|
+
excluded: [...excluded.value],
|
|
1307
|
+
total: total.value,
|
|
1308
|
+
})
|
|
1309
|
+
})
|
|
1310
|
+
// Another filter is another set of rows: what was selected no longer describes it.
|
|
1311
|
+
watch(hiddenValues, () => {
|
|
1312
|
+
if (tokenActive.value) clearSelection()
|
|
1313
|
+
})
|
|
1314
|
+
|
|
983
1315
|
/*
|
|
984
1316
|
* A long press on a row starts selection in hold mode. Presses that begin on
|
|
985
1317
|
* something the row holds that answers clicks of its own (a link, a button, a
|
|
@@ -995,7 +1327,7 @@ function onRowPointerDown(event: PointerEvent, key: RowKey) {
|
|
|
995
1327
|
// Once something is selected, a click on a row adds or removes it, as in a
|
|
996
1328
|
// phone's selection mode; with nothing selected it is the row's click again.
|
|
997
1329
|
function onRowClick(row: T, key: RowKey) {
|
|
998
|
-
if (holdMode.value && props.selected.length) toggleRow(key, !
|
|
1330
|
+
if (holdMode.value && props.selected.length) toggleRow(key, !isSelected(key))
|
|
999
1331
|
else emit("rowClick", row)
|
|
1000
1332
|
}
|
|
1001
1333
|
|
|
@@ -1021,7 +1353,7 @@ const pageAllSelected = computed(
|
|
|
1021
1353
|
// "Select all" beyond the page is a separate, explicit step: the header tick
|
|
1022
1354
|
// only ever means the rows the user can see.
|
|
1023
1355
|
watch(pageAllSelected, (all) => {
|
|
1024
|
-
if (!all && props.allMatching) emit("update:allMatching", false)
|
|
1356
|
+
if (!all && props.allMatching && !props.selectionToken) emit("update:allMatching", false)
|
|
1025
1357
|
})
|
|
1026
1358
|
/*
|
|
1027
1359
|
* One status line that rolls between its states ("60 rows", "3 selected",
|
|
@@ -1030,6 +1362,9 @@ watch(pageAllSelected, (all) => {
|
|
|
1030
1362
|
*/
|
|
1031
1363
|
const statusText = computed(() => {
|
|
1032
1364
|
if (!(props.selectable && props.selected.length)) return countText.value
|
|
1365
|
+
if (tokenActive.value && excluded.value.size) {
|
|
1366
|
+
return text.value.selectedCount.replace("{count}", String(total.value - excluded.value.size))
|
|
1367
|
+
}
|
|
1033
1368
|
return props.allMatching
|
|
1034
1369
|
? text.value.allMatchingSelected.replace("{total}", String(total.value))
|
|
1035
1370
|
: text.value.selectedCount.replace("{count}", String(props.selected.length))
|
|
@@ -1070,7 +1405,102 @@ function skeletonWidth(row: number, col: number): string {
|
|
|
1070
1405
|
return `${SKELETON_WIDTHS[(row * 3 + col * 2) % SKELETON_WIDTHS.length]}%`
|
|
1071
1406
|
}
|
|
1072
1407
|
|
|
1073
|
-
|
|
1408
|
+
// ── expansion, groups and the rows drawn ─────────────────────
|
|
1409
|
+
const VIEWPORT = "[data-reka-scroll-area-viewport]"
|
|
1410
|
+
const expansionOn = computed(() => !!props.expandable && !props.virtualRowHeight)
|
|
1411
|
+
const ownExpanded = ref<RowKey[]>([])
|
|
1412
|
+
const expandedKeys = computed(() => new Set(props.expanded ?? ownExpanded.value))
|
|
1413
|
+
const canExpand = (row: T) => (typeof props.expandable === "function" ? props.expandable(row) : !!props.expandable)
|
|
1414
|
+
|
|
1415
|
+
function toggleExpanded(key: RowKey) {
|
|
1416
|
+
const next = new Set(expandedKeys.value)
|
|
1417
|
+
if (!next.delete(key)) next.add(key)
|
|
1418
|
+
ownExpanded.value = [...next]
|
|
1419
|
+
emit("update:expanded", ownExpanded.value)
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
const ownCollapsed = ref<string[]>([])
|
|
1423
|
+
const collapsedSet = computed(() => new Set(props.collapsedGroups ?? ownCollapsed.value))
|
|
1424
|
+
|
|
1425
|
+
function toggleGroup(id: string) {
|
|
1426
|
+
const next = new Set(collapsedSet.value)
|
|
1427
|
+
if (!next.delete(id)) next.add(id)
|
|
1428
|
+
ownCollapsed.value = [...next]
|
|
1429
|
+
emit("update:collapsedGroups", ownCollapsed.value)
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
type ViewEntry =
|
|
1433
|
+
| { kind: "row"; id: string; row: T; index: number }
|
|
1434
|
+
| { kind: "detail"; id: string; row: T; index: number }
|
|
1435
|
+
| { kind: "group"; id: string; group: string; value: unknown; rows: T[]; label: string; collapsed: boolean }
|
|
1436
|
+
|
|
1437
|
+
function groupValue(row: T): unknown {
|
|
1438
|
+
return typeof props.groupBy === "function" ? props.groupBy(row) : row[props.groupBy as keyof T]
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
const allEntries = computed<ViewEntry[]>(() => {
|
|
1442
|
+
const rows = displayRows.value
|
|
1443
|
+
const indexes = props.groupBy !== undefined ? new Map(rows.map((row, i) => [row, i])) : null
|
|
1444
|
+
const entries: ViewEntry[] = []
|
|
1445
|
+
const pushRow = (row: T, index: number) => {
|
|
1446
|
+
const key = keyFor(row, index)
|
|
1447
|
+
entries.push({ kind: "row", id: String(key), row, index })
|
|
1448
|
+
if (expansionOn.value && expandedKeys.value.has(key) && canExpand(row)) {
|
|
1449
|
+
entries.push({ kind: "detail", id: `lp-detail:${key}`, row, index })
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
if (!indexes) {
|
|
1453
|
+
rows.forEach((row, index) => pushRow(row, index))
|
|
1454
|
+
return entries
|
|
1455
|
+
}
|
|
1456
|
+
for (const group of groupRows(rows, groupValue)) {
|
|
1457
|
+
const collapsed = collapsedSet.value.has(group.id)
|
|
1458
|
+
const name = props.groupLabel?.(group.value, group.rows) ?? group.id
|
|
1459
|
+
const label = text.value.groupCount.replace("{label}", name).replace("{count}", String(group.rows.length))
|
|
1460
|
+
entries.push({ kind: "group", id: `lp-group:${group.id}`, group: group.id, value: group.value, rows: group.rows, label, collapsed })
|
|
1461
|
+
if (!collapsed) for (const row of group.rows) pushRow(row, indexes.get(row) ?? 0)
|
|
1462
|
+
}
|
|
1463
|
+
return entries
|
|
1464
|
+
})
|
|
1465
|
+
|
|
1466
|
+
const viewportEl = shallowRef<HTMLElement | null>(null)
|
|
1467
|
+
const scrollTop = ref(0)
|
|
1468
|
+
const viewportHeight = ref(0)
|
|
1469
|
+
const onViewportScroll = () => (scrollTop.value = viewportEl.value?.scrollTop ?? 0)
|
|
1470
|
+
onMounted(() => {
|
|
1471
|
+
if (props.virtualRowHeight) viewportEl.value = frameEl.value?.querySelector<HTMLElement>(VIEWPORT) ?? null
|
|
1472
|
+
})
|
|
1473
|
+
watch(viewportEl, (el, _, onCleanup) => {
|
|
1474
|
+
if (!el) return
|
|
1475
|
+
el.addEventListener("scroll", onViewportScroll, { passive: true })
|
|
1476
|
+
onCleanup(() => el.removeEventListener("scroll", onViewportScroll))
|
|
1477
|
+
})
|
|
1478
|
+
useResizeObserver(viewportEl, (_, el) => (viewportHeight.value = el.clientHeight), { immediate: true })
|
|
1479
|
+
|
|
1480
|
+
const rowWindowed = computed(() =>
|
|
1481
|
+
props.virtualRowHeight
|
|
1482
|
+
? rowWindow(scrollTop.value, viewportHeight.value, props.virtualRowHeight, allEntries.value.length, props.virtualOverscan)
|
|
1483
|
+
: null,
|
|
1484
|
+
)
|
|
1485
|
+
const viewEntries = computed(() =>
|
|
1486
|
+
rowWindowed.value ? allEntries.value.slice(rowWindowed.value.start, rowWindowed.value.end) : allEntries.value,
|
|
1487
|
+
)
|
|
1488
|
+
/* The row's place among all of them, for assistive tech: the header is row 1. */
|
|
1489
|
+
const rowOffset = (entry: ViewEntry) => (rowWindowed.value?.start ?? 0) + viewEntries.value.indexOf(entry) + 2
|
|
1490
|
+
const rowStyle = computed(() => (props.virtualRowHeight ? { height: `${props.virtualRowHeight}px` } : undefined))
|
|
1491
|
+
|
|
1492
|
+
function isEditable(col: TableColumn<T>, row: T): boolean {
|
|
1493
|
+
return typeof col.editable === "function" ? col.editable(row) : !!col.editable
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
function saveCell(col: TableColumn<T>, row: T, value: string) {
|
|
1497
|
+
const parsed = col.parseEdit ? col.parseEdit(value, row) : value
|
|
1498
|
+
emit("cellEdit", { row, key: col.key, value: parsed, previous: row[col.key] })
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const colSpan = computed(
|
|
1502
|
+
() => visibleColumns.value.length + (props.selectable ? 1 : 0) + (expansionOn.value ? 1 : 0),
|
|
1503
|
+
)
|
|
1074
1504
|
|
|
1075
1505
|
/*
|
|
1076
1506
|
* Keyboard navigation over the rows. A table is one Tab stop, not one per row:
|
|
@@ -1082,10 +1512,9 @@ const colSpan = computed(() => visibleColumns.value.length + (props.selectable ?
|
|
|
1082
1512
|
* cursor survives a re-sort.
|
|
1083
1513
|
*/
|
|
1084
1514
|
const rovingItems = computed(() =>
|
|
1085
|
-
|
|
1086
|
-
id: String(
|
|
1087
|
-
|
|
1088
|
-
})),
|
|
1515
|
+
viewEntries.value.flatMap((entry) =>
|
|
1516
|
+
entry.kind === "row" ? [{ id: entry.id, label: String(entry.row[props.columns[0]?.key ?? ""] ?? "") }] : [],
|
|
1517
|
+
),
|
|
1089
1518
|
)
|
|
1090
1519
|
|
|
1091
1520
|
const rowByKey = computed(() => {
|
|
@@ -1113,7 +1542,7 @@ function onRowKeydown(event: KeyboardEvent) {
|
|
|
1113
1542
|
const row = rowByKey.value.get(key)
|
|
1114
1543
|
if (row) {
|
|
1115
1544
|
const rowKey = keyFor(row, displayRows.value.indexOf(row))
|
|
1116
|
-
toggleRow(rowKey, !
|
|
1545
|
+
toggleRow(rowKey, !isSelected(rowKey))
|
|
1117
1546
|
}
|
|
1118
1547
|
return
|
|
1119
1548
|
}
|
|
@@ -1283,7 +1712,13 @@ usePaletteCommands(() => {
|
|
|
1283
1712
|
:title-icon="menuHead?.icon"
|
|
1284
1713
|
:title-image="menuHead?.image"
|
|
1285
1714
|
>
|
|
1286
|
-
<table
|
|
1715
|
+
<table
|
|
1716
|
+
ref="tableEl"
|
|
1717
|
+
class="w-full border-collapse text-sm"
|
|
1718
|
+
:class="fixedLayout ? 'table-fixed' : ''"
|
|
1719
|
+
:style="tableStyle"
|
|
1720
|
+
:aria-rowcount="virtualRowHeight ? allEntries.length + 1 : undefined"
|
|
1721
|
+
>
|
|
1287
1722
|
<!-- The background belongs on `thead` itself, not only on its row:
|
|
1288
1723
|
`border-collapse` collapses the borders, and with a sticky header the
|
|
1289
1724
|
rows show through that seam — a band of body text rides across the
|
|
@@ -1293,7 +1728,13 @@ usePaletteCommands(() => {
|
|
|
1293
1728
|
:class="stickyHeader ? 'sticky top-0 z-10 bg-surface-soft' : ''"
|
|
1294
1729
|
>
|
|
1295
1730
|
<tr class="border-b border-line bg-surface-soft">
|
|
1296
|
-
<th v-if="
|
|
1731
|
+
<th v-if="expansionOn" class="w-px p-0" :style="fixedLayout ? { width: EXPAND_COLUMN_WIDTH } : undefined" :class="cellY" />
|
|
1732
|
+
<th
|
|
1733
|
+
v-if="selectable"
|
|
1734
|
+
class="w-px p-0"
|
|
1735
|
+
:class="cellY"
|
|
1736
|
+
:style="fixedLayout ? { width: ticksShown ? TICK_COLUMN_WIDTH : '0' } : undefined"
|
|
1737
|
+
>
|
|
1297
1738
|
<div class="lp-tick lp-tick-open" :class="ticksShown ? '' : 'lp-tick-hidden'" :inert="!ticksShown">
|
|
1298
1739
|
<LpCheckbox
|
|
1299
1740
|
:model-value="allChecked"
|
|
@@ -1303,14 +1744,15 @@ usePaletteCommands(() => {
|
|
|
1303
1744
|
</div>
|
|
1304
1745
|
</th>
|
|
1305
1746
|
<th
|
|
1306
|
-
v-for="col in
|
|
1747
|
+
v-for="col in shownColumns"
|
|
1307
1748
|
:key="col.key"
|
|
1308
|
-
:style="col
|
|
1749
|
+
:style="headStyle(col)"
|
|
1309
1750
|
class="px-4 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted"
|
|
1310
|
-
:class="[alignClass(col.align), cellY]"
|
|
1751
|
+
:class="[alignClass(col.align), cellY, col.pin ? PIN_HEAD : '', canResize(col) && !col.pin ? 'relative' : '']"
|
|
1752
|
+
:data-lp-col="measuredCol(col)"
|
|
1311
1753
|
:aria-sort="
|
|
1312
|
-
|
|
1313
|
-
?
|
|
1754
|
+
sortOf(col)
|
|
1755
|
+
? sortOf(col)!.dir === 'asc'
|
|
1314
1756
|
? 'ascending'
|
|
1315
1757
|
: 'descending'
|
|
1316
1758
|
: undefined
|
|
@@ -1329,9 +1771,9 @@ usePaletteCommands(() => {
|
|
|
1329
1771
|
class="group/sort -mx-1 inline-flex items-center gap-1 rounded-small px-1 outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
1330
1772
|
:class="[
|
|
1331
1773
|
col.align === 'right' ? 'flex-row-reverse' : '',
|
|
1332
|
-
|
|
1774
|
+
sortOf(col) ? 'text-ink' : '',
|
|
1333
1775
|
]"
|
|
1334
|
-
@click="toggleSort(col)"
|
|
1776
|
+
@click="toggleSort(col, $event)"
|
|
1335
1777
|
>
|
|
1336
1778
|
{{ col.label }}
|
|
1337
1779
|
<LpIcon
|
|
@@ -1340,11 +1782,12 @@ usePaletteCommands(() => {
|
|
|
1340
1782
|
:size="13"
|
|
1341
1783
|
class="shrink-0 transition-[transform,opacity] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)]"
|
|
1342
1784
|
:class="
|
|
1343
|
-
|
|
1785
|
+
sortOf(col)
|
|
1344
1786
|
? 'opacity-100'
|
|
1345
1787
|
: 'opacity-40 group-hover/sort:opacity-70'
|
|
1346
1788
|
"
|
|
1347
1789
|
/>
|
|
1790
|
+
<span v-if="sortPosition(col)" class="tabular-nums text-brand">{{ sortPosition(col) }}</span>
|
|
1348
1791
|
</button>
|
|
1349
1792
|
</LpContextMenu>
|
|
1350
1793
|
<template v-else>{{ col.label }}</template>
|
|
@@ -1372,6 +1815,20 @@ usePaletteCommands(() => {
|
|
|
1372
1815
|
@update:hidden="(next) => setHidden(col.key, next)"
|
|
1373
1816
|
/>
|
|
1374
1817
|
</LpPopover>
|
|
1818
|
+
<span
|
|
1819
|
+
v-if="canResize(col)"
|
|
1820
|
+
role="separator"
|
|
1821
|
+
aria-orientation="vertical"
|
|
1822
|
+
tabindex="0"
|
|
1823
|
+
:aria-label="text.resizeColumn.replace('{column}', col.label)"
|
|
1824
|
+
:aria-valuenow="widths[col.key]"
|
|
1825
|
+
class="absolute inset-y-0 right-0 w-1.5 cursor-col-resize touch-none outline-none transition-colors duration-fast hover:bg-brand focus-visible:bg-brand"
|
|
1826
|
+
:data-lp-resize="col.key"
|
|
1827
|
+
@pointerdown="startResize($event, col)"
|
|
1828
|
+
@click.stop
|
|
1829
|
+
@dblclick.stop="clearWidth(col)"
|
|
1830
|
+
@keydown="onResizeKey($event, col)"
|
|
1831
|
+
/>
|
|
1375
1832
|
</th>
|
|
1376
1833
|
</tr>
|
|
1377
1834
|
</thead>
|
|
@@ -1389,7 +1846,7 @@ usePaletteCommands(() => {
|
|
|
1389
1846
|
<TransitionGroup
|
|
1390
1847
|
tag="tbody"
|
|
1391
1848
|
name="lp-row"
|
|
1392
|
-
:css="animating"
|
|
1849
|
+
:css="animating && !virtualRowHeight"
|
|
1393
1850
|
:class="busy && displayRows.length ? 'opacity-60 transition-opacity' : 'transition-opacity'"
|
|
1394
1851
|
:aria-busy="busy || undefined"
|
|
1395
1852
|
:ref="(el: unknown) => roving.setContainer((el as { $el?: HTMLElement })?.$el ?? null)"
|
|
@@ -1405,9 +1862,10 @@ usePaletteCommands(() => {
|
|
|
1405
1862
|
:key="`lp-table-skeleton-${n}`"
|
|
1406
1863
|
class="lp-row-empty border-b border-line/60 last:border-0"
|
|
1407
1864
|
>
|
|
1865
|
+
<td v-if="expansionOn" class="w-px p-0" :class="cellY" />
|
|
1408
1866
|
<td v-if="selectable" class="w-px p-0" :class="cellY" />
|
|
1409
1867
|
<td
|
|
1410
|
-
v-for="(col, ci) in
|
|
1868
|
+
v-for="(col, ci) in shownColumns"
|
|
1411
1869
|
:key="col.key"
|
|
1412
1870
|
class="px-4"
|
|
1413
1871
|
:class="[alignClass(col.align), cellY]"
|
|
@@ -1436,43 +1894,101 @@ usePaletteCommands(() => {
|
|
|
1436
1894
|
<!-- A plain <tr>, because TransitionGroup animates element children and
|
|
1437
1895
|
nothing else. The right-click menu is the table's one menu, told
|
|
1438
1896
|
which row the event came from — see `contextMenu`. -->
|
|
1439
|
-
<tr
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
v-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
/>
|
|
1464
|
-
</
|
|
1465
|
-
</
|
|
1466
|
-
<
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1897
|
+
<tr v-if="rowWindowed?.before" key="lp-virtual-before" aria-hidden="true">
|
|
1898
|
+
<td :colspan="colSpan" class="p-0" :style="{ height: `${rowWindowed.before}px` }" />
|
|
1899
|
+
</tr>
|
|
1900
|
+
<!-- One keyed fragment per entry: TransitionGroup reads through it to
|
|
1901
|
+
the row, so the rows animate as they always did. -->
|
|
1902
|
+
<template v-for="entry in viewEntries" :key="entry.id">
|
|
1903
|
+
<tr v-if="entry.kind === 'group'" class="border-b border-line/60 bg-surface-soft/60" :style="rowStyle">
|
|
1904
|
+
<td :colspan="colSpan" class="px-4 text-xs font-medium text-muted" :class="cellY">
|
|
1905
|
+
<slot name="group" :value="entry.value" :rows="entry.rows" :label="entry.label" :collapsed="entry.collapsed" :toggle="() => toggleGroup(entry.group)">
|
|
1906
|
+
<button
|
|
1907
|
+
type="button"
|
|
1908
|
+
class="inline-flex items-center gap-1 rounded-small outline-none transition-colors duration-fast hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
1909
|
+
:aria-expanded="!entry.collapsed"
|
|
1910
|
+
:aria-label="(entry.collapsed ? text.expandGroup : text.collapseGroup).replace('{label}', entry.label)"
|
|
1911
|
+
@click="toggleGroup(entry.group)"
|
|
1912
|
+
>
|
|
1913
|
+
<LpIcon name="lucide:chevron-right" :size="14" class="transition-transform duration-fast motion-reduce:transition-none" :class="entry.collapsed ? '' : 'rotate-90'" />
|
|
1914
|
+
{{ entry.label }}
|
|
1915
|
+
</button>
|
|
1916
|
+
</slot>
|
|
1917
|
+
</td>
|
|
1918
|
+
</tr>
|
|
1919
|
+
<tr v-else-if="entry.kind === 'detail'" class="border-b border-line/60 last:border-0" data-lp-row-detail>
|
|
1920
|
+
<td :colspan="colSpan" class="px-4 py-3">
|
|
1921
|
+
<slot name="expanded" :row="entry.row" :close="() => toggleExpanded(keyFor(entry.row, entry.index))" />
|
|
1922
|
+
</td>
|
|
1923
|
+
</tr>
|
|
1924
|
+
<!-- A plain <tr>, because TransitionGroup animates element children and
|
|
1925
|
+
nothing else. The right-click menu is the table's one menu, told
|
|
1926
|
+
which row the event came from — see `contextMenu`. -->
|
|
1927
|
+
<tr
|
|
1928
|
+
v-else
|
|
1929
|
+
@contextmenu="onRowContextMenu(entry.row)"
|
|
1930
|
+
class="group border-b border-line/60 outline-none transition-colors last:border-0 hover:bg-surface-soft/60 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring data-[active]:bg-surface-soft/40"
|
|
1931
|
+
:class="isSelected(keyFor(entry.row, entry.index)) ? 'bg-brand-soft/40' : ''"
|
|
1932
|
+
:style="rowStyle"
|
|
1933
|
+
:aria-rowindex="virtualRowHeight ? rowOffset(entry) : undefined"
|
|
1934
|
+
v-bind="roving.itemProps(String(keyFor(entry.row, entry.index)))"
|
|
1935
|
+
:data-paint-key="selectable ? String(keyFor(entry.row, entry.index)) : undefined"
|
|
1936
|
+
:data-lp-context-row="rowMenu ? '' : undefined"
|
|
1937
|
+
@click="onRowClick(entry.row, keyFor(entry.row, entry.index))"
|
|
1938
|
+
@pointerdown="onRowPointerDown($event, keyFor(entry.row, entry.index))"
|
|
1939
|
+
@keydown="onRowKeydown"
|
|
1471
1940
|
>
|
|
1472
|
-
<
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1941
|
+
<td v-if="expansionOn" class="w-px p-0 text-center" :class="cellY" @click.stop @keydown.stop>
|
|
1942
|
+
<button
|
|
1943
|
+
v-if="canExpand(entry.row)"
|
|
1944
|
+
type="button"
|
|
1945
|
+
class="inline-grid size-6 place-items-center rounded-small text-muted outline-none transition-colors duration-fast hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
1946
|
+
:aria-expanded="expandedKeys.has(keyFor(entry.row, entry.index))"
|
|
1947
|
+
:aria-label="expandedKeys.has(keyFor(entry.row, entry.index)) ? text.collapseRow : text.expandRow"
|
|
1948
|
+
@click="toggleExpanded(keyFor(entry.row, entry.index))"
|
|
1949
|
+
>
|
|
1950
|
+
<LpIcon name="lucide:chevron-right" :size="14" class="transition-transform duration-fast motion-reduce:transition-none" :class="expandedKeys.has(keyFor(entry.row, entry.index)) ? 'rotate-90' : ''" />
|
|
1951
|
+
</button>
|
|
1952
|
+
</td>
|
|
1953
|
+
<td
|
|
1954
|
+
v-if="selectable"
|
|
1955
|
+
class="w-px cursor-pointer p-0 select-none"
|
|
1956
|
+
:class="cellY"
|
|
1957
|
+
@click.stop
|
|
1958
|
+
@pointerdown.stop="paint.onPointerDown($event, keyFor(entry.row, entry.index))"
|
|
1959
|
+
>
|
|
1960
|
+
<div class="lp-tick lp-tick-open" :class="ticksShown ? '' : 'lp-tick-cue'">
|
|
1961
|
+
<LpCheckbox
|
|
1962
|
+
:model-value="isSelected(keyFor(entry.row, entry.index))"
|
|
1963
|
+
@update:model-value="(v) => toggleRow(keyFor(entry.row, entry.index), v)"
|
|
1964
|
+
/>
|
|
1965
|
+
</div>
|
|
1966
|
+
</td>
|
|
1967
|
+
<td
|
|
1968
|
+
v-for="col in shownColumns"
|
|
1969
|
+
:key="col.key"
|
|
1970
|
+
class="px-4 text-ink"
|
|
1971
|
+
:class="[alignClass(col.align), cellY, col.nowrap ? 'whitespace-nowrap' : '', col.pin ? PIN_CELL : '']"
|
|
1972
|
+
:style="pinStyle(col)"
|
|
1973
|
+
>
|
|
1974
|
+
<slot :name="`cell-${col.key}`" :row="entry.row" :value="entry.row[col.key]" :open-menu="openRowMenu">
|
|
1975
|
+
<!-- The field is its own world: its clicks and keys are not the row's. -->
|
|
1976
|
+
<span v-if="isEditable(col, entry.row)" class="inline-block" @click.stop @pointerdown.stop @keydown.stop>
|
|
1977
|
+
<LpEditable
|
|
1978
|
+
:model-value="String(entry.row[col.key] ?? '')"
|
|
1979
|
+
:label="col.label"
|
|
1980
|
+
size="sm"
|
|
1981
|
+
:validate="(value) => col.validateEdit?.(value, entry.row)"
|
|
1982
|
+
:save="(value) => saveCell(col, entry.row, value)"
|
|
1983
|
+
/>
|
|
1984
|
+
</span>
|
|
1985
|
+
<template v-else>{{ entry.row[col.key] }}</template>
|
|
1986
|
+
</slot>
|
|
1987
|
+
</td>
|
|
1988
|
+
</tr>
|
|
1989
|
+
</template>
|
|
1990
|
+
<tr v-if="rowWindowed?.after" key="lp-virtual-after" aria-hidden="true">
|
|
1991
|
+
<td :colspan="colSpan" class="p-0" :style="{ height: `${rowWindowed.after}px` }" />
|
|
1476
1992
|
</tr>
|
|
1477
1993
|
</TransitionGroup>
|
|
1478
1994
|
</table>
|