@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
|
@@ -320,6 +320,8 @@ export type PaletteLabels = Partial<Record<PaletteMode, string>> & {
|
|
|
320
320
|
pinned?: string
|
|
321
321
|
select: string
|
|
322
322
|
close: string
|
|
323
|
+
/** The close button that stands in for Esc on a touch screen. */
|
|
324
|
+
dismiss: string
|
|
323
325
|
/** Hint for leaving a mode, shown only while inside one. */
|
|
324
326
|
back: string
|
|
325
327
|
searching: string
|
|
@@ -527,6 +529,7 @@ const DEFAULT_LABELS: Required<PaletteLabels> = {
|
|
|
527
529
|
running: "Running…",
|
|
528
530
|
select: "select",
|
|
529
531
|
close: "close",
|
|
532
|
+
dismiss: "Close",
|
|
530
533
|
back: "back",
|
|
531
534
|
searching: "Searching…",
|
|
532
535
|
searchFailed: "Search is unavailable right now.",
|
|
@@ -649,6 +652,43 @@ function actionsOf(c: Command | undefined): Command[] {
|
|
|
649
652
|
|
|
650
653
|
const selectedActions = computed(() => actionsOf(flat.value[selectedIndex.value]))
|
|
651
654
|
|
|
655
|
+
/*
|
|
656
|
+
* On a phone the palette is a sheet over the whole screen, and the on-screen
|
|
657
|
+
* keyboard takes the bottom of it without resizing the layout viewport: the
|
|
658
|
+
* last rows would sit under the keys. The visible part is tracked instead and
|
|
659
|
+
* handed to the CSS, so the list ends where the keyboard begins.
|
|
660
|
+
*/
|
|
661
|
+
const viewport = ref<Record<string, string>>({})
|
|
662
|
+
function trackViewport() {
|
|
663
|
+
const vv = typeof window === "undefined" ? undefined : window.visualViewport
|
|
664
|
+
// The whole visible rectangle, not only its height: a page wider than the
|
|
665
|
+
// phone is zoomed out to fit, and a fixed sheet then measures the wider
|
|
666
|
+
// layout instead of the screen the person is looking at.
|
|
667
|
+
viewport.value = vv
|
|
668
|
+
? {
|
|
669
|
+
"--lp-cmd-vh": `${vv.height}px`,
|
|
670
|
+
"--lp-cmd-vw": `${vv.width}px`,
|
|
671
|
+
"--lp-cmd-vx": `${vv.offsetLeft}px`,
|
|
672
|
+
"--lp-cmd-vy": `${vv.offsetTop}px`,
|
|
673
|
+
}
|
|
674
|
+
: {}
|
|
675
|
+
}
|
|
676
|
+
watch(
|
|
677
|
+
() => props.open,
|
|
678
|
+
(open, _, onCleanup) => {
|
|
679
|
+
const vv = typeof window === "undefined" ? undefined : window.visualViewport
|
|
680
|
+
if (!open || !vv) return
|
|
681
|
+
trackViewport()
|
|
682
|
+
vv.addEventListener("resize", trackViewport)
|
|
683
|
+
vv.addEventListener("scroll", trackViewport)
|
|
684
|
+
onCleanup(() => {
|
|
685
|
+
vv.removeEventListener("resize", trackViewport)
|
|
686
|
+
vv.removeEventListener("scroll", trackViewport)
|
|
687
|
+
})
|
|
688
|
+
},
|
|
689
|
+
{ immediate: true },
|
|
690
|
+
)
|
|
691
|
+
|
|
652
692
|
/*
|
|
653
693
|
* Tab is free for this: the rows are out of the tab order and the box is the
|
|
654
694
|
* only field, so Tab otherwise just walks off to the dialog's edge. Without
|
|
@@ -666,6 +706,18 @@ function onTab(event: KeyboardEvent) {
|
|
|
666
706
|
event.stopPropagation()
|
|
667
707
|
openActions(c)
|
|
668
708
|
}
|
|
709
|
+
/*
|
|
710
|
+
* A long press on a phone, a right click elsewhere: the row's actions. On a
|
|
711
|
+
* touch screen there is no hover to make a row the active one, so this is the
|
|
712
|
+
* only way to the actions of any row but the first. A row without actions
|
|
713
|
+
* keeps the browser's own menu.
|
|
714
|
+
*/
|
|
715
|
+
function onRowContextMenu(event: MouseEvent, c: Command) {
|
|
716
|
+
if (!actionsOf(c).length) return
|
|
717
|
+
event.preventDefault()
|
|
718
|
+
openActions(c)
|
|
719
|
+
}
|
|
720
|
+
|
|
669
721
|
function openActions(c: Command) {
|
|
670
722
|
const actions = actionsOf(c)
|
|
671
723
|
if (!actions.length) return
|
|
@@ -1573,7 +1625,7 @@ const guardOutside = useOutsideGuard()
|
|
|
1573
1625
|
ref="contentRef"
|
|
1574
1626
|
class="lp-cmd-dialog fixed left-1/2 top-[12vh] flex max-h-[76vh] -translate-x-1/2 flex-col overflow-hidden rounded-card border border-line shadow-panel outline-none data-[state=open]:animate-[pop-in_160ms_var(--ease-emphasized)] data-[state=closed]:animate-[pop-out_130ms_ease]"
|
|
1575
1627
|
:class="[frosted ? (layer.nested ? 'lp-glass-overlay lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', previewPane && 'lp-cmd-split']"
|
|
1576
|
-
:style="{ zIndex: layer.panel }"
|
|
1628
|
+
:style="{ zIndex: layer.panel, ...viewport }"
|
|
1577
1629
|
:data-pane="paneOpen ? 'open' : undefined"
|
|
1578
1630
|
:data-editing="editing ? '' : undefined"
|
|
1579
1631
|
@open-auto-focus.prevent
|
|
@@ -1640,13 +1692,22 @@ const guardOutside = useOutsideGuard()
|
|
|
1640
1692
|
:aria-controls="listShown ? listId : undefined"
|
|
1641
1693
|
:aria-activedescendant="activeId"
|
|
1642
1694
|
:placeholder="promptCommand?.prompt?.placeholder ?? mode?.childPlaceholder ?? placeholder"
|
|
1643
|
-
class="h-12 min-w-0 flex-1 bg-transparent text-sm text-ink outline-none placeholder:text-muted"
|
|
1695
|
+
class="lp-entry h-12 min-w-0 flex-1 bg-transparent text-sm text-ink outline-none placeholder:text-muted"
|
|
1644
1696
|
@keydown.enter.prevent="runSelected"
|
|
1645
1697
|
@keydown.backspace="onBackspace"
|
|
1646
1698
|
@keydown.esc="onEscape"
|
|
1647
1699
|
@keydown.tab="onTab"
|
|
1648
1700
|
/>
|
|
1649
|
-
<kbd class="shrink-0 rounded-small border border-line px-1.5 py-0.5 text-[10px] text-muted">{{ text.esc }}</kbd>
|
|
1701
|
+
<kbd class="shrink-0 rounded-small border border-line px-1.5 py-0.5 text-[10px] text-muted [@media(hover:none)_and_(pointer:coarse)]:hidden">{{ text.esc }}</kbd>
|
|
1702
|
+
<!-- A finger has no Esc, and tapping the dim edge is not there when
|
|
1703
|
+
the palette fills the screen: the way out is a button. -->
|
|
1704
|
+
<button
|
|
1705
|
+
type="button"
|
|
1706
|
+
class="hidden shrink-0 rounded-inner px-2 py-1.5 text-sm font-medium text-brand outline-none focus-visible:ring-2 focus-visible:ring-ring [@media(hover:none)_and_(pointer:coarse)]:inline-flex"
|
|
1707
|
+
@click="emit('update:open', false)"
|
|
1708
|
+
>
|
|
1709
|
+
{{ text.dismiss }}
|
|
1710
|
+
</button>
|
|
1650
1711
|
</div>
|
|
1651
1712
|
|
|
1652
1713
|
<div v-if="promptCommand" class="flex flex-col gap-3 p-4">
|
|
@@ -1659,8 +1720,8 @@ const guardOutside = useOutsideGuard()
|
|
|
1659
1720
|
the only way to the row's actions and pin. On a pointer the same
|
|
1660
1721
|
buttons live in the footer, so the list never shifts under this strip. -->
|
|
1661
1722
|
<div v-if="active && (canPin || selectedActions.length) && !promptCommand" class="hidden items-center justify-end gap-2 border-b border-line px-3 py-1 [@media(hover:none)_and_(pointer:coarse)]:flex">
|
|
1662
|
-
<LpButton v-if="selectedActions.length" size="xs" variant="ghost" @click="openActions(active)">{{ text.actions }}
|
|
1663
|
-
<LpButton v-if="canPin" size="xs" variant="ghost" :aria-label="isPinned(active) ? text.unpin : text.pin" :aria-pressed="isPinned(active)" @click="togglePin(active)"><LpIcon name="lucide:star" :size="14" />{{ isPinned(active) ? text.unpin : text.pin }}
|
|
1723
|
+
<LpButton v-if="selectedActions.length" size="xs" variant="ghost" @click="openActions(active)">{{ text.actions }}</LpButton>
|
|
1724
|
+
<LpButton v-if="canPin" size="xs" variant="ghost" :aria-label="isPinned(active) ? text.unpin : text.pin" :aria-pressed="isPinned(active)" @click="togglePin(active)"><LpIcon name="lucide:star" :size="14" />{{ isPinned(active) ? text.unpin : text.pin }}</LpButton>
|
|
1664
1725
|
</div>
|
|
1665
1726
|
<!-- Results -->
|
|
1666
1727
|
<div class="flex min-h-0 flex-1" @pointermove="onPointerMove">
|
|
@@ -1711,6 +1772,7 @@ const guardOutside = useOutsideGuard()
|
|
|
1711
1772
|
: 'text-ink hover:bg-surface-soft/60',
|
|
1712
1773
|
]"
|
|
1713
1774
|
@click="onRowClick($event, c)"
|
|
1775
|
+
@contextmenu="onRowContextMenu($event, c)"
|
|
1714
1776
|
@pointerenter="onPointerEnter(c)"
|
|
1715
1777
|
@pointerleave="onPointerLeave"
|
|
1716
1778
|
>
|
|
@@ -1761,12 +1823,13 @@ const guardOutside = useOutsideGuard()
|
|
|
1761
1823
|
</template>
|
|
1762
1824
|
</span>
|
|
1763
1825
|
</span>
|
|
1764
|
-
|
|
1826
|
+
<!-- Keys mean nothing to a finger: hidden on a touch screen. -->
|
|
1827
|
+
<MenuShortcut v-if="c.shortcut" :combo="c.shortcut" class="[@media(hover:none)_and_(pointer:coarse)]:hidden" />
|
|
1765
1828
|
<!-- The prefix that opens this mode from the box, so the
|
|
1766
1829
|
shortcut is learned by seeing it rather than reading docs. -->
|
|
1767
1830
|
<kbd
|
|
1768
1831
|
v-else-if="c.prefix && c.children"
|
|
1769
|
-
class="shrink-0 rounded-small border border-line px-1.5 font-mono text-[11px] whitespace-pre text-muted"
|
|
1832
|
+
class="shrink-0 rounded-small border border-line px-1.5 font-mono text-[11px] whitespace-pre text-muted [@media(hover:none)_and_(pointer:coarse)]:hidden"
|
|
1770
1833
|
>{{ c.prefix }}</kbd>
|
|
1771
1834
|
<!-- Drills in rather than runs: worth saying before the click,
|
|
1772
1835
|
since the two look identical otherwise. -->
|
|
@@ -1943,6 +2006,25 @@ const guardOutside = useOutsideGuard()
|
|
|
1943
2006
|
.lp-cmd-pane {
|
|
1944
2007
|
width: var(--lp-cmd-pane);
|
|
1945
2008
|
}
|
|
2009
|
+
/*
|
|
2010
|
+
* A phone: a sheet over the whole visible screen rather than a card floating
|
|
2011
|
+
* a tenth of the way down, which left a band of nothing above it and let the
|
|
2012
|
+
* keyboard cover its lower half. Keyed to touch as well as width, so a narrow
|
|
2013
|
+
* desktop window keeps the card.
|
|
2014
|
+
*/
|
|
2015
|
+
@media (hover: none) and (pointer: coarse) and (max-width: 639px) {
|
|
2016
|
+
.lp-cmd-dialog {
|
|
2017
|
+
inset: var(--lp-cmd-vy, 0) auto auto var(--lp-cmd-vx, 0);
|
|
2018
|
+
translate: none;
|
|
2019
|
+
width: var(--lp-cmd-vw, 100%);
|
|
2020
|
+
height: var(--lp-cmd-vh, 100dvh);
|
|
2021
|
+
max-height: var(--lp-cmd-vh, 100dvh);
|
|
2022
|
+
border-width: 0;
|
|
2023
|
+
border-radius: 0;
|
|
2024
|
+
padding-top: env(safe-area-inset-top);
|
|
2025
|
+
padding-bottom: env(safe-area-inset-bottom);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
1946
2028
|
.lp-cmd-dialog[data-editing] .lp-cmd-pane {
|
|
1947
2029
|
width: 100%;
|
|
1948
2030
|
}
|
|
@@ -14,7 +14,7 @@ import { hints } from "../composables/useHints"
|
|
|
14
14
|
import { HINT_SIGNALS, noteContextTarget } from "../composables/useHintSystem"
|
|
15
15
|
import ContextMenuItems from "./ContextMenuItems.vue"
|
|
16
16
|
import MenuTitle from "./MenuTitle.vue"
|
|
17
|
-
import { POPOVER_PANEL } from "./dropdown"
|
|
17
|
+
import { PANEL_FIT, POPOVER_PANEL } from "./dropdown"
|
|
18
18
|
import { useOutsideGuard, usePortalTarget, useView, viewDocument } from "../composables/useView"
|
|
19
19
|
|
|
20
20
|
export interface ContextMenuItemDef {
|
|
@@ -82,7 +82,7 @@ const props = withDefaults(defineProps<{
|
|
|
82
82
|
const enabled = computed(() => props.always || props.items.length > 0)
|
|
83
83
|
const hinted = computed(() => enabled.value && !props.noHint)
|
|
84
84
|
|
|
85
|
-
const PANEL = `${POPOVER_PANEL} z-(--z-popover) min-w-48 p-1 outline-none`
|
|
85
|
+
const PANEL = `${POPOVER_PANEL} ${PANEL_FIT} z-(--z-popover) min-w-48 p-1 outline-none`
|
|
86
86
|
|
|
87
87
|
// A menu opened over a text field takes focus, and on close reka hands it to the
|
|
88
88
|
// trigger, so the caret and the selection the user right-clicked are gone — the
|
|
@@ -125,7 +125,7 @@ const pad = computed(() => (props.size === "sm" ? "py-1.5 text-xs" : "py-2.5 tex
|
|
|
125
125
|
class="group/row grid items-baseline"
|
|
126
126
|
:class="[ROW[layout], pad, divided && i > 0 ? 'border-t border-line' : '']"
|
|
127
127
|
>
|
|
128
|
-
<dt class="flex min-w-0 items-center gap-1 text-muted">
|
|
128
|
+
<dt class="flex min-w-0 items-center gap-1 text-muted [overflow-wrap:anywhere]">
|
|
129
129
|
<slot :name="`label-${keyOf(item)}`" :item="item">
|
|
130
130
|
<slot name="label" :item="item">{{ item.label }}</slot>
|
|
131
131
|
</slot>
|
|
@@ -197,11 +197,14 @@ onBeforeUnmount(release)
|
|
|
197
197
|
const contentClass = computed(() => {
|
|
198
198
|
const surface = frosted.value ? (layer.value.nested ? "lp-glass-overlay lp-glass-overlay--nested" : "lp-glass-overlay") : "bg-surface-raised"
|
|
199
199
|
const base = `fixed z-(--z-modal) flex flex-col border-line ${surface} shadow-panel outline-none`
|
|
200
|
+
// The panel pads itself by the safe area of each screen edge it touches, so
|
|
201
|
+
// on a phone with a notch or a home indicator its header, close button and
|
|
202
|
+
// last row stay clear of them while its surface still reaches the edge.
|
|
200
203
|
const map: Record<Direction, string> = {
|
|
201
|
-
left: "inset-y-0 left-0 border-r rounded-r-card",
|
|
202
|
-
right: "inset-y-0 right-0 border-l rounded-l-card",
|
|
203
|
-
top: "inset-x-0 top-0 border-b rounded-b-card",
|
|
204
|
-
bottom: "inset-x-0 bottom-0 border-t rounded-t-card",
|
|
204
|
+
left: "inset-y-0 left-0 border-r rounded-r-card pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)]",
|
|
205
|
+
right: "inset-y-0 right-0 border-l rounded-l-card pt-[env(safe-area-inset-top)] pb-[env(safe-area-inset-bottom)] pr-[env(safe-area-inset-right)]",
|
|
206
|
+
top: "inset-x-0 top-0 border-b rounded-b-card pt-[env(safe-area-inset-top)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
|
|
207
|
+
bottom: "inset-x-0 bottom-0 border-t rounded-t-card pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]",
|
|
205
208
|
}
|
|
206
209
|
return [base, map[dir.value]]
|
|
207
210
|
})
|
|
@@ -11,7 +11,7 @@ import { HINT_SIGNALS } from "../composables/useHintSystem"
|
|
|
11
11
|
import { DropdownMenuContent, DropdownMenuPortal, DropdownMenuRoot, DropdownMenuTrigger } from "reka-ui"
|
|
12
12
|
import ContextMenuItems from "./ContextMenuItems.vue"
|
|
13
13
|
import type { ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
14
|
-
import { POPOVER_PANEL } from "./dropdown"
|
|
14
|
+
import { PANEL_FIT, POPOVER_PANEL } from "./dropdown"
|
|
15
15
|
import MenuTitle from "./MenuTitle.vue"
|
|
16
16
|
import { useOutsideGuard, usePortalTarget } from "../composables/useView"
|
|
17
17
|
|
|
@@ -33,7 +33,7 @@ const props = withDefaults(defineProps<{
|
|
|
33
33
|
rightClickToo?: boolean
|
|
34
34
|
}>(), { palette: undefined })
|
|
35
35
|
|
|
36
|
-
const PANEL = `${POPOVER_PANEL} z-(--z-popover) min-w-44 p-1 outline-none`
|
|
36
|
+
const PANEL = `${POPOVER_PANEL} ${PANEL_FIT} z-(--z-popover) min-w-44 p-1 outline-none`
|
|
37
37
|
// reka names the menu after its trigger, and aria-labelledby beats aria-label:
|
|
38
38
|
// a `title` only names the menu with that link taken off. MenuTitle itself is
|
|
39
39
|
// hidden from assistive tech, so nothing is announced twice.
|
|
@@ -42,7 +42,7 @@ import { useMergedAttrs } from "../composables/useMergedClass"
|
|
|
42
42
|
import { useReveal } from "../composables/useReveal"
|
|
43
43
|
import { PopoverAnchor, PopoverContent, PopoverPortal, PopoverRoot } from "reka-ui"
|
|
44
44
|
import DropzoneLinkField from "./DropzoneLinkField.vue"
|
|
45
|
-
import { POPOVER_PANEL } from "./dropdown"
|
|
45
|
+
import { PANEL_FIT, POPOVER_PANEL } from "./dropdown"
|
|
46
46
|
import LpIcon from "./LpIcon.vue"
|
|
47
47
|
import { inTextField } from "../composables/textEntry"
|
|
48
48
|
import { useOutsideGuard, usePortalTarget, useView, viewWindow } from "../composables/useView"
|
|
@@ -568,7 +568,7 @@ const guardOutside = useOutsideGuard()
|
|
|
568
568
|
side="bottom"
|
|
569
569
|
align="start"
|
|
570
570
|
:side-offset="6"
|
|
571
|
-
:class="POPOVER_PANEL"
|
|
571
|
+
:class="[POPOVER_PANEL, PANEL_FIT]"
|
|
572
572
|
class="z-(--z-popover) w-80 rounded-card p-3 outline-none"
|
|
573
573
|
@open-auto-focus.prevent
|
|
574
574
|
>
|
|
@@ -264,7 +264,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() => "relative inline-
|
|
|
264
264
|
>
|
|
265
265
|
<span
|
|
266
266
|
aria-hidden="true"
|
|
267
|
-
class="invisible col-start-1 row-start-1 min-w-[4ch]"
|
|
267
|
+
class="lp-entry invisible col-start-1 row-start-1 min-w-[4ch]"
|
|
268
268
|
:class="[PAD[size], multiline ? 'whitespace-pre-wrap break-words' : 'whitespace-pre']"
|
|
269
269
|
>{{ (draft || placeholder || " ") + (multiline ? "\n" : "\u200a") }}</span>
|
|
270
270
|
<component
|
|
@@ -280,7 +280,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() => "relative inline-
|
|
|
280
280
|
:disabled="disabled"
|
|
281
281
|
:readonly="readonly"
|
|
282
282
|
:rows="multiline ? 1 : undefined"
|
|
283
|
-
class="col-start-1 row-start-1 w-full min-w-0 resize-none overflow-hidden bg-transparent text-ink outline-none placeholder:text-muted"
|
|
283
|
+
class="lp-entry col-start-1 row-start-1 w-full min-w-0 resize-none overflow-hidden bg-transparent text-ink outline-none placeholder:text-muted"
|
|
284
284
|
:class="PAD[size]"
|
|
285
285
|
@input="draft = ($event.target as HTMLInputElement).value"
|
|
286
286
|
@keydown="onFieldKey"
|
|
@@ -43,7 +43,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
|
43
43
|
>
|
|
44
44
|
<LpIcon :name="icon" :size="compact ? 16 : 22" />
|
|
45
45
|
</div>
|
|
46
|
-
<div class="flex flex-col gap-1">
|
|
46
|
+
<div class="flex max-w-full flex-col gap-1 [overflow-wrap:anywhere]">
|
|
47
47
|
<p class="font-semibold text-ink" :class="compact ? 'text-sm' : ''">{{ title }}</p>
|
|
48
48
|
<!-- The slot is for a line a string cannot carry: a command, a link. -->
|
|
49
49
|
<p v-if="description || $slots.description" class="max-w-sm text-sm text-muted">
|
|
@@ -302,7 +302,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
302
302
|
<input
|
|
303
303
|
v-if="isRenaming"
|
|
304
304
|
ref="renameField"
|
|
305
|
-
class="w-full min-w-0 rounded-sm border bg-surface-soft px-1 text-sm text-ink outline-none"
|
|
305
|
+
class="lp-entry w-full min-w-0 rounded-sm border bg-surface-soft px-1 text-sm text-ink outline-none"
|
|
306
306
|
:class="renameInvalid ? 'border-danger' : 'border-line-strong'"
|
|
307
307
|
:value="renameValue"
|
|
308
308
|
:aria-label="renameLabel"
|
|
@@ -19,7 +19,7 @@ import { PopoverAnchor, PopoverContent, PopoverPortal, PopoverRoot } from "reka-
|
|
|
19
19
|
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue"
|
|
20
20
|
import { hints } from "../composables/useHints"
|
|
21
21
|
import { useLabels } from "../composables/useLpConfig"
|
|
22
|
-
import { PANEL_SURFACE, POPOVER_ANIM } from "./dropdown"
|
|
22
|
+
import { PANEL_FIT, PANEL_SURFACE, POPOVER_ANIM } from "./dropdown"
|
|
23
23
|
import LpBadge from "./LpBadge.vue"
|
|
24
24
|
import LpButton from "./LpButton.vue"
|
|
25
25
|
import { useOutsideGuard, usePortalTarget } from "../composables/useView"
|
|
@@ -147,7 +147,7 @@ const guardOutside = useOutsideGuard()
|
|
|
147
147
|
side="bottom"
|
|
148
148
|
:side-offset="8"
|
|
149
149
|
:collision-padding="8"
|
|
150
|
-
:class="[PANEL_SURFACE, POPOVER_ANIM, 'z-(--z-popover) w-72 rounded-card p-3 text-sm text-ink outline-none']"
|
|
150
|
+
:class="[PANEL_SURFACE, POPOVER_ANIM, PANEL_FIT, 'z-(--z-popover) w-72 rounded-card p-3 text-sm text-ink outline-none']"
|
|
151
151
|
>
|
|
152
152
|
<p class="flex items-center gap-2 font-medium">
|
|
153
153
|
<LpBadge tone="brand" size="sm">{{ labels.badge }}</LpBadge>
|
|
@@ -34,10 +34,11 @@ import {
|
|
|
34
34
|
HoverCardRoot,
|
|
35
35
|
HoverCardTrigger,
|
|
36
36
|
} from "reka-ui"
|
|
37
|
-
import { computed,
|
|
37
|
+
import { computed, mergeProps, useAttrs, watch } from "vue"
|
|
38
38
|
import { mergeClass } from "../composables/useMergedClass"
|
|
39
|
+
import { useTouchHold } from "../composables/useTouchHold"
|
|
39
40
|
import { useOutsideGuard, usePortalTarget } from "../composables/useView"
|
|
40
|
-
import { POPOVER_PANEL } from "./dropdown"
|
|
41
|
+
import { PANEL_FIT, POPOVER_PANEL } from "./dropdown"
|
|
41
42
|
|
|
42
43
|
const props = withDefaults(
|
|
43
44
|
defineProps<{
|
|
@@ -81,96 +82,32 @@ const props = withDefaults(
|
|
|
81
82
|
const open = defineModel<boolean>("open", { default: false })
|
|
82
83
|
|
|
83
84
|
const panel = computed(() =>
|
|
84
|
-
mergeClass(POPOVER_PANEL, "z-(--z-popover) w-72 rounded-card p-4 text-sm text-ink outline-none", props.panelClass),
|
|
85
|
+
mergeClass(POPOVER_PANEL, PANEL_FIT, "z-(--z-popover) w-72 rounded-card p-4 text-sm text-ink outline-none", props.panelClass),
|
|
85
86
|
)
|
|
86
87
|
|
|
87
88
|
const portalTo = usePortalTarget()
|
|
88
89
|
const guardOutside = useOutsideGuard()
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* a few pixels cancels it. Once it has opened the card, the click the finger
|
|
93
|
-
* produces on lifting is swallowed: the reader asked for the preview, not for
|
|
94
|
-
* the link under it.
|
|
95
|
-
*/
|
|
96
|
-
const SLOP = 8
|
|
97
|
-
/* A long press may produce no click at all; past this the next click is the user's own. */
|
|
98
|
-
const CLICK_AFTER_RELEASE_MS = 350
|
|
99
|
-
let holdTimer = 0
|
|
100
|
-
/* The finger the hold belongs to: a second one must not move or end it. */
|
|
101
|
-
let holdPointer: number | null = null
|
|
102
|
-
let start = { x: 0, y: 0 }
|
|
103
|
-
let held = false
|
|
104
|
-
|
|
105
|
-
function cancelHold() {
|
|
106
|
-
window.clearTimeout(holdTimer)
|
|
107
|
-
holdTimer = 0
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function onPointerUp(event: PointerEvent) {
|
|
111
|
-
if (holdPointer !== null && event.pointerId !== holdPointer) return
|
|
112
|
-
holdPointer = null
|
|
113
|
-
cancelHold()
|
|
114
|
-
if (held) window.setTimeout(() => (held = false), CLICK_AFTER_RELEASE_MS)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// A cancelled press produces no click, so there is nothing left to swallow.
|
|
118
|
-
function onPointerCancel(event?: PointerEvent) {
|
|
119
|
-
if (event && holdPointer !== null && event.pointerId !== holdPointer) return
|
|
120
|
-
holdPointer = null
|
|
121
|
-
cancelHold()
|
|
122
|
-
held = false
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function onPointerDown(event: PointerEvent) {
|
|
126
|
-
// Any new press starts over: on a hybrid laptop the mouse click after a hold
|
|
127
|
-
// must not be taken for that hold's own click.
|
|
128
|
-
if (holdTimer && event.pointerId !== holdPointer) return
|
|
129
|
-
held = false
|
|
130
|
-
if (event.pointerType !== "touch" || props.touch !== "hold" || props.disabled) return
|
|
131
|
-
holdPointer = event.pointerId
|
|
132
|
-
start = { x: event.clientX, y: event.clientY }
|
|
133
|
-
cancelHold()
|
|
134
|
-
holdTimer = window.setTimeout(() => {
|
|
135
|
-
holdTimer = 0
|
|
136
|
-
held = true
|
|
137
|
-
open.value = true
|
|
138
|
-
}, props.holdDelay)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function onPointerMove(event: PointerEvent) {
|
|
142
|
-
if (!holdTimer || event.pointerId !== holdPointer) return
|
|
143
|
-
if (Math.hypot(event.clientX - start.x, event.clientY - start.y) > SLOP) cancelHold()
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function onClickCapture(event: MouseEvent) {
|
|
147
|
-
if (!held) return
|
|
148
|
-
held = false
|
|
149
|
-
event.preventDefault()
|
|
150
|
-
event.stopPropagation()
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// A long press would otherwise also bring up the system's own menu for the link.
|
|
154
|
-
function onContextMenu(event: Event) {
|
|
155
|
-
if (holdTimer || held) event.preventDefault()
|
|
156
|
-
}
|
|
91
|
+
// Press-and-hold opens the card on a touch screen, where there is no hover.
|
|
92
|
+
const hold = useTouchHold({ enabled: () => props.touch === "hold" && !props.disabled, delay: () => props.holdDelay, onHold: () => (open.value = true) })
|
|
157
93
|
|
|
158
94
|
watch(
|
|
159
95
|
() => props.disabled,
|
|
160
96
|
(off) => {
|
|
161
97
|
if (!off) return
|
|
162
|
-
|
|
98
|
+
hold.cancel()
|
|
163
99
|
open.value = false
|
|
164
100
|
},
|
|
165
101
|
)
|
|
166
102
|
|
|
167
|
-
onBeforeUnmount(cancelHold)
|
|
168
|
-
|
|
169
103
|
// Several roots (trigger and portal), so Vue cannot hand attributes on by
|
|
170
104
|
// itself and dropped them: a trigger wrapped around this one from outside (an
|
|
171
105
|
// LpPopover's, as-child) lost its id, ARIA state and click, and its popover
|
|
172
106
|
// never opened. They go to the trigger, which merges them onto the slot's element.
|
|
173
107
|
defineOptions({ inheritAttrs: false })
|
|
108
|
+
// The caller's own listeners on the trigger run beside the hold's, not instead of them.
|
|
109
|
+
const attrs = useAttrs()
|
|
110
|
+
const triggerAttrs = computed(() => mergeProps(attrs, hold.handlers))
|
|
174
111
|
</script>
|
|
175
112
|
|
|
176
113
|
<template>
|
|
@@ -178,14 +115,8 @@ defineOptions({ inheritAttrs: false })
|
|
|
178
115
|
<HoverCardRoot v-else v-model:open="open" :open-delay="openDelay" :close-delay="closeDelay">
|
|
179
116
|
<HoverCardTrigger
|
|
180
117
|
as-child
|
|
181
|
-
v-bind="
|
|
118
|
+
v-bind="triggerAttrs"
|
|
182
119
|
:class="touch === 'hold' ? '[-webkit-touch-callout:none]' : undefined"
|
|
183
|
-
@pointerdown="onPointerDown"
|
|
184
|
-
@pointermove="onPointerMove"
|
|
185
|
-
@pointerup="onPointerUp"
|
|
186
|
-
@pointercancel="onPointerCancel"
|
|
187
|
-
@click.capture="onClickCapture"
|
|
188
|
-
@contextmenu="onContextMenu"
|
|
189
120
|
>
|
|
190
121
|
<slot name="trigger" />
|
|
191
122
|
</HoverCardTrigger>
|
|
@@ -558,7 +558,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
558
558
|
tabindex="0"
|
|
559
559
|
role="application"
|
|
560
560
|
:aria-label="text.canvas"
|
|
561
|
-
class="relative w-full overflow-hidden rounded-card border border-line bg-surface-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
561
|
+
class="relative w-full touch-none overflow-hidden rounded-card border border-line bg-surface-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
562
562
|
:style="frameStyle"
|
|
563
563
|
@wheel.prevent="onWheel"
|
|
564
564
|
@pointerdown="zoom.onPointerDown"
|
|
@@ -209,7 +209,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
|
|
|
209
209
|
:aria-invalid="isInvalidState || undefined"
|
|
210
210
|
:aria-describedby="mergeDescribedBy(attrs['aria-describedby'], field.describedBy)"
|
|
211
211
|
:class="[
|
|
212
|
-
'lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted',
|
|
212
|
+
'lp-autofill lp-entry min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted',
|
|
213
213
|
clearable && '[&::-webkit-search-cancel-button]:hidden',
|
|
214
214
|
hasLeading ? 'pl-2' : padX[size ?? 'md'],
|
|
215
215
|
hasTrailing ? '' : padX[size ?? 'md'],
|
|
@@ -364,8 +364,18 @@ function rotate() {
|
|
|
364
364
|
// Swipe to page through, but only while the image is at fit scale — once zoomed
|
|
365
365
|
// a horizontal drag is a pan, and stealing it would make the image unreachable.
|
|
366
366
|
let swipeStart: { x: number; y: number } | null = null
|
|
367
|
+
// Fingers on the stage. A second one makes the gesture a pinch, and a pinch
|
|
368
|
+
// whose fingers part sideways is not a swipe: it zoomed and then paged away
|
|
369
|
+
// from what it zoomed into.
|
|
370
|
+
const fingers = new Set<number>()
|
|
367
371
|
|
|
368
372
|
function onPointerDown(e: PointerEvent) {
|
|
373
|
+
fingers.add(e.pointerId)
|
|
374
|
+
if (fingers.size > 1) {
|
|
375
|
+
swipeStart = null
|
|
376
|
+
if (!isVideo.value) zoom.onPointerDown(e)
|
|
377
|
+
return
|
|
378
|
+
}
|
|
369
379
|
if (isVideo.value) {
|
|
370
380
|
// A drag on the player's bar is a seek or a volume change, never a page.
|
|
371
381
|
swipeStart = (e.target as Element).closest?.("[data-lp-player-controls]") ? null : { x: e.clientX, y: e.clientY }
|
|
@@ -375,7 +385,14 @@ function onPointerDown(e: PointerEvent) {
|
|
|
375
385
|
swipeStart = zoom.zoomed.value ? null : { x: e.clientX, y: e.clientY }
|
|
376
386
|
}
|
|
377
387
|
|
|
388
|
+
function onPointerCancel(e: PointerEvent) {
|
|
389
|
+
fingers.delete(e.pointerId)
|
|
390
|
+
swipeStart = null
|
|
391
|
+
if (!isVideo.value) zoom.onPointerUp(e)
|
|
392
|
+
}
|
|
393
|
+
|
|
378
394
|
function onPointerUp(e: PointerEvent) {
|
|
395
|
+
fingers.delete(e.pointerId)
|
|
379
396
|
if (!isVideo.value) zoom.onPointerUp(e)
|
|
380
397
|
if (!swipeStart) return
|
|
381
398
|
const dx = e.clientX - swipeStart.x
|
|
@@ -534,7 +551,7 @@ const guardOutside = useOutsideGuard()
|
|
|
534
551
|
@interact-outside="guardOutside"
|
|
535
552
|
@escape-key-down="guardOutside"
|
|
536
553
|
ref="contentRef"
|
|
537
|
-
class="fixed inset-0 flex flex-col outline-none data-[state=open]:animate-[lightbox-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[lightbox-out_140ms_cubic-bezier(0.4,0,1,1)] motion-reduce:animate-none"
|
|
554
|
+
class="fixed inset-0 flex flex-col pt-[env(safe-area-inset-top)] pr-[env(safe-area-inset-right)] pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] outline-none data-[state=open]:animate-[lightbox-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[lightbox-out_140ms_cubic-bezier(0.4,0,1,1)] motion-reduce:animate-none"
|
|
538
555
|
:style="{ zIndex: layer.panel }"
|
|
539
556
|
@keydown="onKeydown"
|
|
540
557
|
>
|
|
@@ -599,13 +616,13 @@ const guardOutside = useOutsideGuard()
|
|
|
599
616
|
<!-- Stage -->
|
|
600
617
|
<div
|
|
601
618
|
ref="viewport"
|
|
602
|
-
class="relative flex min-h-0 flex-1 items-center justify-center overflow-hidden"
|
|
619
|
+
class="relative flex min-h-0 flex-1 touch-none items-center justify-center overflow-hidden"
|
|
603
620
|
:class="isPanning ? 'cursor-grabbing' : isZoomed ? 'cursor-grab' : ''"
|
|
604
621
|
@wheel="onStageWheel"
|
|
605
622
|
@pointerdown="onPointerDown"
|
|
606
623
|
@pointermove="zoom.onPointerMove"
|
|
607
624
|
@pointerup="onPointerUp"
|
|
608
|
-
@pointercancel="
|
|
625
|
+
@pointercancel="onPointerCancel"
|
|
609
626
|
@dblclick="onDoubleClick"
|
|
610
627
|
@click="onViewportClick"
|
|
611
628
|
>
|
|
@@ -15,7 +15,7 @@ import { useGlass } from "../composables/useGlass"
|
|
|
15
15
|
import { useModelOverlayHistory, type OverlayHistoryMode } from "../composables/useOverlayHistory"
|
|
16
16
|
import { useLabels } from "../composables/useLpConfig"
|
|
17
17
|
import { useShift } from "../composables/useShift"
|
|
18
|
-
import { useModalLayer } from "./modalDepth"
|
|
18
|
+
import { MODAL_MAX_HEIGHT, useModalLayer } from "./modalDepth"
|
|
19
19
|
import { CLOSE_ICON } from "./dropdown"
|
|
20
20
|
import LpIcon from "./LpIcon.vue"
|
|
21
21
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
@@ -379,7 +379,7 @@ const guardOutside = useOutsideGuard()
|
|
|
379
379
|
the link to DialogDescription. -->
|
|
380
380
|
<DialogContent
|
|
381
381
|
class="pointer-events-auto flex min-h-0 items-start outline-none data-[state=open]:animate-[rise-in_var(--duration-medium)_var(--ease-emphasized)] data-[state=closed]:animate-[rise-out_120ms_cubic-bezier(0.4,0,1,1)]"
|
|
382
|
-
:style="{ maxHeight:
|
|
382
|
+
:style="{ maxHeight: MODAL_MAX_HEIGHT }"
|
|
383
383
|
v-bind="describedByAttrs"
|
|
384
384
|
@open-auto-focus="onOpenAutoFocus"
|
|
385
385
|
@interact-outside="onInteractOutside"
|
|
@@ -395,9 +395,9 @@ const guardOutside = useOutsideGuard()
|
|
|
395
395
|
</aside>
|
|
396
396
|
<div
|
|
397
397
|
ref="panelRef"
|
|
398
|
-
class="relative flex
|
|
398
|
+
class="relative flex min-h-0 flex-col overflow-hidden rounded-card border border-line shadow-panel"
|
|
399
399
|
:class="[frosted ? (layer.nested ? 'lp-glass-overlay lp-glass-overlay--nested' : 'lp-glass-overlay') : 'bg-surface-raised', widthClass, tweening ? 'transition-[height] duration-fast ease-[var(--ease-emphasized)] motion-reduce:transition-none' : '']"
|
|
400
|
-
:style="{ ...(width ? { width } : {}), ...(hasAside ? { alignSelf: 'stretch' } : {}) }"
|
|
400
|
+
:style="{ maxHeight: MODAL_MAX_HEIGHT, ...(width ? { width } : {}), ...(hasAside ? { alignSelf: 'stretch' } : {}) }"
|
|
401
401
|
>
|
|
402
402
|
<DialogTitle v-if="!(title || $slots.title) && ariaLabel" class="sr-only">
|
|
403
403
|
{{ ariaLabel }}
|
|
@@ -18,6 +18,7 @@ import { useGlass } from "../composables/useGlass"
|
|
|
18
18
|
import { useLabels } from "../composables/useLpConfig"
|
|
19
19
|
import LpButton from "./LpButton.vue"
|
|
20
20
|
import LpIcon from "./LpIcon.vue"
|
|
21
|
+
import { MODAL_MAX_HEIGHT } from "./modalDepth"
|
|
21
22
|
import LpModal from "./LpModal.vue"
|
|
22
23
|
import LpSidebar from "./LpSidebar.vue"
|
|
23
24
|
import type { SidebarItem, SidebarSection } from "./sidebar"
|
|
@@ -132,7 +133,7 @@ const triggerClass = computed(() => TRIGGER_VISIBILITY[props.mobileBreakpoint])
|
|
|
132
133
|
* mirrors LpModal's max-height, which is not a class this file can put in the
|
|
133
134
|
* consuming app's scan.
|
|
134
135
|
*/
|
|
135
|
-
const railStyle = { maxHeight:
|
|
136
|
+
const railStyle = { maxHeight: MODAL_MAX_HEIGHT }
|
|
136
137
|
|
|
137
138
|
/*
|
|
138
139
|
* The rail is part of the dialog, so it takes the same surface: a glass
|
|
@@ -177,7 +177,7 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
|
|
|
177
177
|
:id="field.id"
|
|
178
178
|
:aria-describedby="describedBy"
|
|
179
179
|
:aria-invalid="field.invalid || undefined"
|
|
180
|
-
class="min-w-0 flex-1 bg-transparent text-ink outline-none placeholder:text-muted [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
|
|
180
|
+
class="lp-entry min-w-0 flex-1 bg-transparent text-ink outline-none placeholder:text-muted [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
|
|
181
181
|
:class="steppers ? 'text-center @max-[8.5rem]:px-3 @max-[8.5rem]:text-left' : icon || $slots.leading ? 'pl-2 pr-3' : 'px-3'"
|
|
182
182
|
/>
|
|
183
183
|
<!-- The unit sits between the value and the increment button, so a long one
|
|
@@ -55,7 +55,7 @@ import { useFieldNotice, type FieldNoticeOptions } from "../composables/useField
|
|
|
55
55
|
import { useInputFilter } from "../composables/useInputFilter"
|
|
56
56
|
import { useLabels } from "../composables/useLpConfig"
|
|
57
57
|
import { type Country, filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./countries"
|
|
58
|
-
import { OPTION_ITEM, optionStagger, COMBOBOX_PANEL, POPOVER_PANEL } from "./dropdown"
|
|
58
|
+
import { OPTION_ITEM, optionStagger, COMBOBOX_FIT, COMBOBOX_PANEL, POPOVER_PANEL } from "./dropdown"
|
|
59
59
|
import LpFieldNotice from "./LpFieldNotice.vue"
|
|
60
60
|
import LpIcon from "./LpIcon.vue"
|
|
61
61
|
import {
|
|
@@ -403,7 +403,7 @@ const guardOutside = useOutsideGuard()
|
|
|
403
403
|
@escape-key-down="guardOutside"
|
|
404
404
|
v-bind="COMBOBOX_PANEL"
|
|
405
405
|
align="start"
|
|
406
|
-
:class="[POPOVER_PANEL, 'z-(--z-popover)
|
|
406
|
+
:class="[POPOVER_PANEL, 'z-(--z-popover) w-72 max-w-[calc(100vw-1rem)] overflow-hidden', COMBOBOX_FIT]"
|
|
407
407
|
>
|
|
408
408
|
<div class="border-b border-line p-2">
|
|
409
409
|
<!--
|
|
@@ -417,11 +417,11 @@ const guardOutside = useOutsideGuard()
|
|
|
417
417
|
:placeholder="searchPlaceholder ?? text.search"
|
|
418
418
|
:aria-label="text.searchLabel"
|
|
419
419
|
auto-focus
|
|
420
|
-
class="h-(--size-control-sm) w-full rounded-inner border border-line bg-surface-soft px-2.5 text-sm text-ink outline-none placeholder:text-muted focus:border-brand"
|
|
420
|
+
class="lp-entry h-(--size-control-sm) w-full rounded-inner border border-line bg-surface-soft px-2.5 text-sm text-ink outline-none placeholder:text-muted focus:border-brand"
|
|
421
421
|
@input="query = ($event.target as HTMLInputElement).value"
|
|
422
422
|
/>
|
|
423
423
|
</div>
|
|
424
|
-
<ComboboxViewport class="max-h-56 overflow-y-auto p-1">
|
|
424
|
+
<ComboboxViewport class="max-h-56 min-h-0 flex-1 overflow-y-auto p-1">
|
|
425
425
|
<ComboboxEmpty class="px-3 py-4 text-center text-sm italic text-muted">
|
|
426
426
|
{{ emptyText ?? text.empty }}
|
|
427
427
|
</ComboboxEmpty>
|
|
@@ -454,7 +454,7 @@ const guardOutside = useOutsideGuard()
|
|
|
454
454
|
:required="field.required || undefined"
|
|
455
455
|
:aria-invalid="field.invalid || undefined"
|
|
456
456
|
:aria-describedby="mergeDescribedBy(rest['aria-describedby'], field.describedBy, notice.message.value ? noticeId : undefined)"
|
|
457
|
-
class="lp-autofill min-w-0 flex-1 self-stretch bg-transparent px-3 outline-none placeholder:text-muted"
|
|
457
|
+
class="lp-autofill lp-entry min-w-0 flex-1 self-stretch bg-transparent px-3 outline-none placeholder:text-muted"
|
|
458
458
|
@beforeinput="onBeforeEdit"
|
|
459
459
|
@paste="onPaste"
|
|
460
460
|
@compositionend="onCompositionEnd"
|