@leavepulse/ui 0.78.0 → 0.78.2
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/chunks/{DropzoneLinkField-DXIr0eFz.js → DropzoneLinkField-CyE7TJ5f.js} +1 -1
- package/dist/chunks/FieldLeading-mr81aEAP.js +27 -0
- package/dist/chunks/LpAddressInput-ft8fQOGx.js +401 -0
- package/dist/chunks/{LpAppShell-BNAVTQsY.js → LpAppShell-DkvU6IBB.js} +23 -4
- package/dist/chunks/LpAutocomplete-CrZbd0uv.js +310 -0
- package/dist/chunks/LpCheckbox-CCWCwD0y.js +129 -0
- package/dist/chunks/{LpCodeBlock-DuzmnIHl.js → LpCodeBlock-DBqy4RLd.js} +1 -1
- package/dist/chunks/{LpCodeDisplay-CO8fngxa.js → LpCodeDisplay-D59uPwRb.js} +3 -3
- package/dist/chunks/LpColorPicker-BONURBFN.js +574 -0
- package/dist/chunks/{LpConfirmDialog-6oCIJTDa.js → LpConfirmDialog-CnrmzkuN.js} +1 -1
- package/dist/chunks/{LpConfirmHost-DgHvfT6s.js → LpConfirmHost-CwbObVGH.js} +1 -1
- package/dist/chunks/{LpContextMenu--eMGIql6.js → LpContextMenu-Bx_IhQDS.js} +5 -5
- package/dist/chunks/{LpCopyButton-7EOSIuwa.js → LpCopyButton-sAMvWeLP.js} +1 -1
- package/dist/chunks/{LpDataList-DgBeM01y.js → LpDataList-CaVfsozH.js} +2 -2
- package/dist/chunks/LpDatePicker-C2AOtxgF.js +171 -0
- package/dist/chunks/{LpDateRangePicker-CNQXdTVv.js → LpDateRangePicker-CiXdIAyh.js} +43 -35
- package/dist/chunks/{LpDrawer-Bt9UUqt_.js → LpDrawer-DXNQFpAA.js} +1 -1
- package/dist/chunks/{LpFileTree-BSx1kBoV.js → LpFileTree-CpNurwZp.js} +3 -3
- package/dist/chunks/{LpFileTreeNode-4dVmFHb3.js → LpFileTreeNode-DdF8Cq-s.js} +2 -2
- package/dist/chunks/{LpHoverCard-DanH3Nz1.js → LpHoverCard-CHE9jAJG.js} +6 -6
- package/dist/chunks/{LpInfraNode-BAU40Km2.js → LpInfraNode-Bb5nbuFY.js} +17 -0
- package/dist/chunks/{LpInput-DL6HEJdi.js → LpInput-CbI7z5fQ.js} +21 -25
- package/dist/chunks/{LpInspector-CuoMAhiw.js → LpInspector-DO3fTCQ1.js} +2 -2
- package/dist/chunks/{LpLaneNode-HtnQf2FF.js → LpLaneNode-Bqcdm8Rf.js} +19 -1
- package/dist/chunks/{LpLightbox-xvpQ64MM.js → LpLightbox-CtDTQV01.js} +1 -1
- package/dist/chunks/{LpLogInspector-6cNnXqp4.js → LpLogInspector-CcSJSGan.js} +1 -1
- package/dist/chunks/{LpLogViewer-Dvb1E9_G.js → LpLogViewer-CWu49Dko.js} +4 -4
- package/dist/chunks/{LpMediaGallery-Df-tiWeq.js → LpMediaGallery-BYEwySoF.js} +2 -2
- package/dist/chunks/{LpModal-DrtQ_1A2.js → LpModal-DDvrSAnf.js} +1 -1
- package/dist/chunks/{LpModalNav-DkmGCnZE.js → LpModalNav-DI3BW5ff.js} +2 -2
- package/dist/chunks/{LpNotificationBell-D_wklO1m.js → LpNotificationBell-DUbVyK6R.js} +3 -3
- package/dist/chunks/LpNumberField-C8LfUz_R.js +154 -0
- package/dist/chunks/LpOtpInput-B1X9l9no.js +140 -0
- package/dist/chunks/LpPageScrollbar-D2O0xhJw.js +164 -0
- package/dist/chunks/{LpPaletteTheme-dVtxHx-F.js → LpPaletteTheme-B-xuZiKK.js} +2 -2
- package/dist/chunks/{LpPasswordInput-B-zD6tUS.js → LpPasswordInput-Duc_3wPv.js} +2 -2
- package/dist/chunks/{LpPhoneInput-BSL7NX4z.js → LpPhoneInput-CctcRK9K.js} +124 -118
- package/dist/chunks/{LpPie-9t_guO49.js → LpPie-oVNHjsKD.js} +2 -2
- package/dist/chunks/{LpPipeline-rjXvEDRJ.js → LpPipeline-BSXT6oUJ.js} +1 -1
- package/dist/chunks/LpRadioGroup-DTM0nd9G.js +103 -0
- package/dist/chunks/LpRangeSlider-DJsEZXad.js +97 -0
- package/dist/chunks/{LpRelativeTime-BovUk8MP.js → LpRelativeTime-0zI-44ve.js} +1 -1
- package/dist/chunks/LpRepeater-Czib3rmv.js +193 -0
- package/dist/chunks/{LpScrollArea-ay4Xo_hg.js → LpScrollArea-CqvwfZs2.js} +9 -12
- package/dist/chunks/{LpSecretField-DBvU6iAw.js → LpSecretField-BtlCk3IH.js} +4 -4
- package/dist/chunks/LpSegmented-sskrJKJJ.js +128 -0
- package/dist/chunks/LpSelect-JFZehOkd.js +515 -0
- package/dist/chunks/{LpServiceNode-D8uJ8sGk.js → LpServiceNode-Cln2ntdw.js} +17 -0
- package/dist/chunks/{LpSidebar-kSkpPh3-.js → LpSidebar-CvRvVBTt.js} +4 -4
- package/dist/chunks/{LpSidebarNav-C2mvqrY2.js → LpSidebarNav-CE0jQS8i.js} +2 -2
- package/dist/chunks/{LpSidebarRow-eRP4Q31q.js → LpSidebarRow-DOTg1txp.js} +2 -2
- package/dist/chunks/LpSlider-C4ilCtZX.js +76 -0
- package/dist/chunks/{LpStat-pMNZgFcq.js → LpStat-DPjc6_fX.js} +17 -4
- package/dist/chunks/LpSwitch-DyAYTchz.js +122 -0
- package/dist/chunks/{LpTableFilter-DgARTsA1.js → LpTableFilter-CJg5eIcV.js} +2 -2
- package/dist/chunks/{LpTagsInput-Dt0mteSj.js → LpTagsInput-CkNxpNYa.js} +4 -3
- package/dist/chunks/{LpTextarea-Co0b9PVx.js → LpTextarea-CFK_5QB8.js} +3 -2
- package/dist/chunks/{LpThemeStudio-DzYfE-5k.js → LpThemeStudio-R-wohbCY.js} +3 -3
- package/dist/chunks/{LpThemeSwitcher-CFLkdbBL.js → LpThemeSwitcher-Bzy15MCP.js} +2 -2
- package/dist/chunks/LpTimeField-B5n0m8m0.js +115 -0
- package/dist/chunks/{LpToaster-QFj0P-5m.js → LpToaster-Ct_ZOhCh.js} +1 -1
- package/dist/chunks/{LpTooltip-BJf3lZMq.js → LpTooltip-Bz_rrq5S.js} +5 -5
- package/dist/chunks/{LpTopologyCanvas-BOHHHZ_6.js → LpTopologyCanvas-BNgw9RyZ.js} +14 -9
- package/dist/chunks/{LpTruncate-C6u3gjc6.js → LpTruncate-ellq9RYM.js} +2 -2
- package/dist/chunks/{LpUptimeBar-BnYmR2rj.js → LpUptimeBar-BoxcCDVY.js} +1 -1
- package/dist/chunks/{LpWorkspaceSwitcher-D0e9upAy.js → LpWorkspaceSwitcher-ugv-fyig.js} +2 -2
- package/dist/chunks/{RevealToggle-CCPDE8Mv.js → RevealToggle-D_iyrB8d.js} +1 -1
- package/dist/component-names.d.ts +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldLeading.vue.d.ts +16 -0
- package/dist/components/FieldLeading.vue.js +4 -0
- package/dist/components/LpAddressInput.vue.d.ts +4 -0
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +3 -3
- package/dist/components/LpAppShell.vue.d.ts +16 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +4 -0
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpChart.vue.d.ts +1 -1
- package/dist/components/LpChart.vue.js +1 -1
- package/dist/components/LpCheckbox.vue.d.ts +4 -0
- 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/LpCollapse.vue.d.ts +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +5 -3
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/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/LpDropzone.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpHoverCard.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.d.ts +1 -1
- package/dist/components/LpInfraNode.vue.d.ts +11 -2
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpInspector.vue.js +1 -1
- package/dist/components/LpLaneNode.vue.d.ts +2 -3
- package/dist/components/LpLaneNode.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/LpNumberField.vue.d.ts +17 -2
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.d.ts +4 -0
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPageScrollbar.vue.d.ts +10 -0
- package/dist/components/LpPageScrollbar.vue.js +4 -0
- package/dist/components/LpPaletteTheme.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.d.ts +4 -0
- 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/LpRadioGroup.vue.d.ts +4 -0
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.d.ts +4 -0
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.d.ts +2 -0
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpScrollArea.vue.d.ts +1 -1
- package/dist/components/LpScrollArea.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSegmented.vue.d.ts +4 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +2 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpServiceNode.vue.d.ts +8 -2
- package/dist/components/LpServiceNode.vue.js +1 -1
- package/dist/components/LpShinyText.vue.d.ts +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.d.ts +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.d.ts +4 -0
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpStat.vue.d.ts +16 -0
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpSwitch.vue.d.ts +4 -0
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.js +5 -5
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +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.d.ts +4 -0
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.d.ts +2 -2
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/LpWorkspaceSwitcher.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/calendar.d.ts +2 -0
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/scrollbar.d.ts +34 -0
- package/dist/components/scrollbar.js +47 -0
- package/dist/composables/propCheck.d.ts +1 -1
- package/dist/composables/propCheck.js +15 -4
- package/dist/composables/useField.d.ts +50 -0
- package/dist/composables/useField.js +31 -2
- package/dist/composables/useMergedClass.d.ts +6 -2
- package/dist/composables/useMergedClass.js +6 -6
- package/dist/composables/usePaletteCommands.js +4 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +170 -168
- package/package.json +1 -1
- package/src/component-names.ts +1 -0
- package/src/components/FieldLeading.vue +17 -0
- package/src/components/LpAddressInput.vue +8 -4
- package/src/components/LpAppShell.vue +23 -0
- package/src/components/LpAutocomplete.vue +11 -5
- package/src/components/LpCheckbox.vue +8 -6
- package/src/components/LpColorPicker.vue +7 -4
- package/src/components/LpContextMenu.vue +7 -0
- package/src/components/LpDatePicker.vue +8 -4
- package/src/components/LpDateRangePicker.vue +8 -4
- package/src/components/LpHoverCard.vue +7 -0
- package/src/components/LpInfraNode.vue +4 -3
- package/src/components/LpInput.vue +7 -7
- package/src/components/LpLaneNode.vue +5 -1
- package/src/components/LpNumberField.vue +18 -7
- package/src/components/LpOtpInput.vue +8 -6
- package/src/components/LpPageScrollbar.vue +195 -0
- package/src/components/LpPhoneInput.vue +11 -6
- package/src/components/LpRadioGroup.vue +9 -5
- package/src/components/LpRangeSlider.vue +8 -4
- package/src/components/LpRepeater.vue +16 -4
- package/src/components/LpScrollArea.vue +27 -23
- package/src/components/LpSegmented.vue +8 -4
- package/src/components/LpSelect.vue +8 -7
- package/src/components/LpServiceNode.vue +4 -2
- package/src/components/LpSlider.vue +8 -4
- package/src/components/LpStat.vue +25 -5
- package/src/components/LpSwitch.vue +8 -5
- package/src/components/LpTagsInput.vue +4 -3
- package/src/components/LpTextarea.vue +3 -2
- package/src/components/LpTimeField.vue +8 -6
- package/src/components/LpTooltip.vue +7 -0
- package/src/components/LpTopologyCanvas.vue +3 -1
- package/src/components/calendar.ts +2 -0
- package/src/components/scrollbar.ts +71 -0
- package/src/composables/propCheck.ts +20 -5
- package/src/composables/useField.ts +52 -0
- package/src/composables/useMergedClass.ts +11 -5
- package/src/composables/usePaletteCommands.ts +5 -2
- package/src/index.ts +1 -0
- package/dist/chunks/LpAddressInput-ZqoZ-N2Y.js +0 -393
- package/dist/chunks/LpAutocomplete-DkY6CSyD.js +0 -302
- package/dist/chunks/LpCheckbox-D6RuDW4X.js +0 -124
- package/dist/chunks/LpColorPicker-BxAHEnR5.js +0 -567
- package/dist/chunks/LpDatePicker-DwSDY0mt.js +0 -163
- package/dist/chunks/LpNumberField-CNHsmSDZ.js +0 -134
- package/dist/chunks/LpOtpInput-BRpLBbOD.js +0 -132
- package/dist/chunks/LpRadioGroup-BrazjLY_.js +0 -95
- package/dist/chunks/LpRangeSlider-fz61tzc-.js +0 -89
- package/dist/chunks/LpRepeater-BkJHSPI1.js +0 -180
- package/dist/chunks/LpSegmented-DbEJhbv-.js +0 -123
- package/dist/chunks/LpSelect-Y-iuSymW.js +0 -512
- package/dist/chunks/LpSlider-A4ixovgS.js +0 -68
- package/dist/chunks/LpSwitch-DnC2-0aV.js +0 -115
- package/dist/chunks/LpTimeField-DeOcKWJq.js +0 -110
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* The kit's overlay scrollbar for the document itself, for a page that scrolls
|
|
4
|
+
* the window (LpAppShell's default): the same bar as LpScrollArea's, drawn by
|
|
5
|
+
* the kit over the content instead of the browser's beside it.
|
|
6
|
+
*
|
|
7
|
+
* <LpPageScrollbar top="4rem" />
|
|
8
|
+
*
|
|
9
|
+
* The browser's own bar is hidden while this is mounted, so the page no longer
|
|
10
|
+
* loses its width to a gutter, nor changes width when it starts to scroll. The
|
|
11
|
+
* scrolling stays the browser's: wheel, keyboard, touch, find-in-page, anchors,
|
|
12
|
+
* scroll restoration, sticky, a full-page screenshot and printing are untouched
|
|
13
|
+
* (the bar is not printed, and at rest it is not drawn). What the kit adds is
|
|
14
|
+
* the drawing, a thumb that drags, and a press beside it that pages.
|
|
15
|
+
*
|
|
16
|
+
* Where the browser's bars already float over the content (phones, macOS set
|
|
17
|
+
* to show them while scrolling) it draws nothing and leaves the browser's.
|
|
18
|
+
*
|
|
19
|
+
* `top` and `bottom` keep the bar clear of what covers the window's edges: a
|
|
20
|
+
* sticky header, a bottom bar. Any CSS length, a variable included, and a
|
|
21
|
+
* change eases.
|
|
22
|
+
*/
|
|
23
|
+
import { computed, onBeforeUnmount, onMounted, ref } from "vue"
|
|
24
|
+
import { mergeClass } from "../composables/useMergedClass"
|
|
25
|
+
import { nodeWindow } from "../composables/useView"
|
|
26
|
+
import { BAR_FADE, BAR_Y, BAR_Y_SIZE, hideNativeBar, pageToward, SCROLL_HIDE_DELAY_MS, THUMB_Y, THUMB_Y_ROUND } from "./scrollbar"
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(defineProps<{ top?: string; bottom?: string }>(), { top: "0px", bottom: "0px" })
|
|
29
|
+
|
|
30
|
+
/* Shorter than this the thumb is hard to catch; reka's bar holds the same floor. */
|
|
31
|
+
const MIN_THUMB_PX = 18
|
|
32
|
+
|
|
33
|
+
const barEl = ref<HTMLElement | null>(null)
|
|
34
|
+
const active = ref(false)
|
|
35
|
+
const thumb = ref({ top: 0, height: 0 })
|
|
36
|
+
const scrollable = ref(false)
|
|
37
|
+
const scrolling = ref(false)
|
|
38
|
+
const hovered = ref(false)
|
|
39
|
+
const dragging = ref(false)
|
|
40
|
+
// Unset until the bar first shows: a bar mounting "hidden" would play its fade-out.
|
|
41
|
+
const state = computed(() => (scrolling.value || hovered.value || dragging.value ? "visible" : shownOnce.value ? "hidden" : undefined))
|
|
42
|
+
const shownOnce = ref(false)
|
|
43
|
+
|
|
44
|
+
let releaseNative: (() => void) | undefined
|
|
45
|
+
let doc: Document | undefined
|
|
46
|
+
let hideTimer: ReturnType<typeof setTimeout> | undefined
|
|
47
|
+
let frame = 0
|
|
48
|
+
let sizes: ResizeObserver | undefined
|
|
49
|
+
|
|
50
|
+
function scroller(): HTMLElement | null {
|
|
51
|
+
return (doc?.scrollingElement as HTMLElement | null) ?? null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* The width the browser's bar takes from the layout: 0 where it floats over the content. */
|
|
55
|
+
function nativeBarWidth(d: Document): number {
|
|
56
|
+
const probe = d.createElement("div")
|
|
57
|
+
probe.style.cssText = "position:absolute;top:-9999px;width:100px;height:100px;overflow:scroll"
|
|
58
|
+
d.body.append(probe)
|
|
59
|
+
const width = probe.offsetWidth - probe.clientWidth
|
|
60
|
+
probe.remove()
|
|
61
|
+
return width
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function measure() {
|
|
65
|
+
frame = 0
|
|
66
|
+
const view = scroller()
|
|
67
|
+
const bar = barEl.value
|
|
68
|
+
if (!view || !bar) return
|
|
69
|
+
const range = view.scrollHeight - view.clientHeight
|
|
70
|
+
scrollable.value = range > 1
|
|
71
|
+
if (!scrollable.value) return
|
|
72
|
+
const track = bar.clientHeight
|
|
73
|
+
const height = Math.max(MIN_THUMB_PX, Math.round((track * view.clientHeight) / view.scrollHeight))
|
|
74
|
+
thumb.value = { height, top: Math.round(((track - height) * view.scrollTop) / range) }
|
|
75
|
+
}
|
|
76
|
+
function measureSoon() {
|
|
77
|
+
if (!frame && doc) frame = nodeWindow(doc).requestAnimationFrame(measure)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function onScroll() {
|
|
81
|
+
measureSoon()
|
|
82
|
+
scrolling.value = true
|
|
83
|
+
shownOnce.value = true
|
|
84
|
+
clearTimeout(hideTimer)
|
|
85
|
+
hideTimer = setTimeout(() => (scrolling.value = false), SCROLL_HIDE_DELAY_MS)
|
|
86
|
+
}
|
|
87
|
+
function onEnter() {
|
|
88
|
+
hovered.value = true
|
|
89
|
+
shownOnce.value = true
|
|
90
|
+
}
|
|
91
|
+
function onLeave() {
|
|
92
|
+
hideTimer && clearTimeout(hideTimer)
|
|
93
|
+
hideTimer = setTimeout(() => {
|
|
94
|
+
hovered.value = false
|
|
95
|
+
scrolling.value = false
|
|
96
|
+
}, SCROLL_HIDE_DELAY_MS)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* A dialog that locks the page locks the bar with it. */
|
|
100
|
+
function locked(): boolean {
|
|
101
|
+
return !!doc && nodeWindow(doc).getComputedStyle(doc.body).overflow === "hidden"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function onTrackPointerDown(event: PointerEvent) {
|
|
105
|
+
const view = scroller()
|
|
106
|
+
const thumbEl = barEl.value?.querySelector<HTMLElement>("[data-lp-thumb]")
|
|
107
|
+
if (!view || !thumbEl || event.button !== 0 || thumbEl.contains(event.target as Node) || locked()) return
|
|
108
|
+
event.preventDefault()
|
|
109
|
+
pageToward(view, "y", event.clientY < thumbEl.getBoundingClientRect().top)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function onThumbPointerDown(event: PointerEvent) {
|
|
113
|
+
const view = scroller()
|
|
114
|
+
const bar = barEl.value
|
|
115
|
+
if (!view || !bar || event.button !== 0 || locked()) return
|
|
116
|
+
// Not a text selection across the page, and the release is heard wherever it lands.
|
|
117
|
+
event.preventDefault()
|
|
118
|
+
const handle = event.currentTarget as HTMLElement
|
|
119
|
+
handle.setPointerCapture(event.pointerId)
|
|
120
|
+
dragging.value = true
|
|
121
|
+
const startY = event.clientY
|
|
122
|
+
const startTop = view.scrollTop
|
|
123
|
+
const perPixel = (view.scrollHeight - view.clientHeight) / Math.max(1, bar.clientHeight - thumb.value.height)
|
|
124
|
+
const move = (e: PointerEvent) => view.scrollTo({ top: startTop + (e.clientY - startY) * perPixel, behavior: "instant" })
|
|
125
|
+
const stop = () => {
|
|
126
|
+
dragging.value = false
|
|
127
|
+
handle.removeEventListener("pointermove", move)
|
|
128
|
+
handle.removeEventListener("pointerup", stop)
|
|
129
|
+
handle.removeEventListener("pointercancel", stop)
|
|
130
|
+
handle.removeEventListener("lostpointercapture", stop)
|
|
131
|
+
}
|
|
132
|
+
handle.addEventListener("pointermove", move)
|
|
133
|
+
handle.addEventListener("pointerup", stop)
|
|
134
|
+
handle.addEventListener("pointercancel", stop)
|
|
135
|
+
handle.addEventListener("lostpointercapture", stop)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onMounted(() => {
|
|
139
|
+
const host = barEl.value ?? undefined
|
|
140
|
+
doc = host?.ownerDocument
|
|
141
|
+
if (!doc || nativeBarWidth(doc) === 0) return
|
|
142
|
+
active.value = true
|
|
143
|
+
releaseNative = hideNativeBar(doc)
|
|
144
|
+
const win = nodeWindow(doc)
|
|
145
|
+
win.addEventListener("scroll", onScroll, { passive: true })
|
|
146
|
+
win.addEventListener("resize", measureSoon)
|
|
147
|
+
sizes = new win.ResizeObserver(measureSoon)
|
|
148
|
+
sizes.observe(doc.body)
|
|
149
|
+
if (host) sizes.observe(host)
|
|
150
|
+
measure()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
onBeforeUnmount(() => {
|
|
154
|
+
clearTimeout(hideTimer)
|
|
155
|
+
sizes?.disconnect()
|
|
156
|
+
if (!doc || !active.value) return
|
|
157
|
+
const win = nodeWindow(doc)
|
|
158
|
+
if (frame) win.cancelAnimationFrame(frame)
|
|
159
|
+
win.removeEventListener("scroll", onScroll)
|
|
160
|
+
win.removeEventListener("resize", measureSoon)
|
|
161
|
+
releaseNative?.()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const barClass = computed(() =>
|
|
165
|
+
mergeClass(
|
|
166
|
+
BAR_Y,
|
|
167
|
+
BAR_Y_SIZE.wall,
|
|
168
|
+
BAR_FADE,
|
|
169
|
+
"fixed right-0 z-(--z-sticky) transition-[top,bottom] duration-[var(--duration-medium)] ease-[var(--ease-settle)] print:hidden",
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
</script>
|
|
173
|
+
|
|
174
|
+
<template>
|
|
175
|
+
<!-- Always in the DOM, so it can be measured; drawn only where the kit owns the bar. -->
|
|
176
|
+
<div
|
|
177
|
+
ref="barEl"
|
|
178
|
+
aria-hidden="true"
|
|
179
|
+
data-lp-page-scrollbar
|
|
180
|
+
:class="barClass"
|
|
181
|
+
:data-state="active && scrollable ? state : undefined"
|
|
182
|
+
:style="{ top: props.top, bottom: props.bottom, visibility: active && scrollable ? undefined : 'hidden' }"
|
|
183
|
+
@pointerenter="onEnter"
|
|
184
|
+
@pointerleave="onLeave"
|
|
185
|
+
@pointerdown="onTrackPointerDown"
|
|
186
|
+
>
|
|
187
|
+
<div
|
|
188
|
+
data-lp-thumb
|
|
189
|
+
class="absolute right-px"
|
|
190
|
+
:class="[THUMB_Y, THUMB_Y_ROUND.wall]"
|
|
191
|
+
:style="{ top: `${thumb.top}px`, height: `${thumb.height}px` }"
|
|
192
|
+
@pointerdown.stop="onThumbPointerDown"
|
|
193
|
+
/>
|
|
194
|
+
</div>
|
|
195
|
+
</template>
|
|
@@ -36,6 +36,7 @@ export type PhoneInputLabels = {
|
|
|
36
36
|
* is what a form should store. Until the library arrives the field takes digits
|
|
37
37
|
* as before, unformatted.
|
|
38
38
|
*/
|
|
39
|
+
import FieldWrapper from "./FieldWrapper.vue"
|
|
39
40
|
import { CONTROL_SIZE } from "./controlSize"
|
|
40
41
|
import {
|
|
41
42
|
ComboboxAnchor,
|
|
@@ -48,8 +49,8 @@ import {
|
|
|
48
49
|
ComboboxTrigger,
|
|
49
50
|
ComboboxViewport,
|
|
50
51
|
} from "reka-ui"
|
|
51
|
-
import { computed, onMounted, ref, shallowRef } from "vue"
|
|
52
|
-
import { useField,
|
|
52
|
+
import { computed, onMounted, ref, shallowRef, useAttrs } from "vue"
|
|
53
|
+
import { useField, useOwnField, mergeDescribedBy } from "../composables/useField"
|
|
53
54
|
import { useFieldNotice, type FieldNoticeOptions } from "../composables/useFieldNotice"
|
|
54
55
|
import { useInputFilter } from "../composables/useInputFilter"
|
|
55
56
|
import { useLabels } from "../composables/useLpConfig"
|
|
@@ -101,6 +102,8 @@ const props = withDefaults(
|
|
|
101
102
|
placeholder?: string
|
|
102
103
|
disabled?: boolean
|
|
103
104
|
invalid?: boolean
|
|
105
|
+
/** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
|
|
106
|
+
error?: string
|
|
104
107
|
size?: "sm" | "md" | "lg"
|
|
105
108
|
emptyText?: string
|
|
106
109
|
searchPlaceholder?: string
|
|
@@ -322,7 +325,8 @@ const sizeClass = CONTROL_SIZE
|
|
|
322
325
|
// placing it in a narrow column has to change, so `class="w-48"` must win.
|
|
323
326
|
// style lays out the frame; every other attr (name, autocomplete, @blur…)
|
|
324
327
|
// belongs to the number <input>.
|
|
325
|
-
const
|
|
328
|
+
const attrs = useAttrs()
|
|
329
|
+
const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid || flagged.value, disabled: props.disabled }))
|
|
326
330
|
const text = useLabels<PhoneInputLabels>("phoneInput", {
|
|
327
331
|
selectCountry: "Select country",
|
|
328
332
|
placeholder: "Phone number",
|
|
@@ -345,8 +349,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
|
345
349
|
? "border-danger focus-within:border-danger focus-within:ring-danger-soft"
|
|
346
350
|
: "border-line focus-within:border-brand",
|
|
347
351
|
field.value.disabled ? "cursor-not-allowed opacity-55" : "",
|
|
348
|
-
].join(" "),
|
|
349
|
-
)
|
|
352
|
+
].join(" "), () => ownAttrs())
|
|
350
353
|
const inputAttrs = computed(() => {
|
|
351
354
|
const { style: _s, ...others } = rest.value
|
|
352
355
|
return others
|
|
@@ -360,10 +363,11 @@ const guardOutside = useOutsideGuard()
|
|
|
360
363
|
</script>
|
|
361
364
|
|
|
362
365
|
<template>
|
|
366
|
+
<FieldWrapper v-bind="wrapper">
|
|
363
367
|
<div
|
|
364
368
|
data-lp-ring-owner
|
|
365
369
|
:class="rootClass"
|
|
366
|
-
:style="attrs.style as never"
|
|
370
|
+
:style="ownAttrs() ? (attrs.style as never) : undefined"
|
|
367
371
|
>
|
|
368
372
|
<!-- reset-search-term-on-{select,blur}: as in LpSelect, the trigger shows
|
|
369
373
|
the selection and the box only filters, so reka seeding the chosen value
|
|
@@ -460,4 +464,5 @@ const guardOutside = useOutsideGuard()
|
|
|
460
464
|
/>
|
|
461
465
|
<LpFieldNotice v-if="ownNotice" :notice="notice" :id="noticeId" />
|
|
462
466
|
</div>
|
|
467
|
+
</FieldWrapper>
|
|
463
468
|
</template>
|
|
@@ -6,10 +6,11 @@ export default { inheritAttrs: false }
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
|
+
import FieldWrapper from "./FieldWrapper.vue"
|
|
9
10
|
import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
|
|
10
11
|
import { RadioGroupIndicator, RadioGroupItem, RadioGroupRoot } from "reka-ui"
|
|
11
12
|
import { useId } from "vue"
|
|
12
|
-
import {
|
|
13
|
+
import { useOwnField, mergeDescribedBy } from "../composables/useField"
|
|
13
14
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
14
15
|
import LpIcon from "./LpIcon.vue"
|
|
15
16
|
import type { LpOptionBase } from "./options"
|
|
@@ -22,21 +23,23 @@ export interface RadioOption extends LpOptionBase<string> {
|
|
|
22
23
|
// a disabled option stays visible but cannot be picked. For anything richer
|
|
23
24
|
// (badges, custom layout) pass <LpRadio> children in the default slot instead —
|
|
24
25
|
// they share the same RadioGroupRoot, so selection still flows through v-model.
|
|
25
|
-
const props = withDefaults(defineProps<{ palette?: PaletteControlOptions; modelValue?: string; options?: RadioOption[]; disabled?: boolean
|
|
26
|
+
const props = withDefaults(defineProps<{ palette?: PaletteControlOptions; modelValue?: string; options?: RadioOption[]; disabled?: boolean
|
|
27
|
+
/** A message under the group, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
|
|
28
|
+
error?: string }>(), { palette: undefined })
|
|
26
29
|
defineEmits<{ (e: "update:modelValue", value: string): void }>()
|
|
27
30
|
|
|
28
|
-
const field =
|
|
31
|
+
const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, disabled: props.disabled }))
|
|
29
32
|
const uid = useId()
|
|
30
33
|
|
|
31
34
|
// A radio group stacks with a fixed gap; a dense settings list needs it smaller, and a horizontal layout needs it gone.
|
|
32
35
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
33
|
-
"flex flex-col gap-2",
|
|
34
|
-
)
|
|
36
|
+
"flex flex-col gap-2", () => ownAttrs())
|
|
35
37
|
|
|
36
38
|
usePaletteControl({ disabled: () => field.value.disabled })
|
|
37
39
|
</script>
|
|
38
40
|
|
|
39
41
|
<template>
|
|
42
|
+
<FieldWrapper v-bind="wrapper">
|
|
40
43
|
<RadioGroupRoot
|
|
41
44
|
:model-value="modelValue"
|
|
42
45
|
:disabled="field.disabled"
|
|
@@ -82,4 +85,5 @@ usePaletteControl({ disabled: () => field.value.disabled })
|
|
|
82
85
|
</label>
|
|
83
86
|
</slot>
|
|
84
87
|
</RadioGroupRoot>
|
|
88
|
+
</FieldWrapper>
|
|
85
89
|
</template>
|
|
@@ -6,6 +6,7 @@ export default { inheritAttrs: false }
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
|
+
import FieldWrapper from "./FieldWrapper.vue"
|
|
9
10
|
import SliderRail from "./SliderRail.vue"
|
|
10
11
|
import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
|
|
11
12
|
/*
|
|
@@ -16,7 +17,7 @@ import { usePaletteControl, type PaletteControlOptions } from "../composables/us
|
|
|
16
17
|
* focused or dragged, so the numbers are readable without a second readout.
|
|
17
18
|
*/
|
|
18
19
|
import { SliderRoot, SliderThumb } from "reka-ui"
|
|
19
|
-
import {
|
|
20
|
+
import { useOwnField, mergeDescribedBy } from "../composables/useField"
|
|
20
21
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
21
22
|
import { SLIDER_GLIDE, useSliderDrag } from "./slider"
|
|
22
23
|
|
|
@@ -31,6 +32,8 @@ const props = withDefaults(
|
|
|
31
32
|
/** The least distance the two ends keep between them, in model units. */
|
|
32
33
|
minGap?: number
|
|
33
34
|
disabled?: boolean
|
|
35
|
+
/** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
|
|
36
|
+
error?: string
|
|
34
37
|
/** Accessible names for the two thumbs when there's no visible label. */
|
|
35
38
|
ariaLabels?: [string, string]
|
|
36
39
|
/** Label over each thumb while it is in use: `(value) => "1:05"`. */
|
|
@@ -58,11 +61,10 @@ function onCommit(v: number[] | undefined) {
|
|
|
58
61
|
if (value) emit("change", value)
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
const field =
|
|
64
|
+
const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, disabled: props.disabled }))
|
|
62
65
|
|
|
63
66
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
64
|
-
`relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55 ${SLIDER_GLIDE}`,
|
|
65
|
-
)
|
|
67
|
+
`relative flex h-5 w-full touch-none select-none items-center data-[disabled]:opacity-55 ${SLIDER_GLIDE}`, () => ownAttrs())
|
|
66
68
|
const { dragging, onPointerDown } = useSliderDrag()
|
|
67
69
|
|
|
68
70
|
const thumbClass =
|
|
@@ -74,6 +76,7 @@ usePaletteControl({ disabled: () => field.value.disabled })
|
|
|
74
76
|
</script>
|
|
75
77
|
|
|
76
78
|
<template>
|
|
79
|
+
<FieldWrapper v-bind="wrapper">
|
|
77
80
|
<SliderRoot
|
|
78
81
|
:model-value="[props.modelValue[0], props.modelValue[1]]"
|
|
79
82
|
:min="min"
|
|
@@ -101,4 +104,5 @@ usePaletteControl({ disabled: () => field.value.disabled })
|
|
|
101
104
|
<span v-if="format" :class="labelClass">{{ format(props.modelValue[index]) }}</span>
|
|
102
105
|
</SliderThumb>
|
|
103
106
|
</SliderRoot>
|
|
107
|
+
</FieldWrapper>
|
|
104
108
|
</template>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts" generic="T extends object">
|
|
2
|
+
import FieldWrapper from "./FieldWrapper.vue"
|
|
2
3
|
import { nextTick, ref } from "vue"
|
|
3
|
-
import {
|
|
4
|
+
import { useOwnField, mergeDescribedBy } from "../composables/useField"
|
|
4
5
|
import { useLabels } from "../composables/useLpConfig"
|
|
5
6
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
6
7
|
import { useRepeater, type RepeaterItemKey } from "../composables/useRepeater"
|
|
@@ -17,6 +18,8 @@ const props = defineProps<{
|
|
|
17
18
|
itemKey?: RepeaterItemKey<T>
|
|
18
19
|
label?: string
|
|
19
20
|
disabled?: boolean
|
|
21
|
+
/** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
|
|
22
|
+
error?: string
|
|
20
23
|
labels?: Partial<{ add: string; remove: string; empty: string }>
|
|
21
24
|
}>()
|
|
22
25
|
const model = defineModel<T[]>({ default: () => [] })
|
|
@@ -25,7 +28,7 @@ defineSlots<{
|
|
|
25
28
|
empty?(): unknown
|
|
26
29
|
}>()
|
|
27
30
|
|
|
28
|
-
const field =
|
|
31
|
+
const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, disabled: props.disabled }))
|
|
29
32
|
const text = useLabels("repeater", { add: "Add item", remove: "Remove item {index}", empty: "No items" }, () => props.labels)
|
|
30
33
|
const list = useRepeater({
|
|
31
34
|
value: () => model.value,
|
|
@@ -78,11 +81,19 @@ async function remove(index: number) {
|
|
|
78
81
|
else (addButton.value?.$el as HTMLElement | undefined)?.focus({ preventScroll: true })
|
|
79
82
|
}
|
|
80
83
|
defineExpose({ add, remove, update: list.update })
|
|
81
|
-
const { class: rootClass, attrs: rest } = useMergedAttrs("flex min-w-0 flex-col gap-2")
|
|
84
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs("flex min-w-0 flex-col gap-2", () => ownAttrs())
|
|
82
85
|
</script>
|
|
83
86
|
|
|
84
87
|
<template>
|
|
85
|
-
<
|
|
88
|
+
<FieldWrapper v-bind="wrapper">
|
|
89
|
+
<!-- A group, named by its label, so the field's message is read with it. -->
|
|
90
|
+
<div
|
|
91
|
+
v-bind="rest"
|
|
92
|
+
role="group"
|
|
93
|
+
:aria-label="label || undefined"
|
|
94
|
+
:aria-describedby="mergeDescribedBy(rest['aria-describedby'], field.describedBy)"
|
|
95
|
+
:class="rootClass"
|
|
96
|
+
>
|
|
86
97
|
<div class="flex items-center justify-between gap-2">
|
|
87
98
|
<span v-if="label" class="text-sm font-medium text-ink">{{ label }}</span>
|
|
88
99
|
<LpButton ref="addButton" size="xs" variant="ghost" :disabled="field.disabled" @click="add">
|
|
@@ -105,6 +116,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs("flex min-w-0 flex-col
|
|
|
105
116
|
</div>
|
|
106
117
|
</LpShift>
|
|
107
118
|
</div>
|
|
119
|
+
</FieldWrapper>
|
|
108
120
|
</template>
|
|
109
121
|
|
|
110
122
|
<style>
|
|
@@ -13,8 +13,20 @@ import {
|
|
|
13
13
|
ScrollAreaThumb,
|
|
14
14
|
ScrollAreaViewport,
|
|
15
15
|
} from "reka-ui"
|
|
16
|
-
import { prefersReducedMotion } from "../composables/easing"
|
|
17
16
|
import { nodeWindow } from "../composables/useView"
|
|
17
|
+
import {
|
|
18
|
+
BAR_FADE,
|
|
19
|
+
BAR_X,
|
|
20
|
+
BAR_X_SIZE,
|
|
21
|
+
BAR_Y,
|
|
22
|
+
BAR_Y_SIZE,
|
|
23
|
+
pageToward,
|
|
24
|
+
SCROLL_HIDE_DELAY_MS,
|
|
25
|
+
THUMB_X,
|
|
26
|
+
THUMB_X_ROUND,
|
|
27
|
+
THUMB_Y,
|
|
28
|
+
THUMB_Y_ROUND,
|
|
29
|
+
} from "./scrollbar"
|
|
18
30
|
|
|
19
31
|
// `fade` softens whichever of the top/bottom edges currently has content beyond
|
|
20
32
|
// it, so a clipped list reads as continuing rather than as cut off. On by
|
|
@@ -80,8 +92,7 @@ function onViewportBlur() {
|
|
|
80
92
|
// `opacity-0` + the hidden-state fade is what keeps the bar invisible at rest:
|
|
81
93
|
// with force-mount the element stays in the DOM, so a one-shot fade-out alone
|
|
82
94
|
// would snap back to visible once the animation finished.
|
|
83
|
-
const
|
|
84
|
-
"opacity-0 data-[state=visible]:animate-[fade-in_180ms_ease] data-[state=visible]:opacity-100 data-[state=hidden]:animate-[fade-out_240ms_ease]"
|
|
95
|
+
const edge = computed(() => (props.floating ? "floating" : "wall"))
|
|
85
96
|
|
|
86
97
|
// Dragging the thumb pulls the pointer off the bar and across the content, and
|
|
87
98
|
// the browser reads that as a text selection — the rows highlight blue while
|
|
@@ -197,13 +208,9 @@ onBeforeUnmount(() => {
|
|
|
197
208
|
})
|
|
198
209
|
|
|
199
210
|
/*
|
|
200
|
-
* A press on the bar beside the thumb
|
|
201
|
-
*
|
|
202
|
-
* so a press that
|
|
203
|
-
* thumb, and a press further away teleported it. Like a native bar, a press
|
|
204
|
-
* off the thumb now pages one viewport towards the pointer, smoothly; the
|
|
205
|
-
* thumb itself has a hit area as wide as the bar (see its `before:`), so a
|
|
206
|
-
* press that meant to grab it does.
|
|
211
|
+
* A press on the bar beside the thumb pages towards the pointer (pageToward)
|
|
212
|
+
* instead of reka's jump; the thumb's hit area is as wide as the bar (its
|
|
213
|
+
* `before:`), so a press that meant to grab it does.
|
|
207
214
|
*/
|
|
208
215
|
function onTrackPointerDown(event: PointerEvent, axis: "x" | "y") {
|
|
209
216
|
const el = viewportEl.value
|
|
@@ -213,10 +220,7 @@ function onTrackPointerDown(event: PointerEvent, axis: "x" | "y") {
|
|
|
213
220
|
event.preventDefault()
|
|
214
221
|
event.stopImmediatePropagation()
|
|
215
222
|
const rect = thumb.getBoundingClientRect()
|
|
216
|
-
|
|
217
|
-
const step = (axis === "y" ? el.clientHeight : el.clientWidth) * 0.85 * (before ? -1 : 1)
|
|
218
|
-
const behavior = prefersReducedMotion() ? "auto" : "smooth"
|
|
219
|
-
el.scrollBy(axis === "y" ? { top: step, behavior } : { left: step, behavior })
|
|
223
|
+
pageToward(el, axis, axis === "y" ? event.clientY < rect.top : event.clientX < rect.left)
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
let stopThumbDrag: (() => void) | null = null
|
|
@@ -253,7 +257,7 @@ function onThumbPointerDown(event: PointerEvent) {
|
|
|
253
257
|
class="relative flex flex-col overflow-hidden"
|
|
254
258
|
:class="dragging ? 'select-none' : undefined"
|
|
255
259
|
type="hover"
|
|
256
|
-
:scroll-hide-delay="
|
|
260
|
+
:scroll-hide-delay="SCROLL_HIDE_DELAY_MS"
|
|
257
261
|
>
|
|
258
262
|
<!-- The bar itself is a pointer target far wider than it looks: the visible
|
|
259
263
|
rail is the thumb, sized below, while the track pads it out to a
|
|
@@ -273,8 +277,8 @@ function onThumbPointerDown(event: PointerEvent) {
|
|
|
273
277
|
<ScrollAreaScrollbar
|
|
274
278
|
orientation="vertical"
|
|
275
279
|
force-mount
|
|
276
|
-
class="
|
|
277
|
-
:class="[
|
|
280
|
+
class="z-1"
|
|
281
|
+
:class="[BAR_Y, BAR_FADE, BAR_Y_SIZE[edge]]"
|
|
278
282
|
:style="barInsetTop ? { marginTop: barInsetTop } : undefined"
|
|
279
283
|
@pointerdown.capture="onTrackPointerDown($event, 'y')"
|
|
280
284
|
>
|
|
@@ -286,22 +290,22 @@ function onThumbPointerDown(event: PointerEvent) {
|
|
|
286
290
|
the positioning reka owns. -->
|
|
287
291
|
<ScrollAreaThumb
|
|
288
292
|
data-lp-thumb
|
|
289
|
-
class="relative
|
|
290
|
-
:class="
|
|
293
|
+
class="relative"
|
|
294
|
+
:class="[THUMB_Y, THUMB_Y_ROUND[edge]]"
|
|
291
295
|
@pointerdown="onThumbPointerDown"
|
|
292
296
|
/>
|
|
293
297
|
</ScrollAreaScrollbar>
|
|
294
298
|
|
|
295
299
|
<ScrollAreaScrollbar
|
|
296
300
|
orientation="horizontal"
|
|
297
|
-
class="
|
|
298
|
-
:class="[
|
|
301
|
+
class="z-1"
|
|
302
|
+
:class="[BAR_X, BAR_FADE, BAR_X_SIZE[edge]]"
|
|
299
303
|
@pointerdown.capture="onTrackPointerDown($event, 'x')"
|
|
300
304
|
>
|
|
301
305
|
<ScrollAreaThumb
|
|
302
306
|
data-lp-thumb
|
|
303
|
-
class="relative
|
|
304
|
-
:class="
|
|
307
|
+
class="relative"
|
|
308
|
+
:class="[THUMB_X, THUMB_X_ROUND[edge]]"
|
|
305
309
|
@pointerdown="onThumbPointerDown"
|
|
306
310
|
/>
|
|
307
311
|
</ScrollAreaScrollbar>
|
|
@@ -6,6 +6,7 @@ export default { inheritAttrs: false }
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
|
+
import FieldWrapper from "./FieldWrapper.vue"
|
|
9
10
|
import { CONTROL_SIZE } from "./controlSize"
|
|
10
11
|
import { isHTMLElement } from "../composables/useView"
|
|
11
12
|
import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
|
|
@@ -19,7 +20,7 @@ import { usePaletteControl, type PaletteControlOptions } from "../composables/us
|
|
|
19
20
|
import { Motion } from "motion-v"
|
|
20
21
|
import { ToggleGroupItem, ToggleGroupRoot } from "reka-ui"
|
|
21
22
|
import { ref, useId } from "vue"
|
|
22
|
-
import {
|
|
23
|
+
import { useOwnField, mergeDescribedBy } from "../composables/useField"
|
|
23
24
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
24
25
|
import { usePillTransition } from "../composables/usePillTransition"
|
|
25
26
|
import AttentionDot from "./AttentionDot.vue"
|
|
@@ -47,6 +48,8 @@ const props = withDefaults(
|
|
|
47
48
|
/** Stretch to fill the container with equal-width segments. */
|
|
48
49
|
block?: boolean
|
|
49
50
|
disabled?: boolean
|
|
51
|
+
/** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
|
|
52
|
+
error?: string
|
|
50
53
|
/**
|
|
51
54
|
* The pill glides under the hovered segment and springs back on leave (the
|
|
52
55
|
* default, as in LpTabs). False pins it to the selected segment and hover
|
|
@@ -80,10 +83,9 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
|
80
83
|
// `w-fit`, not left to the parent: in a column a flex item stretches, and
|
|
81
84
|
// the options sat on the left of an empty bar (the theme studio met it).
|
|
82
85
|
props.block ? "flex w-full" : "w-fit",
|
|
83
|
-
].join(" "),
|
|
84
|
-
)
|
|
86
|
+
].join(" "), () => ownAttrs())
|
|
85
87
|
|
|
86
|
-
const field =
|
|
88
|
+
const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, disabled: props.disabled }))
|
|
87
89
|
|
|
88
90
|
const pillId = `lp-segmented-${useId()}`
|
|
89
91
|
const pillTransition = usePillTransition()
|
|
@@ -114,6 +116,7 @@ usePaletteControl({ disabled: () => field.value.disabled })
|
|
|
114
116
|
</script>
|
|
115
117
|
|
|
116
118
|
<template>
|
|
119
|
+
<FieldWrapper v-bind="wrapper">
|
|
117
120
|
<ToggleGroupRoot
|
|
118
121
|
:model-value="modelValue"
|
|
119
122
|
type="single"
|
|
@@ -152,4 +155,5 @@ usePaletteControl({ disabled: () => field.value.disabled })
|
|
|
152
155
|
<AttentionDot v-if="opt.dot && opt.label" class="mt-1.5" />
|
|
153
156
|
</ToggleGroupItem>
|
|
154
157
|
</ToggleGroupRoot>
|
|
158
|
+
</FieldWrapper>
|
|
155
159
|
</template>
|
|
@@ -39,7 +39,8 @@ import {
|
|
|
39
39
|
ComboboxViewport,
|
|
40
40
|
} from "reka-ui"
|
|
41
41
|
import { computed, nextTick, reactive, ref, useAttrs, useId, watch } from "vue"
|
|
42
|
-
import
|
|
42
|
+
import FieldWrapper from "./FieldWrapper.vue"
|
|
43
|
+
import { useFieldBlur, useOwnField, mergeDescribedBy } from "../composables/useField"
|
|
43
44
|
import { useLabels } from "../composables/useLpConfig"
|
|
44
45
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
45
46
|
import { OPTION_ITEM, optionStagger, POPOVER_PANEL } from "./dropdown"
|
|
@@ -75,6 +76,8 @@ const props = withDefaults(
|
|
|
75
76
|
clearable?: boolean
|
|
76
77
|
disabled?: boolean
|
|
77
78
|
invalid?: boolean
|
|
79
|
+
/** A message under the select, as on LpInput: it marks the select invalid (what `form.field()` hands it). */
|
|
80
|
+
error?: string
|
|
78
81
|
emptyText?: string
|
|
79
82
|
// Server-side search: bind with `v-model:searchTerm`. When provided, the
|
|
80
83
|
// parent owns filtering (fetches matching `options` per term), so the
|
|
@@ -110,11 +113,7 @@ const props = withDefaults(
|
|
|
110
113
|
const SIZE_HEIGHT = CONTROL_SIZE
|
|
111
114
|
const anchorHeight = computed(() => SIZE_HEIGHT[props.size])
|
|
112
115
|
|
|
113
|
-
const field =
|
|
114
|
-
wrapped: false,
|
|
115
|
-
invalid: props.invalid,
|
|
116
|
-
disabled: props.disabled,
|
|
117
|
-
}))
|
|
116
|
+
const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid, disabled: props.disabled }))
|
|
118
117
|
const labels = useLabels<SelectLabels>(
|
|
119
118
|
"select",
|
|
120
119
|
{
|
|
@@ -136,7 +135,7 @@ const emptyLabel = computed(() => props.emptyText ?? labels.value.empty)
|
|
|
136
135
|
const loadingLabel = computed(() => props.loadingText ?? labels.value.loading)
|
|
137
136
|
|
|
138
137
|
// The consumer's `class="w-40"` has to beat the root's own layout.
|
|
139
|
-
const { class: rootClass, attrs: rootAttrs } = useMergedAttrs("relative")
|
|
138
|
+
const { class: rootClass, attrs: rootAttrs } = useMergedAttrs("relative", ownAttrs)
|
|
140
139
|
|
|
141
140
|
const emit = defineEmits<{
|
|
142
141
|
// Full v-model state (single value, array for multiple, or empty).
|
|
@@ -450,6 +449,7 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
|
|
|
450
449
|
the seeding left the list pinned to one option until it was cleared by
|
|
451
450
|
hand. Both flags default true and reach the same writer, so both must go:
|
|
452
451
|
we own the term (`query`) and reka must not write to it. -->
|
|
452
|
+
<FieldWrapper v-bind="wrapper">
|
|
453
453
|
<ComboboxRoot
|
|
454
454
|
:model-value="comboModel"
|
|
455
455
|
:multiple="multiple"
|
|
@@ -635,4 +635,5 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
|
|
|
635
635
|
</ComboboxContent>
|
|
636
636
|
</ComboboxPortal>
|
|
637
637
|
</ComboboxRoot>
|
|
638
|
+
</FieldWrapper>
|
|
638
639
|
</template>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* depends_on edges connect these across host frames. Tailwind utilities only
|
|
6
6
|
* (no scoped CSS) so styles reach consumer apps via @source.
|
|
7
7
|
*/
|
|
8
|
-
import { Handle, Position } from "@vue-flow/core"
|
|
8
|
+
import { Handle, Position, type NodeProps } from "@vue-flow/core"
|
|
9
9
|
import { computed } from "vue"
|
|
10
10
|
|
|
11
11
|
export interface ServiceNodeData {
|
|
@@ -16,7 +16,9 @@ export interface ServiceNodeData {
|
|
|
16
16
|
dimmed?: boolean
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// Vue Flow hands a custom node all of NodeProps. Declared, they stop falling
|
|
20
|
+
// through onto the node's root as attributes (position="[object Object]").
|
|
21
|
+
const props = defineProps<Omit<NodeProps<ServiceNodeData>, "selected"> & { selected?: boolean }>()
|
|
20
22
|
|
|
21
23
|
const kindVar: Record<string, string> = {
|
|
22
24
|
service: "var(--color-brand)",
|