@leavepulse/ui 0.72.3 → 0.72.5

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 (143) hide show
  1. package/dist/chunks/{DropzoneLinkField-CvyzXHdc.js → DropzoneLinkField-C-9BCGJn.js} +2 -2
  2. package/dist/chunks/{FieldWrapper-DeOZzjBM.js → FieldWrapper-CORPzo9s.js} +1 -1
  3. package/dist/chunks/{LpActionBar-DZP0IpXS.js → LpActionBar-BP1ld7-S.js} +2 -2
  4. package/dist/chunks/{LpAddressInput-q8Vu3M8z.js → LpAddressInput-Al3HT5ey.js} +3 -3
  5. package/dist/chunks/{LpAlert-CGeYc3fe.js → LpAlert-n5XPAZfv.js} +2 -2
  6. package/dist/chunks/{LpAppShell-oB-QWu74.js → LpAppShell-DMSJg18Q.js} +26 -15
  7. package/dist/chunks/{LpBadge-BVDOfrqI.js → LpBadge-wUYLN4U9.js} +1 -1
  8. package/dist/chunks/{LpBottomNav-w_5LKno0.js → LpBottomNav-CbNQQPuL.js} +1 -1
  9. package/dist/chunks/{LpBreadcrumbs-DJvcYVPM.js → LpBreadcrumbs-DPdbth-c.js} +1 -1
  10. package/dist/chunks/{LpButton-Cse4COe7.js → LpButton-CFb44IjC.js} +1 -1
  11. package/dist/chunks/{LpCard-BoB-UqzE.js → LpCard-r13WRqox.js} +1 -1
  12. package/dist/chunks/{LpCheckbox-B6lfW2GC.js → LpCheckbox-Cs4SHaHX.js} +1 -1
  13. package/dist/chunks/{LpCodeBlock-BjptdRIt.js → LpCodeBlock-DRC8VbXW.js} +1 -1
  14. package/dist/chunks/{LpCodeDisplay-BkVeLcLM.js → LpCodeDisplay-AQx-3nNb.js} +4 -4
  15. package/dist/chunks/{LpConfirmDialog-DqsrdJSK.js → LpConfirmDialog-DY2VjDji.js} +1 -1
  16. package/dist/chunks/{LpConfirmHost-BT8iDrWb.js → LpConfirmHost-DvSZTJ4N.js} +1 -1
  17. package/dist/chunks/{LpCopyButton-DhptBlXE.js → LpCopyButton-xF7wFVoU.js} +1 -1
  18. package/dist/chunks/{LpCountdown-9QnxUFXk.js → LpCountdown-BxE7stYk.js} +1 -1
  19. package/dist/chunks/{LpDataList-_D19wjXa.js → LpDataList-Jxyl9H5w.js} +1 -1
  20. package/dist/chunks/{LpEditable-BdjKjOMG.js → LpEditable-Cy6GLbFx.js} +2 -2
  21. package/dist/chunks/{LpFileTree-Cnw35H2X.js → LpFileTree-zIFEgo3I.js} +1 -1
  22. package/dist/chunks/{LpFileTreeNode-DR3YPYFb.js → LpFileTreeNode-cT6BIWvz.js} +3 -3
  23. package/dist/chunks/{LpFormField-CjnOXjFq.js → LpFormField-Ch69Jd-X.js} +1 -1
  24. package/dist/chunks/{LpHintLayer-DWWn7Xm2.js → LpHintLayer-CgObdUsM.js} +2 -2
  25. package/dist/chunks/{LpHints-Ctl4p_tS.js → LpHints-Bz-BlaSl.js} +3 -3
  26. package/dist/chunks/{LpInfraNode-CZrDvfnL.js → LpInfraNode-46XOCkfq.js} +2 -2
  27. package/dist/chunks/{LpInput-D9uwTIqP.js → LpInput-Tw-yxT4I.js} +1 -1
  28. package/dist/chunks/{LpLightbox-DlNPihiz.js → LpLightbox-B95yu22B.js} +2 -2
  29. package/dist/chunks/{LpLoader-DBVl6KDH.js → LpLoader-DVwkOmt9.js} +1 -1
  30. package/dist/chunks/{LpLogViewer-By_fmDpN.js → LpLogViewer-D25jM2_I.js} +3 -3
  31. package/dist/chunks/{LpMapPicker-CmuOTS7N.js → LpMapPicker-DRGgh89B.js} +1 -1
  32. package/dist/chunks/{LpMediaGallery-Dh5ONZt1.js → LpMediaGallery-Bkram-MO.js} +1 -1
  33. package/dist/chunks/{LpModalNav-CThPOPAF.js → LpModalNav-CE3iLnxB.js} +2 -2
  34. package/dist/chunks/{LpNotificationBell-674IgUph.js → LpNotificationBell-3244LUVc.js} +3 -3
  35. package/dist/chunks/{LpPaletteTheme-hsYXczIM.js → LpPaletteTheme-CdEEMMEF.js} +3 -3
  36. package/dist/chunks/{LpPasswordInput-DNCvyY6N.js → LpPasswordInput-BkHKQDhM.js} +2 -2
  37. package/dist/chunks/{LpPie-DCpGXbRS.js → LpPie-Mr3EtFEd.js} +2 -2
  38. package/dist/chunks/{LpPipeline-wh1SzEHF.js → LpPipeline-B3aOVOkV.js} +2 -2
  39. package/dist/chunks/{LpRepeater-BnOQXjWo.js → LpRepeater-D7ZmnIYd.js} +2 -2
  40. package/dist/chunks/{LpScramble-INTKwGsY.js → LpScramble-Bn9OoOtC.js} +1 -1
  41. package/dist/chunks/{LpSecretField-Ck0Mx6xx.js → LpSecretField-BbmqM9ye.js} +6 -6
  42. package/dist/chunks/{LpShift-DLU4_TFf.js → LpShift-CCq7dofl.js} +28 -5
  43. package/dist/chunks/{LpSidebar-DVPinvaL.js → LpSidebar-BciLMBgY.js} +2 -2
  44. package/dist/chunks/{LpSidebarNav-BBj2_6jV.js → LpSidebarNav-HYcG-ib8.js} +3 -2
  45. package/dist/chunks/{LpSidebarRow-C81reNEm.js → LpSidebarRow-Di2VYz-Z.js} +19 -6
  46. package/dist/chunks/{LpStat-tq-f6ZzL.js → LpStat-D6V6Fdfc.js} +1 -1
  47. package/dist/chunks/{LpStatus-Chbh_JJr.js → LpStatus-DglxttQ7.js} +2 -2
  48. package/dist/chunks/{LpStepMark-C064WOYG.js → LpStepMark-DpzKVyh8.js} +1 -1
  49. package/dist/chunks/{LpStepper-B15etK5F.js → LpStepper-DpsocBNJ.js} +1 -1
  50. package/dist/chunks/{LpSwap-BA5fh77B.js → LpSwap-C96c0LWh.js} +1 -1
  51. package/dist/chunks/{LpSwitch-ByaxQKEm.js → LpSwitch-BnXd1llc.js} +1 -1
  52. package/dist/chunks/{LpTabFace-Dcj2cO6x.js → LpTabFace-DPCwKkH2.js} +1 -1
  53. package/dist/chunks/{LpTableFilter-CT8Xml_m.js → LpTableFilter-CiVBpaq0.js} +3 -3
  54. package/dist/chunks/{LpTagsInput-CSv62X4B.js → LpTagsInput-C-mDG648.js} +2 -2
  55. package/dist/chunks/{LpTextarea-Blv8qdz0.js → LpTextarea-TE-yZv2U.js} +1 -1
  56. package/dist/chunks/{LpThemeStudio-DYQP_qbp.js → LpThemeStudio-BJDKf3_Z.js} +2 -2
  57. package/dist/chunks/{LpThemeSwitcher-BGIVrM1l.js → LpThemeSwitcher-BDrIJswA.js} +2 -2
  58. package/dist/chunks/{LpToaster-CmmqSF2o.js → LpToaster-IwTXjbbU.js} +1 -1
  59. package/dist/chunks/{LpTopologyCanvas-Bq-k5tgI.js → LpTopologyCanvas-B1gIbS0n.js} +1 -1
  60. package/dist/chunks/{LpTour-BU4JX3RU.js → LpTour-BZq-mUMv.js} +1 -1
  61. package/dist/chunks/{LpTruncate-Cb7MDTD7.js → LpTruncate-CflAIwiJ.js} +1 -1
  62. package/dist/chunks/{LpUptimeBar-Cdq76qi_.js → LpUptimeBar-BtkZRwY0.js} +2 -2
  63. package/dist/chunks/{RevealToggle-DhL7mi-3.js → RevealToggle-BEit1n6Z.js} +1 -1
  64. package/dist/chunks/{TourCard-Dvo_vH_P.js → TourCard-DNclH3u_.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.d.ts +0 -2
  71. package/dist/components/LpAppShell.vue.js +1 -1
  72. package/dist/components/LpBadge.vue.js +1 -1
  73. package/dist/components/LpBottomNav.vue.js +1 -1
  74. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  75. package/dist/components/LpButton.vue.js +1 -1
  76. package/dist/components/LpCard.vue.js +1 -1
  77. package/dist/components/LpChart.vue.js +2 -2
  78. package/dist/components/LpCheckbox.vue.js +1 -1
  79. package/dist/components/LpCodeBlock.vue.js +1 -1
  80. package/dist/components/LpCodeDisplay.vue.js +1 -1
  81. package/dist/components/LpCommandPalette.vue.js +2 -2
  82. package/dist/components/LpConfirmDialog.vue.js +1 -1
  83. package/dist/components/LpConfirmHost.vue.js +1 -1
  84. package/dist/components/LpCopyButton.vue.js +1 -1
  85. package/dist/components/LpCountdown.vue.js +1 -1
  86. package/dist/components/LpDataList.vue.js +1 -1
  87. package/dist/components/LpDropzone.vue.js +1 -1
  88. package/dist/components/LpEditable.vue.js +1 -1
  89. package/dist/components/LpFileTree.vue.js +1 -1
  90. package/dist/components/LpFileTreeNode.vue.js +1 -1
  91. package/dist/components/LpFormField.vue.js +1 -1
  92. package/dist/components/LpHintLayer.vue.js +1 -1
  93. package/dist/components/LpHints.vue.js +1 -1
  94. package/dist/components/LpImageEditor.vue.js +1 -1
  95. package/dist/components/LpInfraNode.vue.js +1 -1
  96. package/dist/components/LpInput.vue.js +1 -1
  97. package/dist/components/LpLightbox.vue.js +1 -1
  98. package/dist/components/LpLoader.vue.js +1 -1
  99. package/dist/components/LpLogViewer.vue.js +1 -1
  100. package/dist/components/LpMapPicker.vue.js +1 -1
  101. package/dist/components/LpMediaGallery.vue.js +1 -1
  102. package/dist/components/LpModalNav.vue.js +1 -1
  103. package/dist/components/LpNotificationBell.vue.js +1 -1
  104. package/dist/components/LpPaletteTheme.vue.js +1 -1
  105. package/dist/components/LpPasswordInput.vue.js +1 -1
  106. package/dist/components/LpPie.vue.js +1 -1
  107. package/dist/components/LpPipeline.vue.js +1 -1
  108. package/dist/components/LpRepeater.vue.js +1 -1
  109. package/dist/components/LpScramble.vue.js +1 -1
  110. package/dist/components/LpSecretField.vue.js +1 -1
  111. package/dist/components/LpShift.vue.d.ts +4 -3
  112. package/dist/components/LpShift.vue.js +1 -1
  113. package/dist/components/LpSidebar.vue.js +1 -1
  114. package/dist/components/LpSidebarNav.vue.js +1 -1
  115. package/dist/components/LpSidebarRow.vue.d.ts +2 -0
  116. package/dist/components/LpSidebarRow.vue.js +1 -1
  117. package/dist/components/LpStat.vue.js +1 -1
  118. package/dist/components/LpStatus.vue.js +1 -1
  119. package/dist/components/LpStepMark.vue.js +1 -1
  120. package/dist/components/LpStepper.vue.js +1 -1
  121. package/dist/components/LpSwap.vue.js +1 -1
  122. package/dist/components/LpSwitch.vue.js +1 -1
  123. package/dist/components/LpTabFace.vue.js +1 -1
  124. package/dist/components/LpTable.vue.js +6 -6
  125. package/dist/components/LpTableFilter.vue.js +1 -1
  126. package/dist/components/LpTabs.vue.js +1 -1
  127. package/dist/components/LpTagsInput.vue.js +1 -1
  128. package/dist/components/LpTextarea.vue.js +1 -1
  129. package/dist/components/LpThemeStudio.vue.js +1 -1
  130. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  131. package/dist/components/LpToaster.vue.js +1 -1
  132. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  133. package/dist/components/LpTour.vue.js +1 -1
  134. package/dist/components/LpTruncate.vue.js +1 -1
  135. package/dist/components/LpUptimeBar.vue.js +1 -1
  136. package/dist/components/RevealToggle.vue.js +1 -1
  137. package/dist/components/TourCard.vue.js +1 -1
  138. package/dist/index.js +55 -55
  139. package/package.json +1 -1
  140. package/src/components/LpAppShell.vue +16 -7
  141. package/src/components/LpShift.vue +43 -7
  142. package/src/components/LpSidebarNav.vue +1 -0
  143. package/src/components/LpSidebarRow.vue +18 -1
@@ -31,7 +31,7 @@ export default { inheritAttrs: false }
31
31
  * Overlays that aren't part of the frame (command palette, toaster) live in the
32
32
  * host next to <LpAppShell>.
33
33
  */
34
- import { computed, ref, useId, watch } from "vue"
34
+ import { computed, onMounted, ref, useId, watch } from "vue"
35
35
  import { useLabels } from "../composables/useLpConfig"
36
36
  import LpBottomNav from "./LpBottomNav.vue"
37
37
  import LpButton from "./LpButton.vue"
@@ -180,6 +180,10 @@ const text = useLabels(
180
180
  * a scrolled page landed mid-way down the next one.
181
181
  */
182
182
  const scrollArea = ref<{ viewportEl: HTMLElement | null } | null>(null)
183
+ const bleedId = `lp-shell-bleed-${useId()}`
184
+ const mounted = ref(false)
185
+ onMounted(() => (mounted.value = true))
186
+ const bleeding = computed(() => props.fullBleed && mounted.value)
183
187
  const mainId = `lp-main-${useId()}`
184
188
 
185
189
  // Focus instead of following the hash, which a router would treat as a navigation.
@@ -348,18 +352,23 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
348
352
  </header>
349
353
 
350
354
  <!-- full-bleed: page owns the whole region (canvas/map); otherwise the
351
- single scroll region with padding. -->
355
+ single scroll region with padding. The page is rendered once and
356
+ moved between the two by a teleport rather than drawn in two
357
+ branches: switching branches remounted the whole page, so a section
358
+ tab going from a full-bleed page to a padded one cut instead of
359
+ playing its transition, and lost the page's state. Until mount the
360
+ page stays in the scroll region, which is what a server render has. -->
352
361
  <main :id="mainId" tabindex="-1" class="flex min-h-0 flex-1 flex-col outline-none">
353
- <div v-if="fullBleed" class="min-h-0 flex-1 overflow-hidden">
354
- <slot />
355
- </div>
362
+ <div v-show="bleeding" :id="bleedId" class="min-h-0 flex-1 overflow-hidden" />
356
363
  <LpScrollArea
357
- v-else
364
+ v-show="!bleeding"
358
365
  ref="scrollArea"
359
366
  class="min-h-0 flex-1"
360
367
  :content-class="mergeClass('px-4 py-6 md:px-6', contentClass)"
361
368
  >
362
- <slot />
369
+ <Teleport :to="`#${bleedId}`" :disabled="!bleeding" defer>
370
+ <slot />
371
+ </Teleport>
363
372
  </LpScrollArea>
364
373
  </main>
365
374
  <LpBottomNav
@@ -57,9 +57,10 @@ const props = withDefaults(
57
57
  * Fade the box's own children in and out as they come and go. Without it
58
58
  * the box glides but a control still pops into being as the edge uncovers
59
59
  * it, and one that leaves is gone in a frame while its space closes up.
60
- * Only direct children: what a child does inside itself is its own affair,
61
- * so a control that changes its label carries the label as its `key` and
62
- * is replaced, the old one fading as the new one arrives.
60
+ * Anything that takes room counts, at any depth, since controls often
61
+ * arrive by teleport into a wrapper inside the box; an absolutely placed
62
+ * overlay (a tab's marker) does not, it moves nothing and animates itself.
63
+ * Markup put back unchanged by a re-render is not animated either.
63
64
  */
64
65
  presence?: boolean
65
66
  }>(),
@@ -105,6 +106,7 @@ interface Seen {
105
106
  height: number
106
107
  x: number
107
108
  y: number
109
+ flow: boolean
108
110
  }
109
111
 
110
112
  const seen = new WeakMap<Element, Seen>()
@@ -129,15 +131,48 @@ function remember(box: HTMLElement) {
129
131
  height: r.height,
130
132
  x: r.left,
131
133
  y: r.top,
134
+ flow: inFlow(node),
132
135
  })
133
136
  }
134
137
  }
135
138
 
139
+ // Only what takes room in the box comes and goes visibly. An overlay inside a
140
+ // control (a tab's sliding marker, a badge pinned to a corner) moves nothing
141
+ // around it and is animated by its own component; fading it in on top would
142
+ // override that animation's transform, and the marker would pop instead of
143
+ // sliding.
144
+ function inFlow(node: Element): boolean {
145
+ const position = getComputedStyle(node).position
146
+ return position !== "absolute" && position !== "fixed"
147
+ }
148
+
149
+ // A re-render that puts back the same markup (a page redrawing its header
150
+ // actions, a list re-keyed after a reload) is not a change anyone sees, so the
151
+ // pair is left alone rather than flashing out and in.
152
+ function signature(node: Node): string | undefined {
153
+ return node instanceof Element ? node.outerHTML : undefined
154
+ }
155
+
156
+ function unchanged(records: MutationRecord[]): Set<Node> {
157
+ const removed = new Map<string, Node[]>()
158
+ for (const record of records) record.removedNodes.forEach((node) => {
159
+ const key = signature(node)
160
+ if (key) removed.set(key, [...(removed.get(key) ?? []), node])
161
+ })
162
+ const same = new Set<Node>()
163
+ for (const record of records) record.addedNodes.forEach((node) => {
164
+ const key = signature(node)
165
+ const twin = key ? removed.get(key)?.shift() : undefined
166
+ if (twin) same.add(twin).add(node)
167
+ })
168
+ return same
169
+ }
170
+
136
171
  const offset = () => (props.anchor === "end" ? -6 : 6)
137
172
 
138
173
  function ghost(box: HTMLElement, node: Element) {
139
174
  const at = seen.get(node)
140
- if (!at || !(node instanceof HTMLElement) || node.hasAttribute(GHOST) || at.width === 0) return
175
+ if (!at || !at.flow || !(node instanceof HTMLElement) || node.hasAttribute(GHOST) || at.width === 0) return
141
176
  const copy = node.cloneNode(true) as HTMLElement
142
177
  copy.setAttribute(GHOST, "")
143
178
  copy.setAttribute("aria-hidden", "true")
@@ -172,7 +207,7 @@ function ghost(box: HTMLElement, node: Element) {
172
207
  }
173
208
 
174
209
  function arrive(node: Element) {
175
- if (!(node instanceof HTMLElement) || node.hasAttribute(GHOST)) return
210
+ if (!(node instanceof HTMLElement) || node.hasAttribute(GHOST) || !inFlow(node)) return
176
211
  node.animate(
177
212
  [
178
213
  { opacity: 0, transform: `translateX(${offset()}px) scale(0.96)`, filter: "blur(2px)" },
@@ -208,11 +243,12 @@ watch(
208
243
  )
209
244
  if (real.length && !prefersReducedMotion()) {
210
245
  const added = new Set<Node>()
246
+ const same = unchanged(real)
211
247
  for (const record of real) {
212
- record.removedNodes.forEach((node) => node instanceof Element && ghost(box, node))
248
+ record.removedNodes.forEach((node) => node instanceof Element && !same.has(node) && ghost(box, node))
213
249
  record.addedNodes.forEach((node) => {
214
250
  added.add(node)
215
- if (node instanceof Element) arrive(node)
251
+ if (node instanceof Element && !same.has(node)) arrive(node)
216
252
  })
217
253
  }
218
254
  // Measured in the next frame, which still comes before the paint: by
@@ -281,6 +281,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
281
281
  :pill="false"
282
282
  :pill-id="pillId"
283
283
  compact
284
+ group
284
285
  :on-path="section.items.some((i) => itemActive(i) || onPath(i))"
285
286
  :preview-delay="previewDelay"
286
287
  :item-active="itemActive"
@@ -31,6 +31,8 @@ const props = withDefaults(
31
31
  pill: boolean
32
32
  pillId: string
33
33
  compact?: boolean
34
+ /** A folded group standing in for its pages: a click opens its card. */
35
+ group?: boolean
34
36
  /** A page under another: indented, a size smaller. */
35
37
  nested?: boolean
36
38
  menu?: ContextMenuItemDef[]
@@ -98,11 +100,25 @@ const initials = computed(() =>
98
100
  const hidden = computed(() => (props.compact || !props.expanded) && !!props.item.children?.some(waiting))
99
101
  const hasBadge = computed(() => props.item.badge != null || !!props.item.dot || hidden.value)
100
102
  const card = computed(() => !props.item.disabled && (props.compact || !!props.item.preview))
103
+
104
+ // A folded group with no page of its own is reached only through its card, so
105
+ // a click opens the card rather than waiting for the pointer to rest: a click
106
+ // that does nothing on a nav icon reads as broken.
107
+ const cardOpen = ref(false)
108
+ const opensCard = computed(() => props.compact && props.group && !!props.item.children?.length)
109
+ function press(): void {
110
+ if (opensCard.value) {
111
+ cardOpen.value = !cardOpen.value
112
+ return
113
+ }
114
+ emit("activate", props.item)
115
+ }
101
116
  const delay = computed(() => (props.compact && !props.item.preview ? 250 : props.previewDelay))
102
117
  </script>
103
118
 
104
119
  <template>
105
120
  <LpHoverCard
121
+ v-model:open="cardOpen"
106
122
  :disabled="!card"
107
123
  side="right"
108
124
  align="start"
@@ -136,7 +152,8 @@ const delay = computed(() => (props.compact && !props.item.preview ? 250 : props
136
152
  ? 'text-ink'
137
153
  : 'text-muted hover:text-ink',
138
154
  ]"
139
- @click="emit('activate', item)"
155
+ :aria-expanded="opensCard ? cardOpen : undefined"
156
+ @click="press"
140
157
  @pointerenter="emit('hover', item)"
141
158
  >
142
159
  <span