@leavepulse/ui 0.31.1 → 0.32.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/{LpDropzone-DVDC-ABU.css → LpDropzone-BBoWvi-J.css} +12 -12
- package/dist/assets/{LpImageEditor-DqP5JKCy.css → LpImageEditor-BsSwej47.css} +6 -6
- package/dist/chunks/{LpAddressInput-BDkp3-Bp.js → LpAddressInput-C35TsJXt.js} +19 -14
- package/dist/chunks/{LpAppShell-Chz8Vbtu.js → LpAppShell-lVvpENMH.js} +18 -13
- package/dist/chunks/{LpBreadcrumbs-5uvViCXi.js → LpBreadcrumbs-p4jHe_V-.js} +9 -7
- package/dist/chunks/{LpCalendar-B7ZL0ty3.js → LpCalendar-B7Ko5RdR.js} +12 -7
- package/dist/chunks/{LpCard-D0M2ZCnB.js → LpCard-1SeUYPBT.js} +36 -9
- package/dist/chunks/{LpCheckbox-BO0zuuPh.js → LpCheckbox-CVccKYaH.js} +9 -4
- package/dist/chunks/{LpCodeBlock-BWX1KZwk.js → LpCodeBlock-Bo0B4TFR.js} +37 -32
- package/dist/chunks/{LpDatePicker-dY2q4dr4.js → LpDatePicker-6JFXllj5.js} +1 -1
- package/dist/chunks/{LpDisclosure-B4kVAWfg.js → LpDisclosure-y7wDBgLa.js} +11 -6
- package/dist/chunks/{LpEmptyState-BV3q7Qyi.js → LpEmptyState-Dsdqz1Wj.js} +13 -5
- package/dist/chunks/{LpFileTree-DfFqY__1.js → LpFileTree-C-NFRT0H.js} +19 -14
- package/dist/chunks/{LpFileTreeNode-COchUMNn.js → LpFileTreeNode-DNJUJiiB.js} +13 -6
- package/dist/chunks/{LpFormField-fV_2ZdCk.js → LpFormField-CazBc2-a.js} +14 -9
- package/dist/chunks/{LpInput-Bt4J71xn.js → LpInput-9EXWUM9B.js} +1 -1
- package/dist/chunks/{LpLightbox-Ci35Y_xP.js → LpLightbox-Cixfxezw.js} +29 -6
- package/dist/chunks/{LpLink-BHZ1CLlF.js → LpLink-DCND-zyu.js} +16 -9
- package/dist/chunks/{LpLogViewer-TSRyt3t9.js → LpLogViewer-y53ZqSVq.js} +20 -10
- package/dist/chunks/{LpMapPicker-CXvoTrAe.js → LpMapPicker-ul4oYno6.js} +14 -9
- package/dist/chunks/LpMediaGallery-t1z3bQcY.js +187 -0
- package/dist/chunks/{LpNotificationBell-gdvQY89m.js → LpNotificationBell-lHePf89W.js} +1 -1
- package/dist/chunks/{LpNumberField-NPE8Bumx.js → LpNumberField-CWuX08iX.js} +12 -5
- package/dist/chunks/{LpOtpInput-DqaT0Z75.js → LpOtpInput-y2v-pmiN.js} +11 -6
- package/dist/chunks/{LpPagination-CeGuNe_q.js → LpPagination-5FxjmmB5.js} +21 -19
- package/dist/chunks/{LpPasswordInput-BqMAc5HA.js → LpPasswordInput--sjYCkLS.js} +1 -1
- package/dist/chunks/{LpPhoneInput-NXXjciJJ.js → LpPhoneInput-BDSKVl_z.js} +16 -9
- package/dist/chunks/{LpPie-BL1vTDSM.js → LpPie-c2-q4DJu.js} +14 -8
- package/dist/chunks/{LpRadio-CL-pI0_O.js → LpRadio-B5AfqTpZ.js} +13 -5
- package/dist/chunks/{LpRadioGroup-B4yDTmi6.js → LpRadioGroup-xPIuVDO_.js} +12 -5
- package/dist/chunks/{LpSegmented-BmpanyAo.js → LpSegmented-fV0Sk92p.js} +16 -6
- package/dist/chunks/{LpSidebar-BiWM_lDB.js → LpSidebar-DcYavim2.js} +2 -2
- package/dist/chunks/{LpSidebarNav-CsgeuGAu.js → LpSidebarNav-CqwvT4H8.js} +21 -16
- package/dist/chunks/{LpSlider-B1SF3pRV.js → LpSlider-BwNIBkAK.js} +12 -5
- package/dist/chunks/{LpStat-CDanpzU0.js → LpStat-CQaW8R--.js} +23 -18
- package/dist/chunks/{LpStepper-Wtl-6nUh.js → LpStepper-Bk-XaQYU.js} +15 -10
- package/dist/chunks/{LpSwitch-v6fnccSM.js → LpSwitch-xgnRU2k0.js} +12 -5
- package/dist/chunks/{LpTextarea-BMIOfw5l.js → LpTextarea-t7F6gv8E.js} +1 -1
- package/dist/chunks/{LpUptimeBar-DcQ8HikG.js → LpUptimeBar-DvAMno0S.js} +26 -21
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +34 -17
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +65 -31
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.d.ts +12 -6
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpCalendar.vue.d.ts +15 -6
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.d.ts +26 -0
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +54 -12
- package/dist/components/LpChart.vue.js +15 -9
- package/dist/components/LpCheckbox.vue.d.ts +16 -12
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +25 -6
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.d.ts +24 -14
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDropzone.vue.d.ts +38 -14
- package/dist/components/LpDropzone.vue.js +17 -11
- package/dist/components/LpEmptyState.vue.d.ts +15 -10
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFileTree.vue.d.ts +51 -31
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.d.ts +31 -16
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +14 -13
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.d.ts +33 -10
- package/dist/components/LpImageEditor.vue.js +13 -7
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +46 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +63 -6
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +25 -10
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.d.ts +76 -0
- package/dist/components/LpMediaGallery.vue.js +4 -0
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +12 -6
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.d.ts +18 -6
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +16 -6
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.d.ts +18 -6
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.d.ts +96 -34
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpRadio.vue.d.ts +10 -10
- package/dist/components/LpRadio.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.d.ts +12 -12
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpSegmented.vue.d.ts +12 -6
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.d.ts +28 -17
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSlider.vue.d.ts +13 -6
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpStat.vue.d.ts +35 -11
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStepper.vue.d.ts +17 -5
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwitch.vue.d.ts +8 -6
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.js +1 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +21 -5
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/composables/useMergedClass.d.ts +20 -0
- package/dist/composables/useMergedClass.js +11 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +95 -89
- package/package.json +1 -3
- package/src/component-names.ts +1 -0
- package/src/components/LpAddressInput.vue +16 -1
- package/src/components/LpAppShell.vue +16 -1
- package/src/components/LpBreadcrumbs.vue +16 -1
- package/src/components/LpCalendar.vue +15 -1
- package/src/components/LpCard.vue +50 -5
- package/src/components/LpChart.vue +16 -1
- package/src/components/LpCheckbox.vue +16 -1
- package/src/components/LpCodeBlock.vue +16 -1
- package/src/components/LpDisclosure.vue +15 -1
- package/src/components/LpDropzone.vue +21 -6
- package/src/components/LpEmptyState.vue +21 -3
- package/src/components/LpFileTree.vue +16 -1
- package/src/components/LpFileTreeNode.vue +15 -1
- package/src/components/LpFormField.vue +16 -1
- package/src/components/LpImageEditor.vue +16 -1
- package/src/components/LpLightbox.vue +54 -2
- package/src/components/LpLink.vue +25 -9
- package/src/components/LpLogViewer.vue +20 -2
- package/src/components/LpMapPicker.vue +15 -1
- package/src/components/LpMediaGallery.vue +280 -0
- package/src/components/LpNumberField.vue +15 -1
- package/src/components/LpOtpInput.vue +15 -1
- package/src/components/LpPagination.vue +16 -1
- package/src/components/LpPhoneInput.vue +23 -8
- package/src/components/LpPie.vue +16 -1
- package/src/components/LpRadio.vue +20 -3
- package/src/components/LpRadioGroup.vue +15 -1
- package/src/components/LpSegmented.vue +21 -2
- package/src/components/LpSidebarNav.vue +16 -1
- package/src/components/LpSlider.vue +15 -1
- package/src/components/LpStat.vue +16 -1
- package/src/components/LpStepper.vue +16 -1
- package/src/components/LpSwitch.vue +15 -1
- package/src/components/LpUptimeBar.vue +16 -1
- package/src/composables/useMergedClass.ts +30 -0
- package/src/index.ts +2 -0
- package/src/tokens/tokens.css +29 -0
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own (below) rather than
|
|
3
|
+
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
|
+
// a second time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* Collapsible "show more" section built on reka Collapsible. The trigger is a
|
|
@@ -11,6 +18,7 @@
|
|
|
11
18
|
import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "reka-ui"
|
|
12
19
|
import { type ComponentPublicInstance, computed, getCurrentInstance, nextTick, ref } from "vue"
|
|
13
20
|
import LpIcon from "./LpIcon.vue"
|
|
21
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
14
22
|
|
|
15
23
|
const props = withDefaults(
|
|
16
24
|
defineProps<{
|
|
@@ -65,6 +73,11 @@ function onToggle(value: boolean) {
|
|
|
65
73
|
})
|
|
66
74
|
}
|
|
67
75
|
}
|
|
76
|
+
|
|
77
|
+
// The disclosure carries a bordered control frame; stacking several into one list means removing the per-item border from the outside.
|
|
78
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
79
|
+
"overflow-hidden rounded-control border border-line bg-surface-soft",
|
|
80
|
+
)
|
|
68
81
|
</script>
|
|
69
82
|
|
|
70
83
|
<template>
|
|
@@ -72,7 +85,8 @@ function onToggle(value: boolean) {
|
|
|
72
85
|
ref="root"
|
|
73
86
|
:open="rootOpen"
|
|
74
87
|
:default-open="defaultOpen"
|
|
75
|
-
class="
|
|
88
|
+
:class="rootClass"
|
|
89
|
+
v-bind="rest"
|
|
76
90
|
@update:open="onToggle"
|
|
77
91
|
>
|
|
78
92
|
<CollapsibleTrigger
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own (below) rather than
|
|
3
|
+
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
|
+
// a second time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* A place to put files: drop them, click to browse, or paste from the
|
|
@@ -17,6 +24,7 @@
|
|
|
17
24
|
* the file dialog unreachable without a mouse.
|
|
18
25
|
*/
|
|
19
26
|
import { onBeforeUnmount, onMounted, ref } from "vue"
|
|
27
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
20
28
|
import { useReveal } from "../composables/useReveal"
|
|
21
29
|
import LpIcon from "./LpIcon.vue"
|
|
22
30
|
|
|
@@ -173,6 +181,17 @@ onMounted(() => {
|
|
|
173
181
|
onBeforeUnmount(() => window.removeEventListener("paste", windowPaste))
|
|
174
182
|
|
|
175
183
|
defineExpose({ browse: () => input.value?.click() })
|
|
184
|
+
|
|
185
|
+
// The zone is a full-width block with generous padding by default, which is
|
|
186
|
+
// the right shape on a page and the wrong one in a sidebar — `class="w-40"`
|
|
187
|
+
// and `class="py-2"` have to be able to say so. The drag/reveal state classes
|
|
188
|
+
// go through the same merge so there is a single class source on the root.
|
|
189
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(() => [
|
|
190
|
+
"lp-dropzone relative flex w-full flex-col items-center gap-2 rounded-card px-6 py-8 text-center transition-colors duration-[var(--duration-fast)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-55",
|
|
191
|
+
over.value ? "bg-brand-soft" : TONES[props.tone],
|
|
192
|
+
over.value ? "lp-dropzone--over" : "",
|
|
193
|
+
props.animate && !revealed.value ? "lp-dropzone--waiting" : "",
|
|
194
|
+
].join(" "))
|
|
176
195
|
</script>
|
|
177
196
|
|
|
178
197
|
<template>
|
|
@@ -180,12 +199,8 @@ defineExpose({ browse: () => input.value?.click() })
|
|
|
180
199
|
ref="revealAnchor"
|
|
181
200
|
type="button"
|
|
182
201
|
:disabled="disabled"
|
|
183
|
-
class="
|
|
184
|
-
|
|
185
|
-
over ? 'bg-brand-soft' : TONES[tone],
|
|
186
|
-
over ? 'lp-dropzone--over' : '',
|
|
187
|
-
animate && !revealed ? 'lp-dropzone--waiting' : '',
|
|
188
|
-
]"
|
|
202
|
+
:class="rootClass"
|
|
203
|
+
v-bind="rest"
|
|
189
204
|
@click="input?.click()"
|
|
190
205
|
@paste="onPaste"
|
|
191
206
|
@dragenter.prevent="onDragOver"
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own rather than appended to
|
|
3
|
+
// it, so opt out of the automatic pass-through that would add it a second
|
|
4
|
+
// time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
import LpIcon from "./LpIcon.vue"
|
|
10
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
3
11
|
|
|
4
|
-
defineProps<{
|
|
12
|
+
const props = defineProps<{
|
|
5
13
|
icon?: string
|
|
6
14
|
title: string
|
|
7
15
|
description?: string
|
|
@@ -11,12 +19,22 @@ defineProps<{
|
|
|
11
19
|
*/
|
|
12
20
|
compact?: boolean
|
|
13
21
|
}>()
|
|
22
|
+
|
|
23
|
+
// The empty state pads itself generously, which is right on a page and wrong
|
|
24
|
+
// inside a small card — `class="py-2"` has to reach the root rather than lose
|
|
25
|
+
// a coin toss with stylesheet order.
|
|
26
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
27
|
+
[
|
|
28
|
+
"flex flex-col items-center px-6 text-center",
|
|
29
|
+
props.compact ? "gap-2 py-6" : "gap-3 py-12",
|
|
30
|
+
].join(" "),
|
|
31
|
+
)
|
|
14
32
|
</script>
|
|
15
33
|
|
|
16
34
|
<template>
|
|
17
35
|
<div
|
|
18
|
-
class="
|
|
19
|
-
|
|
36
|
+
:class="rootClass"
|
|
37
|
+
v-bind="rest"
|
|
20
38
|
>
|
|
21
39
|
<div
|
|
22
40
|
v-if="icon"
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own (below) rather than
|
|
3
|
+
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
|
+
// a second time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* File/folder tree — the kit convention for browsing a hierarchy of files so
|
|
@@ -30,6 +37,7 @@ import {
|
|
|
30
37
|
import LpEmptyState from "./LpEmptyState.vue"
|
|
31
38
|
import LpFileTreeNode from "./LpFileTreeNode.vue"
|
|
32
39
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
40
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
33
41
|
|
|
34
42
|
export type { FileNode } from "./fileTree"
|
|
35
43
|
|
|
@@ -476,10 +484,17 @@ function collapseAll() {
|
|
|
476
484
|
}
|
|
477
485
|
|
|
478
486
|
defineExpose({ reveal, expandAll, collapseAll, checkAll, clearChecked, summary })
|
|
487
|
+
|
|
488
|
+
// The tree fills its container; a consumer giving it a fixed height or width in a split pane must be able to say so.
|
|
489
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
490
|
+
"flex h-full w-full min-w-0 flex-col gap-1 overflow-hidden",
|
|
491
|
+
)
|
|
479
492
|
</script>
|
|
480
493
|
|
|
481
494
|
<template>
|
|
482
|
-
<div v-if="loading"
|
|
495
|
+
<div v-if="loading"
|
|
496
|
+
:class="rootClass"
|
|
497
|
+
v-bind="rest">
|
|
483
498
|
<div
|
|
484
499
|
v-for="n in skeletonRows"
|
|
485
500
|
:key="n"
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own (below) rather than
|
|
3
|
+
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
|
+
// a second time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* One row of LpFileTree plus its recursively rendered children. Split out so the
|
|
@@ -22,6 +29,7 @@ import {
|
|
|
22
29
|
import LpCheckbox from "./LpCheckbox.vue"
|
|
23
30
|
import LpContextMenu from "./LpContextMenu.vue"
|
|
24
31
|
import LpIcon from "./LpIcon.vue"
|
|
32
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
25
33
|
|
|
26
34
|
const props = defineProps<{
|
|
27
35
|
node: FileNode
|
|
@@ -146,12 +154,18 @@ function onActivate() {
|
|
|
146
154
|
emit("select", props.node)
|
|
147
155
|
if (isDir.value) emit("toggle", props.node)
|
|
148
156
|
}
|
|
157
|
+
|
|
158
|
+
// A node is a list row whose min-width the consumer may need to release when the tree scrolls horizontally.
|
|
159
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
160
|
+
"min-w-0",
|
|
161
|
+
)
|
|
149
162
|
</script>
|
|
150
163
|
|
|
151
164
|
<template>
|
|
152
165
|
<li
|
|
153
166
|
role="treeitem"
|
|
154
|
-
class="
|
|
167
|
+
:class="rootClass"
|
|
168
|
+
v-bind="rest"
|
|
155
169
|
:aria-level="depth + 1"
|
|
156
170
|
:aria-selected="isSelected"
|
|
157
171
|
:aria-expanded="canExpand ? isOpen : undefined"
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own (below) rather than
|
|
3
|
+
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
|
+
// a second time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* Form-control wrapper: label + the control (default slot) + hint/error.
|
|
@@ -11,6 +18,7 @@
|
|
|
11
18
|
import { Label } from "reka-ui"
|
|
12
19
|
import { useId } from "vue"
|
|
13
20
|
import LpShift from "./LpShift.vue"
|
|
21
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
14
22
|
|
|
15
23
|
defineProps<{
|
|
16
24
|
label?: string
|
|
@@ -20,6 +28,11 @@ defineProps<{
|
|
|
20
28
|
}>()
|
|
21
29
|
|
|
22
30
|
const id = useId()
|
|
31
|
+
|
|
32
|
+
// The field stacks label and control with a fixed gap; a denser form needs that gap smaller.
|
|
33
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
34
|
+
"flex min-w-0 flex-col gap-1.5",
|
|
35
|
+
)
|
|
23
36
|
</script>
|
|
24
37
|
|
|
25
38
|
<template>
|
|
@@ -28,7 +41,9 @@ const id = useId()
|
|
|
28
41
|
to `min-width: auto` and so refuse to go narrower than the widest label or
|
|
29
42
|
placeholder inside them — which is how a two-column form ended up wider
|
|
30
43
|
than the phone holding it. -->
|
|
31
|
-
<div
|
|
44
|
+
<div
|
|
45
|
+
:class="rootClass"
|
|
46
|
+
v-bind="rest">
|
|
32
47
|
<Label v-if="label" :for="id" class="text-sm font-medium text-ink">
|
|
33
48
|
{{ label }}
|
|
34
49
|
<span v-if="required" class="text-danger">*</span>
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own (below) rather than
|
|
3
|
+
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
|
+
// a second time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* Straighten-and-crop for a picture on its way to being uploaded.
|
|
@@ -23,6 +30,7 @@ import { useZoomPan } from "../composables/useZoomPan"
|
|
|
23
30
|
import LpButton from "./LpButton.vue"
|
|
24
31
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
25
32
|
import LpIcon from "./LpIcon.vue"
|
|
33
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
26
34
|
|
|
27
35
|
const props = withDefaults(
|
|
28
36
|
defineProps<{
|
|
@@ -364,10 +372,17 @@ const SHORTCUTS = [
|
|
|
364
372
|
] as const
|
|
365
373
|
|
|
366
374
|
defineExpose({ export: exportImage, reset, edited, shortcuts: SHORTCUTS })
|
|
375
|
+
|
|
376
|
+
// The editor lays out canvas and controls on a fixed gap, which a narrow panel has to tighten.
|
|
377
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
378
|
+
"flex flex-col gap-3",
|
|
379
|
+
)
|
|
367
380
|
</script>
|
|
368
381
|
|
|
369
382
|
<template>
|
|
370
|
-
<div ref="revealAnchor"
|
|
383
|
+
<div ref="revealAnchor"
|
|
384
|
+
:class="rootClass"
|
|
385
|
+
v-bind="rest">
|
|
371
386
|
<!-- The frame is the crop. The picture moves under it, which is the
|
|
372
387
|
interaction every avatar cropper already taught people. -->
|
|
373
388
|
<!-- Focusable, because everything here is otherwise a mouse gesture: a
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/*
|
|
3
3
|
* Full-screen image viewer — the Discord-style lightbox: click a thumbnail, the
|
|
4
4
|
* image fills the screen, arrows step through the set, the wheel zooms toward
|
|
5
|
-
* the cursor
|
|
5
|
+
* the cursor over the picture and pages through the set off it, dragging pans,
|
|
6
|
+
* and the backdrop or Esc closes it.
|
|
6
7
|
*
|
|
7
8
|
* Built on reka's Dialog so focus trapping, Esc and scroll locking come from the
|
|
8
9
|
* same primitive the rest of the kit's overlays use. The zoom/pan maths lives in
|
|
@@ -114,6 +115,57 @@ function go(next: number) {
|
|
|
114
115
|
const canPrev = computed(() => total.value > 1 && (props.loop || current.value > 0))
|
|
115
116
|
const canNext = computed(() => total.value > 1 && (props.loop || current.value < total.value - 1))
|
|
116
117
|
|
|
118
|
+
/*
|
|
119
|
+
* Wheel over the PICTURE zooms; wheel anywhere else in the stage pages.
|
|
120
|
+
*
|
|
121
|
+
* The stage is mostly empty space — an image is letterboxed inside it, so the
|
|
122
|
+
* bands above and below a wide photo are a large part of what the pointer is
|
|
123
|
+
* actually over. Zooming there is a gesture aimed at nothing, while paging is
|
|
124
|
+
* what a set of images invites. Over the image itself the old behaviour stands:
|
|
125
|
+
* that is where "look closer" is unambiguous.
|
|
126
|
+
*
|
|
127
|
+
* A zoomed-in image is the exception. Once it is scaled up, the wheel is the
|
|
128
|
+
* only way back out, and the picture no longer fits its own box — so while
|
|
129
|
+
* zoomed, the whole stage zooms rather than stranding the viewer at 4x if they
|
|
130
|
+
* drift off the edge.
|
|
131
|
+
*/
|
|
132
|
+
const wheelAccum = ref(0)
|
|
133
|
+
let wheelTimer: ReturnType<typeof setTimeout> | undefined
|
|
134
|
+
|
|
135
|
+
/* A mouse notch arrives as one large delta; a trackpad sends a stream of small
|
|
136
|
+
* ones. Stepping per event would fling a flick through the whole set, so the
|
|
137
|
+
* deltas accumulate and a step is taken per threshold's worth of travel. */
|
|
138
|
+
const WHEEL_STEP = 60
|
|
139
|
+
|
|
140
|
+
function onStageWheel(e: WheelEvent) {
|
|
141
|
+
const overImage = (e.target as HTMLElement | null)?.closest("[data-lightbox-image]")
|
|
142
|
+
if (overImage || isZoomed.value) {
|
|
143
|
+
zoom.onWheel(e)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
if (total.value < 2) return
|
|
147
|
+
|
|
148
|
+
// The stage does not scroll, so this only stops the PAGE behind the dialog
|
|
149
|
+
// from taking the gesture.
|
|
150
|
+
e.preventDefault()
|
|
151
|
+
|
|
152
|
+
wheelAccum.value += e.deltaY
|
|
153
|
+
// Idle between gestures clears the remainder: a half-step left over from one
|
|
154
|
+
// flick must not add itself to the next one minutes later.
|
|
155
|
+
clearTimeout(wheelTimer)
|
|
156
|
+
wheelTimer = setTimeout(() => (wheelAccum.value = 0), 200)
|
|
157
|
+
|
|
158
|
+
while (Math.abs(wheelAccum.value) >= WHEEL_STEP) {
|
|
159
|
+
const dir = wheelAccum.value > 0 ? 1 : -1
|
|
160
|
+
wheelAccum.value -= dir * WHEEL_STEP
|
|
161
|
+
if (dir > 0 ? !canNext.value : !canPrev.value) {
|
|
162
|
+
wheelAccum.value = 0
|
|
163
|
+
break
|
|
164
|
+
}
|
|
165
|
+
go(current.value + dir)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
117
169
|
// Reset the view whenever the image changes — carrying a 4x zoom onto the next
|
|
118
170
|
// picture means landing on a random crop of it.
|
|
119
171
|
watch(
|
|
@@ -336,7 +388,7 @@ const ARROW =
|
|
|
336
388
|
ref="viewport"
|
|
337
389
|
class="relative flex min-h-0 flex-1 items-center justify-center overflow-hidden"
|
|
338
390
|
:class="isPanning ? 'cursor-grabbing' : isZoomed ? 'cursor-grab' : ''"
|
|
339
|
-
@wheel="
|
|
391
|
+
@wheel="onStageWheel"
|
|
340
392
|
@pointerdown="onPointerDown"
|
|
341
393
|
@pointermove="zoom.onPointerMove"
|
|
342
394
|
@pointerup="onPointerUp"
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own rather than appended to
|
|
3
|
+
// it, so opt out of the automatic pass-through that would add it a second
|
|
4
|
+
// time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
import { computed, type Component } from "vue"
|
|
10
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
3
11
|
|
|
4
12
|
const props = withDefaults(
|
|
5
13
|
defineProps<{
|
|
@@ -94,6 +102,21 @@ const REVEAL_TRIGGER = {
|
|
|
94
102
|
const revealClass = computed(
|
|
95
103
|
() => `${REVEAL_BASE} ${REVEAL_TRIGGER[props.revealOn]}`,
|
|
96
104
|
)
|
|
105
|
+
|
|
106
|
+
// A link is inline text: the gap to its icon and the underline treatment are
|
|
107
|
+
// the parts a consumer restyles, and `class="gap-0"` or `class="no-underline"`
|
|
108
|
+
// must beat the default rather than depend on stylesheet order.
|
|
109
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
110
|
+
[
|
|
111
|
+
"inline-flex items-center gap-1 underline-offset-2 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring",
|
|
112
|
+
colorClass.value,
|
|
113
|
+
props.variant === "reveal"
|
|
114
|
+
? revealClass.value
|
|
115
|
+
: props.variant === "underline"
|
|
116
|
+
? "underline decoration-current/40 hover:decoration-current"
|
|
117
|
+
: "hover:underline",
|
|
118
|
+
].join(" "),
|
|
119
|
+
)
|
|
97
120
|
</script>
|
|
98
121
|
|
|
99
122
|
<template>
|
|
@@ -102,15 +125,8 @@ const revealClass = computed(
|
|
|
102
125
|
:href="isAnchor ? href : undefined"
|
|
103
126
|
:target="target"
|
|
104
127
|
:rel="rel"
|
|
105
|
-
class="
|
|
106
|
-
|
|
107
|
-
colorClass,
|
|
108
|
-
variant === 'reveal'
|
|
109
|
-
? revealClass
|
|
110
|
-
: variant === 'underline'
|
|
111
|
-
? 'underline decoration-current/40 hover:decoration-current'
|
|
112
|
-
: 'hover:underline',
|
|
113
|
-
]"
|
|
128
|
+
:class="rootClass"
|
|
129
|
+
v-bind="rest"
|
|
114
130
|
>
|
|
115
131
|
<slot />
|
|
116
132
|
</component>
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own rather than appended to
|
|
3
|
+
// it, so opt out of the automatic pass-through that would add it a second
|
|
4
|
+
// time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* Terminal-flavoured log stream. Monospace rows, a tonal level gutter, optional
|
|
@@ -23,6 +30,7 @@ import { useHotkeys } from "../composables/useHotkeys"
|
|
|
23
30
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
24
31
|
import LpIcon from "./LpIcon.vue"
|
|
25
32
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
33
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
26
34
|
|
|
27
35
|
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "success"
|
|
28
36
|
|
|
@@ -632,14 +640,24 @@ const transitionProps = {
|
|
|
632
640
|
"leave-to-class": "max-h-0 -translate-y-1 opacity-0",
|
|
633
641
|
"move-class": "transition-transform duration-200 ease-[var(--ease-emphasized)]",
|
|
634
642
|
}
|
|
643
|
+
|
|
644
|
+
// The viewer draws its own card frame and hides its overflow; a consumer
|
|
645
|
+
// fitting it into a panel that already has a border, or giving it a fixed
|
|
646
|
+
// height, needs those to give way rather than lose to stylesheet order.
|
|
647
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
648
|
+
[
|
|
649
|
+
"relative overflow-hidden rounded-card border border-line bg-surface font-mono text-xs leading-relaxed",
|
|
650
|
+
frozen.value ? "ring-1 ring-brand" : "",
|
|
651
|
+
].join(" "),
|
|
652
|
+
)
|
|
635
653
|
</script>
|
|
636
654
|
|
|
637
655
|
<template>
|
|
638
656
|
<div
|
|
639
657
|
ref="root"
|
|
640
658
|
tabindex="-1"
|
|
641
|
-
class="
|
|
642
|
-
|
|
659
|
+
:class="rootClass"
|
|
660
|
+
v-bind="rest"
|
|
643
661
|
@pointerenter="hovered = true"
|
|
644
662
|
@pointerleave="hovered = false"
|
|
645
663
|
>
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own (below) rather than
|
|
3
|
+
// appended to it, so opt out of the automatic pass-through that would add it
|
|
4
|
+
// a second time, unmerged.
|
|
5
|
+
export default { inheritAttrs: false }
|
|
6
|
+
</script>
|
|
7
|
+
|
|
1
8
|
<script setup lang="ts">
|
|
2
9
|
/*
|
|
3
10
|
* Point picker on a raster tile map — drag to pan, wheel or the buttons to
|
|
@@ -17,6 +24,7 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"
|
|
|
17
24
|
import { prefersReducedMotion } from "../composables/easing"
|
|
18
25
|
import { type LatLon, OSM_TILES, type TileSource, project, tileUrl, unproject } from "./address"
|
|
19
26
|
import LpIcon from "./LpIcon.vue"
|
|
27
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
20
28
|
|
|
21
29
|
const props = withDefaults(
|
|
22
30
|
defineProps<{
|
|
@@ -536,11 +544,17 @@ defineExpose({ zoomIn: () => zoomBy(1), zoomOut: () => zoomBy(-1), recentre, loc
|
|
|
536
544
|
|
|
537
545
|
const CONTROL =
|
|
538
546
|
"flex size-8 items-center justify-center rounded-control bg-black/45 text-white/90 outline-none backdrop-blur-sm transition-[background-color,scale] duration-[var(--duration-fast)] hover:bg-black/65 hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40"
|
|
547
|
+
|
|
548
|
+
// The picker is a bordered box with a fixed frame; a consumer fitting it into a panel of their own needs the border and radius to be overridable.
|
|
549
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
550
|
+
"relative overflow-hidden rounded-control border border-line bg-surface-soft",
|
|
551
|
+
)
|
|
539
552
|
</script>
|
|
540
553
|
|
|
541
554
|
<template>
|
|
542
555
|
<div
|
|
543
|
-
class="
|
|
556
|
+
:class="rootClass"
|
|
557
|
+
v-bind="rest"
|
|
544
558
|
:style="{ height }"
|
|
545
559
|
>
|
|
546
560
|
<div
|