@leavepulse/ui 0.86.2 → 0.87.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{LpTable-CEFsRz5J.css → LpTable-BqZNpWgC.css} +51 -19
- package/dist/chunks/{ContextMenuItems-D15cGEw9.js → ContextMenuItems-D0jObgzN.js} +27 -3
- package/dist/chunks/{DropzoneLinkField-Dz7fIaLh.js → DropzoneLinkField-DhwYRnvd.js} +1 -1
- package/dist/chunks/{FieldLeading-DZZLK02U.js → FieldLeading-k9EJOKOI.js} +1 -1
- package/dist/chunks/{FieldWrapper-BDDw1F9H.js → FieldWrapper-CVTPnpoG.js} +1 -1
- package/dist/chunks/{IconCloseButton-Cn_w4aN3.js → IconCloseButton-CmWOJAXG.js} +1 -1
- package/dist/chunks/{LayoutCanvas-Dr5eSfAu.js → LayoutCanvas-j4TZqx1A.js} +1 -1
- package/dist/chunks/{LayoutNode-CViJnx6q.js → LayoutNode-4Eaf8ZWj.js} +1 -1
- package/dist/chunks/{LpActionBar-C0LK-Aqs.js → LpActionBar-Cx_MSZl9.js} +1 -1
- package/dist/chunks/{LpAddressInput-B9sSvodU.js → LpAddressInput-CfqQR5jE.js} +7 -7
- package/dist/chunks/{LpAlert-Bpd7122M.js → LpAlert-CYtbpRYW.js} +1 -1
- package/dist/chunks/{LpAppShell-DpsQtXvG.js → LpAppShell-BQG98odS.js} +4 -4
- package/dist/chunks/{LpAutocomplete-CQID7Zc-.js → LpAutocomplete-VNID5zdy.js} +4 -4
- package/dist/chunks/{LpAvatar-DLkiMGKx.js → LpAvatar-CfLBqlY1.js} +1 -1
- package/dist/chunks/{LpBadge-RFp8uHFt.js → LpBadge-Bh5roBr_.js} +1 -1
- package/dist/chunks/{LpBottomNav-BcJVkAhh.js → LpBottomNav-9AqvZlRW.js} +1 -1
- package/dist/chunks/{LpBreadcrumbs-DJ_DB45r.js → LpBreadcrumbs-Zv1fn9e_.js} +1 -1
- package/dist/chunks/{LpCalendar-D6tfCr2G.js → LpCalendar-aACjUhpM.js} +1 -1
- package/dist/chunks/{LpCheckbox-DcXC2qd5.js → LpCheckbox-P7tEw8GO.js} +2 -2
- package/dist/chunks/{LpCodeBlock-kGT15Zou.js → LpCodeBlock-BNThgUdQ.js} +2 -2
- package/dist/chunks/{LpCodeDisplay-DlwzZo9o.js → LpCodeDisplay-DsiE-Qy9.js} +4 -4
- package/dist/chunks/{LpColorPicker-Cjna_Bio.js → LpColorPicker-EXpwcX-t.js} +3 -3
- package/dist/chunks/{LpConfirmDialog-bfcX5F9d.js → LpConfirmDialog-DPiPVRn_.js} +2 -2
- package/dist/chunks/{LpConfirmHost-DIgloymi.js → LpConfirmHost-Ddiljlog.js} +1 -1
- package/dist/chunks/{LpContextMenu-CUFZIQX2.js → LpContextMenu-DYpDm6iI.js} +2 -2
- package/dist/chunks/{LpContextSwitcher-CMweLbv2.js → LpContextSwitcher-BPDSkKyA.js} +2 -2
- package/dist/chunks/{LpCopyButton-TGoE43mE.js → LpCopyButton-CxX682t4.js} +1 -1
- package/dist/chunks/{LpDataList-Due909Cy.js → LpDataList-DCtF7krm.js} +2 -2
- package/dist/chunks/{LpDatePicker-B5gzwI3T.js → LpDatePicker-BElEHVm6.js} +5 -5
- package/dist/chunks/{LpDateRangePicker-CwNnpyoL.js → LpDateRangePicker-vLt-drEu.js} +3 -3
- package/dist/chunks/{LpDisclosure-CsJ7nnFz.js → LpDisclosure-D4JZ-o8K.js} +1 -1
- package/dist/chunks/{LpDrawer-C1q2bl1r.js → LpDrawer-C97k-3L2.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-DRABI-Bl.js → LpDropdownMenu-BPEsqZCN.js} +2 -2
- package/dist/chunks/{LpEditable-DCPdDDBb.js → LpEditable-CeRAzYMv.js} +2 -2
- package/dist/chunks/{LpEmptyState-DT8U4R8L.js → LpEmptyState-DrHBR3yh.js} +1 -1
- package/dist/chunks/{LpFieldNotice-mcmO-O-J.js → LpFieldNotice-DUPo1Yii.js} +1 -1
- package/dist/chunks/{LpFileTree-B365yOLv.js → LpFileTree-BcQgsw4e.js} +4 -4
- package/dist/chunks/{LpFileTreeNode-DfvIGmSz.js → LpFileTreeNode-Dk2WqkPF.js} +4 -4
- package/dist/chunks/{LpFormField-C5GOuOY-.js → LpFormField-jbM7K1Hg.js} +1 -1
- package/dist/chunks/{LpHintLayer-B0choo1r.js → LpHintLayer-CUSRLtLu.js} +1 -1
- package/dist/chunks/{LpHints-BEetUVTl.js → LpHints-BF40CP4_.js} +3 -3
- package/dist/chunks/{LpIcon-DCGhNsCU.js → LpIcon-CxHFPwKt.js} +1 -1
- package/dist/chunks/{LpInfraNode-BsExhdPE.js → LpInfraNode-BYiMFDnA.js} +2 -2
- package/dist/chunks/{LpInput-CKMblRtp.js → LpInput-BU5IsOlE.js} +3 -3
- package/dist/chunks/{LpInspector-BNxjuWMP.js → LpInspector-CEko6-pS.js} +4 -4
- package/dist/chunks/{LpLightbox-DVyiB4dw.js → LpLightbox-REei-y5e.js} +4 -4
- package/dist/chunks/{LpLogInspector-Yj310k4n.js → LpLogInspector-Bwz351LY.js} +2 -2
- package/dist/chunks/{LpLogViewer-Bki-IdNC.js → LpLogViewer-BCkqdH66.js} +6 -6
- package/dist/chunks/{LpMapPicker-CnpJhiY0.js → LpMapPicker-DVfiy3rR.js} +1 -1
- package/dist/chunks/{LpMediaGallery-OJemmbV7.js → LpMediaGallery-BF1Bch3v.js} +4 -4
- package/dist/chunks/{LpModal-B5y9_LgA.js → LpModal-BK2T5uIG.js} +1 -1
- package/dist/chunks/{LpModalNav-Er99S_AP.js → LpModalNav-Dk4eHpg6.js} +3 -3
- package/dist/chunks/{LpNotificationBell-DmR1xDd-.js → LpNotificationBell-CMwUXhFg.js} +4 -4
- package/dist/chunks/{LpNotificationFeed-DK7D1f-v.js → LpNotificationFeed-BXsRp9jm.js} +4 -4
- package/dist/chunks/{LpNumberField-BeYT7zzt.js → LpNumberField-QUMXvpqx.js} +3 -3
- package/dist/chunks/{LpOtpInput-DqwY-U5R.js → LpOtpInput-C14XagVB.js} +1 -1
- package/dist/chunks/{LpPagination-V1wEhu1K.js → LpPagination-DGLntZJN.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-CWFz4H2D.js → LpPaletteTheme-wiJtP5hV.js} +2 -2
- package/dist/chunks/{LpPasswordInput-LaOXNYuV.js → LpPasswordInput-W9AZfzaL.js} +2 -2
- package/dist/chunks/{LpPhoneInput-D7Tq2iaz.js → LpPhoneInput-DsRv-Qla.js} +3 -3
- package/dist/chunks/{LpPie-CAsGO9jE.js → LpPie-pbrNFbRP.js} +2 -2
- package/dist/chunks/{LpPipeline-D-uPcbMN.js → LpPipeline-BHWmvOiv.js} +2 -2
- package/dist/chunks/{LpQuota-BR8ck2a9.js → LpQuota-BaCq5YOQ.js} +1 -1
- package/dist/chunks/{LpRadioGroup-DSOCL9MD.js → LpRadioGroup-D94KMStE.js} +2 -2
- package/dist/chunks/{LpRangeCalendar-QepHrmba.js → LpRangeCalendar-C9gkNMYw.js} +1 -1
- package/dist/chunks/{LpRangeSlider-BaAM7XIs.js → LpRangeSlider-CkP9hYUC.js} +1 -1
- package/dist/chunks/{LpRepeater-BKq5XPh4.js → LpRepeater-DLLcbQ6J.js} +2 -2
- package/dist/chunks/{LpSecretField-Dp-c7wWc.js → LpSecretField-Au7ZQTtm.js} +5 -5
- package/dist/chunks/{LpSection-BGzlKIWO.js → LpSection-RKCwB0xN.js} +1 -1
- package/dist/chunks/{LpSegmented-CiaF9DUK.js → LpSegmented-DD8MPj4j.js} +2 -2
- package/dist/chunks/{LpSelect-CB6Xbnfk.js → LpSelect-BeXz1TWh.js} +4 -4
- package/dist/chunks/{LpSidebar-CjE90o3s.js → LpSidebar-DUFlf6Uu.js} +4 -4
- package/dist/chunks/{LpSidebarNav-Bgz2tXSq.js → LpSidebarNav-C34YEZIR.js} +3 -3
- package/dist/chunks/{LpSidebarRow-CDBnvvd8.js → LpSidebarRow-BeEGgsZi.js} +3 -3
- package/dist/chunks/{LpSlider-DTb_Ivkm.js → LpSlider-BCy5rjwK.js} +1 -1
- package/dist/chunks/{LpStat-DM5Wfb6K.js → LpStat-eVz7iUfD.js} +2 -2
- package/dist/chunks/{LpStatus-_fIwWXQV.js → LpStatus-C6GqSHJj.js} +1 -1
- package/dist/chunks/{LpStepMark-iDZw1DYf.js → LpStepMark-CPGVj1XG.js} +1 -1
- package/dist/chunks/{LpStepper-CjC3cp1F.js → LpStepper-DBNRA6Q2.js} +1 -1
- package/dist/chunks/{LpSwitch-BNHyYDhX.js → LpSwitch-D7hmVsif.js} +1 -1
- package/dist/chunks/{LpTabFace-gATEe9pQ.js → LpTabFace-uoZgD_SN.js} +2 -2
- package/dist/chunks/{LpTableFilter-B_BFoBqS.js → LpTableFilter-BGFNKyUX.js} +2 -2
- package/dist/chunks/{LpTagsInput-By7LMb47.js → LpTagsInput-D9uOSlvf.js} +5 -5
- package/dist/chunks/{LpTextarea-Ck6RAfzu.js → LpTextarea-DwE-lQt4.js} +1 -1
- package/dist/chunks/{LpThemeStudio-RyI-SWiw.js → LpThemeStudio-COn2H76d.js} +5 -5
- package/dist/chunks/{LpThemeSwitcher-BZYJ0AXy.js → LpThemeSwitcher-B3R3nNid.js} +3 -3
- package/dist/chunks/{LpTimeField-BTKJVeNt.js → LpTimeField-Mi8H1g_9.js} +2 -2
- package/dist/chunks/{LpToaster-YcLJFSsH.js → LpToaster-DOJn6Vey.js} +2 -2
- package/dist/chunks/{LpTopologyCanvas-paC1Dup-.js → LpTopologyCanvas-5RFMP4gR.js} +4 -4
- package/dist/chunks/{LpTour-D_AN_SsZ.js → LpTour-DUI_hoPv.js} +1 -1
- package/dist/chunks/{LpTruncate-DCZjfcE-.js → LpTruncate-CynJhsEw.js} +1 -1
- package/dist/chunks/{LpVideoCompress-BfulQ8Rs.js → LpVideoCompress-CPUxXh-c.js} +1 -1
- package/dist/chunks/{LpVideoPlayer--87I5Kqu.js → LpVideoPlayer-BbCjEQAx.js} +4 -4
- package/dist/chunks/{LpWindow-Bo5qQgVY.js → LpWindow-Dp_0nNqg.js} +2 -2
- package/dist/chunks/{LpWorkspaceSwitcher-D50sHs9N.js → LpWorkspaceSwitcher-bjkncjSl.js} +3 -3
- package/dist/chunks/{MenuTitle-DCHcfyPH.js → MenuTitle-YcSjF8xo.js} +1 -1
- package/dist/chunks/{OptionContent-o58kS7Zh.js → OptionContent-DZb5eKRV.js} +1 -1
- package/dist/chunks/{PickerField-DNo1k89A.js → PickerField-CjOTUXDK.js} +1 -1
- package/dist/chunks/{RevealToggle-w7-BMcgC.js → RevealToggle-Bl1NcfxB.js} +1 -1
- package/dist/chunks/{TourCard-0yYxSdd4.js → TourCard-BovXtMbO.js} +1 -1
- package/dist/chunks/{VideoThumb-DL_QkAJY.js → VideoThumb-BfDkIXqE.js} +1 -1
- package/dist/chunks/{VideoVolume-D-2TmsVj.js → VideoVolume-DE3i1ree.js} +2 -2
- package/dist/chunks/{icons-V0k93Puc.js → icons-C8ccRCKw.js} +1 -1
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldLeading.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/IconCloseButton.vue.js +1 -1
- 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/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- 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.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpChart.vue.js +2 -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/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +2 -2
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.d.ts +7 -0
- 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/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +2 -2
- 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.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +1 -1
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpInspector.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLogInspector.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNotificationFeed.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.js +1 -1
- package/dist/components/LpPipeline.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/LpRepeater.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/LpShortcutsDialog.vue.js +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/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/LpSwitch.vue.js +1 -1
- package/dist/components/LpTabFace.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +76 -1
- package/dist/components/LpTable.vue.js +516 -102
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.js +2 -2
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.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/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpVideoCompress.vue.js +1 -1
- package/dist/components/LpVideoPlayer.vue.js +1 -1
- package/dist/components/LpVideoTrim.vue.js +4 -4
- package/dist/components/LpWindow.vue.js +1 -1
- package/dist/components/LpWorkspaceSwitcher.vue.js +1 -1
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.js +1 -1
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/VideoFlash.vue.js +1 -1
- package/dist/components/VideoThumb.vue.js +1 -1
- package/dist/components/VideoVolume.vue.js +1 -1
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/tableFeatures.d.ts +9 -0
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +90 -85
- package/package.json +1 -1
- package/src/components/ContextMenuItems.vue +22 -0
- package/src/components/LpContextMenu.vue +7 -0
- package/src/components/LpTable.vue +401 -3
- package/src/components/tableFeatures.ts +10 -0
- package/src/index.ts +1 -1
- package/src/kit-icons.ts +1 -1
- package/src/locales/uk.ts +5 -0
|
@@ -22,6 +22,8 @@ import LpScrollArea from "./LpScrollArea.vue"
|
|
|
22
22
|
import LpRollingText from "./LpRollingText.vue"
|
|
23
23
|
import LpShift from "./LpShift.vue"
|
|
24
24
|
import LpTableFilter, { type TableFilterOption } from "./LpTableFilter.vue"
|
|
25
|
+
import LpDropdownMenu from "./LpDropdownMenu.vue"
|
|
26
|
+
import LpSegmented from "./LpSegmented.vue"
|
|
25
27
|
import { useKitStorage } from "../composables/usePersisted"
|
|
26
28
|
import { useAsyncTask } from "../composables/useAsyncTask"
|
|
27
29
|
import { useTableFacets } from "./tableFacets"
|
|
@@ -41,6 +43,7 @@ import {
|
|
|
41
43
|
sortByKeys,
|
|
42
44
|
type ColumnPin,
|
|
43
45
|
type ColumnPreset,
|
|
46
|
+
type TableLayout,
|
|
44
47
|
} from "./tableFeatures"
|
|
45
48
|
import { devWarn } from "../composables/devWarn"
|
|
46
49
|
import { startPointerDrag } from "../composables/pointerDrag"
|
|
@@ -194,6 +197,11 @@ export interface TableLabels {
|
|
|
194
197
|
groupCount: string
|
|
195
198
|
expandGroup: string
|
|
196
199
|
collapseGroup: string
|
|
200
|
+
layoutTable: string
|
|
201
|
+
layoutCards: string
|
|
202
|
+
layoutList: string
|
|
203
|
+
layoutSwitch: string
|
|
204
|
+
sortBy: string
|
|
197
205
|
}
|
|
198
206
|
|
|
199
207
|
export interface TableExportOptions extends CsvOptions {
|
|
@@ -241,6 +249,7 @@ export interface TablePage<Row> {
|
|
|
241
249
|
type RowKey = string | number
|
|
242
250
|
|
|
243
251
|
|
|
252
|
+
|
|
244
253
|
const props = withDefaults(
|
|
245
254
|
defineProps<{
|
|
246
255
|
/** Publish loaded rows and existing row actions to the enclosing command scope. */
|
|
@@ -411,6 +420,30 @@ const props = withDefaults(
|
|
|
411
420
|
virtualRowHeight?: number
|
|
412
421
|
/** Rows drawn beyond each edge of the view. */
|
|
413
422
|
virtualOverscan?: number
|
|
423
|
+
/**
|
|
424
|
+
* How the rows are drawn (v-model:layout): `table`, `cards` or `list`. Left
|
|
425
|
+
* unset the table keeps the choice itself, remembered with `persist`.
|
|
426
|
+
*/
|
|
427
|
+
layout?: TableLayout
|
|
428
|
+
/**
|
|
429
|
+
* The layouts the user can switch between, offered as a switch in the bar
|
|
430
|
+
* above the rows. One layout, or none given, means no switch.
|
|
431
|
+
*/
|
|
432
|
+
layouts?: readonly TableLayout[]
|
|
433
|
+
/**
|
|
434
|
+
* Below this width in px (the table's own, not the window's) a `table`
|
|
435
|
+
* layout is drawn as a `list`: a phone gets one line per row instead of a
|
|
436
|
+
* table that loses its columns to `hideBelow` or scrolls sideways.
|
|
437
|
+
*/
|
|
438
|
+
layoutBelow?: number
|
|
439
|
+
/**
|
|
440
|
+
* `framed` (default) draws the rows on a surface with a border; `plain`
|
|
441
|
+
* leaves the frame out, for a table that already sits in a card or a
|
|
442
|
+
* section of its own.
|
|
443
|
+
*/
|
|
444
|
+
variant?: "framed" | "plain"
|
|
445
|
+
/** The narrowest a card may get in the `cards` layout; the grid fits as many as fit. */
|
|
446
|
+
cardWidth?: string
|
|
414
447
|
}>(),
|
|
415
448
|
{ palette: undefined,
|
|
416
449
|
rows: () => [],
|
|
@@ -436,6 +469,11 @@ const props = withDefaults(
|
|
|
436
469
|
expanded: undefined,
|
|
437
470
|
virtualRowHeight: undefined,
|
|
438
471
|
virtualOverscan: 6,
|
|
472
|
+
layout: undefined,
|
|
473
|
+
layouts: undefined,
|
|
474
|
+
layoutBelow: undefined,
|
|
475
|
+
variant: "framed",
|
|
476
|
+
cardWidth: "16rem",
|
|
439
477
|
},
|
|
440
478
|
)
|
|
441
479
|
|
|
@@ -482,6 +520,11 @@ const labelSet = useLabels<TableLabels>(
|
|
|
482
520
|
groupCount: "{label} ({count})",
|
|
483
521
|
expandGroup: "Expand {label}",
|
|
484
522
|
collapseGroup: "Collapse {label}",
|
|
523
|
+
layoutTable: "Table",
|
|
524
|
+
layoutCards: "Cards",
|
|
525
|
+
layoutList: "List",
|
|
526
|
+
layoutSwitch: "View",
|
|
527
|
+
sortBy: "Sort",
|
|
485
528
|
},
|
|
486
529
|
() => props.labels,
|
|
487
530
|
)
|
|
@@ -528,6 +571,7 @@ const emit = defineEmits<{
|
|
|
528
571
|
(e: "update:expanded", value: RowKey[]): void
|
|
529
572
|
(e: "cellEdit", edit: TableCellEdit<T>): void
|
|
530
573
|
(e: "rowClick", row: T): void
|
|
574
|
+
(e: "update:layout", value: TableLayout): void
|
|
531
575
|
}>()
|
|
532
576
|
|
|
533
577
|
function keyFor(row: T, index: number): RowKey {
|
|
@@ -1174,6 +1218,14 @@ onMounted(() => {
|
|
|
1174
1218
|
if (state.hidden) writeHidden(mergeRestored(hiddenValues.value, state.hidden))
|
|
1175
1219
|
if (state.columns) hiddenColumns.value = new Set(state.columns)
|
|
1176
1220
|
}
|
|
1221
|
+
// Kept beside the table state rather than in it: the layout is a choice
|
|
1222
|
+
// about how to look, and an older table state must not lose its sort and
|
|
1223
|
+
// filters to a field it never had.
|
|
1224
|
+
const layout = store.get(`${storageKey.value}:layout`)
|
|
1225
|
+
if ((layout === "table" || layout === "cards" || layout === "list") && (!props.layouts || props.layouts.includes(layout))) {
|
|
1226
|
+
ownLayout.value = layout
|
|
1227
|
+
if (props.layout !== undefined && props.layout !== layout) emit("update:layout", layout)
|
|
1228
|
+
}
|
|
1177
1229
|
}
|
|
1178
1230
|
restored.value = true
|
|
1179
1231
|
})
|
|
@@ -1389,6 +1441,7 @@ const showBar = computed(
|
|
|
1389
1441
|
props.summary ||
|
|
1390
1442
|
props.exportable ||
|
|
1391
1443
|
!!slots.toolbar ||
|
|
1444
|
+
layoutChoices.value.length > 0 ||
|
|
1392
1445
|
(!!props.selectable && props.selected.length > 0) ||
|
|
1393
1446
|
activeFilters.value.length > 0,
|
|
1394
1447
|
)
|
|
@@ -1401,6 +1454,102 @@ const filteredOut = computed(() => {
|
|
|
1401
1454
|
|
|
1402
1455
|
const cellY = computed(() => (props.density === "compact" ? "py-1.5" : "py-3"))
|
|
1403
1456
|
|
|
1457
|
+
// ── layouts ──────────────────────────────────────────────────
|
|
1458
|
+
//
|
|
1459
|
+
// One set of rows and tools, drawn three ways. Everything above this line
|
|
1460
|
+
// (sort, filters, selection, the menu, export, the palette) reads
|
|
1461
|
+
// `displayRows` and knows nothing of how they are drawn, so a card grid or a
|
|
1462
|
+
// list gets all of it for free instead of every page building its own.
|
|
1463
|
+
const ownLayout = ref<TableLayout>("table")
|
|
1464
|
+
const chosenLayout = computed<TableLayout>(() => props.layout ?? ownLayout.value)
|
|
1465
|
+
|
|
1466
|
+
// The table's own width, not the window's: the same table sits in a wide page
|
|
1467
|
+
// and in a narrow side panel.
|
|
1468
|
+
const frameWidth = ref(Number.POSITIVE_INFINITY)
|
|
1469
|
+
useResizeObserver(frameEl, (_, el) => (frameWidth.value = el.clientWidth), { immediate: true })
|
|
1470
|
+
|
|
1471
|
+
const activeLayout = computed<TableLayout>(() =>
|
|
1472
|
+
chosenLayout.value === "table" && props.layoutBelow && frameWidth.value < props.layoutBelow
|
|
1473
|
+
? "list"
|
|
1474
|
+
: chosenLayout.value,
|
|
1475
|
+
)
|
|
1476
|
+
// While the width forces a list the switch would change nothing on screen, so
|
|
1477
|
+
// it steps aside until there is room for the choice again.
|
|
1478
|
+
const forcedList = computed(() => chosenLayout.value === "table" && activeLayout.value === "list")
|
|
1479
|
+
const layoutChoices = computed(() =>
|
|
1480
|
+
props.layouts && props.layouts.length > 1 && !forcedList.value ? props.layouts : [],
|
|
1481
|
+
)
|
|
1482
|
+
const LAYOUT_ICONS: Record<TableLayout, string> = {
|
|
1483
|
+
table: "lucide:table-2",
|
|
1484
|
+
cards: "lucide:layout-grid",
|
|
1485
|
+
list: "lucide:list",
|
|
1486
|
+
}
|
|
1487
|
+
const layoutOptions = computed(() =>
|
|
1488
|
+
layoutChoices.value.map((value) => {
|
|
1489
|
+
const label = text.value[`layout${value[0]!.toUpperCase()}${value.slice(1)}`] ?? value
|
|
1490
|
+
return { value, icon: LAYOUT_ICONS[value], ariaLabel: label }
|
|
1491
|
+
}),
|
|
1492
|
+
)
|
|
1493
|
+
|
|
1494
|
+
function chooseLayout(next: string) {
|
|
1495
|
+
const layout = next as TableLayout
|
|
1496
|
+
if (layout === chosenLayout.value) return
|
|
1497
|
+
ownLayout.value = layout
|
|
1498
|
+
emit("update:layout", layout)
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// Without a header the sort and the column filters need a home: the bar.
|
|
1502
|
+
const headerless = computed(() => activeLayout.value !== "table")
|
|
1503
|
+
const sortableColumns = computed(() => props.columns.filter((col) => col.sortable && col.label))
|
|
1504
|
+
const filterableColumns = computed(() => props.columns.filter((col) => col.filterable && col.label))
|
|
1505
|
+
|
|
1506
|
+
function sortMenu(): ContextMenuItemDef[] {
|
|
1507
|
+
const current = activeSort.value
|
|
1508
|
+
const dir = current?.dir ?? "asc"
|
|
1509
|
+
return [
|
|
1510
|
+
// The columns, the one sorted by ticked; picking one keeps the direction.
|
|
1511
|
+
...sortableColumns.value.map((col): ContextMenuItemDef => ({
|
|
1512
|
+
label: col.label,
|
|
1513
|
+
selected: current?.key === col.key,
|
|
1514
|
+
onSelect: () => writeSorts([{ key: col.key, dir }]),
|
|
1515
|
+
})),
|
|
1516
|
+
{
|
|
1517
|
+
label: text.value.sortAsc,
|
|
1518
|
+
icon: "lucide:arrow-up",
|
|
1519
|
+
separatorBefore: true,
|
|
1520
|
+
disabled: !current,
|
|
1521
|
+
selected: current?.dir === "asc",
|
|
1522
|
+
onSelect: () => current && writeSorts([{ key: current.key, dir: "asc" }]),
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
label: text.value.sortDesc,
|
|
1526
|
+
icon: "lucide:arrow-down",
|
|
1527
|
+
disabled: !current,
|
|
1528
|
+
selected: current?.dir === "desc",
|
|
1529
|
+
onSelect: () => current && writeSorts([{ key: current.key, dir: "desc" }]),
|
|
1530
|
+
},
|
|
1531
|
+
{ label: text.value.sortClear, icon: "lucide:x", separatorBefore: true, disabled: !current, onSelect: () => writeSorts([]) },
|
|
1532
|
+
]
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
const sortSummary = computed(() => {
|
|
1536
|
+
const current = activeSort.value
|
|
1537
|
+
const col = current && props.columns.find((c) => c.key === current.key)
|
|
1538
|
+
return col ? col.label : text.value.sortBy
|
|
1539
|
+
})
|
|
1540
|
+
|
|
1541
|
+
// A card shows the columns the user keeps, not the ones a narrow table drops:
|
|
1542
|
+
// the card has its own room, and `hideBelow` was a decision about a row.
|
|
1543
|
+
const cardColumns = computed(() =>
|
|
1544
|
+
orderPinned(props.columns.filter((col) => !hiddenColumns.value.has(col.key))),
|
|
1545
|
+
)
|
|
1546
|
+
const titleColumn = computed(() => cardColumns.value[0])
|
|
1547
|
+
const fieldColumns = computed(() => cardColumns.value.slice(1).filter((col) => col.label))
|
|
1548
|
+
|
|
1549
|
+
watch(chosenLayout, (layout) => {
|
|
1550
|
+
if (storageKey.value && restored.value) store.set(`${storageKey.value}:layout`, layout)
|
|
1551
|
+
})
|
|
1552
|
+
|
|
1404
1553
|
/*
|
|
1405
1554
|
* Skeleton bars of uneven width, the way real cells are: a column of identical
|
|
1406
1555
|
* bars reads as a striped background rather than as rows of text. Fixed per
|
|
@@ -1484,7 +1633,7 @@ watch(viewportEl, (el, _, onCleanup) => {
|
|
|
1484
1633
|
useResizeObserver(viewportEl, (_, el) => (viewportHeight.value = el.clientHeight), { immediate: true })
|
|
1485
1634
|
|
|
1486
1635
|
const rowWindowed = computed(() =>
|
|
1487
|
-
props.virtualRowHeight
|
|
1636
|
+
props.virtualRowHeight && !headerless.value
|
|
1488
1637
|
? rowWindow(scrollTop.value, viewportHeight.value, props.virtualRowHeight, allEntries.value.length, props.virtualOverscan)
|
|
1489
1638
|
: null,
|
|
1490
1639
|
)
|
|
@@ -1627,6 +1776,47 @@ usePaletteCommands(() => {
|
|
|
1627
1776
|
<div v-if="$slots.toolbar" class="flex min-w-0 flex-wrap items-center gap-2 text-sm">
|
|
1628
1777
|
<slot name="toolbar" />
|
|
1629
1778
|
</div>
|
|
1779
|
+
<!-- Without a header the sort and the column funnels live here: the same
|
|
1780
|
+
menu and the same filter, reached from the bar instead. -->
|
|
1781
|
+
<div v-if="headerless && (sortableColumns.length || filterableColumns.length)" class="flex flex-wrap items-center gap-1 text-sm">
|
|
1782
|
+
<LpDropdownMenu v-if="sortableColumns.length" :items="sortMenu()">
|
|
1783
|
+
<template #trigger>
|
|
1784
|
+
<LpButton variant="ghost" size="sm" :aria-label="text.sortBy">
|
|
1785
|
+
<LpIcon
|
|
1786
|
+
:name="activeSort ? (activeSort.dir === 'asc' ? 'lucide:arrow-up' : 'lucide:arrow-down') : 'lucide:arrow-up-down'"
|
|
1787
|
+
:size="14"
|
|
1788
|
+
/>
|
|
1789
|
+
{{ sortSummary }}
|
|
1790
|
+
</LpButton>
|
|
1791
|
+
</template>
|
|
1792
|
+
</LpDropdownMenu>
|
|
1793
|
+
<LpPopover
|
|
1794
|
+
v-for="col in filterableColumns"
|
|
1795
|
+
:key="col.key"
|
|
1796
|
+
align="start"
|
|
1797
|
+
panel-class="w-72"
|
|
1798
|
+
:aria-label="text.filterColumn.replace('{column}', col.label)"
|
|
1799
|
+
>
|
|
1800
|
+
<template #trigger>
|
|
1801
|
+
<LpButton
|
|
1802
|
+
variant="ghost"
|
|
1803
|
+
size="sm"
|
|
1804
|
+
:class="hiddenValues[col.key]?.size ? 'text-brand' : ''"
|
|
1805
|
+
:aria-label="text.filterColumn.replace('{column}', col.label)"
|
|
1806
|
+
:data-lp-filter="col.key"
|
|
1807
|
+
>
|
|
1808
|
+
<LpIcon :name="hiddenValues[col.key]?.size ? 'lucide:filter-x' : 'lucide:filter'" :size="14" />
|
|
1809
|
+
{{ col.label }}
|
|
1810
|
+
</LpButton>
|
|
1811
|
+
</template>
|
|
1812
|
+
<LpTableFilter
|
|
1813
|
+
:options="facetOptions.get(col.key)?.value ?? []"
|
|
1814
|
+
:hidden="hiddenValues[col.key] ?? new Set()"
|
|
1815
|
+
:labels="filterLabels()"
|
|
1816
|
+
@update:hidden="(next) => setHidden(col.key, next)"
|
|
1817
|
+
/>
|
|
1818
|
+
</LpPopover>
|
|
1819
|
+
</div>
|
|
1630
1820
|
<div v-if="activeFilters.length" class="flex min-w-0 flex-1 flex-wrap items-center gap-1.5">
|
|
1631
1821
|
<LpBadge
|
|
1632
1822
|
v-for="f in activeFilters"
|
|
@@ -1696,6 +1886,14 @@ usePaletteCommands(() => {
|
|
|
1696
1886
|
<slot name="bulk" :selected="selected" :all-matching="allMatching" :clear="clearSelection" />
|
|
1697
1887
|
</span>
|
|
1698
1888
|
</Transition>
|
|
1889
|
+
<LpSegmented
|
|
1890
|
+
v-if="layoutOptions.length"
|
|
1891
|
+
:model-value="chosenLayout"
|
|
1892
|
+
:options="layoutOptions"
|
|
1893
|
+
size="sm"
|
|
1894
|
+
:aria-label="text.layoutSwitch"
|
|
1895
|
+
@update:model-value="chooseLayout"
|
|
1896
|
+
/>
|
|
1699
1897
|
<LpButton v-if="exportable" variant="ghost" size="sm" @click="exportCsv()">
|
|
1700
1898
|
<LpIcon name="lucide:download" :size="14" />
|
|
1701
1899
|
{{ text.exportCsv }}
|
|
@@ -1718,7 +1916,9 @@ usePaletteCommands(() => {
|
|
|
1718
1916
|
<LpScrollArea
|
|
1719
1917
|
v-bind="scrollAttrs"
|
|
1720
1918
|
:fade="false"
|
|
1721
|
-
|
|
1919
|
+
v-if="!headerless"
|
|
1920
|
+
class="min-h-0 flex-1"
|
|
1921
|
+
:class="variant === 'framed' ? 'rounded-card border border-line bg-surface' : ''"
|
|
1722
1922
|
:style="load && floor ? { minHeight: `${floor + 2}px` } : undefined"
|
|
1723
1923
|
:bar-inset-top="barInsetTop"
|
|
1724
1924
|
>
|
|
@@ -1790,7 +1990,7 @@ usePaletteCommands(() => {
|
|
|
1790
1990
|
<LpContextMenu v-if="col.sortable || col.filterable" :items="headerMenu(col)">
|
|
1791
1991
|
<button
|
|
1792
1992
|
type="button"
|
|
1793
|
-
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"
|
|
1993
|
+
class="group/sort -mx-1 inline-flex items-center gap-1 rounded-small px-1 uppercase outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
1794
1994
|
:class="[
|
|
1795
1995
|
col.align === 'right' ? 'flex-row-reverse' : '',
|
|
1796
1996
|
sortOf(col) ? 'text-ink' : '',
|
|
@@ -2016,6 +2216,169 @@ usePaletteCommands(() => {
|
|
|
2016
2216
|
</table>
|
|
2017
2217
|
</LpContextMenu>
|
|
2018
2218
|
</LpScrollArea>
|
|
2219
|
+
<!-- Cards and list: the same rows, menu, selection and keyboard, drawn
|
|
2220
|
+
without a header. The one menu wraps the lot, as it does the table. -->
|
|
2221
|
+
<LpContextMenu
|
|
2222
|
+
v-else
|
|
2223
|
+
:items="contextMenu"
|
|
2224
|
+
always
|
|
2225
|
+
:no-hint="!rowMenu"
|
|
2226
|
+
:title="menuHead?.title"
|
|
2227
|
+
:subtitle="menuHead?.subtitle"
|
|
2228
|
+
:title-icon="menuHead?.icon"
|
|
2229
|
+
:title-image="menuHead?.image"
|
|
2230
|
+
>
|
|
2231
|
+
<TransitionGroup
|
|
2232
|
+
:key="activeLayout"
|
|
2233
|
+
tag="div"
|
|
2234
|
+
name="lp-row"
|
|
2235
|
+
:css="animating"
|
|
2236
|
+
v-bind="scrollAttrs"
|
|
2237
|
+
class="min-h-0 transition-opacity"
|
|
2238
|
+
:class="[
|
|
2239
|
+
busy && displayRows.length ? 'opacity-60' : '',
|
|
2240
|
+
activeLayout === 'cards'
|
|
2241
|
+
? 'grid gap-3'
|
|
2242
|
+
: variant === 'framed' ? 'overflow-hidden rounded-card border border-line bg-surface' : '',
|
|
2243
|
+
]"
|
|
2244
|
+
:style="activeLayout === 'cards' ? { gridTemplateColumns: `repeat(auto-fill, minmax(min(${cardWidth}, 100%), 1fr))` } : undefined"
|
|
2245
|
+
:aria-busy="busy || undefined"
|
|
2246
|
+
:ref="(el: unknown) => roving.setContainer((el as { $el?: HTMLElement })?.$el ?? null)"
|
|
2247
|
+
>
|
|
2248
|
+
<template v-if="displayRows.length === 0 && busy">
|
|
2249
|
+
<div
|
|
2250
|
+
v-for="n in skeletonRows"
|
|
2251
|
+
:key="`lp-table-skeleton-${n}`"
|
|
2252
|
+
class="lp-row-empty"
|
|
2253
|
+
:class="activeLayout === 'cards' ? 'flex flex-col gap-3 rounded-card border border-line bg-surface p-4' : ['flex flex-col gap-2 border-b border-line/60 px-4 last:border-0', cellY]"
|
|
2254
|
+
>
|
|
2255
|
+
<LpSkeleton class="h-3.5" :style="{ width: skeletonWidth(n, 0) }" />
|
|
2256
|
+
<LpSkeleton class="h-3" :style="{ width: skeletonWidth(n, 1) }" />
|
|
2257
|
+
<LpSkeleton v-if="activeLayout === 'cards'" class="h-3" :style="{ width: skeletonWidth(n, 2) }" />
|
|
2258
|
+
<span v-if="n === 1" class="sr-only">{{ text.loading }}</span>
|
|
2259
|
+
</div>
|
|
2260
|
+
</template>
|
|
2261
|
+
<div
|
|
2262
|
+
v-else-if="displayRows.length === 0"
|
|
2263
|
+
key="lp-table-empty"
|
|
2264
|
+
class="lp-row-empty"
|
|
2265
|
+
:class="activeLayout === 'cards' ? 'col-span-full rounded-card border border-line bg-surface' : ''"
|
|
2266
|
+
>
|
|
2267
|
+
<LpEmptyState
|
|
2268
|
+
compact
|
|
2269
|
+
:icon="failed ? 'lucide:circle-alert' : filteredOut ? 'lucide:filter-x' : emptyIcon"
|
|
2270
|
+
:title="failed ? text.error : filteredOut ? text.noMatches : text.empty"
|
|
2271
|
+
>
|
|
2272
|
+
<LpButton v-if="filteredOut" variant="outline" size="sm" @click="resetFilters">
|
|
2273
|
+
{{ text.resetFilters }}
|
|
2274
|
+
</LpButton>
|
|
2275
|
+
</LpEmptyState>
|
|
2276
|
+
</div>
|
|
2277
|
+
<template v-for="entry in allEntries" :key="entry.id">
|
|
2278
|
+
<div
|
|
2279
|
+
v-if="entry.kind === 'group'"
|
|
2280
|
+
class="text-xs font-medium text-muted"
|
|
2281
|
+
:class="activeLayout === 'cards' ? 'col-span-full pt-2' : ['border-b border-line/60 bg-surface-soft/60 px-4', cellY]"
|
|
2282
|
+
>
|
|
2283
|
+
<slot name="group" :value="entry.value" :rows="entry.rows" :label="entry.label" :collapsed="entry.collapsed" :toggle="() => toggleGroup(entry.group)">
|
|
2284
|
+
<button
|
|
2285
|
+
type="button"
|
|
2286
|
+
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"
|
|
2287
|
+
:aria-expanded="!entry.collapsed"
|
|
2288
|
+
:aria-label="(entry.collapsed ? text.expandGroup : text.collapseGroup).replace('{label}', entry.label)"
|
|
2289
|
+
@click="toggleGroup(entry.group)"
|
|
2290
|
+
>
|
|
2291
|
+
<LpIcon name="lucide:chevron-right" :size="14" class="transition-transform duration-fast motion-reduce:transition-none" :class="entry.collapsed ? '' : 'rotate-90'" />
|
|
2292
|
+
{{ entry.label }}
|
|
2293
|
+
</button>
|
|
2294
|
+
</slot>
|
|
2295
|
+
</div>
|
|
2296
|
+
<div
|
|
2297
|
+
v-else-if="entry.kind === 'detail'"
|
|
2298
|
+
class="px-4 py-3"
|
|
2299
|
+
:class="activeLayout === 'cards' ? 'col-span-full' : 'border-b border-line/60 last:border-0'"
|
|
2300
|
+
data-lp-row-detail
|
|
2301
|
+
>
|
|
2302
|
+
<slot name="expanded" :row="entry.row" :close="() => toggleExpanded(keyFor(entry.row, entry.index))" />
|
|
2303
|
+
</div>
|
|
2304
|
+
<div
|
|
2305
|
+
v-else
|
|
2306
|
+
class="lp-item group relative outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring"
|
|
2307
|
+
:class="[
|
|
2308
|
+
activeLayout === 'cards'
|
|
2309
|
+
? ['flex min-w-0 flex-col overflow-hidden rounded-card border bg-surface hover:border-line-strong focus-visible:ring-offset-0', isSelected(keyFor(entry.row, entry.index)) ? 'border-brand bg-brand-soft/30' : 'border-line']
|
|
2310
|
+
: ['flex min-w-0 items-center gap-3 border-b border-line/60 px-4 last:border-0 hover:bg-surface-soft/60 focus-visible:ring-inset data-[active]:bg-surface-soft/40', cellY, isSelected(keyFor(entry.row, entry.index)) ? 'bg-brand-soft/40' : ''],
|
|
2311
|
+
]"
|
|
2312
|
+
v-bind="roving.itemProps(String(keyFor(entry.row, entry.index)))"
|
|
2313
|
+
:data-paint-key="selectable ? String(keyFor(entry.row, entry.index)) : undefined"
|
|
2314
|
+
:data-lp-context-row="rowMenu ? '' : undefined"
|
|
2315
|
+
@contextmenu="onRowContextMenu(entry.row)"
|
|
2316
|
+
@click="onRowClick(entry.row, keyFor(entry.row, entry.index))"
|
|
2317
|
+
@pointerdown="onRowPointerDown($event, keyFor(entry.row, entry.index))"
|
|
2318
|
+
@keydown="onRowKeydown"
|
|
2319
|
+
>
|
|
2320
|
+
<div
|
|
2321
|
+
v-if="selectable"
|
|
2322
|
+
class="cursor-pointer select-none"
|
|
2323
|
+
:class="activeLayout === 'cards' ? 'absolute top-2 right-2 z-[1]' : ['shrink-0 transition-[margin] duration-medium ease-[var(--ease-emphasized)] motion-reduce:transition-none', ticksShown ? '' : '-mr-3']"
|
|
2324
|
+
@click.stop
|
|
2325
|
+
@pointerdown.stop="paint.onPointerDown($event, keyFor(entry.row, entry.index))"
|
|
2326
|
+
>
|
|
2327
|
+
<div
|
|
2328
|
+
class="lp-tick"
|
|
2329
|
+
:class="activeLayout === 'cards' ? 'lp-tick-card' : ticksShown ? 'lp-tick-line' : ''"
|
|
2330
|
+
:data-cue="activeLayout === 'cards' && !ticksShown ? '' : undefined"
|
|
2331
|
+
:inert="activeLayout !== 'cards' && !ticksShown"
|
|
2332
|
+
>
|
|
2333
|
+
<LpCheckbox
|
|
2334
|
+
:model-value="isSelected(keyFor(entry.row, entry.index))"
|
|
2335
|
+
@update:model-value="(v) => toggleRow(keyFor(entry.row, entry.index), v)"
|
|
2336
|
+
/>
|
|
2337
|
+
</div>
|
|
2338
|
+
</div>
|
|
2339
|
+
<!-- `#card` draws the inside of a card; the frame, selection, focus
|
|
2340
|
+
and menu stay the table's, so every card answers alike. Without
|
|
2341
|
+
it the card is built from the columns and their cell slots: the
|
|
2342
|
+
first column names the row, the rest are its facts. -->
|
|
2343
|
+
<slot
|
|
2344
|
+
v-if="activeLayout === 'cards'"
|
|
2345
|
+
name="card"
|
|
2346
|
+
:row="entry.row"
|
|
2347
|
+
:selected="isSelected(keyFor(entry.row, entry.index))"
|
|
2348
|
+
:toggle="() => toggleRow(keyFor(entry.row, entry.index), !isSelected(keyFor(entry.row, entry.index)))"
|
|
2349
|
+
:open-menu="openRowMenu"
|
|
2350
|
+
>
|
|
2351
|
+
<div class="flex min-w-0 flex-col gap-2 p-4">
|
|
2352
|
+
<div v-if="titleColumn" class="min-w-0 truncate pr-6 font-medium text-ink">
|
|
2353
|
+
<slot :name="`cell-${titleColumn.key}`" :row="entry.row" :value="entry.row[titleColumn.key]" :open-menu="openRowMenu">{{ entry.row[titleColumn.key] }}</slot>
|
|
2354
|
+
</div>
|
|
2355
|
+
<dl v-if="fieldColumns.length" class="grid grid-cols-[auto_minmax(0,1fr)] items-baseline gap-x-3 gap-y-1 text-sm">
|
|
2356
|
+
<template v-for="col in fieldColumns" :key="col.key">
|
|
2357
|
+
<dt class="text-xs text-muted">{{ col.label }}</dt>
|
|
2358
|
+
<dd class="min-w-0 truncate text-ink">
|
|
2359
|
+
<slot :name="`cell-${col.key}`" :row="entry.row" :value="entry.row[col.key]" :open-menu="openRowMenu">{{ entry.row[col.key] }}</slot>
|
|
2360
|
+
</dd>
|
|
2361
|
+
</template>
|
|
2362
|
+
</dl>
|
|
2363
|
+
</div>
|
|
2364
|
+
</slot>
|
|
2365
|
+
<div v-else class="min-w-0 flex-1">
|
|
2366
|
+
<div v-if="titleColumn" class="min-w-0 truncate font-medium text-ink">
|
|
2367
|
+
<slot :name="`cell-${titleColumn.key}`" :row="entry.row" :value="entry.row[titleColumn.key]" :open-menu="openRowMenu">{{ entry.row[titleColumn.key] }}</slot>
|
|
2368
|
+
</div>
|
|
2369
|
+
<div v-if="fieldColumns.length" class="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted">
|
|
2370
|
+
<template v-for="(col, ci) in fieldColumns" :key="col.key">
|
|
2371
|
+
<span v-if="ci > 0" aria-hidden="true">·</span>
|
|
2372
|
+
<span class="min-w-0 truncate" :title="col.label">
|
|
2373
|
+
<slot :name="`cell-${col.key}`" :row="entry.row" :value="entry.row[col.key]" :open-menu="openRowMenu">{{ entry.row[col.key] }}</slot>
|
|
2374
|
+
</span>
|
|
2375
|
+
</template>
|
|
2376
|
+
</div>
|
|
2377
|
+
</div>
|
|
2378
|
+
</div>
|
|
2379
|
+
</template>
|
|
2380
|
+
</TransitionGroup>
|
|
2381
|
+
</LpContextMenu>
|
|
2019
2382
|
</LpShift>
|
|
2020
2383
|
<LpPagination
|
|
2021
2384
|
v-if="load && total > pageSize"
|
|
@@ -2132,6 +2495,41 @@ tr:hover .lp-tick-cue,
|
|
|
2132
2495
|
}
|
|
2133
2496
|
}
|
|
2134
2497
|
|
|
2498
|
+
/*
|
|
2499
|
+
* A list line's tick opens from nothing, as the table's column does, so lines
|
|
2500
|
+
* do not carry an empty gap until selection starts.
|
|
2501
|
+
*/
|
|
2502
|
+
.lp-tick-line {
|
|
2503
|
+
max-width: 2rem;
|
|
2504
|
+
opacity: 1;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
/*
|
|
2508
|
+
* A card's tick sits in its corner at its own size, with no column to hold
|
|
2509
|
+
* open; a cue shows it only while
|
|
2510
|
+
* the card is pointed at or holds focus, the same rule as a row's.
|
|
2511
|
+
*/
|
|
2512
|
+
.lp-tick-card {
|
|
2513
|
+
max-width: none;
|
|
2514
|
+
padding-inline: 0;
|
|
2515
|
+
opacity: 1;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
.lp-tick[data-cue] {
|
|
2519
|
+
opacity: 0;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
.lp-item:hover .lp-tick[data-cue],
|
|
2523
|
+
.lp-tick[data-cue]:focus-within {
|
|
2524
|
+
opacity: 1;
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
@media (hover: none) {
|
|
2528
|
+
.lp-item:hover .lp-tick[data-cue] {
|
|
2529
|
+
opacity: 0;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2135
2533
|
@media (prefers-reduced-motion: reduce) {
|
|
2136
2534
|
.lp-row-enter-active,
|
|
2137
2535
|
.lp-row-leave-active,
|
|
@@ -147,3 +147,13 @@ export function presetHidden(
|
|
|
147
147
|
// A preset naming nothing that exists must not leave a table with no columns.
|
|
148
148
|
return hidden.length === columns.length ? new Set() : new Set(hidden)
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* How the rows are drawn. The table's tools (sort, filters, search, selection,
|
|
153
|
+
* the row menu, export, persistence, the palette) are the same in each; only
|
|
154
|
+
* the drawing changes.
|
|
155
|
+
* - `table`: rows under a header.
|
|
156
|
+
* - `cards`: a grid of cards, for rows that are pictures or read best as a whole.
|
|
157
|
+
* - `list`: one compact line per row with no header, for narrow places.
|
|
158
|
+
*/
|
|
159
|
+
export type TableLayout = "table" | "cards" | "list"
|
package/src/index.ts
CHANGED
|
@@ -270,7 +270,7 @@ export { default as LpTable } from "./components/LpTable.vue"
|
|
|
270
270
|
export type { SortState, TableColumn, TablePage, TableQuery } from "./components/LpTable.vue"
|
|
271
271
|
export type { TableLabels } from "./components/LpTable.vue"
|
|
272
272
|
export type { TableCellEdit, TableExportOptions, TableSelectionToken } from "./components/LpTable.vue"
|
|
273
|
-
export type { ColumnPin, ColumnPreset } from "./components/tableFeatures"
|
|
273
|
+
export type { ColumnPin, ColumnPreset, TableLayout } from "./components/tableFeatures"
|
|
274
274
|
export { compareValues } from "./components/tableSort"
|
|
275
275
|
export { filterRows, showOnly, shownValues } from "./components/tableFilter"
|
|
276
276
|
export type { TableFilters } from "./components/tableFilter"
|