@leavepulse/ui 0.87.0 → 0.88.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-DCZB4EAf.css → LpCommandPalette-CO_5TFOQ.css} +38 -19
- package/dist/assets/{LpDropzone-CfL3Vrx4.css → LpDropzone-CVGUQqgY.css} +10 -10
- package/dist/assets/{LpImageEditor-y3B-aW3H.css → LpImageEditor-D_ECuegW.css} +6 -6
- package/dist/assets/{LpTable-ClBFQeTd.css → LpTable-BqZNpWgC.css} +25 -25
- package/dist/chunks/{ContextMenuItems-D0jObgzN.js → ContextMenuItems-LujPZSWM.js} +2 -1
- package/dist/chunks/{DropzoneLinkField-DhwYRnvd.js → DropzoneLinkField-Ckuh8zFC.js} +2 -2
- package/dist/chunks/{IconCloseButton-CmWOJAXG.js → IconCloseButton-C_V7qngw.js} +1 -1
- package/dist/chunks/{LpActionBar-Cx_MSZl9.js → LpActionBar-43WHmTsM.js} +1 -1
- package/dist/chunks/{LpAddressInput-CfqQR5jE.js → LpAddressInput-BCqR0zmv.js} +5 -5
- package/dist/chunks/{LpAlert-CYtbpRYW.js → LpAlert-Q0h7Gucu.js} +1 -1
- package/dist/chunks/{LpAppShell-BQG98odS.js → LpAppShell-BWaVRsDB.js} +3 -3
- package/dist/chunks/{LpAutocomplete-VNID5zdy.js → LpAutocomplete-YPxAPHEo.js} +5 -5
- package/dist/chunks/{LpButton-DSHioIfD.js → LpButton-BvVoF-PO.js} +1 -1
- package/dist/chunks/{LpCheckbox-P7tEw8GO.js → LpCheckbox-C9VnPIQf.js} +1 -1
- package/dist/chunks/{LpCodeBlock-BNThgUdQ.js → LpCodeBlock-BgWcg2mk.js} +3 -3
- package/dist/chunks/{LpCodeDisplay-DsiE-Qy9.js → LpCodeDisplay-DBi1YPLp.js} +3 -3
- package/dist/chunks/{LpColorPicker-EXpwcX-t.js → LpColorPicker-KGMG8QEh.js} +4 -4
- package/dist/chunks/{LpConfirmDialog-DPiPVRn_.js → LpConfirmDialog-y6k0ryWR.js} +2 -2
- package/dist/chunks/{LpConfirmHost-Ddiljlog.js → LpConfirmHost-D-X0tDym.js} +1 -1
- package/dist/chunks/{LpContextMenu-DYpDm6iI.js → LpContextMenu-DEFirphS.js} +3 -3
- package/dist/chunks/{LpContextSwitcher-BPDSkKyA.js → LpContextSwitcher-COw6GHPW.js} +1 -1
- package/dist/chunks/{LpCopyButton-CxX682t4.js → LpCopyButton-BGzw95Xc.js} +2 -2
- package/dist/chunks/{LpDataList-DCtF7krm.js → LpDataList-B56Gu6Yt.js} +3 -3
- package/dist/chunks/{LpDatePicker-BElEHVm6.js → LpDatePicker-Dlyvgsan.js} +2 -2
- package/dist/chunks/{LpDateRangePicker-vLt-drEu.js → LpDateRangePicker-CAn2yGbn.js} +1 -1
- package/dist/chunks/{LpDrawer-C97k-3L2.js → LpDrawer-iRHwJOGn.js} +4 -4
- package/dist/chunks/{LpDropdownMenu-BPEsqZCN.js → LpDropdownMenu-CdCe7D2d.js} +3 -3
- package/dist/chunks/{LpEditable-CeRAzYMv.js → LpEditable-DfRUAGkA.js} +2 -2
- package/dist/chunks/{LpEmptyState-DrHBR3yh.js → LpEmptyState-DLOm07KH.js} +1 -1
- package/dist/chunks/{LpFileTree-BcQgsw4e.js → LpFileTree-BN_zybBN.js} +3 -3
- package/dist/chunks/{LpFileTreeNode-Dk2WqkPF.js → LpFileTreeNode-CC48DbT5.js} +3 -3
- package/dist/chunks/{LpHintLayer-CUSRLtLu.js → LpHintLayer-nuMqfzvy.js} +3 -3
- package/dist/chunks/{LpHints-BF40CP4_.js → LpHints-ByAd3a76.js} +3 -3
- package/dist/chunks/{LpHoverCard-CHE9jAJG.js → LpHoverCard-DYguY2AP.js} +10 -61
- package/dist/chunks/{LpInput-BU5IsOlE.js → LpInput-DGMpxrcg.js} +1 -1
- package/dist/chunks/{LpInspector-CEko6-pS.js → LpInspector-oTfkOdYd.js} +2 -2
- package/dist/chunks/{LpLightbox-REei-y5e.js → LpLightbox-a24V_0Wk.js} +19 -7
- package/dist/chunks/{LpLogInspector-Bwz351LY.js → LpLogInspector-BhJTJIst.js} +1 -1
- package/dist/chunks/{LpLogViewer-BCkqdH66.js → LpLogViewer-BuIzr_6h.js} +4 -4
- package/dist/chunks/{LpMediaGallery-BF1Bch3v.js → LpMediaGallery-B7DOV_aL.js} +2 -2
- package/dist/chunks/{LpModal-BK2T5uIG.js → LpModal-D1E7rQzw.js} +5 -5
- package/dist/chunks/{LpModalNav-Dk4eHpg6.js → LpModalNav-DivqT1NV.js} +5 -4
- package/dist/chunks/{LpNotificationBell-CMwUXhFg.js → LpNotificationBell-KRgdRZs-.js} +4 -4
- package/dist/chunks/{LpNotificationFeed-BXsRp9jm.js → LpNotificationFeed-CMGYU8c-.js} +5 -5
- package/dist/chunks/{LpNumberField-QUMXvpqx.js → LpNumberField-C0LSu-tl.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-wiJtP5hV.js → LpPaletteTheme-CLwO-SDf.js} +3 -3
- package/dist/chunks/{LpPasswordInput-W9AZfzaL.js → LpPasswordInput-LPVI7D3v.js} +2 -2
- package/dist/chunks/{LpPhoneInput-DsRv-Qla.js → LpPhoneInput-BdglZsGH.js} +5 -5
- package/dist/chunks/{LpPie-pbrNFbRP.js → LpPie-CsiBXe-e.js} +3 -3
- package/dist/chunks/{LpPipeline-BHWmvOiv.js → LpPipeline-DVcoSs--.js} +1 -1
- package/dist/chunks/{LpPopover-BQSuxtai.js → LpPopover-w2rr4x7n.js} +2 -2
- package/dist/chunks/{LpRadio-CWA5Eb3M.js → LpRadio-4KyxeSz4.js} +1 -1
- package/dist/chunks/{LpRadioGroup-D94KMStE.js → LpRadioGroup-Cr_fmrBY.js} +1 -1
- package/dist/chunks/{LpRangeSlider-CkP9hYUC.js → LpRangeSlider-DbaITHtC.js} +1 -1
- package/dist/chunks/{LpRelativeTime-0zI-44ve.js → LpRelativeTime-CN5bWD73.js} +1 -1
- package/dist/chunks/{LpRepeater-DLLcbQ6J.js → LpRepeater-C0rns2xW.js} +1 -1
- package/dist/chunks/{LpSecretField-Au7ZQTtm.js → LpSecretField-BwrZ4TfT.js} +5 -5
- package/dist/chunks/{LpSection-RKCwB0xN.js → LpSection-CwsfNR7N.js} +1 -1
- package/dist/chunks/{LpSegmented-DD8MPj4j.js → LpSegmented-DzFYP0ZF.js} +1 -1
- package/dist/chunks/{LpSelect-BeXz1TWh.js → LpSelect-Dffg5r4I.js} +5 -5
- package/dist/chunks/{LpSidebar-DUFlf6Uu.js → LpSidebar-DQtCYlT4.js} +4 -4
- package/dist/chunks/{LpSidebarNav-C34YEZIR.js → LpSidebarNav-CFcI7--7.js} +1 -1
- package/dist/chunks/{LpSidebarRow-BeEGgsZi.js → LpSidebarRow-C5jXpcRL.js} +2 -2
- package/dist/chunks/{LpSlider-BCy5rjwK.js → LpSlider-DUd4gXnw.js} +1 -1
- package/dist/chunks/{LpStat-eVz7iUfD.js → LpStat-SyVb5JXm.js} +1 -1
- package/dist/chunks/{LpSwitch-D7hmVsif.js → LpSwitch-D-mcaA6C.js} +1 -1
- package/dist/chunks/{LpTableFilter-BGFNKyUX.js → LpTableFilter-CILHffC1.js} +3 -3
- package/dist/chunks/{LpTagsInput-D9uOSlvf.js → LpTagsInput-DD8-xe3F.js} +2 -2
- package/dist/chunks/{LpTextarea-DwE-lQt4.js → LpTextarea-CQg-dVxN.js} +1 -1
- package/dist/chunks/{LpThemeStudio-COn2H76d.js → LpThemeStudio-BPPctXgd.js} +4 -4
- package/dist/chunks/{LpThemeSwitcher-B3R3nNid.js → LpThemeSwitcher-B3c44xFH.js} +4 -4
- package/dist/chunks/{LpTimeField-Mi8H1g_9.js → LpTimeField-Dio7Tj9A.js} +1 -1
- package/dist/chunks/{LpToaster-DOJn6Vey.js → LpToaster-Cdx1tnHB.js} +2 -2
- package/dist/chunks/{LpTooltip-Bz_rrq5S.js → LpTooltip-BKde8Xjv.js} +43 -7
- package/dist/chunks/{LpTopologyCanvas-5RFMP4gR.js → LpTopologyCanvas-CQ0l6A5h.js} +3 -3
- package/dist/chunks/{LpTour-DUI_hoPv.js → LpTour-BGUKUxSZ.js} +1 -1
- package/dist/chunks/{LpTruncate-CynJhsEw.js → LpTruncate-C_C8eiIE.js} +2 -2
- package/dist/chunks/{LpUptimeBar-G7PfNJUH.js → LpUptimeBar-Dt6SY_-m.js} +1 -1
- package/dist/chunks/{LpVideoCompress-CPUxXh-c.js → LpVideoCompress-CvxNdyjp.js} +2 -2
- package/dist/chunks/{LpVideoPlayer-BbCjEQAx.js → LpVideoPlayer-FMuxTAU8.js} +4 -4
- package/dist/chunks/{LpWindow-Dp_0nNqg.js → LpWindow-DsPeiDCZ.js} +1 -1
- package/dist/chunks/{LpWorkspaceSwitcher-bjkncjSl.js → LpWorkspaceSwitcher-DCIiOQt_.js} +4 -4
- package/dist/chunks/{PickerField-CjOTUXDK.js → PickerField-DzF3UVnX.js} +1 -1
- package/dist/chunks/{RevealToggle-Bl1NcfxB.js → RevealToggle-BsYxY6Oz.js} +2 -2
- package/dist/chunks/{TourCard-BovXtMbO.js → TourCard-yrLqv4Oz.js} +1 -1
- package/dist/chunks/{VideoVolume-DE3i1ree.js → VideoVolume-CzL5mZPE.js} +2 -2
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/IconCloseButton.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpChart.vue.js +2 -2
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +2 -0
- package/dist/components/LpCommandPalette.vue.js +69 -33
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpContextSwitcher.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.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 +5 -5
- package/dist/components/LpEditable.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpHoverCard.vue.d.ts +3 -3
- package/dist/components/LpHoverCard.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +4 -4
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpInspector.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLogInspector.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNotificationFeed.vue.js +1 -1
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/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/LpPopover.vue.js +1 -1
- package/dist/components/LpRadio.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.js +16 -13
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +9 -0
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/LpVideoCompress.vue.js +1 -1
- package/dist/components/LpVideoPlayer.vue.js +1 -1
- package/dist/components/LpVideoTrim.vue.js +4 -4
- package/dist/components/LpWindow.vue.js +1 -1
- package/dist/components/LpWorkspaceSwitcher.vue.js +1 -1
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/VideoVolume.vue.js +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/badge.js +1 -1
- package/dist/components/dropdown.d.ts +14 -0
- package/dist/components/dropdown.js +7 -1
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/modalDepth.d.ts +1 -0
- package/dist/components/modalDepth.js +2 -0
- package/dist/composables/useReveal.js +3 -3
- package/dist/composables/useTouchHold.d.ts +19 -0
- package/dist/composables/useTouchHold.js +60 -0
- package/dist/index.js +72 -71
- package/package.json +1 -1
- package/src/components/ContextMenuItems.vue +2 -1
- package/src/components/IconCloseButton.vue +1 -1
- package/src/components/LpAlert.vue +1 -1
- package/src/components/LpAutocomplete.vue +4 -4
- package/src/components/LpButton.vue +1 -1
- package/src/components/LpCheckbox.vue +1 -1
- package/src/components/LpCodeBlock.vue +2 -2
- package/src/components/LpColorPicker.vue +3 -3
- package/src/components/LpCommandPalette.vue +89 -7
- package/src/components/LpContextMenu.vue +2 -2
- package/src/components/LpDataList.vue +1 -1
- package/src/components/LpDrawer.vue +7 -4
- package/src/components/LpDropdownMenu.vue +2 -2
- package/src/components/LpDropzone.vue +2 -2
- package/src/components/LpEditable.vue +2 -2
- package/src/components/LpEmptyState.vue +1 -1
- package/src/components/LpFileTreeNode.vue +1 -1
- package/src/components/LpHintLayer.vue +2 -2
- package/src/components/LpHoverCard.vue +11 -80
- package/src/components/LpImageEditor.vue +1 -1
- package/src/components/LpInput.vue +1 -1
- package/src/components/LpLightbox.vue +20 -3
- package/src/components/LpModal.vue +4 -4
- package/src/components/LpModalNav.vue +2 -1
- package/src/components/LpNumberField.vue +1 -1
- package/src/components/LpPhoneInput.vue +5 -5
- package/src/components/LpPopover.vue +2 -2
- package/src/components/LpRadio.vue +1 -1
- package/src/components/LpRadioGroup.vue +1 -1
- package/src/components/LpRangeSlider.vue +1 -1
- package/src/components/LpSection.vue +9 -6
- package/src/components/LpSegmented.vue +1 -1
- package/src/components/LpSelect.vue +4 -4
- package/src/components/LpSlider.vue +1 -1
- package/src/components/LpStat.vue +1 -1
- package/src/components/LpSwitch.vue +1 -1
- package/src/components/LpTable.vue +6 -1
- package/src/components/LpTableFilter.vue +1 -1
- package/src/components/LpTagsInput.vue +2 -2
- package/src/components/LpTextarea.vue +1 -1
- package/src/components/LpThemeSwitcher.vue +2 -2
- package/src/components/LpTimeField.vue +1 -1
- package/src/components/LpToaster.vue +1 -1
- package/src/components/LpTooltip.vue +59 -5
- package/src/components/badge.ts +1 -1
- package/src/components/dropdown.ts +24 -0
- package/src/components/modalDepth.ts +6 -0
- package/src/composables/useReveal.ts +7 -4
- package/src/composables/useTouchHold.ts +92 -0
- package/src/locales/uk.ts +1 -0
- package/src/tokens/utilities.css +34 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "reka-ui"
|
|
8
8
|
import { computed, useAttrs } from "vue"
|
|
9
9
|
import { mergeClass } from "../composables/useMergedClass"
|
|
10
|
-
import { POPOVER_PANEL } from "./dropdown"
|
|
10
|
+
import { PANEL_FIT, POPOVER_PANEL } from "./dropdown"
|
|
11
11
|
import { useOutsideGuard, usePortalTarget } from "../composables/useView"
|
|
12
12
|
|
|
13
13
|
// The trigger owns its attributes through the slot; fallthrough tags the panel.
|
|
@@ -37,7 +37,7 @@ const open = defineModel<boolean>("open")
|
|
|
37
37
|
// Merged rather than appended, so `panelClass="w-80 p-0"` replaces the default
|
|
38
38
|
// width and padding instead of racing them in stylesheet order.
|
|
39
39
|
const panel = computed(() =>
|
|
40
|
-
mergeClass(POPOVER_PANEL, "z-(--z-popover) w-64 rounded-card p-3 text-sm text-ink outline-none", props.panelClass),
|
|
40
|
+
mergeClass(POPOVER_PANEL, PANEL_FIT, "z-(--z-popover) w-64 rounded-card p-3 text-sm text-ink outline-none", props.panelClass),
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
/*
|
|
@@ -32,7 +32,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
|
32
32
|
<RadioGroupItem
|
|
33
33
|
:value="value"
|
|
34
34
|
:disabled="disabled"
|
|
35
|
-
class="flex size-[18px] shrink-0 items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover/radio:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
35
|
+
class="lp-tap flex size-[18px] shrink-0 items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover/radio:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
36
36
|
>
|
|
37
37
|
<RadioGroupIndicator
|
|
38
38
|
class="flex data-[state=checked]:animate-[indicator-in_140ms_var(--ease-emphasized)]"
|
|
@@ -68,7 +68,7 @@ usePaletteControl({ disabled: () => field.value.disabled })
|
|
|
68
68
|
:disabled="opt.disabled"
|
|
69
69
|
:aria-describedby="opt.description ? `${uid}-${i}-desc` : undefined"
|
|
70
70
|
:class="opt.description ? 'mt-px' : ''"
|
|
71
|
-
class="flex size-[18px] shrink-0 items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover/radio:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
71
|
+
class="lp-tap flex size-[18px] shrink-0 items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover/radio:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
72
72
|
>
|
|
73
73
|
<RadioGroupIndicator
|
|
74
74
|
class="flex data-[state=checked]:animate-[indicator-in_140ms_var(--ease-emphasized)]"
|
|
@@ -64,7 +64,7 @@ function onCommit(v: number[] | undefined) {
|
|
|
64
64
|
const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, disabled: props.disabled }))
|
|
65
65
|
|
|
66
66
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
67
|
-
`relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55 ${SLIDER_GLIDE}`, () => ownAttrs())
|
|
67
|
+
`lp-tap relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55 ${SLIDER_GLIDE}`, () => ownAttrs())
|
|
68
68
|
const { dragging, onPointerDown } = useSliderDrag()
|
|
69
69
|
|
|
70
70
|
const thumbClass =
|
|
@@ -112,12 +112,15 @@ const classes = computed(() =>
|
|
|
112
112
|
line of their own even when they would have fitted beside the title.
|
|
113
113
|
With it the column takes the space that is left and shrinks first.
|
|
114
114
|
|
|
115
|
-
`
|
|
116
|
-
routinely carry something with no spaces in it (a
|
|
117
|
-
container id), and an unbreakable run pushes
|
|
118
|
-
side of whatever card the section sits in.
|
|
119
|
-
shrink; only this lets the text inside it give
|
|
120
|
-
|
|
115
|
+
`overflow-wrap: anywhere` covers the title AND the description in one
|
|
116
|
+
place. Both routinely carry something with no spaces in it (a
|
|
117
|
+
hostname, a URL, a container id), and an unbreakable run pushes
|
|
118
|
+
straight out through the side of whatever card the section sits in.
|
|
119
|
+
min-w-0 lets the column shrink; only this lets the text inside it give
|
|
120
|
+
way. `break-words` was not enough: the title is an item of a flex row,
|
|
121
|
+
and break-word leaves its minimum width at the whole run, so a hash
|
|
122
|
+
in a title widened a phone page until the browser zoomed it out. -->
|
|
123
|
+
<div class="min-w-0 flex-1 [overflow-wrap:anywhere]">
|
|
121
124
|
<div class="flex flex-wrap items-center gap-2">
|
|
122
125
|
<LpIcon v-if="icon" :name="icon" :size="16" class="shrink-0 text-muted" />
|
|
123
126
|
<component :is="as" :class="classes.title()">
|
|
@@ -135,7 +135,7 @@ usePaletteControl({ disabled: () => field.value.disabled })
|
|
|
135
135
|
:value="opt.value"
|
|
136
136
|
:disabled="opt.disabled"
|
|
137
137
|
:aria-label="opt.ariaLabel ?? (opt.label ? undefined : opt.value)"
|
|
138
|
-
class="relative inline-flex h-full items-center justify-center gap-1.5 rounded-inner font-medium outline-none transition-colors duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] focus-visible:ring-2 focus-visible:ring-ring data-[state=on]:text-ink data-[state=off]:text-muted data-[state=off]:hover:text-ink disabled:cursor-not-allowed disabled:opacity-55"
|
|
138
|
+
class="lp-tap relative inline-flex h-full items-center justify-center gap-1.5 rounded-inner font-medium outline-none transition-colors duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] focus-visible:ring-2 focus-visible:ring-ring data-[state=on]:text-ink data-[state=off]:text-muted data-[state=off]:hover:text-ink disabled:cursor-not-allowed disabled:opacity-55"
|
|
139
139
|
:class="[block ? 'flex-1' : '', size === 'lg' ? 'px-4' : 'px-3', hovered === opt.value ? 'text-ink' : '']"
|
|
140
140
|
@pointerenter="onEnter(opt)"
|
|
141
141
|
@pointerup="releaseFocusAfterPointer"
|
|
@@ -43,7 +43,7 @@ import FieldWrapper from "./FieldWrapper.vue"
|
|
|
43
43
|
import { useFieldBlur, useOwnField, mergeDescribedBy } from "../composables/useField"
|
|
44
44
|
import { useLabels } from "../composables/useLpConfig"
|
|
45
45
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
46
|
-
import { OPTION_ITEM, optionStagger, POPOVER_PANEL } from "./dropdown"
|
|
46
|
+
import { COMBOBOX_FIT, OPTION_ITEM, optionStagger, POPOVER_PANEL } from "./dropdown"
|
|
47
47
|
import LpIcon from "./LpIcon.vue"
|
|
48
48
|
import OptionContent from "./OptionContent.vue"
|
|
49
49
|
import LpRollingText from "./LpRollingText.vue"
|
|
@@ -599,18 +599,18 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
|
|
|
599
599
|
@escape-key-down="guardOutside"
|
|
600
600
|
position="popper"
|
|
601
601
|
:side-offset="6"
|
|
602
|
-
:class="[POPOVER_PANEL, 'z-(--z-popover)
|
|
602
|
+
:class="[POPOVER_PANEL, 'z-(--z-popover) w-(--reka-combobox-trigger-width) overflow-hidden', COMBOBOX_FIT]"
|
|
603
603
|
>
|
|
604
604
|
<div v-if="searchable || serverSide" class="border-b border-line p-2">
|
|
605
605
|
<ComboboxInput
|
|
606
606
|
v-model="query"
|
|
607
607
|
:placeholder="searchPlaceholderText"
|
|
608
608
|
:aria-label="labels.searchLabel"
|
|
609
|
-
class="h-(--size-control-sm) w-full rounded-md border border-line bg-surface-soft px-2.5 text-sm text-ink outline-none placeholder:text-muted focus:border-brand"
|
|
609
|
+
class="lp-entry h-(--size-control-sm) w-full rounded-md border border-line bg-surface-soft px-2.5 text-sm text-ink outline-none placeholder:text-muted focus:border-brand"
|
|
610
610
|
/>
|
|
611
611
|
</div>
|
|
612
612
|
|
|
613
|
-
<ComboboxViewport class="max-h-56 overflow-y-auto p-1">
|
|
613
|
+
<ComboboxViewport class="max-h-56 min-h-0 flex-1 overflow-y-auto p-1">
|
|
614
614
|
<div v-if="loading" class="px-3 py-4 text-center text-sm italic text-muted">
|
|
615
615
|
{{ loadingLabel }}
|
|
616
616
|
</div>
|
|
@@ -51,7 +51,7 @@ const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, di
|
|
|
51
51
|
|
|
52
52
|
// A slider is full-width by default; putting one in a toolbar means constraining it, and a fixed h-5 has to yield to a denser row.
|
|
53
53
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
54
|
-
`relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55 ${SLIDER_GLIDE}`, () => ownAttrs())
|
|
54
|
+
`lp-tap relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55 ${SLIDER_GLIDE}`, () => ownAttrs())
|
|
55
55
|
const { dragging, onPointerDown } = useSliderDrag()
|
|
56
56
|
|
|
57
57
|
usePaletteControl({ disabled: () => field.value.disabled })
|
|
@@ -174,7 +174,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
174
174
|
the same as a live server row elsewhere on the page. -->
|
|
175
175
|
<LpStatus v-if="online" state="online" size="sm" hide-label />
|
|
176
176
|
<LpIcon v-if="icon" :name="icon" :size="14" class="text-muted" />
|
|
177
|
-
<span class="text-[11px] font-semibold uppercase tracking-[0.08em] text-muted">
|
|
177
|
+
<span class="min-w-0 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted [overflow-wrap:anywhere]">
|
|
178
178
|
<Transition v-bind="FADE">
|
|
179
179
|
<LpSkeleton v-if="loading && !label" class="inline-block h-[0.8em] w-20 align-middle" />
|
|
180
180
|
<span v-else>{{ label }}</span>
|
|
@@ -58,7 +58,7 @@ const slots = useSlots()
|
|
|
58
58
|
const labelled = computed(() => Boolean(props.label || props.description || slots.default))
|
|
59
59
|
|
|
60
60
|
const TRACK =
|
|
61
|
-
"group/switch inline-flex h-5 w-9 shrink-0 items-center rounded-pill border border-line bg-surface-soft px-0.5 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
61
|
+
"lp-tap group/switch inline-flex h-5 w-9 shrink-0 items-center rounded-pill border border-line bg-surface-soft px-0.5 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
|
|
62
62
|
const THUMB =
|
|
63
63
|
"h-3.5 w-3.5 rounded-full bg-ink shadow transition-[translate,width,background-color] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-active/switch:w-5 group-data-[state=checked]/switch:translate-x-4 group-data-[state=checked]/switch:bg-ink-inverse group-data-[state=checked]/switch:group-active/switch:translate-x-2.5 motion-reduce:group-active/switch:w-3.5"
|
|
64
64
|
|
|
@@ -1473,7 +1473,12 @@ const activeLayout = computed<TableLayout>(() =>
|
|
|
1473
1473
|
? "list"
|
|
1474
1474
|
: chosenLayout.value,
|
|
1475
1475
|
)
|
|
1476
|
-
|
|
1476
|
+
// While the width forces a list the switch would change nothing on screen, so
|
|
1477
|
+
// it steps aside until there is room for the choice again.
|
|
1478
|
+
const forcedList = computed(() => chosenLayout.value === "table" && activeLayout.value === "list")
|
|
1479
|
+
const layoutChoices = computed(() =>
|
|
1480
|
+
props.layouts && props.layouts.length > 1 && !forcedList.value ? props.layouts : [],
|
|
1481
|
+
)
|
|
1477
1482
|
const LAYOUT_ICONS: Record<TableLayout, string> = {
|
|
1478
1483
|
table: "lucide:table-2",
|
|
1479
1484
|
cards: "lucide:layout-grid",
|
|
@@ -163,7 +163,7 @@ const action =
|
|
|
163
163
|
<span v-if="option.count !== undefined" class="text-xs text-muted tabular-nums">{{ option.count }}</span>
|
|
164
164
|
<button
|
|
165
165
|
type="button"
|
|
166
|
-
class="rounded-control px-1.5 py-0.5 text-xs font-medium text-brand opacity-0 outline-none transition-opacity duration-fast group-hover/opt:opacity-100 hover:bg-brand-soft focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring"
|
|
166
|
+
class="rounded-control px-1.5 py-0.5 text-xs font-medium text-brand opacity-0 outline-none transition-opacity duration-fast group-hover/opt:opacity-100 [@media(hover:none)]:opacity-100 hover:bg-brand-soft focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring"
|
|
167
167
|
@click="set(onlyValues(values, [option.value]))"
|
|
168
168
|
>
|
|
169
169
|
{{ labels.only }}
|
|
@@ -513,7 +513,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
|
|
|
513
513
|
:aria-expanded="options ? listOpen : undefined"
|
|
514
514
|
:aria-controls="options ? listId : undefined"
|
|
515
515
|
:aria-activedescendant="listOpen && activeIndex >= 0 ? optionId(activeIndex) : undefined"
|
|
516
|
-
class="min-w-24 flex-1 bg-transparent px-1 outline-none placeholder:text-muted"
|
|
516
|
+
class="lp-entry min-w-24 flex-1 bg-transparent px-1 outline-none placeholder:text-muted"
|
|
517
517
|
@focus="onFocus"
|
|
518
518
|
@keydown="onKeydown"
|
|
519
519
|
@beforeinput="onBeforeInput"
|
|
@@ -535,7 +535,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
|
|
|
535
535
|
align="start"
|
|
536
536
|
:side-offset="6"
|
|
537
537
|
:collision-padding="8"
|
|
538
|
-
:class="[POPOVER_PANEL, 'z-(--z-popover) max-h-
|
|
538
|
+
:class="[POPOVER_PANEL, 'z-(--z-popover) max-h-[min(18rem,var(--reka-popper-available-height,18rem))] w-(--reka-popper-anchor-width) overflow-y-auto p-1 outline-none']"
|
|
539
539
|
@open-auto-focus.prevent
|
|
540
540
|
@close-auto-focus.prevent
|
|
541
541
|
@interact-outside="keepOpenInside"
|
|
@@ -82,7 +82,7 @@ defineExpose({ el: box, fit })
|
|
|
82
82
|
const textareaClass = computed(() =>
|
|
83
83
|
mergeClass(
|
|
84
84
|
props.autosize ? "w-full resize-none" : "w-full resize-y",
|
|
85
|
-
"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",
|
|
85
|
+
"lp-entry 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",
|
|
86
86
|
field.value.invalid ? "border-danger focus:border-danger focus:ring-danger-soft" : "border-line",
|
|
87
87
|
hasField.value ? "" : (attrs.class as string),
|
|
88
88
|
),
|
|
@@ -20,7 +20,7 @@ import { presets } from "../theme/presets"
|
|
|
20
20
|
import { STUDIO_SYSTEM, useThemeStudio } from "../theme/studio"
|
|
21
21
|
import type { TokenSet } from "../theme/tokens"
|
|
22
22
|
import { useTheme } from "../theme/useTheme"
|
|
23
|
-
import { POPOVER_PANEL } from "./dropdown"
|
|
23
|
+
import { PANEL_FIT, POPOVER_PANEL } from "./dropdown"
|
|
24
24
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
25
25
|
import LpIcon from "./LpIcon.vue"
|
|
26
26
|
import LpRollingText from "./LpRollingText.vue"
|
|
@@ -374,7 +374,7 @@ usePaletteTheme(() => tuner ?? undefined, { hide: props.studioHide })
|
|
|
374
374
|
align="end"
|
|
375
375
|
:side-offset="6"
|
|
376
376
|
:aria-label="text.studio"
|
|
377
|
-
:class="[POPOVER_PANEL, 'z-(--z-popover) w-96
|
|
377
|
+
:class="[POPOVER_PANEL, PANEL_FIT, 'z-(--z-popover) w-96 rounded-card p-3 text-sm text-ink outline-none']"
|
|
378
378
|
data-lp-glide-position
|
|
379
379
|
@interact-outside="keepForTrigger"
|
|
380
380
|
@escape-key-down="guardOutside"
|
|
@@ -80,7 +80,7 @@ const shellSize = CONTROL_SIZE
|
|
|
80
80
|
// `w-full` is the default, not a rule: a time column of `w-28` must win over it.
|
|
81
81
|
const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
82
82
|
[
|
|
83
|
-
"flex w-full items-center gap-0.5 rounded-control border bg-surface-soft px-3 text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55",
|
|
83
|
+
"lp-entry flex w-full items-center gap-0.5 rounded-control border bg-surface-soft px-3 text-ink transition-colors duration-[var(--duration-fast)] focus-within:ring-2 focus-within:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-55",
|
|
84
84
|
shellSize[props.size],
|
|
85
85
|
field.value.invalid ? "border-danger focus-within:ring-danger-soft" : "border-line focus-within:border-brand",
|
|
86
86
|
].join(" "), () => ownAttrs())
|
|
@@ -330,7 +330,7 @@ const portalTo = usePortalTarget()
|
|
|
330
330
|
<Teleport :to="portalTo">
|
|
331
331
|
<ToastViewport
|
|
332
332
|
:data-lp-toasts="viewportId"
|
|
333
|
-
class="fixed bottom-0 right-0 z-(--z-toast) flex w-[min(92vw,22rem)] flex-col gap-2 p-4 outline-none"
|
|
333
|
+
class="fixed bottom-0 right-0 z-(--z-toast) flex w-[min(92vw,22rem)] flex-col gap-2 p-4 pr-[max(1rem,env(safe-area-inset-right))] pb-[max(1rem,env(safe-area-inset-bottom))] outline-none"
|
|
334
334
|
/>
|
|
335
335
|
</Teleport>
|
|
336
336
|
</ToastProvider>
|
|
@@ -36,13 +36,14 @@ import {
|
|
|
36
36
|
TooltipRoot,
|
|
37
37
|
TooltipTrigger,
|
|
38
38
|
} from "reka-ui"
|
|
39
|
-
import { onBeforeUnmount, ref, watch, type FunctionalComponent } from "vue"
|
|
39
|
+
import { computed, mergeProps, onBeforeUnmount, ref, useAttrs, watch, type FunctionalComponent } from "vue"
|
|
40
40
|
import {
|
|
41
41
|
springSettled,
|
|
42
42
|
springStep,
|
|
43
43
|
type SpringState,
|
|
44
44
|
} from "../composables/usePillTransition"
|
|
45
45
|
import { PANEL_SURFACE, TOOLTIP_ANIM } from "./dropdown"
|
|
46
|
+
import { useTouchHold } from "../composables/useTouchHold"
|
|
46
47
|
import { prefersReducedMotion } from "../composables/easing"
|
|
47
48
|
import { useOutsideGuard, usePortalTarget, useView, viewWindow } from "../composables/useView"
|
|
48
49
|
|
|
@@ -86,8 +87,16 @@ const props = withDefaults(
|
|
|
86
87
|
* a scroll that should have dismissed it.
|
|
87
88
|
*/
|
|
88
89
|
disabled?: boolean
|
|
90
|
+
/**
|
|
91
|
+
* Touch has no hover, and a tap is the control's own. With `hold` a
|
|
92
|
+
* press-and-hold shows the tip (and that press does not also press the
|
|
93
|
+
* control); the next touch anywhere, or Escape, hides it. `none` leaves
|
|
94
|
+
* touch to the trigger. A followed tip (`followCursor`) never holds: its
|
|
95
|
+
* trigger is a chart or canvas with its own touch reading.
|
|
96
|
+
*/
|
|
97
|
+
touch?: "hold" | "none"
|
|
89
98
|
}>(),
|
|
90
|
-
{ side: "top" },
|
|
99
|
+
{ side: "top", touch: "hold" },
|
|
91
100
|
)
|
|
92
101
|
|
|
93
102
|
/*
|
|
@@ -123,6 +132,9 @@ const target = { x: 0, y: 0 }
|
|
|
123
132
|
const offset = ref({ x: 0, y: 0 })
|
|
124
133
|
const tipBody = ref<HTMLElement | null>(null)
|
|
125
134
|
|
|
135
|
+
/* How long a finger rests on the trigger before the tip shows. */
|
|
136
|
+
const HOLD_MS = 450
|
|
137
|
+
|
|
126
138
|
/* reka's own collision padding, which the follow offset would otherwise undo. */
|
|
127
139
|
const EDGE = 8
|
|
128
140
|
|
|
@@ -241,7 +253,30 @@ const open = ref(false)
|
|
|
241
253
|
/* What reka last asked for, regardless of whether it was allowed at the time. */
|
|
242
254
|
const wanted = ref(false)
|
|
243
255
|
|
|
256
|
+
/*
|
|
257
|
+
* A tip opened by a touch hold stays until the next touch or Escape. reka
|
|
258
|
+
* would close it as soon as the finger lifts (a touch leaves the trigger
|
|
259
|
+
* then), so while it is held its own requests to close are not taken.
|
|
260
|
+
*/
|
|
261
|
+
const heldOpen = ref(false)
|
|
262
|
+
const hold = useTouchHold({
|
|
263
|
+
enabled: () => props.touch === "hold" && !props.followCursor && !props.disabled,
|
|
264
|
+
delay: () => HOLD_MS,
|
|
265
|
+
onHold: () => {
|
|
266
|
+
heldOpen.value = true
|
|
267
|
+
wanted.value = true
|
|
268
|
+
open.value = true
|
|
269
|
+
},
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
function release() {
|
|
273
|
+
heldOpen.value = false
|
|
274
|
+
wanted.value = false
|
|
275
|
+
open.value = false
|
|
276
|
+
}
|
|
277
|
+
|
|
244
278
|
const onOpenChange = (next: boolean) => {
|
|
279
|
+
if (!next && heldOpen.value) return
|
|
245
280
|
// What reka wants, kept apart from what is currently allowed.
|
|
246
281
|
wanted.value = next
|
|
247
282
|
open.value = next && !props.disabled
|
|
@@ -265,8 +300,11 @@ const onOpenChange = (next: boolean) => {
|
|
|
265
300
|
watch(
|
|
266
301
|
() => props.disabled,
|
|
267
302
|
(off) => {
|
|
268
|
-
if (off)
|
|
269
|
-
|
|
303
|
+
if (off) {
|
|
304
|
+
hold.cancel()
|
|
305
|
+
heldOpen.value = false
|
|
306
|
+
open.value = false
|
|
307
|
+
} else if (wanted.value) open.value = true
|
|
270
308
|
},
|
|
271
309
|
)
|
|
272
310
|
|
|
@@ -289,6 +327,18 @@ watch(
|
|
|
289
327
|
},
|
|
290
328
|
)
|
|
291
329
|
|
|
330
|
+
watch(heldOpen, (held, _, onCleanup) => {
|
|
331
|
+
if (!held) return
|
|
332
|
+
const win = viewWindow(view.value)
|
|
333
|
+
const onKey = (event: KeyboardEvent) => event.key === "Escape" && release()
|
|
334
|
+
win.addEventListener("pointerdown", release, true)
|
|
335
|
+
win.addEventListener("keydown", onKey, true)
|
|
336
|
+
onCleanup(() => {
|
|
337
|
+
win.removeEventListener("pointerdown", release, true)
|
|
338
|
+
win.removeEventListener("keydown", onKey, true)
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
292
342
|
onBeforeUnmount(stopSpring)
|
|
293
343
|
|
|
294
344
|
// Overlays open in the window this component renders in (an LpFrame's, inside one).
|
|
@@ -302,6 +352,9 @@ const guardOutside = useOutsideGuard()
|
|
|
302
352
|
// LpPopover's, as-child) lost its id, ARIA state and click, and its popover
|
|
303
353
|
// never opened. They go to the trigger, which merges them onto the slot's element.
|
|
304
354
|
defineOptions({ inheritAttrs: false })
|
|
355
|
+
// The caller's own listeners on the trigger run beside the hold's, not instead of them.
|
|
356
|
+
const attrs = useAttrs()
|
|
357
|
+
const triggerAttrs = computed(() => mergeProps(attrs, hold.handlers))
|
|
305
358
|
</script>
|
|
306
359
|
|
|
307
360
|
<template>
|
|
@@ -324,8 +377,9 @@ defineOptions({ inheritAttrs: false })
|
|
|
324
377
|
lands on the real button rather than on a wrapper. -->
|
|
325
378
|
<TooltipTrigger
|
|
326
379
|
as-child
|
|
327
|
-
v-bind="
|
|
380
|
+
v-bind="triggerAttrs"
|
|
328
381
|
:aria-label="label ? content : undefined"
|
|
382
|
+
:class="touch === 'hold' && !followCursor ? '[-webkit-touch-callout:none]' : undefined"
|
|
329
383
|
@pointermove="onPointerMove"
|
|
330
384
|
>
|
|
331
385
|
<slot />
|
package/src/components/badge.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* keyboard handling), so it still looks like the badge's own.
|
|
6
6
|
*/
|
|
7
7
|
export const BADGE_REMOVE =
|
|
8
|
-
"grid size-4 shrink-0 place-items-center rounded-pill text-muted outline-none transition-colors duration-fast hover:bg-ink/10 hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
8
|
+
"lp-tap grid size-4 shrink-0 place-items-center rounded-pill text-muted outline-none transition-colors duration-fast hover:bg-ink/10 hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
@@ -45,6 +45,21 @@ export const PANEL_SURFACE =
|
|
|
45
45
|
*/
|
|
46
46
|
export const POPOVER_PANEL = PANEL_SURFACE + " " + POPOVER_ANIM
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Keeps a free-form panel (popover, menu, hover card) inside the window. Its
|
|
50
|
+
* width stops 0.5rem short of each edge, so a w-96 panel or a long menu label
|
|
51
|
+
* no longer runs off a phone; its height stops at the room reka measured on the
|
|
52
|
+
* side it opened, and the rest scrolls. A date picker opened in landscape used
|
|
53
|
+
* to hang off the top of the screen with its first weeks out of reach.
|
|
54
|
+
*/
|
|
55
|
+
export const PANEL_FIT = "max-w-[calc(100vw-1rem)] max-h-(--reka-popper-available-height) overflow-y-auto"
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A list row on a touch screen: 2.5rem tall instead of 2, so a thumb picks the
|
|
59
|
+
* row it is on and not its neighbour. A mouse keeps the compact row.
|
|
60
|
+
*/
|
|
61
|
+
export const TOUCH_ROW = "[@media(hover:none)_and_(pointer:coarse)]:py-2.5"
|
|
62
|
+
|
|
48
63
|
/**
|
|
49
64
|
* A selectable option row: layout, highlight state, and the staggered cascade.
|
|
50
65
|
* Cascades downward by default and upward when the panel is flipped above the
|
|
@@ -53,6 +68,7 @@ export const POPOVER_PANEL = PANEL_SURFACE + " " + POPOVER_ANIM
|
|
|
53
68
|
*/
|
|
54
69
|
export const OPTION_ITEM =
|
|
55
70
|
"flex cursor-pointer items-center gap-2 rounded-inner px-2.5 py-1.5 text-sm text-ink outline-none " +
|
|
71
|
+
TOUCH_ROW + " " +
|
|
56
72
|
"animate-[rise-in_180ms_var(--ease-emphasized)_both] [--rise:-6px] group-data-[side=top]:[--rise:6px] " +
|
|
57
73
|
"data-[highlighted]:bg-brand-soft data-[highlighted]:text-brand"
|
|
58
74
|
|
|
@@ -74,6 +90,14 @@ export const optionStagger = (index: number): { animationDelay: string } => ({
|
|
|
74
90
|
/* Where a combobox list sits under its field. */
|
|
75
91
|
export const COMBOBOX_PANEL = { collisionPadding: 8, position: "popper", sideOffset: 6 } as const
|
|
76
92
|
|
|
93
|
+
/*
|
|
94
|
+
* A combobox list's height: 18rem, or less where reka measured less room on the
|
|
95
|
+
* side it opened (a phone in landscape has about 170px under a field). The
|
|
96
|
+
* panel is a column so its option list, which carries `min-h-0 flex-1`, is what
|
|
97
|
+
* shrinks and scrolls; the search field above it stays.
|
|
98
|
+
*/
|
|
99
|
+
export const COMBOBOX_FIT = "flex flex-col max-h-[min(18rem,var(--reka-popper-available-height,18rem))]"
|
|
100
|
+
|
|
77
101
|
export const CLOSE_ICON =
|
|
78
102
|
"transition-transform duration-medium ease-[var(--ease-emphasized)] " +
|
|
79
103
|
"group-hover:rotate-90 group-active:scale-90 motion-reduce:group-hover:rotate-0"
|
|
@@ -2,6 +2,12 @@ import { computed, ref, type ComputedRef, type Ref } from "vue"
|
|
|
2
2
|
|
|
3
3
|
const STEP = 20
|
|
4
4
|
|
|
5
|
+
/*
|
|
6
|
+
* A dialog's ceiling: 90% of the window, and never closer than 1rem to its
|
|
7
|
+
* edges or to a notch or home indicator on a phone that draws under them.
|
|
8
|
+
*/
|
|
9
|
+
export const MODAL_MAX_HEIGHT = "min(90vh, calc(100dvh - 2rem - env(safe-area-inset-top) - env(safe-area-inset-bottom)))"
|
|
10
|
+
|
|
5
11
|
/*
|
|
6
12
|
* One opening-order stack for every modal surface. Pinned to a global symbol
|
|
7
13
|
* like the toast queue: a barrel import and a deep import can load this module
|
|
@@ -69,9 +69,12 @@ export function useReveal(options: UseRevealOptions = {}): UseReveal {
|
|
|
69
69
|
const { threshold = 0.25, rootMargin = "0px" } = options
|
|
70
70
|
|
|
71
71
|
const el = ref<Element | null>(null)
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
72
|
+
// The same starting value on the server and on the hydrating client, or every
|
|
73
|
+
// component with an entrance hydrated with a mismatch (the server said
|
|
74
|
+
// revealed, the client said waiting). Only an explicit `immediate` starts
|
|
75
|
+
// revealed; the observer, and reduced motion, decide on the client once
|
|
76
|
+
// mounted, the way useMediaQuery does.
|
|
77
|
+
const revealed = ref(!!toValue(options.immediate))
|
|
75
78
|
|
|
76
79
|
let observer: IntersectionObserver | null = null
|
|
77
80
|
|
|
@@ -88,7 +91,7 @@ export function useReveal(options: UseRevealOptions = {}): UseReveal {
|
|
|
88
91
|
watch(
|
|
89
92
|
[el, () => toValue(options.immediate)],
|
|
90
93
|
([node, immediate]) => {
|
|
91
|
-
if (revealed.value) return
|
|
94
|
+
if (revealed.value || typeof window === "undefined") return
|
|
92
95
|
if (immediate || prefersReducedMotion()) {
|
|
93
96
|
reveal()
|
|
94
97
|
return
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { onBeforeUnmount } from "vue"
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Press-and-hold on a touch screen, for things a pointer reaches by hovering
|
|
5
|
+
* (a hover card, a tooltip). A hold that drifts is a scroll, not a hold, so
|
|
6
|
+
* moving past a few pixels cancels it. Once it has fired, the click the finger
|
|
7
|
+
* produces on lifting is swallowed: the reader asked for the preview, not for
|
|
8
|
+
* the link or button under it. A mouse or pen is left alone.
|
|
9
|
+
*
|
|
10
|
+
* The handlers go on the trigger: pointerdown, pointermove, pointerup,
|
|
11
|
+
* pointercancel, click (capture) and contextmenu.
|
|
12
|
+
*/
|
|
13
|
+
const SLOP = 8
|
|
14
|
+
/* A long press may produce no click at all; past this the next click is the user's own. */
|
|
15
|
+
const CLICK_AFTER_RELEASE_MS = 350
|
|
16
|
+
|
|
17
|
+
export interface TouchHoldOptions {
|
|
18
|
+
/** Whether a touch press may start a hold now. */
|
|
19
|
+
enabled: () => boolean
|
|
20
|
+
/** How long the finger must stay, in ms. */
|
|
21
|
+
delay: () => number
|
|
22
|
+
onHold: () => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useTouchHold(options: TouchHoldOptions) {
|
|
26
|
+
let timer = 0
|
|
27
|
+
/* The finger the hold belongs to: a second one must not move or end it. */
|
|
28
|
+
let finger: number | null = null
|
|
29
|
+
let start = { x: 0, y: 0 }
|
|
30
|
+
let held = false
|
|
31
|
+
|
|
32
|
+
function stop() {
|
|
33
|
+
window.clearTimeout(timer)
|
|
34
|
+
timer = 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function onPointerDown(event: PointerEvent) {
|
|
38
|
+
// Any new press starts over: on a hybrid laptop the mouse click after a
|
|
39
|
+
// hold must not be taken for that hold's own click.
|
|
40
|
+
if (timer && event.pointerId !== finger) return
|
|
41
|
+
held = false
|
|
42
|
+
if (event.pointerType !== "touch" || !options.enabled()) return
|
|
43
|
+
finger = event.pointerId
|
|
44
|
+
start = { x: event.clientX, y: event.clientY }
|
|
45
|
+
stop()
|
|
46
|
+
timer = window.setTimeout(() => {
|
|
47
|
+
timer = 0
|
|
48
|
+
held = true
|
|
49
|
+
options.onHold()
|
|
50
|
+
}, options.delay())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function onPointerMove(event: PointerEvent) {
|
|
54
|
+
if (!timer || event.pointerId !== finger) return
|
|
55
|
+
if (Math.hypot(event.clientX - start.x, event.clientY - start.y) > SLOP) stop()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function onPointerUp(event: PointerEvent) {
|
|
59
|
+
if (finger !== null && event.pointerId !== finger) return
|
|
60
|
+
finger = null
|
|
61
|
+
stop()
|
|
62
|
+
if (held) window.setTimeout(() => (held = false), CLICK_AFTER_RELEASE_MS)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// A cancelled press produces no click, so there is nothing left to swallow.
|
|
66
|
+
function onPointerCancel(event?: PointerEvent) {
|
|
67
|
+
if (event && finger !== null && event.pointerId !== finger) return
|
|
68
|
+
finger = null
|
|
69
|
+
stop()
|
|
70
|
+
held = false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function onClickCapture(event: MouseEvent) {
|
|
74
|
+
if (!held) return
|
|
75
|
+
held = false
|
|
76
|
+
event.preventDefault()
|
|
77
|
+
event.stopImmediatePropagation()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// A long press would otherwise also bring up the system's own menu.
|
|
81
|
+
function onContextMenu(event: Event) {
|
|
82
|
+
if (timer || held) event.preventDefault()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onBeforeUnmount(stop)
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
/** Drops a hold in progress, as if the finger had been lifted off the screen. */
|
|
89
|
+
cancel: () => onPointerCancel(),
|
|
90
|
+
handlers: { onPointerdown: onPointerDown, onPointermove: onPointerMove, onPointerup: onPointerUp, onPointercancel: onPointerCancel, onClickCapture, onContextmenu: onContextMenu },
|
|
91
|
+
}
|
|
92
|
+
}
|
package/src/locales/uk.ts
CHANGED
package/src/tokens/utilities.css
CHANGED
|
@@ -171,3 +171,37 @@
|
|
|
171
171
|
animation: none;
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
|
+
|
|
175
|
+
/*
|
|
176
|
+
* Text entry on a touch screen. iOS Safari zooms the page in when a field whose
|
|
177
|
+
* text is smaller than 16px takes focus, and leaves it zoomed after the field
|
|
178
|
+
* loses it. Every kit field that takes typing carries this class, so on a
|
|
179
|
+
* coarse pointer its text is 16px; a mouse keeps the size it was drawn at.
|
|
180
|
+
* Unlayered on purpose: it has to win over the field's own `text-sm`.
|
|
181
|
+
*/
|
|
182
|
+
@media (hover: none) and (pointer: coarse) {
|
|
183
|
+
.lp-entry {
|
|
184
|
+
font-size: 16px;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/*
|
|
189
|
+
* A control drawn smaller than a fingertip: a checkbox, a switch, an icon or a
|
|
190
|
+
* small button, a chip's remove. On a coarse pointer an invisible ::after
|
|
191
|
+
* grows what it answers to to 40px on each axis, centred on the control, so it
|
|
192
|
+
* keeps its look and a thumb still lands on it. Bigger controls are left as
|
|
193
|
+
* they are. Both rules sit in the components layer, so a control placed with
|
|
194
|
+
* `absolute`, or one that draws its own ::after, keeps what its classes say.
|
|
195
|
+
*/
|
|
196
|
+
@layer components {
|
|
197
|
+
@media (hover: none) and (pointer: coarse) {
|
|
198
|
+
.lp-tap {
|
|
199
|
+
position: relative;
|
|
200
|
+
}
|
|
201
|
+
.lp-tap::after {
|
|
202
|
+
content: "";
|
|
203
|
+
position: absolute;
|
|
204
|
+
inset: min(0px, calc((100% - 40px) / 2));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|