@leavepulse/ui 0.71.1 → 0.72.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/dist/assets/{LpCommandPalette-DgPZX0us.css → LpCommandPalette-Rtbg669l.css} +19 -19
- package/dist/assets/{LpNumberFlow-rZkwQwlU.css → LpNumberFlow-Chp9pEDe.css} +8 -0
- package/dist/assets/LpTabs-CDchzoRL.css +18 -0
- package/dist/chunks/{ContextMenuItems-eS7qx9gD.js → ContextMenuItems-BXumicbh.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-DkFhdfEJ.js → DropzoneLinkField-D2Rnkcys.js} +2 -2
- package/dist/chunks/{FieldWrapper-YzbvvC3M.js → FieldWrapper-DnA1rxZ8.js} +1 -1
- package/dist/chunks/{LayoutCanvas-Ba4A9QC5.js → LayoutCanvas-CNOAUk7D.js} +1 -1
- package/dist/chunks/{LayoutNode-CTTTLMDM.js → LayoutNode-DF5BMN0J.js} +1 -1
- package/dist/chunks/{LpActionBar-B7anGlkI.js → LpActionBar-DfiBL1dE.js} +3 -3
- package/dist/chunks/{LpAddressInput-CD0bzrt-.js → LpAddressInput-B1bB3F6M.js} +7 -7
- package/dist/chunks/{LpAlert-BHi7ARqu.js → LpAlert-CpRF9eYr.js} +3 -3
- package/dist/chunks/{LpAppShell-1RmBp6xQ.js → LpAppShell-D8Q0Jv9d.js} +68 -18
- package/dist/chunks/{LpAutocomplete-DDqe2M5g.js → LpAutocomplete-DOu7FN5P.js} +2 -2
- package/dist/chunks/{LpAvatar-DqfKCIdr.js → LpAvatar-Cg6Az3wz.js} +1 -1
- package/dist/chunks/{LpBadge-C8fKEmTF.js → LpBadge-vtCpsDmv.js} +3 -3
- package/dist/chunks/LpBottomNav-VXJf0cKc.js +89 -0
- package/dist/chunks/{LpBreadcrumbs-DhcoStFa.js → LpBreadcrumbs-BdzjQcql.js} +3 -3
- package/dist/chunks/{LpButton-BQDAFXBd.js → LpButton-Dk7eT4ro.js} +1 -1
- package/dist/chunks/{LpCalendar-NmrA47J9.js → LpCalendar-CuoWks-0.js} +1 -1
- package/dist/chunks/{LpCard-CqKLnEhl.js → LpCard-Ce3rw8zm.js} +1 -1
- package/dist/chunks/{LpCheckbox-BoOuLnGB.js → LpCheckbox-DLX4amba.js} +2 -2
- package/dist/chunks/{LpCodeBlock-Czu-fq1M.js → LpCodeBlock-j0W5bpmF.js} +2 -2
- package/dist/chunks/{LpCodeDisplay-BK2FHbtM.js → LpCodeDisplay-COdiv4xu.js} +6 -6
- package/dist/chunks/{LpColorPicker-Ee1DZiRd.js → LpColorPicker-1y1QJHZR.js} +56 -28
- package/dist/chunks/{LpCommandScope-ubtWDMa0.js → LpCommandScope-BlKcgBnK.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-i15pulMT.js → LpConfirmDialog-w1ni5nxh.js} +3 -3
- package/dist/chunks/{LpConfirmHost-D0oGavtd.js → LpConfirmHost-Br_-Gn-6.js} +1 -1
- package/dist/chunks/{LpContextMenu-DXpFJjlO.js → LpContextMenu-rx2tCjwk.js} +2 -2
- package/dist/chunks/{LpCopyButton-Dmq7DmFw.js → LpCopyButton-DaD1KCn6.js} +2 -2
- package/dist/chunks/{LpCountdown-DTdMF8DL.js → LpCountdown-Cgy0mVEq.js} +3 -3
- package/dist/chunks/{LpDataList-CFPtSsBB.js → LpDataList-CkcGFJgv.js} +3 -3
- package/dist/chunks/{LpDatePicker-cC9K-Pk8.js → LpDatePicker-CA0QTfBC.js} +4 -4
- package/dist/chunks/{LpDateRangePicker-BVQKPbvo.js → LpDateRangePicker-u3J51xXk.js} +2 -2
- package/dist/chunks/{LpDisclosure-hRnX3FWL.js → LpDisclosure-Cpa7Hc5T.js} +1 -1
- package/dist/chunks/{LpDrawer-u4rxnuR-.js → LpDrawer-RRamxbLV.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-BgZ0Z_Im.js → LpDropdownMenu-Dl7Z4ZhS.js} +2 -2
- package/dist/chunks/{LpEditable-CjxuOVh_.js → LpEditable-D3PvD6Zm.js} +5 -5
- package/dist/chunks/{LpEmptyState-CKFTtQgJ.js → LpEmptyState-EQHiRYhG.js} +7 -3
- package/dist/chunks/{LpFieldNotice-CPI8EwtU.js → LpFieldNotice-CzQ2_5rQ.js} +1 -1
- package/dist/chunks/{LpFileTree-BH0gNlSJ.js → LpFileTree-D7KTaJiM.js} +2 -2
- package/dist/chunks/{LpFileTreeNode-gPVB1oXM.js → LpFileTreeNode-14GVqQTd.js} +5 -5
- package/dist/chunks/{LpFormField-gRASfyj2.js → LpFormField-MSQhRCTz.js} +2 -2
- package/dist/chunks/{LpGauge-BdjPGnqw.js → LpGauge-BO1PYYqD.js} +2 -2
- package/dist/chunks/{LpHintLayer-C2asBIfs.js → LpHintLayer-CmYm37z7.js} +2 -2
- package/dist/chunks/{LpHints-CNZQ76JV.js → LpHints-iLIRUkCj.js} +3 -3
- package/dist/chunks/{LpIcon-DkP-9mzK.js → LpIcon--5WSb3jJ.js} +1 -1
- package/dist/chunks/{LpInfraNode-yTh4-5qa.js → LpInfraNode-BT0VQrOa.js} +2 -2
- package/dist/chunks/{LpInput-2H-5ILOB.js → LpInput-1eNvtzXs.js} +2 -2
- package/dist/chunks/{LpLightbox-lAt5uIfx.js → LpLightbox-DWw6OSA4.js} +3 -3
- package/dist/chunks/{LpLoader-DjJEX8hL.js → LpLoader-B82_CcV0.js} +1 -1
- package/dist/chunks/{LpLogViewer-x78YfhJf.js → LpLogViewer-pzanZASC.js} +6 -6
- package/dist/chunks/{LpMapPicker-Dp5RSCi9.js → LpMapPicker-DNIUvF6V.js} +2 -2
- package/dist/chunks/{LpMediaGallery-D92oJfY_.js → LpMediaGallery-Dx8xzg5N.js} +3 -3
- package/dist/chunks/{LpModal-CpH0bNo7.js → LpModal-BnjZ2iem.js} +1 -1
- package/dist/chunks/{LpModalNav-BH4ANQGJ.js → LpModalNav-Cu04W1pV.js} +4 -4
- package/dist/chunks/{LpNotificationBell-BdUQqZhs.js → LpNotificationBell-PSQqFqEv.js} +8 -8
- package/dist/chunks/{LpNumberField-DZY3Z670.js → LpNumberField-e0IBkWU1.js} +1 -1
- package/dist/chunks/{LpNumberFlow-5khYxsfX.js → LpNumberFlow-CcxEGUy0.js} +10 -7
- package/dist/chunks/{LpPagination-DbeACavM.js → LpPagination-BIIiYHXR.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-CqWNRwHE.js → LpPaletteTheme-D05ywM1W.js} +8 -6
- package/dist/chunks/{LpPasswordInput-BzAZsf5V.js → LpPasswordInput-vdcw-koG.js} +2 -2
- package/dist/chunks/{LpPhoneInput-BMpd-0QR.js → LpPhoneInput-CETEp5W7.js} +2 -2
- package/dist/chunks/{LpPie-DdFSTbGE.js → LpPie-BlWDxCtG.js} +6 -6
- package/dist/chunks/{LpPipeline-zjEM1Lwc.js → LpPipeline-BXjoxkq_.js} +4 -4
- package/dist/chunks/{LpProgress-BX33EKkN.js → LpProgress-rOlyafqp.js} +1 -1
- package/dist/chunks/{LpQuota-DqNv1yjs.js → LpQuota-B12yijdB.js} +3 -3
- package/dist/chunks/{LpRadioGroup-CsXeV9Ct.js → LpRadioGroup-ggaPL92T.js} +1 -1
- package/dist/chunks/{LpRangeCalendar-l-0nSFmY.js → LpRangeCalendar-CYYUYqJ7.js} +1 -1
- package/dist/chunks/{LpRelativeTime-UM8EkfrN.js → LpRelativeTime-DwgCb5-8.js} +1 -1
- package/dist/chunks/{LpRepeater-OvzByE5L.js → LpRepeater-fliVBcN7.js} +3 -3
- package/dist/chunks/LpRollingText-B54Pje7P.js +86 -0
- package/dist/chunks/{LpScramble-TDO5Kdou.js → LpScramble-Cw-af0Yh.js} +1 -1
- package/dist/chunks/{LpSecretField-DwQiPOko.js → LpSecretField-D_IMxEGW.js} +7 -7
- package/dist/chunks/{LpSection-DcQsieHG.js → LpSection-DFK_uoJE.js} +1 -1
- package/dist/chunks/{LpSegmented-D2VST0o3.js → LpSegmented-CeACsMMA.js} +4 -2
- package/dist/chunks/{LpSelect-DYX2-u5A.js → LpSelect-Cxq4yBGd.js} +7 -7
- package/dist/chunks/LpShift-BAp8lGoa.js +160 -0
- package/dist/chunks/LpSidebar-BftYsk5d.js +494 -0
- package/dist/chunks/LpSidebarNav-BzNWMZfc.js +357 -0
- package/dist/chunks/LpSidebarRow-BIReMT1f.js +302 -0
- package/dist/chunks/{LpStat-DIsQfbeD.js → LpStat-BFCM6fUn.js} +9 -7
- package/dist/chunks/{LpStatus-BQU1TUsv.js → LpStatus-BWTLVPVh.js} +3 -3
- package/dist/chunks/{LpStepMark-CXa7DJy_.js → LpStepMark-DkdkebLC.js} +3 -3
- package/dist/chunks/{LpStepper-C9RM_yTG.js → LpStepper-BgqjKrqg.js} +1 -1
- package/dist/chunks/{LpSwap-DzE6M3Mg.js → LpSwap-B5YOXgtx.js} +1 -1
- package/dist/chunks/{LpSwitch-bGZxUDSx.js → LpSwitch-DyrRes7s.js} +1 -1
- package/dist/chunks/LpTabFace-D6BMCbAP.js +77 -0
- package/dist/chunks/{LpTableFilter-CTZMqFDU.js → LpTableFilter-Dkzamzkl.js} +3 -3
- package/dist/chunks/{LpTagsInput-DrjmrOge.js → LpTagsInput-DAZNNehI.js} +5 -5
- package/dist/chunks/{LpTextarea-BbYqmG1S.js → LpTextarea-DbmVPj2X.js} +32 -7
- package/dist/chunks/{useTheme-BOV_-AiY.js → LpThemeProvider-8MG6yqU9.js} +66 -1
- package/dist/chunks/{LpThemeStudio-B2nPaG-h.js → LpThemeStudio-DWtG6OZZ.js} +140 -106
- package/dist/chunks/LpThemeStudioSection-B7bt8Iyg.js +32 -0
- package/dist/chunks/{LpThemeSwitcher-C0A5sg_E.js → LpThemeSwitcher-Dcx-VkSk.js} +9 -8
- package/dist/chunks/{LpTimeField-DQ8WvPr2.js → LpTimeField-BawSVsmt.js} +1 -1
- package/dist/chunks/{LpToaster-BTTl_wuy.js → LpToaster-50hp_B5O.js} +2 -2
- package/dist/chunks/{LpTopologyCanvas-B0wEvAoq.js → LpTopologyCanvas-CnUh8uzS.js} +43 -13
- package/dist/chunks/{LpTour-BgCfNlaW.js → LpTour-DC3LTlxg.js} +1 -1
- package/dist/chunks/{LpTruncate-TcqfkyJM.js → LpTruncate-Ced4gquB.js} +1 -1
- package/dist/chunks/{LpUptimeBar-DRadGq4Y.js → LpUptimeBar-CuB0LX8K.js} +4 -4
- package/dist/chunks/{MenuTitle-Dab3EGOp.js → MenuTitle-BmCNNTKH.js} +1 -1
- package/dist/chunks/{OptionContent-DPzNsybd.js → OptionContent-22UT8zLn.js} +1 -1
- package/dist/chunks/{PickerField-DwLw7j0L.js → PickerField-Bfh8id3d.js} +2 -2
- package/dist/chunks/{RevealToggle-BVFM_FAU.js → RevealToggle-B0ITuIX-.js} +2 -2
- package/dist/chunks/{TourCard--2IfgnUU.js → TourCard-CBkj1GYi.js} +5 -5
- package/dist/chunks/{icons-CLu1iR1j.js → icons-DMITlfw5.js} +1 -1
- package/dist/chunks/{palette-BiY7yQZL.js → palette-Cvxl_1tM.js} +1 -1
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldWrapper.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.d.ts +67 -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 +28 -0
- package/dist/components/LpBottomNav.vue.js +4 -0
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- 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.js +26 -8
- 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.d.ts +15 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +47 -26
- 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/LpCopyButton.vue.js +1 -1
- package/dist/components/LpCountdown.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.d.ts +2 -0
- 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/LpGauge.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 +2 -2
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLoader.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +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/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +2 -2
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPaletteStats.vue.js +2 -2
- package/dist/components/LpPaletteTheme.vue.d.ts +2 -0
- 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/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/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpRollingText.vue.d.ts +2 -2
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpScramble.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/LpShift.vue.d.ts +20 -0
- package/dist/components/LpShift.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.d.ts +74 -23
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.d.ts +30 -2
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.d.ts +49 -0
- package/dist/components/LpSidebarRow.vue.js +4 -0
- package/dist/components/LpStat.vue.d.ts +11 -0
- 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.d.ts +14 -0
- package/dist/components/LpTabFace.vue.js +4 -0
- package/dist/components/LpTable.vue.js +11 -11
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +12 -4
- package/dist/components/LpTabs.vue.js +22 -74
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +19 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.d.ts +2 -2
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeStudioSection.vue.d.ts +4 -1
- package/dist/components/LpThemeStudioSection.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.d.ts +3 -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/LpUptimeBar.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/chart.d.ts +14 -0
- package/dist/components/chart.js +13 -0
- package/dist/components/format.d.ts +10 -3
- package/dist/components/format.js +14 -2
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/rolling.d.ts +5 -0
- package/dist/components/rolling.js +23 -0
- package/dist/components/sidebar.d.ts +33 -0
- package/dist/components/tabs.d.ts +3 -0
- package/dist/components/tabs.js +6 -0
- package/dist/composables/usePaletteCommands.js +1 -0
- package/dist/composables/useReveal.js +1 -22
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +156 -154
- package/dist/theme/palette.d.ts +3 -0
- package/package.json +1 -1
- package/src/component-names.ts +1 -0
- package/src/components/LpAppShell.vue +75 -7
- package/src/components/LpBottomNav.vue +84 -0
- package/src/components/LpChart.vue +23 -3
- package/src/components/LpColorPicker.vue +32 -1
- package/src/components/LpCommandPalette.vue +23 -5
- package/src/components/LpEmptyState.vue +4 -1
- package/src/components/LpNumberFlow.vue +23 -9
- package/src/components/LpPaletteTheme.vue +3 -3
- package/src/components/LpRollingText.vue +27 -2
- package/src/components/LpSegmented.vue +3 -1
- package/src/components/LpSelect.vue +5 -4
- package/src/components/LpShift.vue +174 -2
- package/src/components/LpSidebar.vue +304 -18
- package/src/components/LpSidebarNav.vue +200 -70
- package/src/components/LpSidebarRow.vue +238 -0
- package/src/components/LpStat.vue +12 -4
- package/src/components/LpTabFace.vue +52 -0
- package/src/components/LpTabs.vue +15 -42
- package/src/components/LpTextarea.vue +34 -4
- package/src/components/LpThemeStudio.vue +92 -56
- package/src/components/LpThemeStudioSection.vue +15 -7
- package/src/components/LpThemeSwitcher.vue +4 -1
- package/src/components/LpTopologyCanvas.vue +35 -4
- package/src/components/chart.ts +26 -0
- package/src/components/format.ts +31 -5
- package/src/components/rolling.ts +28 -0
- package/src/components/sidebar.ts +33 -0
- package/src/components/tabs.ts +9 -0
- package/src/composables/usePaletteCommands.ts +3 -0
- package/src/composables/useReveal.ts +7 -30
- package/src/index.ts +2 -1
- package/src/kit-icons.ts +1 -1
- package/src/theme/palette.ts +4 -2
- package/dist/assets/LpTabs-CO3EcsNV.css +0 -18
- package/dist/chunks/LpRollingText-DiL717LK.js +0 -51
- package/dist/chunks/LpShift-DXNsauQZ.js +0 -40
- package/dist/chunks/LpSidebar-D44peKq2.js +0 -256
- package/dist/chunks/LpSidebarNav--VwkXO_a.js +0 -211
- package/dist/chunks/LpThemeProvider-DcbfOxsU.js +0 -69
- package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +0 -20
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* What a tab shows: its icon with a dot or a count on the corner, its label,
|
|
4
|
+
* and a trailing dot or count where there is no icon to carry them. Shared by
|
|
5
|
+
* LpTabs and LpBottomNav, so a section tab and a page in the bottom bar read
|
|
6
|
+
* the same. Not exported on its own.
|
|
7
|
+
*/
|
|
8
|
+
import AttentionDot from "./AttentionDot.vue"
|
|
9
|
+
import LpBadge from "./LpBadge.vue"
|
|
10
|
+
import LpIcon from "./LpIcon.vue"
|
|
11
|
+
import LpRollingText from "./LpRollingText.vue"
|
|
12
|
+
|
|
13
|
+
defineProps<{
|
|
14
|
+
item: { label: string; icon?: string; dot?: boolean; badge?: number | string }
|
|
15
|
+
bottom?: boolean
|
|
16
|
+
compact?: boolean
|
|
17
|
+
active?: boolean
|
|
18
|
+
}>()
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<span v-if="item.icon" class="relative z-10 inline-flex">
|
|
23
|
+
<LpIcon :name="item.icon" :size="bottom ? 22 : 14" />
|
|
24
|
+
<AttentionDot v-if="item.dot" corner />
|
|
25
|
+
<!-- On a bottom bar the count rides on the icon's corner, as phone
|
|
26
|
+
navs draw it: the label under the icon has no room beside it. -->
|
|
27
|
+
<Transition name="lp-tab-badge">
|
|
28
|
+
<LpBadge
|
|
29
|
+
v-if="bottom && item.badge != null"
|
|
30
|
+
size="sm"
|
|
31
|
+
:tone="active ? 'brand' : 'neutral'"
|
|
32
|
+
class="absolute -right-3 -top-1.5 min-w-[18px] justify-center"
|
|
33
|
+
>
|
|
34
|
+
<LpRollingText :value="item.badge" />
|
|
35
|
+
</LpBadge>
|
|
36
|
+
</Transition>
|
|
37
|
+
</span>
|
|
38
|
+
<span class="relative z-10" :class="bottom ? 'max-w-full truncate' : compact && item.icon ? 'sr-only' : ''">{{ item.label }}</span>
|
|
39
|
+
<AttentionDot v-if="item.dot && !item.icon" />
|
|
40
|
+
<!-- The count rolls when it changes, and the chip scales in and out
|
|
41
|
+
rather than popping. -->
|
|
42
|
+
<Transition name="lp-tab-badge">
|
|
43
|
+
<LpBadge
|
|
44
|
+
v-if="!(bottom && item.icon) && item.badge != null"
|
|
45
|
+
size="sm"
|
|
46
|
+
:tone="active ? 'brand' : 'neutral'"
|
|
47
|
+
class="relative z-10 min-w-[18px] justify-center"
|
|
48
|
+
>
|
|
49
|
+
<LpRollingText :value="item.badge" />
|
|
50
|
+
</LpBadge>
|
|
51
|
+
</Transition>
|
|
52
|
+
</template>
|
|
@@ -5,13 +5,10 @@ import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "reka-ui"
|
|
|
5
5
|
import { computed, nextTick, ref, useId, watch } from "vue"
|
|
6
6
|
import { usePillTransition } from "../composables/usePillTransition"
|
|
7
7
|
import { prefersReducedMotion } from "../composables/easing"
|
|
8
|
-
import AttentionDot from "./AttentionDot.vue"
|
|
9
|
-
import LpBadge from "./LpBadge.vue"
|
|
10
8
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
11
|
-
import LpIcon from "./LpIcon.vue"
|
|
12
|
-
import LpRollingText from "./LpRollingText.vue"
|
|
13
9
|
import LpSkeleton from "./LpSkeleton.vue"
|
|
14
|
-
import
|
|
10
|
+
import LpTabFace from "./LpTabFace.vue"
|
|
11
|
+
import { BOTTOM_BAR, BOTTOM_ITEM, BOTTOM_MARKER, pickTab } from "./tabs"
|
|
15
12
|
|
|
16
13
|
export interface TabItem {
|
|
17
14
|
value: string
|
|
@@ -61,8 +58,15 @@ const props = withDefaults(
|
|
|
61
58
|
* where the pointer crosses the bar on its way elsewhere.
|
|
62
59
|
*/
|
|
63
60
|
hoverFollow?: boolean
|
|
61
|
+
/**
|
|
62
|
+
* Icons only, the label kept for screen readers and shown on hover. For a
|
|
63
|
+
* header that cannot fit every label: a tab cut off at the edge, or hidden
|
|
64
|
+
* under the actions beside it, cannot be reached at all. Tabs without an
|
|
65
|
+
* icon keep their label.
|
|
66
|
+
*/
|
|
67
|
+
compact?: boolean
|
|
64
68
|
}>(),
|
|
65
|
-
{ variant: "contained", accent: false, block: false, hoverFollow: true },
|
|
69
|
+
{ variant: "contained", accent: false, block: false, hoverFollow: true, compact: false },
|
|
66
70
|
)
|
|
67
71
|
|
|
68
72
|
const bottom = computed(() => props.variant === "bottom")
|
|
@@ -170,7 +174,7 @@ usePaletteCommands(() => props.items.map(item => ({
|
|
|
170
174
|
ref="list"
|
|
171
175
|
:aria-label="label"
|
|
172
176
|
:class="bottom
|
|
173
|
-
?
|
|
177
|
+
? BOTTOM_BAR
|
|
174
178
|
: [
|
|
175
179
|
'max-w-full gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
|
|
176
180
|
block ? 'flex w-full' : 'inline-flex',
|
|
@@ -190,10 +194,11 @@ usePaletteCommands(() => props.items.map(item => ({
|
|
|
190
194
|
<TabsTrigger
|
|
191
195
|
:value="item.value"
|
|
192
196
|
:disabled="item.disabled"
|
|
197
|
+
:title="compact && item.icon && !bottom ? item.label : undefined"
|
|
193
198
|
class="relative inline-flex items-center justify-center outline-none transition-[color,opacity] duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-45"
|
|
194
199
|
:class="[
|
|
195
200
|
bottom
|
|
196
|
-
?
|
|
201
|
+
? BOTTOM_ITEM
|
|
197
202
|
: 'shrink-0 gap-1.5 whitespace-nowrap text-sm',
|
|
198
203
|
block && !bottom ? 'flex-1' : '',
|
|
199
204
|
variant === 'underline' ? 'rounded-t-inner px-3 pt-1.5 pb-2' : '',
|
|
@@ -217,10 +222,7 @@ usePaletteCommands(() => props.items.map(item => ({
|
|
|
217
222
|
accent ? 'bg-brand' : 'bg-ink',
|
|
218
223
|
]
|
|
219
224
|
: bottom
|
|
220
|
-
? [
|
|
221
|
-
'absolute inset-x-[22%] top-0 z-0 h-0.5 rounded-b-full',
|
|
222
|
-
accent ? 'bg-brand' : 'bg-ink',
|
|
223
|
-
]
|
|
225
|
+
? [BOTTOM_MARKER, accent ? 'bg-brand' : 'bg-ink']
|
|
224
226
|
: [
|
|
225
227
|
'absolute inset-0 z-0 rounded-inner shadow-sm',
|
|
226
228
|
accent
|
|
@@ -228,36 +230,7 @@ usePaletteCommands(() => props.items.map(item => ({
|
|
|
228
230
|
: 'border border-line bg-surface-raised',
|
|
229
231
|
]"
|
|
230
232
|
/>
|
|
231
|
-
<
|
|
232
|
-
<LpIcon :name="item.icon" :size="bottom ? 22 : 14" />
|
|
233
|
-
<AttentionDot v-if="item.dot" corner />
|
|
234
|
-
<!-- On a bottom bar the count rides on the icon's corner, as phone
|
|
235
|
-
navs draw it: the label under the icon has no room beside it. -->
|
|
236
|
-
<Transition name="lp-tab-badge">
|
|
237
|
-
<LpBadge
|
|
238
|
-
v-if="bottom && item.badge != null"
|
|
239
|
-
size="sm"
|
|
240
|
-
:tone="current === item.value ? 'brand' : 'neutral'"
|
|
241
|
-
class="absolute -right-3 -top-1.5 min-w-[18px] justify-center"
|
|
242
|
-
>
|
|
243
|
-
<LpRollingText :value="item.badge" />
|
|
244
|
-
</LpBadge>
|
|
245
|
-
</Transition>
|
|
246
|
-
</span>
|
|
247
|
-
<span class="relative z-10" :class="bottom ? 'max-w-full truncate' : ''">{{ item.label }}</span>
|
|
248
|
-
<AttentionDot v-if="item.dot && !item.icon" />
|
|
249
|
-
<!-- The count rolls when it changes, and the chip scales in and out
|
|
250
|
-
rather than popping. -->
|
|
251
|
-
<Transition name="lp-tab-badge">
|
|
252
|
-
<LpBadge
|
|
253
|
-
v-if="!(bottom && item.icon) && item.badge != null"
|
|
254
|
-
size="sm"
|
|
255
|
-
:tone="current === item.value ? 'brand' : 'neutral'"
|
|
256
|
-
class="relative z-10 min-w-[18px] justify-center"
|
|
257
|
-
>
|
|
258
|
-
<LpRollingText :value="item.badge" />
|
|
259
|
-
</LpBadge>
|
|
260
|
-
</Transition>
|
|
233
|
+
<LpTabFace :item="item" :bottom="bottom" :compact="compact" :active="current === item.value" />
|
|
261
234
|
</TabsTrigger>
|
|
262
235
|
</LpContextMenu>
|
|
263
236
|
<!-- Items that arrive later (counts from a server, tabs from a route)
|
|
@@ -4,7 +4,7 @@ export default { inheritAttrs: false }
|
|
|
4
4
|
|
|
5
5
|
<script setup lang="ts">
|
|
6
6
|
import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
|
|
7
|
-
import { computed, useAttrs } from "vue"
|
|
7
|
+
import { computed, nextTick, onMounted, ref, useAttrs, watch } from "vue"
|
|
8
8
|
import { useFieldControl } from "../composables/useField"
|
|
9
9
|
import { mergeClass } from "../composables/useMergedClass"
|
|
10
10
|
import FieldWrapper from "./FieldWrapper.vue"
|
|
@@ -25,7 +25,14 @@ const props = withDefaults(defineProps<{
|
|
|
25
25
|
* the <textarea> for native validation.
|
|
26
26
|
*/
|
|
27
27
|
required?: boolean
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Grows with what is typed, wrapped lines included, from `rows` up to
|
|
30
|
+
* `maxRows`, then scrolls; the drag handle goes, since the height is the
|
|
31
|
+
* text's. For a composer, where a fixed box is either cramped or empty.
|
|
32
|
+
*/
|
|
33
|
+
autosize?: boolean
|
|
34
|
+
maxRows?: number
|
|
35
|
+
}>(), { palette: undefined, maxRows: 8 })
|
|
29
36
|
|
|
30
37
|
defineEmits<{ (e: "update:modelValue", value: string): void }>()
|
|
31
38
|
|
|
@@ -49,9 +56,31 @@ const field = useFieldControl(() => ({
|
|
|
49
56
|
disabled: props.disabled,
|
|
50
57
|
}))
|
|
51
58
|
|
|
59
|
+
const box = ref<HTMLTextAreaElement | null>(null)
|
|
60
|
+
|
|
61
|
+
// Measured rather than styled with `field-sizing: content`, which WebKit does
|
|
62
|
+
// not have: an app in a WebKitGTK window would keep a fixed box.
|
|
63
|
+
function fit() {
|
|
64
|
+
const el = box.value
|
|
65
|
+
if (!props.autosize || !el) return
|
|
66
|
+
const style = getComputedStyle(el)
|
|
67
|
+
const line = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.4
|
|
68
|
+
const borders = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth)
|
|
69
|
+
const padding = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)
|
|
70
|
+
el.style.height = "auto"
|
|
71
|
+
const max = line * props.maxRows + padding + borders
|
|
72
|
+
el.style.height = `${Math.min(el.scrollHeight + borders, max)}px`
|
|
73
|
+
el.style.overflowY = el.scrollHeight + borders > max + 1 ? "auto" : "hidden"
|
|
74
|
+
}
|
|
75
|
+
watch(() => props.modelValue, () => nextTick(fit))
|
|
76
|
+
onMounted(fit)
|
|
77
|
+
|
|
78
|
+
defineExpose({ el: box, fit })
|
|
79
|
+
|
|
52
80
|
const textareaClass = computed(() =>
|
|
53
81
|
mergeClass(
|
|
54
|
-
"w-full resize-
|
|
82
|
+
props.autosize ? "w-full resize-none" : "w-full resize-y",
|
|
83
|
+
"rounded-control border bg-surface-soft px-3 py-2 text-sm text-ink outline-none transition-colors duration-[var(--duration-fast)] placeholder:text-muted focus:border-brand focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-55",
|
|
55
84
|
field.value.invalid ? "border-danger focus:border-danger focus:ring-danger-soft" : "border-line",
|
|
56
85
|
hasField.value ? "" : (attrs.class as string),
|
|
57
86
|
),
|
|
@@ -72,6 +101,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
|
|
|
72
101
|
:style="attrs.style"
|
|
73
102
|
>
|
|
74
103
|
<textarea
|
|
104
|
+
ref="box"
|
|
75
105
|
v-bind="textareaAttrs"
|
|
76
106
|
:id="field.id"
|
|
77
107
|
:value="modelValue"
|
|
@@ -83,7 +113,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
|
|
|
83
113
|
:aria-describedby="field.describedBy"
|
|
84
114
|
:class="textareaClass"
|
|
85
115
|
:style="hasField ? undefined : (attrs.style as never)"
|
|
86
|
-
@input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
|
|
116
|
+
@input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value); fit()"
|
|
87
117
|
/>
|
|
88
118
|
</FieldWrapper>
|
|
89
119
|
</template>
|
|
@@ -10,7 +10,7 @@ import { usePaletteTheme } from "../theme/palette"
|
|
|
10
10
|
* An app adds its own rows through `#start` and `#end` (LpThemeStudioSection
|
|
11
11
|
* gives them the panel's look) and drops built-in ones with `hide`.
|
|
12
12
|
*/
|
|
13
|
-
import { computed, ref } from "vue"
|
|
13
|
+
import { computed, h, ref, type FunctionalComponent } from "vue"
|
|
14
14
|
import { useLabels } from "../composables/useLpConfig"
|
|
15
15
|
import { STUDIO_BRANDS, STUDIO_SYSTEM, useThemeStudio, type StudioState, type ThemeStudio } from "../theme/studio"
|
|
16
16
|
import type { TokenSet } from "../theme/tokens"
|
|
@@ -20,7 +20,10 @@ import LpCodeBlock from "./LpCodeBlock.vue"
|
|
|
20
20
|
import LpColorPicker from "./LpColorPicker.vue"
|
|
21
21
|
import LpIcon from "./LpIcon.vue"
|
|
22
22
|
import LpSegmented from "./LpSegmented.vue"
|
|
23
|
+
import LpDisclosure from "./LpDisclosure.vue"
|
|
24
|
+
import LpThemeProvider from "./LpThemeProvider.vue"
|
|
23
25
|
import LpThemeStudioSection from "./LpThemeStudioSection.vue"
|
|
26
|
+
import { withBrand } from "../theme/define"
|
|
24
27
|
|
|
25
28
|
export type ThemeStudioPart = "themes" | "brand" | "corners" | "size" | "motion" | "export"
|
|
26
29
|
|
|
@@ -100,17 +103,37 @@ function origin(event: Event): RevealOrigin | undefined {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
const TILE =
|
|
103
|
-
"flex min-w-0 flex-col
|
|
104
|
-
const
|
|
106
|
+
"group flex min-w-0 flex-col gap-1.5 text-left text-xs text-muted transition-colors duration-(--duration-fast) hover:text-ink aria-pressed:text-ink"
|
|
107
|
+
const FRAME = "relative block aspect-[8/5] w-full overflow-hidden rounded-control transition-[box-shadow] duration-(--duration-fast)"
|
|
108
|
+
const ring = (name: string) =>
|
|
109
|
+
state.value.theme === name ? "ring-2 ring-brand" : "ring-1 ring-line group-hover:ring-line-strong"
|
|
110
|
+
|
|
111
|
+
/** A theme as it would look with the tuning picked here: the brand rides on whichever theme is chosen. */
|
|
112
|
+
const tuned = (theme: TokenSet): TokenSet => (state.value.brand ? withBrand(theme, state.value.brand) : theme)
|
|
113
|
+
|
|
114
|
+
// A window of the theme in miniature: its surfaces, text and a button in its
|
|
115
|
+
// brand. Painted through LpThemeProvider, so it shows the tokens as the app
|
|
116
|
+
// would, rather than a guess made from a few of them.
|
|
117
|
+
const Miniature: FunctionalComponent<{ theme: TokenSet }> = (p) =>
|
|
118
|
+
h(LpThemeProvider, { theme: p.theme, class: "absolute inset-0 flex bg-surface" }, () => [
|
|
119
|
+
h("span", { class: "h-full w-1/4 border-r border-line bg-surface-raised" }),
|
|
120
|
+
h("span", { class: "flex min-w-0 flex-1 flex-col gap-[10%] p-[10%]" }, [
|
|
121
|
+
h("span", { class: "h-[12%] w-3/4 rounded-full bg-ink/80" }),
|
|
122
|
+
h("span", { class: "h-[10%] w-1/2 rounded-full bg-muted/60" }),
|
|
123
|
+
h("span", { class: "mt-auto h-[24%] w-1/2 rounded-control bg-brand" }),
|
|
124
|
+
]),
|
|
125
|
+
])
|
|
105
126
|
|
|
106
127
|
const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K, value: string) => studio.tune({ [key]: value } as Partial<StudioState>)
|
|
107
128
|
|
|
108
|
-
usePaletteTheme(() => studio ?? undefined)
|
|
129
|
+
usePaletteTheme(() => studio ?? undefined, { hide: props.hide })
|
|
109
130
|
</script>
|
|
110
131
|
|
|
111
132
|
<template>
|
|
112
|
-
<div class="flex flex-col gap-4">
|
|
113
|
-
|
|
133
|
+
<div class="@container flex flex-col gap-4">
|
|
134
|
+
<!-- An app that titles the studio itself (a settings section) passes an
|
|
135
|
+
empty title; the row then shows only while Reset has something to undo. -->
|
|
136
|
+
<div v-if="text.title || changed" class="flex items-center justify-between">
|
|
114
137
|
<p class="text-sm font-semibold text-ink">{{ text.title }}</p>
|
|
115
138
|
<LpButton v-if="changed" variant="ghost" size="xs" @click="studio.reset(origin($event))">
|
|
116
139
|
<LpIcon name="lucide:rotate-ccw" :size="12" />{{ text.reset }}
|
|
@@ -120,39 +143,39 @@ usePaletteTheme(() => studio ?? undefined)
|
|
|
120
143
|
<slot name="start" :studio="studio" />
|
|
121
144
|
|
|
122
145
|
<LpThemeStudioSection v-if="shows('themes')" :label="text.themes">
|
|
123
|
-
<div class="grid grid-cols-
|
|
124
|
-
<!-- Follows the OS between its pair:
|
|
146
|
+
<div class="grid grid-cols-3 gap-2 @md:grid-cols-4 @2xl:grid-cols-8">
|
|
147
|
+
<!-- Follows the OS between its pair: both halves, split corner to corner. -->
|
|
125
148
|
<button
|
|
126
149
|
v-if="studio.system.value"
|
|
127
150
|
type="button"
|
|
128
|
-
:class="
|
|
151
|
+
:class="TILE"
|
|
129
152
|
:aria-pressed="state.theme === STUDIO_SYSTEM"
|
|
130
153
|
@click="studio.pickTheme(STUDIO_SYSTEM, origin($event))"
|
|
131
154
|
>
|
|
132
|
-
<span
|
|
133
|
-
:
|
|
134
|
-
|
|
135
|
-
|
|
155
|
+
<span :class="[FRAME, ring(STUDIO_SYSTEM)]">
|
|
156
|
+
<Miniature :theme="tuned(studio.system.value.light)" />
|
|
157
|
+
<span class="absolute inset-0 [clip-path:polygon(100%_0,100%_100%,0_100%)]">
|
|
158
|
+
<Miniature :theme="tuned(studio.system.value.dark)" />
|
|
159
|
+
</span>
|
|
160
|
+
</span>
|
|
136
161
|
<span class="max-w-full truncate">{{ text.system }}</span>
|
|
137
162
|
</button>
|
|
138
163
|
<button
|
|
139
164
|
v-for="t in studio.themes.value"
|
|
140
165
|
:key="t.name"
|
|
141
166
|
type="button"
|
|
142
|
-
:class="
|
|
167
|
+
:class="TILE"
|
|
143
168
|
:aria-pressed="state.theme === t.name"
|
|
144
169
|
@click="studio.pickTheme(t.name, origin($event))"
|
|
145
170
|
>
|
|
146
|
-
<span :class="[
|
|
147
|
-
<span class="absolute inset-x-0 bottom-0 h-1/2" :style="{ background: t.colors.brand }" />
|
|
148
|
-
</span>
|
|
171
|
+
<span :class="[FRAME, ring(t.name)]"><Miniature :theme="tuned(t)" /></span>
|
|
149
172
|
<span class="max-w-full truncate">{{ t.name }}</span>
|
|
150
173
|
</button>
|
|
151
174
|
</div>
|
|
152
175
|
</LpThemeStudioSection>
|
|
153
176
|
|
|
154
177
|
<LpThemeStudioSection v-if="shows('brand')" :label="text.brand">
|
|
155
|
-
<div class="flex flex-wrap items-center gap-
|
|
178
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
156
179
|
<button
|
|
157
180
|
v-for="color in swatches"
|
|
158
181
|
:key="color"
|
|
@@ -164,55 +187,68 @@ usePaletteTheme(() => studio ?? undefined)
|
|
|
164
187
|
:aria-pressed="brand === color"
|
|
165
188
|
@click="setBrand(color)"
|
|
166
189
|
/>
|
|
167
|
-
<LpColorPicker
|
|
190
|
+
<LpColorPicker
|
|
191
|
+
variant="swatch"
|
|
192
|
+
size="sm"
|
|
193
|
+
:selected="!swatches.includes(brand)"
|
|
194
|
+
:swatches="[]"
|
|
195
|
+
:model-value="brand"
|
|
196
|
+
:aria-label="text.anyBrand"
|
|
197
|
+
@update:model-value="setBrand"
|
|
198
|
+
/>
|
|
168
199
|
</div>
|
|
169
200
|
</LpThemeStudioSection>
|
|
170
201
|
|
|
171
|
-
<
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
</
|
|
202
|
+
<div v-if="shows('corners') || shows('size') || shows('motion')" class="flex flex-col gap-1.5">
|
|
203
|
+
<LpThemeStudioSection v-if="shows('corners')" inline :label="text.corners">
|
|
204
|
+
<LpSegmented
|
|
205
|
+
:model-value="state.corners"
|
|
206
|
+
size="sm"
|
|
207
|
+
:options="[{ value: 'sharp', label: text.sharp }, { value: 'default', label: text.default }, { value: 'round', label: text.round }]"
|
|
208
|
+
@update:model-value="tuneTo('corners', $event)"
|
|
209
|
+
/>
|
|
210
|
+
</LpThemeStudioSection>
|
|
211
|
+
<LpThemeStudioSection v-if="shows('size')" inline :label="text.size">
|
|
212
|
+
<LpSegmented
|
|
213
|
+
:model-value="state.size"
|
|
214
|
+
size="sm"
|
|
215
|
+
:options="[{ value: 'compact', label: text.compact }, { value: 'default', label: text.default }, { value: 'comfortable', label: text.comfortable }]"
|
|
216
|
+
@update:model-value="tuneTo('size', $event)"
|
|
217
|
+
/>
|
|
218
|
+
</LpThemeStudioSection>
|
|
219
|
+
<LpThemeStudioSection v-if="shows('motion')" inline :label="text.motion">
|
|
220
|
+
<LpSegmented
|
|
221
|
+
:model-value="state.motion"
|
|
222
|
+
size="sm"
|
|
223
|
+
:options="[{ value: 'fast', label: text.fast }, { value: 'normal', label: text.normal }, { value: 'slow', label: text.slow }]"
|
|
224
|
+
@update:model-value="tuneTo('motion', $event)"
|
|
225
|
+
/>
|
|
226
|
+
</LpThemeStudioSection>
|
|
227
|
+
</div>
|
|
197
228
|
|
|
198
229
|
<slot name="end" :studio="studio" />
|
|
199
230
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
231
|
+
<!-- Taking the theme into code is the developer's errand, not the person
|
|
232
|
+
picking colours: folded, it no longer opened every studio with an
|
|
233
|
+
empty block of code. -->
|
|
234
|
+
<LpDisclosure v-if="shows('export')">
|
|
235
|
+
<template #trigger>
|
|
236
|
+
<span class="text-xs font-medium text-muted-strong">{{ text.export }}</span>
|
|
237
|
+
</template>
|
|
238
|
+
<div class="flex flex-col gap-2 pt-1">
|
|
203
239
|
<LpSegmented
|
|
204
240
|
v-model="format"
|
|
205
241
|
size="sm"
|
|
206
242
|
:options="[{ value: 'code', label: 'Code' }, { value: 'json', label: 'JSON' }, { value: 'css', label: 'CSS' }]"
|
|
207
243
|
/>
|
|
244
|
+
<LpCodeBlock
|
|
245
|
+
:model-value="exported[format]"
|
|
246
|
+
:lang="format === 'json' ? 'json' : format === 'css' ? 'plain' : 'ts'"
|
|
247
|
+
copyable
|
|
248
|
+
height="140px"
|
|
249
|
+
class="text-[11px]"
|
|
250
|
+
/>
|
|
208
251
|
</div>
|
|
209
|
-
|
|
210
|
-
:model-value="exported[format]"
|
|
211
|
-
:lang="format === 'json' ? 'json' : format === 'css' ? 'plain' : 'ts'"
|
|
212
|
-
copyable
|
|
213
|
-
height="140px"
|
|
214
|
-
class="text-[11px]"
|
|
215
|
-
/>
|
|
216
|
-
</div>
|
|
252
|
+
</LpDisclosure>
|
|
217
253
|
</div>
|
|
218
254
|
</template>
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/*
|
|
3
|
-
* One row of LpThemeStudio: a
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* One row of LpThemeStudio: a label and its controls. The studio builds its
|
|
4
|
+
* own rows with it, and an app adding its own (a font, a density of its own,
|
|
5
|
+
* a layout switch) through the studio's slots uses it too, so the added row
|
|
6
|
+
* reads as part of the panel.
|
|
7
|
+
*
|
|
8
|
+
* `inline` puts a single control beside its label, as a settings row does: a
|
|
9
|
+
* three-way switch under a label of its own read as three loose rows of
|
|
10
|
+
* buttons, and on a wide page it sat in the corner of an empty stripe.
|
|
7
11
|
*/
|
|
8
|
-
defineProps<{ label: string }>()
|
|
12
|
+
defineProps<{ label: string; inline?: boolean }>()
|
|
9
13
|
</script>
|
|
10
14
|
|
|
11
15
|
<template>
|
|
12
|
-
<div class="flex flex-
|
|
13
|
-
<span class="text-
|
|
16
|
+
<div v-if="inline" class="flex min-h-8 flex-wrap items-center justify-between gap-x-4 gap-y-1.5">
|
|
17
|
+
<span class="text-xs font-medium text-muted-strong">{{ label }}</span>
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
<div v-else class="flex flex-col gap-2">
|
|
21
|
+
<span class="text-xs font-medium text-muted-strong">{{ label }}</span>
|
|
14
22
|
<slot />
|
|
15
23
|
</div>
|
|
16
24
|
</template>
|
|
@@ -228,6 +228,9 @@ function followSystem() {
|
|
|
228
228
|
// With the studio, "System" is its base theme, so the tuning stays on top.
|
|
229
229
|
if (tuner) {
|
|
230
230
|
tuner.pickTheme(STUDIO_SYSTEM)
|
|
231
|
+
// A v-model bound to the switcher learns "system" too, or it would keep
|
|
232
|
+
// the previous name and win over the studio: the check stayed on it.
|
|
233
|
+
emit("update:modelValue", STUDIO_SYSTEM)
|
|
231
234
|
emit("change", tuner.theme.value)
|
|
232
235
|
return
|
|
233
236
|
}
|
|
@@ -300,7 +303,7 @@ const triggerLabel = computed(
|
|
|
300
303
|
() => props["aria-label"] ?? text.value.trigger.replace("{name}", labelName.value),
|
|
301
304
|
)
|
|
302
305
|
|
|
303
|
-
usePaletteTheme(() => tuner ?? undefined)
|
|
306
|
+
usePaletteTheme(() => tuner ?? undefined, { hide: props.studioHide })
|
|
304
307
|
</script>
|
|
305
308
|
|
|
306
309
|
<template>
|
|
@@ -20,9 +20,10 @@ import {
|
|
|
20
20
|
type Connection,
|
|
21
21
|
type Node,
|
|
22
22
|
type NodeTypesObject,
|
|
23
|
+
type Padding,
|
|
23
24
|
} from "@vue-flow/core"
|
|
24
25
|
import { MiniMap } from "@vue-flow/minimap"
|
|
25
|
-
import { computed, defineComponent, h, markRaw, watch, type Component } from "vue"
|
|
26
|
+
import { computed, defineComponent, h, markRaw, ref, watch, type Component } from "vue"
|
|
26
27
|
import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
|
|
27
28
|
import LpLaneNode from "./LpLaneNode.vue"
|
|
28
29
|
import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
|
|
@@ -325,6 +326,7 @@ const {
|
|
|
325
326
|
getSelectedNodes,
|
|
326
327
|
fitView,
|
|
327
328
|
setViewport,
|
|
329
|
+
onNodesInitialized,
|
|
328
330
|
} = useVueFlow()
|
|
329
331
|
onNodeClick(({ node }) => emit("node-select", node.id))
|
|
330
332
|
// A node usually has two levels of interest — pick it out, or open it — and a
|
|
@@ -389,13 +391,42 @@ onEdgeContextMenu(({ edge, event }) => {
|
|
|
389
391
|
onPaneClick(() => emit("pane-click"))
|
|
390
392
|
onConnect((conn) => emit("connect", conn))
|
|
391
393
|
|
|
394
|
+
// The legend and the minimap float over the canvas, so a plain fit puts the
|
|
395
|
+
// outermost nodes under them. Fitting reserves their corner instead: the side
|
|
396
|
+
// they sit on gets their height on top of the usual margin. On a phone both
|
|
397
|
+
// are hidden and only the margin is left.
|
|
398
|
+
const legendBox = ref<HTMLElement | null>(null)
|
|
399
|
+
const MINIMAP_HEIGHT = 165
|
|
400
|
+
const FRAME = 24
|
|
401
|
+
function framePadding(): Padding {
|
|
402
|
+
const wide = typeof window === "undefined" || window.matchMedia("(min-width: 640px)").matches
|
|
403
|
+
const legendHeight = wide && props.legend ? (legendBox.value?.offsetHeight ?? 0) + 15 : 0
|
|
404
|
+
const legendTop = props.legendPosition.startsWith("top")
|
|
405
|
+
const bottom = Math.max(legendTop ? 0 : legendHeight, wide && props.minimap ? MINIMAP_HEIGHT : 0)
|
|
406
|
+
return {
|
|
407
|
+
top: `${FRAME + (legendTop ? legendHeight : 0)}px`,
|
|
408
|
+
bottom: `${FRAME + bottom}px`,
|
|
409
|
+
left: `${FRAME}px`,
|
|
410
|
+
right: `${FRAME}px`,
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
function fitFramed(params: Parameters<typeof fitView>[0] = {}) {
|
|
414
|
+
return fitView({ padding: framePadding(), ...params })
|
|
415
|
+
}
|
|
416
|
+
let fitted = false
|
|
417
|
+
onNodesInitialized(() => {
|
|
418
|
+
if (fitted) return
|
|
419
|
+
fitted = true
|
|
420
|
+
void fitFramed()
|
|
421
|
+
})
|
|
422
|
+
|
|
392
423
|
// Restore a saved viewport (pan/zoom). The host calls this after loading the
|
|
393
424
|
// user's persisted CanvasViewport on mount.
|
|
394
425
|
async function applyViewport(v: CanvasViewport): Promise<void> {
|
|
395
426
|
await setViewport({ x: v.x, y: v.y, zoom: v.zoom })
|
|
396
427
|
}
|
|
397
428
|
|
|
398
|
-
defineExpose({ fitView, setViewport: applyViewport })
|
|
429
|
+
defineExpose({ fitView: fitFramed, setViewport: applyViewport })
|
|
399
430
|
</script>
|
|
400
431
|
|
|
401
432
|
<template>
|
|
@@ -413,7 +444,6 @@ defineExpose({ fitView, setViewport: applyViewport })
|
|
|
413
444
|
:default-viewport="{ zoom: 0.9 }"
|
|
414
445
|
:min-zoom="0.3"
|
|
415
446
|
:max-zoom="2"
|
|
416
|
-
fit-view-on-init
|
|
417
447
|
class="lp-topology"
|
|
418
448
|
>
|
|
419
449
|
<Background :gap="22" :size="1.4" pattern-color="var(--color-line-strong)" />
|
|
@@ -428,13 +458,14 @@ defineExpose({ fitView, setViewport: applyViewport })
|
|
|
428
458
|
node-color="var(--color-surface-soft)"
|
|
429
459
|
mask-color="rgba(0,0,0,0.55)"
|
|
430
460
|
/>
|
|
431
|
-
<Controls v-if="controls" />
|
|
461
|
+
<Controls v-if="controls" :fit-view-params="{ padding: framePadding() }" />
|
|
432
462
|
|
|
433
463
|
<!-- Edge-type legend: makes the colour/dash encoding self-explanatory. -->
|
|
434
464
|
<!-- On a phone the legend would sit on the graph itself; the edge styles
|
|
435
465
|
stay readable without it there. -->
|
|
436
466
|
<Panel v-if="legend" :position="legendPosition" :class="legendOffset" class="max-sm:hidden">
|
|
437
467
|
<div
|
|
468
|
+
ref="legendBox"
|
|
438
469
|
class="flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur"
|
|
439
470
|
>
|
|
440
471
|
<div
|
package/src/components/chart.ts
CHANGED
|
@@ -28,6 +28,13 @@ export interface ChartSeries {
|
|
|
28
28
|
* own scale and its own ticks.
|
|
29
29
|
*/
|
|
30
30
|
axis?: "left" | "right"
|
|
31
|
+
/**
|
|
32
|
+
* Start with this series switched off, the legend turning it back on. For a
|
|
33
|
+
* series worth having but loud next to the rest: messages in thousands beside
|
|
34
|
+
* people in tens flatten the people line on a shared axis until they are
|
|
35
|
+
* hidden, so the chart opens on the readable view.
|
|
36
|
+
*/
|
|
37
|
+
hidden?: boolean
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
export type ChartAxis = "left" | "right"
|
|
@@ -58,6 +65,25 @@ export interface ChartMarker {
|
|
|
58
65
|
color?: string
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Which x labels to print with every `stride`-th one shown. The last point is
|
|
70
|
+
* always labelled (it is usually "now"), and when it lands within half a
|
|
71
|
+
* stride to the previous shown label that one gives way: "20 22 23" read as
|
|
72
|
+
* one smudge at the end of an hourly axis.
|
|
73
|
+
*/
|
|
74
|
+
export function xLabelIndices(count: number, stride: number): number[] {
|
|
75
|
+
if (count <= 0) return []
|
|
76
|
+
const step = Math.max(1, Math.floor(stride))
|
|
77
|
+
const out: number[] = []
|
|
78
|
+
for (let i = 0; i < count; i += step) out.push(i)
|
|
79
|
+
const last = count - 1
|
|
80
|
+
if (out[out.length - 1] !== last) {
|
|
81
|
+
if (out.length > 1 && last - out[out.length - 1] <= step / 2) out.pop()
|
|
82
|
+
out.push(last)
|
|
83
|
+
}
|
|
84
|
+
return out
|
|
85
|
+
}
|
|
86
|
+
|
|
61
87
|
/** The x index a marker points at, or -1 when it names nothing on the axis. */
|
|
62
88
|
export const markerIndex = (at: number | string, labels: string[], count: number): number => {
|
|
63
89
|
if (typeof at === "number") return Number.isInteger(at) && at >= 0 && at < count ? at : -1
|