@leavepulse/ui 0.73.0 → 0.73.4

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.
Files changed (144) hide show
  1. package/dist/chunks/{DropzoneLinkField-C-9BCGJn.js → DropzoneLinkField-ppY9xalk.js} +2 -2
  2. package/dist/chunks/{FieldWrapper-CORPzo9s.js → FieldWrapper-y5bKBNxA.js} +1 -1
  3. package/dist/chunks/{LpActionBar-BP1ld7-S.js → LpActionBar-B55msXTr.js} +2 -2
  4. package/dist/chunks/{LpAddressInput-Al3HT5ey.js → LpAddressInput-mf_dEpj_.js} +3 -3
  5. package/dist/chunks/{LpAlert-n5XPAZfv.js → LpAlert-Cg7oIx1B.js} +2 -2
  6. package/dist/chunks/{LpAppShell-rr5kqiK3.js → LpAppShell-B0Yu9MVs.js} +4 -4
  7. package/dist/chunks/{LpBadge-wUYLN4U9.js → LpBadge-0A6sPks3.js} +1 -1
  8. package/dist/chunks/{LpBottomNav-CbNQQPuL.js → LpBottomNav-Dik-ZcHv.js} +1 -1
  9. package/dist/chunks/{LpBreadcrumbs-DPdbth-c.js → LpBreadcrumbs-ssZV3_od.js} +1 -1
  10. package/dist/chunks/{LpButton-CFb44IjC.js → LpButton-BgdewXxF.js} +1 -1
  11. package/dist/chunks/{LpCard-r13WRqox.js → LpCard-BJ59q6hR.js} +1 -1
  12. package/dist/chunks/{LpCheckbox-Cs4SHaHX.js → LpCheckbox-B0kJ-t6T.js} +1 -1
  13. package/dist/chunks/{LpCodeBlock-DRC8VbXW.js → LpCodeBlock-CXR_7t_F.js} +1 -1
  14. package/dist/chunks/{LpCodeDisplay-CE9TxCA4.js → LpCodeDisplay-BCghfBFC.js} +4 -4
  15. package/dist/chunks/{LpConfirmDialog-DY2VjDji.js → LpConfirmDialog-CaA4pAre.js} +1 -1
  16. package/dist/chunks/{LpConfirmHost-DvSZTJ4N.js → LpConfirmHost-CBIzALSL.js} +1 -1
  17. package/dist/chunks/{LpCopyButton-xF7wFVoU.js → LpCopyButton-Cw-lF7Q3.js} +1 -1
  18. package/dist/chunks/{LpCountdown-DgeY3cLG.js → LpCountdown-BNNhnQAK.js} +1 -1
  19. package/dist/chunks/{LpDataList-Jxyl9H5w.js → LpDataList-CcAA-rD9.js} +1 -1
  20. package/dist/chunks/{LpEditable-Cy6GLbFx.js → LpEditable-CSuJIPbG.js} +2 -2
  21. package/dist/chunks/{LpFileTree-zIFEgo3I.js → LpFileTree-CxOh3rL2.js} +1 -1
  22. package/dist/chunks/{LpFileTreeNode-cT6BIWvz.js → LpFileTreeNode-9ue2P7ER.js} +3 -3
  23. package/dist/chunks/{LpFormField-Ch69Jd-X.js → LpFormField-BBFg8i2I.js} +1 -1
  24. package/dist/chunks/{LpHintLayer-CgObdUsM.js → LpHintLayer-Db_s9_DL.js} +2 -2
  25. package/dist/chunks/{LpHints-Bz-BlaSl.js → LpHints-Cxf5GXsx.js} +3 -3
  26. package/dist/chunks/{LpInfraNode-46XOCkfq.js → LpInfraNode-6NLxkAnr.js} +2 -2
  27. package/dist/chunks/{LpInput-Tw-yxT4I.js → LpInput-B2dTt4pc.js} +1 -1
  28. package/dist/chunks/{LpLightbox-B95yu22B.js → LpLightbox-Coe4OBUl.js} +2 -2
  29. package/dist/chunks/{LpLoader-DVwkOmt9.js → LpLoader-DPTNhqLu.js} +1 -1
  30. package/dist/chunks/{LpLogViewer-D25jM2_I.js → LpLogViewer-DxiR6Mbd.js} +3 -3
  31. package/dist/chunks/{LpMapPicker-DRGgh89B.js → LpMapPicker-DeJRMfFN.js} +1 -1
  32. package/dist/chunks/{LpMediaGallery-Bkram-MO.js → LpMediaGallery-B7Km8N9l.js} +1 -1
  33. package/dist/chunks/{LpModalNav-DQpV2kdn.js → LpModalNav-BmxEyqOx.js} +2 -2
  34. package/dist/chunks/{LpNotificationBell-3244LUVc.js → LpNotificationBell-u8-ZjRON.js} +3 -3
  35. package/dist/chunks/{LpPaletteTheme-DiLYKQiZ.js → LpPaletteTheme-CWAIjBCi.js} +3 -3
  36. package/dist/chunks/{LpPasswordInput-BkHKQDhM.js → LpPasswordInput-Ih7Lv43S.js} +2 -2
  37. package/dist/chunks/{LpPie-DrZepi0p.js → LpPie-Idujdb3M.js} +2 -2
  38. package/dist/chunks/{LpPipeline-B3aOVOkV.js → LpPipeline-QCZWWB0G.js} +2 -2
  39. package/dist/chunks/{LpRepeater-D7ZmnIYd.js → LpRepeater-DghUJ3bc.js} +2 -2
  40. package/dist/chunks/{LpScramble-Bn9OoOtC.js → LpScramble-DeT_D36q.js} +1 -1
  41. package/dist/chunks/{LpSecretField-BbmqM9ye.js → LpSecretField-osKPNdNT.js} +6 -6
  42. package/dist/chunks/{LpShift-CCq7dofl.js → LpShift-kKUeG9NS.js} +4 -2
  43. package/dist/chunks/{LpSidebar-BoNLKgh-.js → LpSidebar-CR_WKcn2.js} +2 -2
  44. package/dist/chunks/{LpSidebarNav-Bky8chrv.js → LpSidebarNav-h3YunOAr.js} +2 -2
  45. package/dist/chunks/{LpSidebarRow-BRYhNDSo.js → LpSidebarRow-SjV0Ouhq.js} +1 -1
  46. package/dist/chunks/{LpStat-BVf2UvEc.js → LpStat-DXqKD1h5.js} +34 -18
  47. package/dist/chunks/{LpStatus-DglxttQ7.js → LpStatus-DfZREVe0.js} +2 -2
  48. package/dist/chunks/{LpStepMark-DpzKVyh8.js → LpStepMark-TaAyuNsH.js} +1 -1
  49. package/dist/chunks/{LpStepper-DpsocBNJ.js → LpStepper-CKSTFzHE.js} +1 -1
  50. package/dist/chunks/{LpSwap-C96c0LWh.js → LpSwap-KIS24N8W.js} +1 -1
  51. package/dist/chunks/{LpSwitch-BnXd1llc.js → LpSwitch-DlZHmj-M.js} +1 -1
  52. package/dist/chunks/{LpTabFace-DPCwKkH2.js → LpTabFace-JFJLW0kA.js} +1 -1
  53. package/dist/chunks/{LpTableFilter-CiVBpaq0.js → LpTableFilter-47ANnx_a.js} +3 -3
  54. package/dist/chunks/{LpTagsInput-C-mDG648.js → LpTagsInput-CHoiid8u.js} +2 -2
  55. package/dist/chunks/{LpTextarea-TE-yZv2U.js → LpTextarea-CU7C1pLO.js} +1 -1
  56. package/dist/chunks/{LpThemeStudio-BpORDvH6.js → LpThemeStudio-zyOH3dF2.js} +2 -2
  57. package/dist/chunks/{LpThemeSwitcher-BnEn_mvf.js → LpThemeSwitcher-BXJhQYGD.js} +2 -2
  58. package/dist/chunks/{LpToaster-IwTXjbbU.js → LpToaster-C1D35SKk.js} +1 -1
  59. package/dist/chunks/{LpTopologyCanvas-B1gIbS0n.js → LpTopologyCanvas-1OufbBtQ.js} +1 -1
  60. package/dist/chunks/{LpTour-BZq-mUMv.js → LpTour-XshJslGg.js} +1 -1
  61. package/dist/chunks/{LpTruncate-CflAIwiJ.js → LpTruncate-qgKA-3K3.js} +1 -1
  62. package/dist/chunks/{LpUptimeBar-DqNvvMBK.js → LpUptimeBar-qhvvmmoI.js} +2 -2
  63. package/dist/chunks/{RevealToggle-BEit1n6Z.js → RevealToggle-6MlmohxS.js} +1 -1
  64. package/dist/chunks/{TourCard-DNclH3u_.js → TourCard-BuB-i4kv.js} +3 -3
  65. package/dist/components/DropzoneLinkField.vue.js +1 -1
  66. package/dist/components/FieldWrapper.vue.js +1 -1
  67. package/dist/components/LpActionBar.vue.js +1 -1
  68. package/dist/components/LpAddressInput.vue.js +1 -1
  69. package/dist/components/LpAlert.vue.js +1 -1
  70. package/dist/components/LpAppShell.vue.js +1 -1
  71. package/dist/components/LpBadge.vue.js +1 -1
  72. package/dist/components/LpBottomNav.vue.js +1 -1
  73. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  74. package/dist/components/LpButton.vue.js +1 -1
  75. package/dist/components/LpCard.vue.js +1 -1
  76. package/dist/components/LpChart.vue.js +2 -2
  77. package/dist/components/LpCheckbox.vue.js +1 -1
  78. package/dist/components/LpCodeBlock.vue.js +1 -1
  79. package/dist/components/LpCodeDisplay.vue.js +1 -1
  80. package/dist/components/LpCommandPalette.vue.js +2 -2
  81. package/dist/components/LpConfirmDialog.vue.js +1 -1
  82. package/dist/components/LpConfirmHost.vue.js +1 -1
  83. package/dist/components/LpCopyButton.vue.js +1 -1
  84. package/dist/components/LpCountdown.vue.js +1 -1
  85. package/dist/components/LpDataList.vue.js +1 -1
  86. package/dist/components/LpDropzone.vue.js +1 -1
  87. package/dist/components/LpEditable.vue.js +1 -1
  88. package/dist/components/LpFileTree.vue.js +1 -1
  89. package/dist/components/LpFileTreeNode.vue.js +1 -1
  90. package/dist/components/LpFormField.vue.js +1 -1
  91. package/dist/components/LpHintLayer.vue.js +1 -1
  92. package/dist/components/LpHints.vue.js +1 -1
  93. package/dist/components/LpImageEditor.vue.js +1 -1
  94. package/dist/components/LpInfraNode.vue.js +1 -1
  95. package/dist/components/LpInput.vue.js +1 -1
  96. package/dist/components/LpLightbox.vue.js +1 -1
  97. package/dist/components/LpLoader.vue.js +1 -1
  98. package/dist/components/LpLogViewer.vue.js +1 -1
  99. package/dist/components/LpMapPicker.vue.js +1 -1
  100. package/dist/components/LpMediaGallery.vue.js +1 -1
  101. package/dist/components/LpModalNav.vue.js +1 -1
  102. package/dist/components/LpNotificationBell.vue.js +1 -1
  103. package/dist/components/LpPaletteTheme.vue.js +1 -1
  104. package/dist/components/LpPasswordInput.vue.js +1 -1
  105. package/dist/components/LpPie.vue.js +1 -1
  106. package/dist/components/LpPipeline.vue.js +1 -1
  107. package/dist/components/LpRepeater.vue.js +1 -1
  108. package/dist/components/LpScramble.vue.js +1 -1
  109. package/dist/components/LpSecretField.vue.js +1 -1
  110. package/dist/components/LpShift.vue.js +1 -1
  111. package/dist/components/LpSidebar.vue.js +1 -1
  112. package/dist/components/LpSidebarNav.vue.js +1 -1
  113. package/dist/components/LpSidebarRow.vue.js +1 -1
  114. package/dist/components/LpStat.vue.js +1 -1
  115. package/dist/components/LpStatus.vue.js +1 -1
  116. package/dist/components/LpStepMark.vue.js +1 -1
  117. package/dist/components/LpStepper.vue.js +1 -1
  118. package/dist/components/LpSwap.vue.js +1 -1
  119. package/dist/components/LpSwitch.vue.js +1 -1
  120. package/dist/components/LpTabFace.vue.js +1 -1
  121. package/dist/components/LpTable.vue.js +6 -6
  122. package/dist/components/LpTableFilter.vue.js +1 -1
  123. package/dist/components/LpTabs.vue.js +1 -1
  124. package/dist/components/LpTagsInput.vue.js +1 -1
  125. package/dist/components/LpTextarea.vue.js +1 -1
  126. package/dist/components/LpThemeStudio.vue.js +1 -1
  127. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  128. package/dist/components/LpToaster.vue.js +1 -1
  129. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  130. package/dist/components/LpTour.vue.js +1 -1
  131. package/dist/components/LpTruncate.vue.js +1 -1
  132. package/dist/components/LpUptimeBar.vue.js +1 -1
  133. package/dist/components/RevealToggle.vue.js +1 -1
  134. package/dist/components/TourCard.vue.js +1 -1
  135. package/dist/composables/useFitScale.d.ts +5 -0
  136. package/dist/composables/useFitScale.js +30 -0
  137. package/dist/composables/useShift.d.ts +2 -0
  138. package/dist/composables/useShift.js +5 -0
  139. package/dist/index.js +55 -55
  140. package/package.json +1 -1
  141. package/src/components/LpShift.vue +4 -2
  142. package/src/components/LpStat.vue +24 -3
  143. package/src/composables/useFitScale.ts +43 -0
  144. package/src/composables/useShift.ts +10 -0
@@ -8,11 +8,12 @@ export default { inheritAttrs: false }
8
8
  <script setup lang="ts">
9
9
  // Stat tile: dot + caps label, big mono value, optional delta/trend badge and a
10
10
  // subtitle. A product pattern, fully token-driven — re-skins with the theme.
11
- import { computed } from "vue"
11
+ import { computed, ref } from "vue"
12
12
  import LpIcon from "./LpIcon.vue"
13
13
  import LpNumberFlow from "./LpNumberFlow.vue"
14
14
  import LpSkeleton from "./LpSkeleton.vue"
15
15
  import LpStatus from "./LpStatus.vue"
16
+ import { useFitScale } from "../composables/useFitScale"
16
17
  import { useMergedAttrs } from "../composables/useMergedClass"
17
18
  import { TONES } from "./tone"
18
19
 
@@ -124,6 +125,20 @@ const FADE = {
124
125
  leaveToClass: "opacity-0",
125
126
  } as const
126
127
 
128
+ /*
129
+ * The figure never wraps, so on a narrow tile a long one ("200 h 23 min", a
130
+ * word instead of a number) ran past the edge and was cut mid-letter. It now
131
+ * shrinks to the tile's width, down to `FIT_MIN` of its size, and only past
132
+ * that is it cut, with an ellipsis and the whole value on hover. The line box
133
+ * keeps its height, so a row of tiles stays level whichever of them shrank.
134
+ */
135
+ const FIT_MIN = 0.6
136
+ const row = ref<HTMLElement | null>(null)
137
+ const figure = ref<HTMLElement | null>(null)
138
+ const fit = useFitScale(row, figure, FIT_MIN)
139
+ const figureStyle = computed(() => (fit.scale.value < 1 ? { fontSize: `calc(var(--text-3xl, 1.875rem) * ${fit.scale.value})`, lineHeight: "calc(var(--text-3xl, 1.875rem) * var(--text-3xl--line-height, 1.2))" } : undefined))
140
+ const figureTitle = computed(() => (fit.clipped.value && !props.loading ? `${props.prefix ?? ""}${props.value}${props.valueSuffix ?? ""}` : undefined))
141
+
127
142
  // A stat tile spaces its label and value on a fixed gap; a dense KPI row needs that tighter.
128
143
  const { class: rootClass, attrs: rest } = useMergedAttrs(
129
144
  "flex flex-col gap-2",
@@ -148,10 +163,16 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
148
163
  </div>
149
164
  <!-- A narrow tile moves the delta under the figure rather than breaking the
150
165
  figure itself: "2085 h" split across two lines reads as two values. -->
151
- <div class="flex flex-wrap items-baseline gap-x-2">
166
+ <div ref="row" class="flex min-w-0 flex-wrap items-baseline gap-x-2">
152
167
  <!-- The placeholder sits INSIDE the value's own type size, so its line box
153
168
  is the real one: the tile cannot change height when the figure lands. -->
154
- <span class="whitespace-nowrap font-mono text-3xl font-medium tracking-tight text-ink">
169
+ <span
170
+ ref="figure"
171
+ class="max-w-full whitespace-nowrap font-mono text-3xl font-medium tracking-tight text-ink"
172
+ :class="fit.clipped.value ? 'inline-block overflow-hidden text-ellipsis align-bottom' : ''"
173
+ :style="figureStyle"
174
+ :title="figureTitle"
175
+ >
155
176
  <!-- Each side wrapped in a plain element: LpNumberFlow renders more
156
177
  than one root node, which a Transition cannot fade. -->
157
178
  <Transition v-bind="FADE">
@@ -0,0 +1,43 @@
1
+ import { onBeforeUnmount, onMounted, ref, watch, type Ref } from "vue"
2
+
3
+ /*
4
+ * How much a line that must not wrap has to shrink to fit its box. Text width
5
+ * grows in step with the font size, so the width the content would have at
6
+ * full size is its current width over the current scale, and one measurement
7
+ * gives the answer. The scale stops at `min`: past it the text would be too
8
+ * small to read, and `clipped` says the caller should cut it instead.
9
+ */
10
+ export function useFitScale(
11
+ box: Ref<HTMLElement | null | undefined>,
12
+ content: Ref<HTMLElement | null | undefined>,
13
+ min = 0.6,
14
+ ) {
15
+ const scale = ref(1)
16
+ const clipped = ref(false)
17
+ let observer: ResizeObserver | undefined
18
+
19
+ function measure() {
20
+ const room = box.value?.clientWidth ?? 0
21
+ const width = content.value?.scrollWidth ?? 0
22
+ if (!room || !width) return
23
+ const natural = width / scale.value
24
+ const next = Math.max(min, Math.min(1, Math.floor((room / natural) * 100) / 100))
25
+ if (next !== scale.value) scale.value = next
26
+ clipped.value = natural * next > room + 0.5
27
+ }
28
+
29
+ function observe() {
30
+ observer?.disconnect()
31
+ if (typeof ResizeObserver === "undefined") return
32
+ observer = new ResizeObserver(measure)
33
+ if (box.value) observer.observe(box.value)
34
+ if (content.value) observer.observe(content.value)
35
+ measure()
36
+ }
37
+
38
+ onMounted(observe)
39
+ watch([box, content], observe, { flush: "post" })
40
+ onBeforeUnmount(() => observer?.disconnect())
41
+
42
+ return { scale, clipped }
43
+ }
@@ -62,6 +62,9 @@ export interface UseShift {
62
62
  retune: () => void
63
63
  }
64
64
 
65
+ /** Set on the box while it eases its width: where that width will land. */
66
+ export const TARGET_WIDTH = "--lp-shift-to-w"
67
+
65
68
  /** Kept in step with the `duration-fast` token the transition utilities use. */
66
69
  const TWEEN_MS = 160
67
70
 
@@ -190,6 +193,8 @@ export function useShift(
190
193
  if (wantsWidth()) node.style.width = ""
191
194
  const toHeight = wantsHeight() ? node.offsetHeight : 0
192
195
  const toWidth = wantsWidth() ? node.offsetWidth : 0
196
+ // offsetWidth is rounded, and content that needs 406.4px wraps at 406.
197
+ const exactWidth = wantsWidth() ? Math.ceil(node.getBoundingClientRect().width) : 0
193
198
  restoreScroll(tops)
194
199
  lastHeight = toHeight
195
200
  lastWidth = toWidth
@@ -215,6 +220,10 @@ export function useShift(
215
220
  void node.offsetHeight
216
221
  if (movesHeight) node.style.height = `${toHeight}px`
217
222
  if (movesWidth) node.style.width = `${toWidth}px`
223
+ // The width the box is heading for, for content that would otherwise lay
224
+ // itself out at every in-between width: a wrapping row inside a widening
225
+ // box broke onto a second line for the length of the tween.
226
+ if (movesWidth) node.style.setProperty(TARGET_WIDTH, `${exactWidth}px`)
218
227
  restoreScroll(tops)
219
228
 
220
229
  inFlight = true
@@ -233,6 +242,7 @@ export function useShift(
233
242
  // clearing it let the box spread to the window's edges.
234
243
  if (wantsHeight()) node.style.height = ""
235
244
  if (wantsWidth()) node.style.width = ""
245
+ node.style.removeProperty(TARGET_WIDTH)
236
246
  // Re-read rather than trusting the target: the layout around may have
237
247
  // moved meanwhile, and the next tween has to start from where it sits.
238
248
  lastHeight = node.offsetHeight