@leavepulse/ui 0.43.4 → 0.45.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 +121 -7
- package/dist/assets/{LpCommandPalette-B-0Est4Z.css → LpCommandPalette-nGNnzXqQ.css} +21 -21
- package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
- package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
- package/dist/assets/{LpRollingText-CR1mb0Vt.css → LpRollingText-BGnE6q9j.css} +3 -0
- package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-CqYX3yy6.css} +28 -7
- package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
- package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-CPwWxTJS.css} +49 -9
- package/dist/chunks/ContextMenuItems-BtKU7074.js +124 -0
- package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-BkaGXTjx.js} +26 -9
- package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-B3_9E1Xa.js} +28 -12
- package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-CnfFVIwC.js} +26 -12
- package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BwX8uo4_.js} +54 -49
- package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-B6ilrM7T.js} +16 -9
- package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-C53_RaGF.js} +53 -26
- package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-C8IgSMsi.js} +52 -24
- package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-C1XStdOo.js} +19 -10
- package/dist/chunks/LpBreadcrumbs-CQJd73Gm.js +95 -0
- package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-CKSwC6Mu.js} +45 -15
- package/dist/chunks/{LpCalendar-B7Ko5RdR.js → LpCalendar-DrA2McTU.js} +1 -1
- package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-C2IvucmX.js} +1 -1
- package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-Bkj13CbW.js} +8 -4
- package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-ca7ANFyW.js} +49 -17
- package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-zz4VGkG4.js} +75 -26
- package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-DCw5jR1t.js} +59 -17
- package/dist/chunks/LpConfirmHost-CbywMpMP.js +49 -0
- package/dist/chunks/LpContextMenu-BqkAYa9X.js +69 -0
- package/dist/chunks/LpDatePicker-DuusgT69.js +195 -0
- package/dist/chunks/LpDateRangePicker-DHeuznfC.js +208 -0
- package/dist/chunks/{LpDisclosure-ChQYmjlR.js → LpDisclosure-Cc23-JOT.js} +1 -1
- package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-Dt4aB4Fr.js} +65 -28
- package/dist/chunks/{LpDropdownMenu-DJpoqRCb.js → LpDropdownMenu-DdJGQzPn.js} +2 -1
- package/dist/chunks/{LpEmptyState-Dsdqz1Wj.js → LpEmptyState-BJbZXC8D.js} +1 -1
- package/dist/chunks/LpFieldNotice-wtieLUTj.js +64 -0
- package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CNurOvkV.js} +25 -10
- package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-D_qRRdsH.js} +3 -3
- package/dist/chunks/LpFormField-CtVYj6tO.js +101 -0
- package/dist/chunks/{LpIcon-CCnX5_2j.js → LpIcon-DYtaid0K.js} +20 -6
- package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-D-YGQbEv.js} +58 -20
- package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-DSDahF23.js} +148 -61
- package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
- package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-KypbgOyo.js} +300 -72
- package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-Cc84LaM3.js} +39 -14
- package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-YKr-cQPN.js} +7 -4
- package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DMMDQHAu.js} +52 -29
- package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-BVPDyIup.js} +10 -7
- package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-CCwyJP5C.js} +79 -33
- package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-B77O-z28.js} +21 -8
- package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
- package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
- package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-BcDlo3r_.js} +31 -20
- package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-eKzQhbQP.js} +11 -8
- package/dist/chunks/LpPhoneInput-BCbrrhgE.js +336 -0
- package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BEJ2SfI6.js} +84 -28
- package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-DCKeqiVn.js} +5 -2
- package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
- package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
- package/dist/chunks/LpRangeCalendar-DZuaRV0W.js +331 -0
- package/dist/chunks/{LpRollingText-CM5gkSeF.js → LpRollingText-DB7upf-5.js} +1 -1
- package/dist/chunks/{LpScrollArea-D8xyKXcF.js → LpScrollArea-Bry1fdOM.js} +14 -2
- package/dist/chunks/{LpSection-aM6J_u51.js → LpSection-tKEH0jYf.js} +1 -1
- package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-C09EEam2.js} +8 -4
- package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DNIL6MB9.js} +50 -31
- package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-CQNB0-yU.js} +30 -22
- package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-kx-LdqNU.js} +16 -11
- package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
- package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
- package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
- package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-B-7udBRP.js} +2 -2
- package/dist/chunks/{LpStepper-Bk-XaQYU.js → LpStepper-C7XiVKZj.js} +34 -19
- package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-OrcOJX9m.js} +41 -27
- package/dist/chunks/LpTableFilter-BV8PpLM9.js +158 -0
- package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
- package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-29iW1XOx.js} +20 -6
- package/dist/chunks/LpTagsInput-D1SuqbhB.js +300 -0
- package/dist/chunks/LpTextarea-uuScWo6-.js +77 -0
- package/dist/chunks/LpThemeProvider-Be4r2TQX.js +35 -0
- package/dist/chunks/LpThemeSwitcher-B4aVPbNC.js +166 -0
- package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
- package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-Cqe7fXnh.js} +14 -8
- package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster-DpJvoCOh.js} +94 -31
- package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CXVKcOXT.js} +14 -6
- package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
- package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-6ozNrQKh.js} +8 -2
- package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-ugcv25xj.js} +44 -15
- package/dist/chunks/icons-CkA0njzm.js +43 -0
- package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-DJm1JSsY.js} +215 -260
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.d.ts +8 -0
- package/dist/components/ContextMenuItems.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -0
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.d.ts +3 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +9 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +10 -0
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +31 -6
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +3 -3
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +35 -98
- 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 +47 -3
- package/dist/components/LpChart.vue.js +377 -97
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +2 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +2 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +14 -3
- package/dist/components/LpCommandPalette.vue.js +142 -77
- package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.d.ts +9 -0
- package/dist/components/LpConfirmHost.vue.js +4 -0
- package/dist/components/LpContextMenu.vue.d.ts +3 -5
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +24 -6
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.d.ts +60 -0
- package/dist/components/LpDateRangePicker.vue.js +4 -0
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +10 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +23 -7
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.d.ts +8 -0
- package/dist/components/LpFieldNotice.vue.js +4 -0
- package/dist/components/LpFileTree.vue.d.ts +11 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +13 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpIcon.vue.d.ts +0 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +93 -58
- package/dist/components/LpInput.vue.d.ts +22 -3
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +17 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +11 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +72 -3
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +4 -4
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -6
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +0 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +11 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +2 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +2 -0
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.d.ts +13 -0
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +21 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +2 -0
- 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/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.d.ts +51 -0
- package/dist/components/LpRangeCalendar.vue.js +4 -0
- package/dist/components/LpRollingText.vue.d.ts +1 -1
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.d.ts +2 -2
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.d.ts +5 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +1 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +7 -1
- package/dist/components/LpShortcutsDialog.vue.js +60 -15
- package/dist/components/LpSidebar.vue.d.ts +6 -2
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -0
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +107 -3
- package/dist/components/LpTable.vue.js +540 -128
- package/dist/components/LpTableFilter.vue.d.ts +31 -0
- package/dist/components/LpTableFilter.vue.js +4 -0
- package/dist/components/LpTableOfContents.vue.d.ts +4 -2
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +2 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTagsInput.vue.d.ts +77 -0
- package/dist/components/LpTagsInput.vue.js +4 -0
- package/dist/components/LpTextarea.vue.d.ts +19 -6
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +22 -0
- package/dist/components/LpThemeProvider.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +15 -2
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTilt.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.d.ts +6 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +1 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
- package/dist/components/LpTooltipProvider.vue.js +4 -0
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +32 -1
- package/dist/components/chart.js +42 -6
- package/dist/components/codeHighlight.d.ts +6 -0
- package/dist/components/codeHighlight.js +92 -20
- package/dist/components/color.d.ts +1 -1
- package/dist/components/color.js +20 -9
- package/dist/components/commandSearch.js +3 -0
- package/dist/components/confirm.d.ts +25 -0
- package/dist/components/confirm.js +26 -0
- package/dist/components/countries.d.ts +6 -0
- package/dist/components/countries.js +7 -0
- package/dist/components/dateRange.d.ts +43 -0
- package/dist/components/dateRange.js +76 -0
- package/dist/components/exportData.d.ts +58 -0
- package/dist/components/exportData.js +165 -0
- package/dist/components/fileTree.js +16 -7
- package/dist/components/imageCrop.d.ts +42 -0
- package/dist/components/imageCrop.js +47 -0
- package/dist/components/link.d.ts +12 -0
- package/dist/components/link.js +19 -0
- package/dist/components/logSearch.d.ts +18 -0
- package/dist/components/logSearch.js +36 -0
- package/dist/components/modalDepth.d.ts +18 -2
- package/dist/components/modalDepth.js +17 -5
- package/dist/components/otp.d.ts +5 -0
- package/dist/components/otp.js +10 -0
- package/dist/components/phone.d.ts +41 -0
- package/dist/components/phone.js +80 -0
- package/dist/components/sidebar.d.ts +7 -1
- package/dist/components/tableFilter.d.ts +36 -0
- package/dist/components/tableFilter.js +84 -0
- package/dist/components/tableSort.d.ts +8 -0
- package/dist/components/tableSort.js +31 -0
- package/dist/components/tags.d.ts +20 -0
- package/dist/components/tags.js +37 -0
- package/dist/composables/useField.d.ts +38 -0
- package/dist/composables/useField.js +45 -0
- package/dist/composables/useFieldNotice.d.ts +17 -0
- package/dist/composables/useFieldNotice.js +28 -0
- package/dist/composables/useForm.d.ts +53 -0
- package/dist/composables/useForm.js +207 -0
- package/dist/composables/useFormDraft.d.ts +10 -3
- package/dist/composables/useFormDraft.js +9 -14
- package/dist/composables/useHotkeys.d.ts +81 -5
- package/dist/composables/useHotkeys.js +223 -25
- package/dist/composables/useInputFilter.d.ts +17 -0
- package/dist/composables/useInputFilter.js +47 -10
- package/dist/composables/useInputMask.d.ts +59 -0
- package/dist/composables/useInputMask.js +170 -0
- package/dist/composables/useLpConfig.d.ts +24 -0
- package/dist/composables/useLpConfig.js +44 -0
- package/dist/composables/usePaintSelect.d.ts +22 -0
- package/dist/composables/usePaintSelect.js +169 -0
- package/dist/composables/useRovingFocus.js +7 -0
- package/dist/composables/useShift.js +7 -2
- package/dist/composables/useTilt.d.ts +2 -2
- package/dist/composables/useTilt.js +5 -6
- package/dist/composables/useToast.d.ts +20 -0
- package/dist/composables/useToast.js +28 -4
- package/dist/composables/useZoomPan.d.ts +3 -3
- package/dist/composables/useZoomPan.js +4 -4
- package/dist/icons-all.d.ts +1 -0
- package/dist/icons-all.js +3 -0
- package/dist/icons.d.ts +14 -1
- package/dist/index.d.ts +60 -4
- package/dist/index.js +245 -141
- package/dist/kit-icons.d.ts +2 -0
- package/dist/layout/tree.d.ts +2 -0
- package/dist/layout/tree.js +16 -1
- package/dist/layout/useLayout.d.ts +15 -0
- package/dist/layout/useLayout.js +41 -14
- package/dist/theme/backdrop.d.ts +12 -0
- package/dist/theme/presets.d.ts +0 -2
- package/dist/theme/useTheme.d.ts +32 -9
- package/package.json +18 -8
- package/src/component-names.ts +9 -7
- package/src/components/ContextMenuItems.vue +88 -0
- package/src/components/LayoutCanvas.vue +26 -6
- package/src/components/LayoutNode.vue +30 -4
- package/src/components/LpActionBar.vue +31 -8
- package/src/components/LpAddressInput.vue +57 -41
- package/src/components/LpAlert.vue +16 -3
- package/src/components/LpAppShell.vue +45 -12
- package/src/components/LpAutocomplete.vue +47 -10
- package/src/components/LpAvatar.vue +20 -6
- package/src/components/LpBreadcrumbs.vue +65 -41
- package/src/components/LpButton.vue +60 -14
- package/src/components/LpChart.vue +351 -28
- package/src/components/LpCheckbox.vue +7 -3
- package/src/components/LpCodeBlock.vue +50 -14
- package/src/components/LpColorPicker.vue +73 -10
- package/src/components/LpCommandPalette.vue +115 -20
- package/src/components/LpConfirmDialog.vue +68 -17
- package/src/components/LpConfirmHost.vue +59 -0
- package/src/components/LpContextMenu.vue +15 -116
- package/src/components/LpDatePicker.vue +104 -68
- package/src/components/LpDateRangePicker.vue +224 -0
- package/src/components/LpDrawer.vue +56 -19
- package/src/components/LpDropdownMenu.vue +1 -0
- package/src/components/LpDropzone.vue +21 -4
- package/src/components/LpFieldNotice.vue +65 -0
- package/src/components/LpFileTree.vue +33 -7
- package/src/components/LpFormField.vue +59 -7
- package/src/components/LpIcon.vue +22 -4
- package/src/components/LpImageEditor.vue +96 -57
- package/src/components/LpInput.vue +62 -11
- package/src/components/LpLightbox.vue +140 -29
- package/src/components/LpLink.vue +24 -13
- package/src/components/LpLogViewer.vue +311 -39
- package/src/components/LpMapPicker.vue +36 -13
- package/src/components/LpMediaGallery.vue +8 -2
- package/src/components/LpModal.vue +54 -28
- package/src/components/LpModalNav.vue +5 -2
- package/src/components/LpNotificationBell.vue +77 -23
- package/src/components/LpNumberField.vue +21 -6
- package/src/components/LpNumberFlow.vue +26 -10
- package/src/components/LpOtpInput.vue +43 -8
- package/src/components/LpPagination.vue +23 -7
- package/src/components/LpPasswordInput.vue +4 -1
- package/src/components/LpPhoneInput.vue +224 -39
- package/src/components/LpPie.vue +102 -20
- package/src/components/LpPopover.vue +4 -0
- package/src/components/LpProgress.vue +5 -3
- package/src/components/LpRadioGroup.vue +9 -2
- package/src/components/LpRangeCalendar.vue +365 -0
- package/src/components/LpRollingText.vue +3 -0
- package/src/components/LpScrollArea.vue +22 -2
- package/src/components/LpSegmented.vue +12 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +83 -9
- package/src/components/LpSidebar.vue +14 -9
- package/src/components/LpSidebarNav.vue +17 -5
- package/src/components/LpSkeleton.vue +4 -1
- package/src/components/LpSlider.vue +7 -1
- package/src/components/LpSparkline.vue +11 -3
- package/src/components/LpStepper.vue +23 -10
- package/src/components/LpSwitch.vue +31 -8
- package/src/components/LpTable.vue +668 -90
- package/src/components/LpTableFilter.vue +175 -0
- package/src/components/LpTableOfContents.vue +50 -32
- package/src/components/LpTabs.vue +23 -6
- package/src/components/LpTagsInput.vue +364 -0
- package/src/components/LpTextarea.vue +44 -6
- package/src/components/LpThemeProvider.vue +41 -0
- package/src/components/LpThemeSwitcher.vue +76 -33
- package/src/components/LpTilt.vue +2 -2
- package/src/components/LpTimeField.vue +14 -3
- package/src/components/LpToaster.vue +115 -27
- package/src/components/LpTooltip.vue +29 -5
- package/src/components/LpTooltipProvider.vue +24 -0
- package/src/components/LpTopologyCanvas.vue +19 -2
- package/src/components/LpUptimeBar.vue +46 -8
- package/src/components/chart.ts +82 -8
- package/src/components/codeHighlight.ts +111 -15
- package/src/components/color.ts +30 -9
- package/src/components/commandSearch.ts +6 -0
- package/src/components/confirm.ts +58 -0
- package/src/components/countries.ts +12 -0
- package/src/components/dateRange.ts +134 -0
- package/src/components/exportData.ts +228 -0
- package/src/components/fileTree.ts +19 -7
- package/src/components/imageCrop.ts +107 -0
- package/src/components/link.ts +34 -0
- package/src/components/logSearch.ts +46 -0
- package/src/components/modalDepth.ts +48 -6
- package/src/components/otp.ts +16 -0
- package/src/components/phone.ts +140 -0
- package/src/components/sidebar.ts +7 -1
- package/src/components/tableFilter.ts +122 -0
- package/src/components/tableSort.ts +37 -0
- package/src/components/tags.ts +62 -0
- package/src/composables/useField.ts +83 -0
- package/src/composables/useFieldNotice.ts +59 -0
- package/src/composables/useForm.ts +314 -0
- package/src/composables/useFormDraft.ts +25 -20
- package/src/composables/useHotkeys.ts +355 -28
- package/src/composables/useInputFilter.ts +77 -14
- package/src/composables/useInputMask.ts +254 -0
- package/src/composables/useLpConfig.ts +87 -0
- package/src/composables/usePaintSelect.ts +243 -0
- package/src/composables/useRovingFocus.ts +11 -0
- package/src/composables/useShift.ts +13 -2
- package/src/composables/useTilt.ts +5 -6
- package/src/composables/useToast.ts +56 -4
- package/src/composables/useZoomPan.ts +6 -6
- package/src/iconSubset.d.mts +10 -0
- package/src/iconSubset.mjs +51 -0
- package/src/icons-all.ts +10 -0
- package/src/icons.ts +59 -8
- package/src/index.ts +73 -4
- package/src/kit-icons.ts +5 -0
- package/src/layout/tree.ts +30 -0
- package/src/layout/useLayout.ts +68 -18
- package/src/nuxt.ts +87 -17
- package/src/runtime/config.plugin.ts +14 -0
- package/src/runtime/theme.plugin.ts +43 -23
- package/src/theme/backdrop.ts +69 -0
- package/src/theme/presets.ts +6 -64
- package/src/theme/useTheme.ts +206 -100
- package/src/tokens/tokens.css +46 -0
- package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
- package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
- package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
- package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
- package/dist/chunks/LpPhoneInput-BDSKVl_z.js +0 -209
- package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default { inheritAttrs: false }
|
|
3
|
+
|
|
4
|
+
export type TagsInputLabels = {
|
|
5
|
+
remove: string
|
|
6
|
+
duplicate: string
|
|
7
|
+
max: string
|
|
8
|
+
added: string
|
|
9
|
+
removed: string
|
|
10
|
+
}
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
/*
|
|
15
|
+
* Free-form list of short values: emails, labels, hostnames. Typing a
|
|
16
|
+
* delimiter or Enter turns the text into a tag, a paste is split into many,
|
|
17
|
+
* Backspace in an empty field selects the last tag and removes it on the next
|
|
18
|
+
* press, and the arrow keys walk the tags (reka TagsInput).
|
|
19
|
+
*
|
|
20
|
+
* Adding is the kit's own (see tags.ts) rather than reka's, so a tag that
|
|
21
|
+
* fails `validate`, the limit or the duplicate policy stays in the field for
|
|
22
|
+
* fixing instead of vanishing. The reason floats under the field
|
|
23
|
+
* (LpFieldNotice) rather than taking a line, so a refused paste never moves
|
|
24
|
+
* the form, and a repeat makes the tag it repeats pulse.
|
|
25
|
+
*/
|
|
26
|
+
import {
|
|
27
|
+
TagsInputInput,
|
|
28
|
+
TagsInputItem,
|
|
29
|
+
TagsInputItemDelete,
|
|
30
|
+
TagsInputItemText,
|
|
31
|
+
TagsInputRoot,
|
|
32
|
+
} from "reka-ui"
|
|
33
|
+
import { computed, ref, useAttrs, useId } from "vue"
|
|
34
|
+
import { fieldMessageId, useField, useFieldControl } from "../composables/useField"
|
|
35
|
+
import { prefersReducedMotion } from "../composables/easing"
|
|
36
|
+
import { useFieldNotice, type FieldNoticeOptions } from "../composables/useFieldNotice"
|
|
37
|
+
import { useShift } from "../composables/useShift"
|
|
38
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
39
|
+
import { mergeClass } from "../composables/useMergedClass"
|
|
40
|
+
import LpFieldNotice from "./LpFieldNotice.vue"
|
|
41
|
+
import LpFormField from "./LpFormField.vue"
|
|
42
|
+
import LpIcon from "./LpIcon.vue"
|
|
43
|
+
import { addTags, splitTags, type TagDuplicates, type TagRejection, type TagValidator } from "./tags"
|
|
44
|
+
|
|
45
|
+
const props = withDefaults(
|
|
46
|
+
defineProps<{
|
|
47
|
+
modelValue?: string[]
|
|
48
|
+
placeholder?: string
|
|
49
|
+
/** Characters (or a RegExp) that end a tag while typing and split a paste. */
|
|
50
|
+
delimiter?: string | readonly string[] | RegExp
|
|
51
|
+
/** At most this many tags. */
|
|
52
|
+
max?: number
|
|
53
|
+
/**
|
|
54
|
+
* "reject" keeps a repeat typed with a note, "ignore" drops it with a passing
|
|
55
|
+
* note, "allow" keeps it. Either way the tag it repeats pulses.
|
|
56
|
+
*/
|
|
57
|
+
duplicates?: TagDuplicates
|
|
58
|
+
/** Return an error message to refuse a tag. */
|
|
59
|
+
validate?: TagValidator
|
|
60
|
+
/** Applied to each tag before it is checked and stored (trim is always applied). */
|
|
61
|
+
normalize?: (tag: string) => string
|
|
62
|
+
/** Turn leftover text into a tag when the field loses focus. */
|
|
63
|
+
addOnBlur?: boolean
|
|
64
|
+
disabled?: boolean
|
|
65
|
+
invalid?: boolean
|
|
66
|
+
required?: boolean
|
|
67
|
+
label?: string
|
|
68
|
+
hint?: string
|
|
69
|
+
error?: string
|
|
70
|
+
labels?: Partial<TagsInputLabels>
|
|
71
|
+
}>(),
|
|
72
|
+
{ modelValue: () => [], delimiter: () => [",", "\n", "\t"], duplicates: "reject", addOnBlur: true },
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
const emit = defineEmits<{
|
|
76
|
+
(e: "update:modelValue", value: string[]): void
|
|
77
|
+
(e: "reject", rejected: TagRejection[]): void
|
|
78
|
+
(e: "blur", event: FocusEvent): void
|
|
79
|
+
}>()
|
|
80
|
+
|
|
81
|
+
const text = useLabels<TagsInputLabels>(
|
|
82
|
+
"tagsInput",
|
|
83
|
+
{
|
|
84
|
+
remove: "Remove {tag}",
|
|
85
|
+
duplicate: "{tag} is already added",
|
|
86
|
+
max: "Up to {max} items",
|
|
87
|
+
added: "{tag} added",
|
|
88
|
+
removed: "{tag} removed",
|
|
89
|
+
},
|
|
90
|
+
() => props.labels,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const attrs = useAttrs()
|
|
94
|
+
const hasField = computed(() => !!props.label || !!props.hint || !!props.error)
|
|
95
|
+
/*
|
|
96
|
+
* Notes go to the field that frames the control, which fades its own hint out
|
|
97
|
+
* under them: this component's own LpFormField when it has a label or hint,
|
|
98
|
+
* the one around it otherwise, and a notice of its own only when it stands
|
|
99
|
+
* alone.
|
|
100
|
+
*/
|
|
101
|
+
const notice = useFieldNotice()
|
|
102
|
+
const outerField = useField()
|
|
103
|
+
type FieldHandle = { notify: (text: string, options?: FieldNoticeOptions) => void; clearNotice: () => void }
|
|
104
|
+
const ownField = ref<FieldHandle | null>(null)
|
|
105
|
+
function bindField(c: unknown): void {
|
|
106
|
+
ownField.value = hasField.value && c ? (c as FieldHandle) : null
|
|
107
|
+
}
|
|
108
|
+
const standalone = computed(() => !hasField.value && !outerField?.value.notify)
|
|
109
|
+
function say(message: string, options?: FieldNoticeOptions): void {
|
|
110
|
+
if (hasField.value) ownField.value?.notify(message, options)
|
|
111
|
+
else if (outerField?.value.notify) outerField.value.notify(message, options)
|
|
112
|
+
else notice.show(message, options)
|
|
113
|
+
}
|
|
114
|
+
function unsay(): void {
|
|
115
|
+
if (hasField.value) ownField.value?.clearNotice()
|
|
116
|
+
else if (outerField?.value.clearNotice) outerField.value.clearNotice()
|
|
117
|
+
else notice.clear()
|
|
118
|
+
}
|
|
119
|
+
/** A refusal the user still has to act on: the text stays typed and the field reads invalid. */
|
|
120
|
+
const stuck = ref(false)
|
|
121
|
+
const announcement = ref("")
|
|
122
|
+
const noticeId = `${useId()}-notice`
|
|
123
|
+
|
|
124
|
+
const field = useFieldControl(() => ({
|
|
125
|
+
wrapped: hasField.value,
|
|
126
|
+
hasMessage: !!props.error || !!props.hint,
|
|
127
|
+
id: typeof attrs.id === "string" ? attrs.id : undefined,
|
|
128
|
+
invalid: props.invalid || !!props.error || stuck.value,
|
|
129
|
+
required: props.required,
|
|
130
|
+
disabled: props.disabled,
|
|
131
|
+
}))
|
|
132
|
+
|
|
133
|
+
const describedBy = computed(
|
|
134
|
+
() =>
|
|
135
|
+
[
|
|
136
|
+
field.value.describedBy,
|
|
137
|
+
hasField.value && field.value.id ? `${field.value.id}-notice` : undefined,
|
|
138
|
+
standalone.value && notice.message.value ? noticeId : undefined,
|
|
139
|
+
]
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.join(" ") || undefined,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const rootAttrs = computed(() => {
|
|
145
|
+
const { class: _c, style: _s, id: _i, ...rest } = attrs
|
|
146
|
+
return rest
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
function explain(rejected: TagRejection): string {
|
|
150
|
+
if (rejected.reason === "invalid") return rejected.message ?? ""
|
|
151
|
+
if (rejected.reason === "max") return text.value.max.replace("{max}", String(props.max))
|
|
152
|
+
return text.value.duplicate.replace("{tag}", rejected.value)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const inputEl = ref<HTMLInputElement | null>(null)
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
* The box grows a row at a time as tags wrap, and shrinks back when they go:
|
|
159
|
+
* eased rather than snapped, so the fields below glide instead of jumping.
|
|
160
|
+
*/
|
|
161
|
+
const { el: boxEl, tweening, resizing } = useShift({ axis: "height" })
|
|
162
|
+
|
|
163
|
+
/*
|
|
164
|
+
* Keys follow the tag, not its position: keyed by index, removing the first
|
|
165
|
+
* tag re-keyed every later one and they all replayed their entry. A repeated
|
|
166
|
+
* tag (duplicates "allow") is told apart by which occurrence it is.
|
|
167
|
+
*/
|
|
168
|
+
const keyed = computed(() => {
|
|
169
|
+
const seen = new Map<string, number>()
|
|
170
|
+
return props.modelValue.map((tag) => {
|
|
171
|
+
const n = seen.get(tag) ?? 0
|
|
172
|
+
seen.set(tag, n + 1)
|
|
173
|
+
return { tag, key: `${n}:${tag}` }
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
/** Draw the eye to a tag that was just typed again. */
|
|
178
|
+
function pulse(tag: string): void {
|
|
179
|
+
if (prefersReducedMotion()) return
|
|
180
|
+
const chip = boxEl.value?.querySelector<HTMLElement>(`[data-tag-key="${CSS.escape(`0:${tag}`)}"]`)
|
|
181
|
+
chip?.animate(
|
|
182
|
+
[
|
|
183
|
+
{ transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
|
|
184
|
+
{ transform: "scale(1.08)", boxShadow: "0 0 0 2px var(--color-ring)" },
|
|
185
|
+
{ transform: "scale(1)", boxShadow: "0 0 0 0 transparent" },
|
|
186
|
+
],
|
|
187
|
+
{ duration: 420, easing: "ease-out" },
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Add whatever `raw` splits into; returns the text that should stay typed. */
|
|
192
|
+
function add(raw: string): string {
|
|
193
|
+
const parts = splitTags(raw, props.delimiter)
|
|
194
|
+
if (!parts.length) return ""
|
|
195
|
+
const { tags, rejected, ignored } = addTags(props.modelValue, parts, {
|
|
196
|
+
max: props.max,
|
|
197
|
+
duplicates: props.duplicates,
|
|
198
|
+
validate: props.validate,
|
|
199
|
+
normalize: props.normalize,
|
|
200
|
+
})
|
|
201
|
+
if (tags.length !== props.modelValue.length) {
|
|
202
|
+
const added = tags.slice(props.modelValue.length)
|
|
203
|
+
announcement.value = added.map((t) => text.value.added.replace("{tag}", t)).join(", ")
|
|
204
|
+
emit("update:modelValue", tags)
|
|
205
|
+
}
|
|
206
|
+
stuck.value = rejected.length > 0
|
|
207
|
+
const first = rejected[0]
|
|
208
|
+
if (first) {
|
|
209
|
+
say(explain(first), { tone: first.reason === "invalid" ? "danger" : "info", duration: 0 })
|
|
210
|
+
emit("reject", rejected)
|
|
211
|
+
} else if (ignored.length) {
|
|
212
|
+
say(text.value.duplicate.replace("{tag}", ignored[0]!))
|
|
213
|
+
} else {
|
|
214
|
+
unsay()
|
|
215
|
+
}
|
|
216
|
+
for (const r of rejected) if (r.reason === "duplicate") pulse(props.normalize ? props.normalize(r.value) : r.value)
|
|
217
|
+
for (const tag of ignored) pulse(tag)
|
|
218
|
+
return rejected.map((r) => r.value).join(joiner.value)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Rejected parts go back into the field joined by a delimiter, so committing
|
|
222
|
+
// them again splits them the same way.
|
|
223
|
+
const joiner = computed(() => {
|
|
224
|
+
const d = props.delimiter
|
|
225
|
+
const visible = d instanceof RegExp ? undefined : (typeof d === "string" ? [d] : d).find((x) => x.trim())
|
|
226
|
+
return visible ? `${visible} ` : " "
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
function commit(el: HTMLInputElement): void {
|
|
230
|
+
el.value = add(el.value)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function isDelimiter(data: string): boolean {
|
|
234
|
+
const d = props.delimiter
|
|
235
|
+
if (d instanceof RegExp) return new RegExp(d.source, d.flags.replace(/[gy]/g, "")).test(data)
|
|
236
|
+
return (typeof d === "string" ? [d] : d).includes(data)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function onKeydown(event: KeyboardEvent): void {
|
|
240
|
+
if (event.isComposing || !(event.target instanceof HTMLInputElement)) return
|
|
241
|
+
if (event.key === "Enter" && event.target.value.trim()) {
|
|
242
|
+
event.preventDefault()
|
|
243
|
+
commit(event.target)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function onBeforeInput(event: InputEvent): void {
|
|
248
|
+
if (event.isComposing || !(event.target instanceof HTMLInputElement)) return
|
|
249
|
+
if (event.inputType === "insertText" && event.data && isDelimiter(event.data)) {
|
|
250
|
+
event.preventDefault()
|
|
251
|
+
commit(event.target)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function onInput(): void {
|
|
256
|
+
if (!stuck.value) return
|
|
257
|
+
stuck.value = false
|
|
258
|
+
unsay()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function onPaste(event: ClipboardEvent): void {
|
|
262
|
+
const pasted = event.clipboardData?.getData("text") ?? ""
|
|
263
|
+
if (!(event.target instanceof HTMLInputElement) || !pasted) return
|
|
264
|
+
if (splitTags(pasted, props.delimiter).length < 2) return
|
|
265
|
+
event.preventDefault()
|
|
266
|
+
const el = event.target
|
|
267
|
+
const typed = el.value.slice(0, el.selectionStart ?? el.value.length) + pasted + el.value.slice(el.selectionEnd ?? el.value.length)
|
|
268
|
+
el.value = add(typed)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function onBlur(event: FocusEvent): void {
|
|
272
|
+
emit("blur", event)
|
|
273
|
+
if (props.addOnBlur && event.target instanceof HTMLInputElement && event.target.value.trim()) commit(event.target)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function onModel(value: (string | Record<string, unknown>)[] | null): void {
|
|
277
|
+
const next = (value ?? []).map(String)
|
|
278
|
+
const removed = props.modelValue.filter((t) => !next.includes(t))
|
|
279
|
+
if (removed.length) announcement.value = removed.map((t) => text.value.removed.replace("{tag}", t)).join(", ")
|
|
280
|
+
stuck.value = false
|
|
281
|
+
unsay()
|
|
282
|
+
emit("update:modelValue", next)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
defineExpose({ focus: () => inputEl.value?.focus(), add: (value: string) => add(value) })
|
|
286
|
+
</script>
|
|
287
|
+
|
|
288
|
+
<template>
|
|
289
|
+
<component
|
|
290
|
+
:is="hasField ? LpFormField : 'div'"
|
|
291
|
+
:ref="bindField"
|
|
292
|
+
:label="hasField ? label : undefined"
|
|
293
|
+
:hint="hasField ? hint : undefined"
|
|
294
|
+
:error="hasField ? error : undefined"
|
|
295
|
+
:required="hasField ? required : undefined"
|
|
296
|
+
:id="hasField ? field.id : undefined"
|
|
297
|
+
:class="hasField ? attrs.class : 'contents'"
|
|
298
|
+
:style="hasField ? attrs.style : undefined"
|
|
299
|
+
>
|
|
300
|
+
<div class="relative">
|
|
301
|
+
<TagsInputRoot
|
|
302
|
+
v-bind="rootAttrs"
|
|
303
|
+
:model-value="modelValue"
|
|
304
|
+
:disabled="field.disabled"
|
|
305
|
+
:required="field.required"
|
|
306
|
+
:id="field.id"
|
|
307
|
+
:delimiter="/(?!)/"
|
|
308
|
+
data-lp-ring-owner
|
|
309
|
+
:ref="(c) => (boxEl = (c as { $el?: HTMLElement } | null)?.$el ?? null)"
|
|
310
|
+
:class="
|
|
311
|
+
mergeClass(
|
|
312
|
+
'flex min-h-(--size-control-md) w-full flex-wrap items-center gap-1.5 rounded-control border border-line bg-surface-soft px-2 py-1.5 text-sm text-ink transition-colors duration-[var(--duration-fast)] focus-within:border-brand focus-within:ring-2 focus-within:ring-ring has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-55',
|
|
313
|
+
field.invalid ? 'border-danger focus-within:border-danger focus-within:ring-danger-soft' : '',
|
|
314
|
+
hasField ? '' : (attrs.class as string),
|
|
315
|
+
tweening
|
|
316
|
+
? 'transition-[height,border-color,box-shadow] duration-fast ease-[var(--ease-emphasized)] motion-reduce:transition-none'
|
|
317
|
+
: '',
|
|
318
|
+
resizing ? 'overflow-hidden' : '',
|
|
319
|
+
)
|
|
320
|
+
"
|
|
321
|
+
:style="hasField ? undefined : attrs.style"
|
|
322
|
+
@update:model-value="onModel"
|
|
323
|
+
>
|
|
324
|
+
<!-- Tags grow in when added and shrink out when removed; the ones
|
|
325
|
+
already there on mount stay still (no `appear`). -->
|
|
326
|
+
<TransitionGroup
|
|
327
|
+
enter-active-class="animate-[chip-in_var(--duration-fast)_var(--ease-emphasized)] motion-reduce:animate-none"
|
|
328
|
+
leave-active-class="animate-[chip-out_var(--duration-fast)_ease] motion-reduce:animate-none"
|
|
329
|
+
>
|
|
330
|
+
<TagsInputItem
|
|
331
|
+
v-for="{ tag, key } in keyed"
|
|
332
|
+
:key="key"
|
|
333
|
+
:data-tag-key="key"
|
|
334
|
+
:value="tag"
|
|
335
|
+
class="inline-flex max-w-full items-center gap-1 rounded-pill bg-surface-raised py-0.5 pr-1 pl-2.5 text-xs font-medium text-ink outline-none data-[state=active]:ring-2 data-[state=active]:ring-ring"
|
|
336
|
+
>
|
|
337
|
+
<TagsInputItemText class="truncate" />
|
|
338
|
+
<TagsInputItemDelete
|
|
339
|
+
:aria-labelledby="undefined"
|
|
340
|
+
:aria-label="text.remove.replace('{tag}', tag)"
|
|
341
|
+
class="grid size-4 shrink-0 place-items-center rounded-pill text-muted transition-colors hover:bg-ink/10 hover:text-ink"
|
|
342
|
+
>
|
|
343
|
+
<LpIcon name="lucide:x" :size="12" />
|
|
344
|
+
</TagsInputItemDelete>
|
|
345
|
+
</TagsInputItem>
|
|
346
|
+
</TransitionGroup>
|
|
347
|
+
<TagsInputInput
|
|
348
|
+
:ref="(c) => (inputEl = (c as { $el?: HTMLInputElement } | null)?.$el ?? null)"
|
|
349
|
+
:placeholder="modelValue.length ? undefined : placeholder"
|
|
350
|
+
:aria-invalid="field.invalid || undefined"
|
|
351
|
+
:aria-describedby="describedBy"
|
|
352
|
+
class="min-w-24 flex-1 bg-transparent px-1 outline-none placeholder:text-muted"
|
|
353
|
+
@keydown="onKeydown"
|
|
354
|
+
@beforeinput="onBeforeInput"
|
|
355
|
+
@input="onInput"
|
|
356
|
+
@paste="onPaste"
|
|
357
|
+
@blur="onBlur"
|
|
358
|
+
/>
|
|
359
|
+
</TagsInputRoot>
|
|
360
|
+
<LpFieldNotice v-if="standalone" :notice="notice" :id="noticeId" />
|
|
361
|
+
</div>
|
|
362
|
+
<span class="sr-only" aria-live="polite">{{ announcement }}</span>
|
|
363
|
+
</component>
|
|
364
|
+
</template>
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default { inheritAttrs: false }
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from "vue"
|
|
6
|
+
import { computed, useAttrs } from "vue"
|
|
7
|
+
import { useFieldControl } from "../composables/useField"
|
|
8
|
+
import { mergeClass } from "../composables/useMergedClass"
|
|
3
9
|
import LpFormField from "./LpFormField.vue"
|
|
4
10
|
|
|
5
11
|
const props = defineProps<{
|
|
@@ -21,25 +27,57 @@ const props = defineProps<{
|
|
|
21
27
|
defineEmits<{ (e: "update:modelValue", value: string): void }>()
|
|
22
28
|
|
|
23
29
|
const hasField = computed(() => !!props.label || !!props.hint || !!props.error)
|
|
30
|
+
|
|
31
|
+
// class/style lay out the field when there is one, and style the <textarea>
|
|
32
|
+
// when it stands alone; every other attr (name, maxlength, autofocus, @blur…)
|
|
33
|
+
// belongs to the <textarea> itself.
|
|
34
|
+
const attrs = useAttrs()
|
|
35
|
+
const textareaAttrs = computed(() => {
|
|
36
|
+
const { class: _c, style: _s, ...rest } = attrs
|
|
37
|
+
return rest
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const field = useFieldControl(() => ({
|
|
41
|
+
wrapped: hasField.value,
|
|
42
|
+
hasMessage: !!props.error || !!props.hint,
|
|
43
|
+
id: typeof attrs.id === "string" ? attrs.id : undefined,
|
|
44
|
+
invalid: props.invalid || !!props.error,
|
|
45
|
+
required: props.required,
|
|
46
|
+
disabled: props.disabled,
|
|
47
|
+
}))
|
|
48
|
+
|
|
49
|
+
const textareaClass = computed(() =>
|
|
50
|
+
mergeClass(
|
|
51
|
+
"w-full resize-y 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",
|
|
52
|
+
field.value.invalid ? "border-danger focus:border-danger focus:ring-danger-soft" : "border-line",
|
|
53
|
+
hasField.value ? "" : (attrs.class as string),
|
|
54
|
+
),
|
|
55
|
+
)
|
|
24
56
|
</script>
|
|
25
57
|
|
|
26
58
|
<template>
|
|
27
59
|
<component
|
|
28
60
|
:is="hasField ? LpFormField : 'div'"
|
|
61
|
+
:id="hasField ? field.id : undefined"
|
|
29
62
|
:label="hasField ? label : undefined"
|
|
30
63
|
:hint="hasField ? hint : undefined"
|
|
31
64
|
:error="hasField ? error : undefined"
|
|
32
65
|
:required="hasField ? required : undefined"
|
|
33
|
-
:class="hasField ?
|
|
66
|
+
:class="hasField ? attrs.class : 'contents'"
|
|
67
|
+
:style="hasField ? attrs.style : undefined"
|
|
34
68
|
>
|
|
35
69
|
<textarea
|
|
70
|
+
v-bind="textareaAttrs"
|
|
71
|
+
:id="field.id"
|
|
36
72
|
:value="modelValue"
|
|
37
73
|
:placeholder="placeholder"
|
|
38
74
|
:rows="rows ?? 4"
|
|
39
|
-
:disabled="disabled"
|
|
40
|
-
:required="required || undefined"
|
|
41
|
-
|
|
42
|
-
:
|
|
75
|
+
:disabled="field.disabled"
|
|
76
|
+
:required="field.required || undefined"
|
|
77
|
+
:aria-invalid="field.invalid || undefined"
|
|
78
|
+
:aria-describedby="field.describedBy"
|
|
79
|
+
:class="textareaClass"
|
|
80
|
+
:style="hasField ? undefined : (attrs.style as never)"
|
|
43
81
|
@input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
|
|
44
82
|
/>
|
|
45
83
|
</component>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default { inheritAttrs: false }
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
/*
|
|
7
|
+
* Applies a theme to a subtree instead of the whole page: a dark sidebar in a
|
|
8
|
+
* light app, a live preview of a theme being edited, an embedded widget that
|
|
9
|
+
* keeps its own look. Every token is declared on this element, derived ones
|
|
10
|
+
* included, so kit components inside re-skin while the page around them keeps
|
|
11
|
+
* the global theme. Nothing is persisted; this is presentation only.
|
|
12
|
+
*/
|
|
13
|
+
import { computed, type Component } from "vue"
|
|
14
|
+
import type { TokenSet } from "../theme/tokens"
|
|
15
|
+
import { themeEntries } from "../theme/useTheme"
|
|
16
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(
|
|
19
|
+
defineProps<{
|
|
20
|
+
theme: TokenSet
|
|
21
|
+
/** Root element. A wrapper that should not affect layout can use `class="contents"`. */
|
|
22
|
+
as?: string | Component
|
|
23
|
+
}>(),
|
|
24
|
+
{ as: "div" },
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Paints its own surface and text so the scoped theme reads as one; a class
|
|
28
|
+
// like `bg-transparent` overrides that.
|
|
29
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs("bg-surface text-ink")
|
|
30
|
+
|
|
31
|
+
const style = computed(() => ({
|
|
32
|
+
...Object.fromEntries(themeEntries(props.theme)),
|
|
33
|
+
colorScheme: props.theme.mode,
|
|
34
|
+
}))
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<component :is="as" :style="style" :data-theme-mode="theme.mode" :class="rootClass" v-bind="rest">
|
|
39
|
+
<slot />
|
|
40
|
+
</component>
|
|
41
|
+
</template>
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* fetches; it just drives the theme engine.
|
|
13
13
|
*/
|
|
14
14
|
import { computed, onMounted, ref } from "vue"
|
|
15
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
15
16
|
import { presets } from "../theme/presets"
|
|
16
17
|
import type { TokenSet } from "../theme/tokens"
|
|
17
18
|
import { useTheme } from "../theme/useTheme"
|
|
@@ -33,9 +34,9 @@ const props = withDefaults(
|
|
|
33
34
|
cycleThemes?: (string | TokenSet)[]
|
|
34
35
|
/**
|
|
35
36
|
* Trigger style:
|
|
36
|
-
* - "swatch" (default): a
|
|
37
|
+
* - "swatch" (default): a round chip previewing the theme's surface and brand.
|
|
37
38
|
* - "icon": a sun/moon icon following the active theme's mode.
|
|
38
|
-
* - "pill":
|
|
39
|
+
* - "pill": the chip and the theme name in a rounded pill.
|
|
39
40
|
*/
|
|
40
41
|
variant?: "swatch" | "icon" | "pill"
|
|
41
42
|
/** Show the active theme's name next to the trigger (forced on for "pill"). */
|
|
@@ -51,6 +52,13 @@ const props = withDefaults(
|
|
|
51
52
|
transition?: boolean
|
|
52
53
|
size?: "sm" | "md" | "lg"
|
|
53
54
|
"aria-label"?: string
|
|
55
|
+
/**
|
|
56
|
+
* Offer a "System" entry in the menu that follows the OS colour scheme,
|
|
57
|
+
* switching between these two themes (names from `themes`, or TokenSets).
|
|
58
|
+
*/
|
|
59
|
+
system?: { light: string | TokenSet; dark: string | TokenSet }
|
|
60
|
+
/** Text overrides. `trigger` may contain `{name}`. */
|
|
61
|
+
labels?: Partial<{ trigger: string; system: string }>
|
|
54
62
|
}>(),
|
|
55
63
|
{
|
|
56
64
|
variant: "swatch",
|
|
@@ -66,6 +74,12 @@ const emit = defineEmits<{
|
|
|
66
74
|
|
|
67
75
|
const theme = useTheme()
|
|
68
76
|
|
|
77
|
+
const text = useLabels(
|
|
78
|
+
"themeSwitcher",
|
|
79
|
+
{ trigger: "Theme: {name}. Click to switch, right-click to choose.", system: "System" },
|
|
80
|
+
() => props.labels,
|
|
81
|
+
)
|
|
82
|
+
|
|
69
83
|
const list = computed<TokenSet[]>(() =>
|
|
70
84
|
props.themes && props.themes.length ? props.themes : Object.values(presets),
|
|
71
85
|
)
|
|
@@ -75,9 +89,6 @@ const list = computed<TokenSet[]>(() =>
|
|
|
75
89
|
const activeName = computed(
|
|
76
90
|
() => props.modelValue ?? (theme.currentName.value || list.value[0]?.name || ""),
|
|
77
91
|
)
|
|
78
|
-
const active = computed<TokenSet | undefined>(
|
|
79
|
-
() => list.value.find((t) => t.name === activeName.value) ?? list.value[0],
|
|
80
|
-
)
|
|
81
92
|
|
|
82
93
|
// Apply the consumer's default on mount when nothing is cached yet. init()
|
|
83
94
|
// hydrates from the cache first (a saved choice wins), so this only takes effect
|
|
@@ -93,14 +104,12 @@ onMounted(() => {
|
|
|
93
104
|
})
|
|
94
105
|
|
|
95
106
|
const SIZES = { sm: "size-7", md: "size-8", lg: "size-9" } as const
|
|
107
|
+
const HEIGHTS = { sm: "h-7", md: "h-8", lg: "h-9" } as const
|
|
96
108
|
const ICON_SIZE = { sm: 15, md: 16, lg: 18 } as const
|
|
109
|
+
const CHIP = { sm: "size-3.5", md: "size-4", lg: "size-[18px]" } as const
|
|
97
110
|
|
|
98
111
|
const wantsLabel = computed(() => props.showLabel || props.variant === "pill")
|
|
99
112
|
|
|
100
|
-
// Labelless icon variant renders as a plain square icon button (no border/fill),
|
|
101
|
-
// so it sits inline with other toolbar icon buttons. Other variants stay framed.
|
|
102
|
-
const bareIcon = computed(() => props.variant === "icon" && !wantsLabel.value)
|
|
103
|
-
|
|
104
113
|
// Apply a theme through the kit's self-managed setter (it persists + updates
|
|
105
114
|
// the shared `current`), then notify. The reveal animates from the click point.
|
|
106
115
|
function applyTheme(t: TokenSet, ev?: MouseEvent) {
|
|
@@ -140,18 +149,49 @@ function cycle(ev: MouseEvent) {
|
|
|
140
149
|
// Right-click menu: every theme, the active one checked. A circular-reveal from
|
|
141
150
|
// the (saved) click point isn't available here, so the menu picks reveal from
|
|
142
151
|
// the trigger centre via applyWithTransition's default origin.
|
|
143
|
-
|
|
144
|
-
list.value.
|
|
152
|
+
function resolve(t: string | TokenSet): TokenSet | undefined {
|
|
153
|
+
return typeof t === "string" ? list.value.find((x) => x.name === t) : t
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const systemPair = computed(() => {
|
|
157
|
+
if (!props.system) return null
|
|
158
|
+
const light = resolve(props.system.light)
|
|
159
|
+
const dark = resolve(props.system.dark)
|
|
160
|
+
return light && dark ? { light, dark } : null
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
function followSystem() {
|
|
164
|
+
const pair = systemPair.value
|
|
165
|
+
if (!pair) return
|
|
166
|
+
theme.setSystem(pair)
|
|
167
|
+
const next = theme.current.value as TokenSet | null
|
|
168
|
+
if (next) {
|
|
169
|
+
emit("update:modelValue", next.name)
|
|
170
|
+
emit("change", next)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const menu = computed<ContextMenuItemDef[]>(() => {
|
|
175
|
+
const following = theme.system.value
|
|
176
|
+
const items: ContextMenuItemDef[] = list.value.map((t) => ({
|
|
145
177
|
label: t.name,
|
|
146
178
|
icon:
|
|
147
|
-
t.name === activeName.value
|
|
179
|
+
!following && t.name === activeName.value
|
|
148
180
|
? "lucide:check"
|
|
149
181
|
: t.mode === "dark"
|
|
150
182
|
? "lucide:moon"
|
|
151
183
|
: "lucide:sun",
|
|
152
184
|
onSelect: () => applyTheme(t),
|
|
153
|
-
}))
|
|
154
|
-
)
|
|
185
|
+
}))
|
|
186
|
+
if (systemPair.value) {
|
|
187
|
+
items.unshift({
|
|
188
|
+
label: text.value.system,
|
|
189
|
+
icon: following ? "lucide:check" : "lucide:monitor",
|
|
190
|
+
onSelect: followSystem,
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
return items
|
|
194
|
+
})
|
|
155
195
|
|
|
156
196
|
/*
|
|
157
197
|
* The theme the SERVER rendered is not necessarily the one the client restores:
|
|
@@ -176,7 +216,7 @@ const labelName = computed(() =>
|
|
|
176
216
|
)
|
|
177
217
|
|
|
178
218
|
const triggerLabel = computed(
|
|
179
|
-
() => props["aria-label"] ??
|
|
219
|
+
() => props["aria-label"] ?? text.value.trigger.replace("{name}", labelName.value),
|
|
180
220
|
)
|
|
181
221
|
</script>
|
|
182
222
|
|
|
@@ -186,34 +226,37 @@ const triggerLabel = computed(
|
|
|
186
226
|
type="button"
|
|
187
227
|
:aria-label="triggerLabel"
|
|
188
228
|
:title="triggerLabel"
|
|
189
|
-
class="inline-flex items-center justify-center gap-2
|
|
229
|
+
class="inline-flex shrink-0 items-center justify-center gap-2 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring"
|
|
190
230
|
:class="
|
|
191
|
-
|
|
192
|
-
? [
|
|
193
|
-
|
|
194
|
-
'border border-line bg-surface-raised
|
|
195
|
-
wantsLabel ? 'py-1 pr-2.5' : '',
|
|
231
|
+
wantsLabel
|
|
232
|
+
? [
|
|
233
|
+
HEIGHTS[size],
|
|
234
|
+
'rounded-full border border-line bg-surface-raised pl-2 pr-3 text-ink hover:border-line-strong hover:bg-surface-soft',
|
|
196
235
|
]
|
|
236
|
+
: [SIZES[size], 'rounded-control text-muted hover:bg-surface-soft hover:text-ink']
|
|
197
237
|
"
|
|
198
238
|
@click="cycle"
|
|
199
239
|
>
|
|
200
|
-
<!--
|
|
240
|
+
<!-- A preview of the applied theme: its surface and brand halves. Read from
|
|
241
|
+
the applied variables rather than the theme object, so the server's
|
|
242
|
+
render (painted from the cookie) and the client's agree. -->
|
|
201
243
|
<span
|
|
202
244
|
v-if="variant !== 'icon'"
|
|
203
|
-
class="
|
|
204
|
-
:class="
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
class="shrink-0 rounded-full shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--color-ink)_22%,transparent)]"
|
|
246
|
+
:class="CHIP[size]"
|
|
247
|
+
style="background: linear-gradient(135deg, var(--color-surface) 50%, var(--color-brand) 50%)"
|
|
248
|
+
/>
|
|
249
|
+
<!-- sun/moon by mode, chosen by CSS from the applied theme's mode -->
|
|
250
|
+
<span v-else class="grid shrink-0 place-items-center">
|
|
251
|
+
<LpIcon
|
|
252
|
+
name="lucide:moon"
|
|
253
|
+
:size="ICON_SIZE[size]"
|
|
254
|
+
class="in-data-[theme-mode=light]:hidden"
|
|
210
255
|
/>
|
|
211
|
-
</span>
|
|
212
|
-
<!-- icon: sun/moon by mode -->
|
|
213
|
-
<span v-else class="grid shrink-0 place-items-center" :class="SIZES[size]">
|
|
214
256
|
<LpIcon
|
|
215
|
-
|
|
257
|
+
name="lucide:sun"
|
|
216
258
|
:size="ICON_SIZE[size]"
|
|
259
|
+
class="hidden in-data-[theme-mode=light]:block"
|
|
217
260
|
/>
|
|
218
261
|
</span>
|
|
219
262
|
|