@leavepulse/ui 0.72.8 → 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 (155) 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-CPCG0I-c.js → LpAppShell-B0Yu9MVs.js} +11 -5
  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/{LpTableOfContents-CraicEs4.js → LpTableOfContents-BN5zvpnx.js} +2 -2
  55. package/dist/chunks/{LpTagsInput-C-mDG648.js → LpTagsInput-CHoiid8u.js} +2 -2
  56. package/dist/chunks/{LpTextarea-TE-yZv2U.js → LpTextarea-CU7C1pLO.js} +1 -1
  57. package/dist/chunks/{LpThemeStudio-BpORDvH6.js → LpThemeStudio-zyOH3dF2.js} +2 -2
  58. package/dist/chunks/{LpThemeSwitcher-BnEn_mvf.js → LpThemeSwitcher-BXJhQYGD.js} +2 -2
  59. package/dist/chunks/{LpToaster-IwTXjbbU.js → LpToaster-C1D35SKk.js} +1 -1
  60. package/dist/chunks/{LpTopologyCanvas-B1gIbS0n.js → LpTopologyCanvas-1OufbBtQ.js} +1 -1
  61. package/dist/chunks/{LpTour-BZq-mUMv.js → LpTour-XshJslGg.js} +1 -1
  62. package/dist/chunks/{LpTruncate-CflAIwiJ.js → LpTruncate-qgKA-3K3.js} +1 -1
  63. package/dist/chunks/{LpUptimeBar-DqNvvMBK.js → LpUptimeBar-qhvvmmoI.js} +2 -2
  64. package/dist/chunks/{RevealToggle-BEit1n6Z.js → RevealToggle-6MlmohxS.js} +1 -1
  65. package/dist/chunks/{TourCard-DNclH3u_.js → TourCard-BuB-i4kv.js} +3 -3
  66. package/dist/components/DropzoneLinkField.vue.js +1 -1
  67. package/dist/components/FieldWrapper.vue.js +1 -1
  68. package/dist/components/LpActionBar.vue.js +1 -1
  69. package/dist/components/LpAddressInput.vue.js +1 -1
  70. package/dist/components/LpAlert.vue.js +1 -1
  71. package/dist/components/LpAppShell.vue.d.ts +20 -0
  72. package/dist/components/LpAppShell.vue.js +1 -1
  73. package/dist/components/LpBadge.vue.js +1 -1
  74. package/dist/components/LpBottomNav.vue.js +1 -1
  75. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  76. package/dist/components/LpButton.vue.js +1 -1
  77. package/dist/components/LpCard.vue.js +1 -1
  78. package/dist/components/LpChart.vue.js +2 -2
  79. package/dist/components/LpCheckbox.vue.js +1 -1
  80. package/dist/components/LpCodeBlock.vue.js +1 -1
  81. package/dist/components/LpCodeDisplay.vue.js +1 -1
  82. package/dist/components/LpCommandPalette.vue.js +2 -2
  83. package/dist/components/LpConfirmDialog.vue.js +1 -1
  84. package/dist/components/LpConfirmHost.vue.js +1 -1
  85. package/dist/components/LpCopyButton.vue.js +1 -1
  86. package/dist/components/LpCountdown.vue.js +1 -1
  87. package/dist/components/LpDataList.vue.js +1 -1
  88. package/dist/components/LpDropzone.vue.js +1 -1
  89. package/dist/components/LpEditable.vue.js +1 -1
  90. package/dist/components/LpFileTree.vue.js +1 -1
  91. package/dist/components/LpFileTreeNode.vue.js +1 -1
  92. package/dist/components/LpFormField.vue.js +1 -1
  93. package/dist/components/LpHintLayer.vue.js +1 -1
  94. package/dist/components/LpHints.vue.js +1 -1
  95. package/dist/components/LpImageEditor.vue.js +1 -1
  96. package/dist/components/LpInfraNode.vue.js +1 -1
  97. package/dist/components/LpInput.vue.js +1 -1
  98. package/dist/components/LpLightbox.vue.js +1 -1
  99. package/dist/components/LpLoader.vue.js +1 -1
  100. package/dist/components/LpLogViewer.vue.js +1 -1
  101. package/dist/components/LpMapPicker.vue.js +1 -1
  102. package/dist/components/LpMediaGallery.vue.js +1 -1
  103. package/dist/components/LpModalNav.vue.js +1 -1
  104. package/dist/components/LpNotificationBell.vue.js +1 -1
  105. package/dist/components/LpPaletteTheme.vue.js +1 -1
  106. package/dist/components/LpPasswordInput.vue.js +1 -1
  107. package/dist/components/LpPie.vue.js +1 -1
  108. package/dist/components/LpPipeline.vue.js +1 -1
  109. package/dist/components/LpRepeater.vue.js +1 -1
  110. package/dist/components/LpScramble.vue.js +1 -1
  111. package/dist/components/LpSecretField.vue.js +1 -1
  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.js +1 -1
  116. package/dist/components/LpStat.vue.js +1 -1
  117. package/dist/components/LpStatus.vue.js +1 -1
  118. package/dist/components/LpStepMark.vue.js +1 -1
  119. package/dist/components/LpStepper.vue.js +1 -1
  120. package/dist/components/LpSwap.vue.js +1 -1
  121. package/dist/components/LpSwitch.vue.js +1 -1
  122. package/dist/components/LpTabFace.vue.js +1 -1
  123. package/dist/components/LpTable.vue.js +6 -6
  124. package/dist/components/LpTableFilter.vue.js +1 -1
  125. package/dist/components/LpTableOfContents.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/composables/scroll.d.ts +2 -0
  139. package/dist/composables/scroll.js +4 -0
  140. package/dist/composables/scrollMemory.d.ts +1 -0
  141. package/dist/composables/scrollMemory.js +111 -0
  142. package/dist/composables/useFitScale.d.ts +5 -0
  143. package/dist/composables/useFitScale.js +30 -0
  144. package/dist/composables/useShift.d.ts +2 -0
  145. package/dist/composables/useShift.js +5 -0
  146. package/dist/index.js +56 -56
  147. package/package.json +1 -1
  148. package/src/components/LpAppShell.vue +18 -2
  149. package/src/components/LpShift.vue +4 -2
  150. package/src/components/LpStat.vue +24 -3
  151. package/src/components/LpTableOfContents.vue +4 -2
  152. package/src/composables/scroll.ts +5 -0
  153. package/src/composables/scrollMemory.ts +145 -0
  154. package/src/composables/useFitScale.ts +43 -0
  155. package/src/composables/useShift.ts +10 -0
package/dist/index.js CHANGED
@@ -1,22 +1,22 @@
1
1
  import { _ } from "./chunks/LayoutCanvas-CNOAUk7D.js";
2
- import { _ as _2 } from "./chunks/LpAddressInput-Al3HT5ey.js";
3
- import { _ as _3 } from "./chunks/LpMapPicker-DRGgh89B.js";
2
+ import { _ as _2 } from "./chunks/LpAddressInput-mf_dEpj_.js";
3
+ import { _ as _3 } from "./chunks/LpMapPicker-DeJRMfFN.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
- import { _ as _4 } from "./chunks/LpActionBar-BP1ld7-S.js";
6
- import { _ as _5 } from "./chunks/LpAlert-n5XPAZfv.js";
7
- import { _ as _6 } from "./chunks/LpAppShell-CPCG0I-c.js";
5
+ import { _ as _4 } from "./chunks/LpActionBar-B55msXTr.js";
6
+ import { _ as _5 } from "./chunks/LpAlert-Cg7oIx1B.js";
7
+ import { _ as _6 } from "./chunks/LpAppShell-B0Yu9MVs.js";
8
8
  import { _ as _7 } from "./chunks/LpAutocomplete-DOu7FN5P.js";
9
9
  import { _ as _8 } from "./chunks/LpAvatar-Cg6Az3wz.js";
10
- import { _ as _9 } from "./chunks/LpBadge-wUYLN4U9.js";
11
- import { _ as _10 } from "./chunks/LpBreadcrumbs-DPdbth-c.js";
12
- import { _ as _11 } from "./chunks/LpButton-CFb44IjC.js";
10
+ import { _ as _9 } from "./chunks/LpBadge-0A6sPks3.js";
11
+ import { _ as _10 } from "./chunks/LpBreadcrumbs-ssZV3_od.js";
12
+ import { _ as _11 } from "./chunks/LpButton-BgdewXxF.js";
13
13
  import { _ as _12 } from "./chunks/LpCalendar-CuoWks-0.js";
14
- import { _ as _13 } from "./chunks/LpCard-r13WRqox.js";
14
+ import { _ as _13 } from "./chunks/LpCard-BJ59q6hR.js";
15
15
  import { default as default2 } from "./components/LpChart.vue.js";
16
16
  import { backgroundOf, csvField, downloadFile, fileStem, inlineSvg, resolveCssColor, svgToPng, toCsv } from "./components/exportData.js";
17
17
  import { CHART_PALETTE, axisTicks, chartSummary, formatTick, markerIndex, niceScale, resolveSlices } from "./components/chart.js";
18
- import { _ as _14 } from "./chunks/LpCheckbox-Cs4SHaHX.js";
19
- import { _ as _15 } from "./chunks/LpCodeBlock-DRC8VbXW.js";
18
+ import { _ as _14 } from "./chunks/LpCheckbox-B0kJ-t6T.js";
19
+ import { _ as _15 } from "./chunks/LpCodeBlock-CXR_7t_F.js";
20
20
  import { tokenizeLines } from "./components/codeHighlight.js";
21
21
  import { _ as _16 } from "./chunks/LpColorPicker-1y1QJHZR.js";
22
22
  import { DEFAULT_SWATCHES, clampHsv, formatColor, hsvToHsl, hsvToRgb, isLight, normalizeHue, opaqueCss, parseColor, parseHex, rgbToHex, rgbToHsv } from "./components/color.js";
@@ -30,8 +30,8 @@ import { default as default6 } from "./components/LpPaletteStats.vue.js";
30
30
  import { default as default7 } from "./components/LpPaletteSwatches.vue.js";
31
31
  import { default as default8 } from "./components/LpKeys.vue.js";
32
32
  import { comboKeys, comboSteps } from "./components/keys.js";
33
- import { _ as _18 } from "./chunks/LpConfirmDialog-DY2VjDji.js";
34
- import { _ as _19 } from "./chunks/LpConfirmHost-DvSZTJ4N.js";
33
+ import { _ as _18 } from "./chunks/LpConfirmDialog-CaA4pAre.js";
34
+ import { _ as _19 } from "./chunks/LpConfirmHost-CBIzALSL.js";
35
35
  import { confirm, useConfirm } from "./components/confirm.js";
36
36
  import { _ as _20 } from "./chunks/LpContextMenu-rx2tCjwk.js";
37
37
  import { _ as _21 } from "./chunks/LpDatePicker-CA0QTfBC.js";
@@ -44,31 +44,31 @@ import { _ as _26 } from "./chunks/LpDrawer-RRamxbLV.js";
44
44
  import { _ as _27 } from "./chunks/LpDropdownMenu-Dl7Z4ZhS.js";
45
45
  import { default as default9 } from "./components/LpDropzone.vue.js";
46
46
  import { _ as _28 } from "./chunks/LpEmptyState-EQHiRYhG.js";
47
- import { _ as _29 } from "./chunks/LpFileTree-zIFEgo3I.js";
47
+ import { _ as _29 } from "./chunks/LpFileTree-CxOh3rL2.js";
48
48
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
49
49
  import { _ as _30 } from "./chunks/LpFieldNotice-CzQ2_5rQ.js";
50
- import { _ as _31 } from "./chunks/LpFormField-Ch69Jd-X.js";
50
+ import { _ as _31 } from "./chunks/LpFormField-BBFg8i2I.js";
51
51
  import { _ as _32 } from "./chunks/LpIcon--5WSb3jJ.js";
52
52
  import { i, b, a, c } from "./chunks/icons-DMITlfw5.js";
53
53
  import { default as default10 } from "./components/LpImageEditor.vue.js";
54
- import { _ as _33 } from "./chunks/LpInfraNode-46XOCkfq.js";
55
- import { _ as _34 } from "./chunks/LpInput-Tw-yxT4I.js";
56
- import { _ as _35 } from "./chunks/LpLightbox-B95yu22B.js";
54
+ import { _ as _33 } from "./chunks/LpInfraNode-6NLxkAnr.js";
55
+ import { _ as _34 } from "./chunks/LpInput-B2dTt4pc.js";
56
+ import { _ as _35 } from "./chunks/LpLightbox-Coe4OBUl.js";
57
57
  import { fileNameOf } from "./components/lightbox.js";
58
58
  import { _ as _36 } from "./chunks/LpLink-Krkjhe_y.js";
59
- import { _ as _37 } from "./chunks/LpLogViewer-D25jM2_I.js";
59
+ import { _ as _37 } from "./chunks/LpLogViewer-DxiR6Mbd.js";
60
60
  import { countLevels, matchPositions, matchesTerm, ringWindow, stepMatch } from "./components/logSearch.js";
61
- import { _ as _38 } from "./chunks/LpMediaGallery-Bkram-MO.js";
61
+ import { _ as _38 } from "./chunks/LpMediaGallery-B7Km8N9l.js";
62
62
  import { _ as _39 } from "./chunks/LpModal-BnjZ2iem.js";
63
- import { _ as _40 } from "./chunks/LpModalNav-DQpV2kdn.js";
64
- import { _ as _41 } from "./chunks/LpNotificationBell-3244LUVc.js";
63
+ import { _ as _40 } from "./chunks/LpModalNav-BmxEyqOx.js";
64
+ import { _ as _41 } from "./chunks/LpNotificationBell-u8-ZjRON.js";
65
65
  import { _ as _42 } from "./chunks/LpNumberField-e0IBkWU1.js";
66
66
  import { _ as _43 } from "./chunks/LpTimeField-BawSVsmt.js";
67
67
  import { _ as _44 } from "./chunks/LpNumberFlow-Vqj0tP7B.js";
68
68
  import { _ as _45 } from "./chunks/LpOtpInput-CeBPsM7D.js";
69
69
  import { _ as _46 } from "./chunks/LpPagination-BIIiYHXR.js";
70
- import { _ as _47 } from "./chunks/LpPasswordInput-BkHKQDhM.js";
71
- import { _ as _48 } from "./chunks/LpPie-DrZepi0p.js";
70
+ import { _ as _47 } from "./chunks/LpPasswordInput-Ih7Lv43S.js";
71
+ import { _ as _48 } from "./chunks/LpPie-Idujdb3M.js";
72
72
  import { _ as _49 } from "./chunks/LpPhoneInput-CETEp5W7.js";
73
73
  import { filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
74
74
  import { _ as _50 } from "./chunks/LpPopover-DmZ3wUtP.js";
@@ -82,41 +82,41 @@ import { _ as _57 } from "./chunks/LpSection-DFK_uoJE.js";
82
82
  import { _ as _58 } from "./chunks/LpSelect-Cxq4yBGd.js";
83
83
  import { fitChips, toggledValues } from "./components/select.js";
84
84
  import { _ as _59 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
85
- import { _ as _60 } from "./chunks/LpShift-CCq7dofl.js";
86
- import { _ as _61 } from "./chunks/LpSwap-C96c0LWh.js";
85
+ import { _ as _60 } from "./chunks/LpShift-kKUeG9NS.js";
86
+ import { _ as _61 } from "./chunks/LpSwap-KIS24N8W.js";
87
87
  import { _ as _62 } from "./chunks/LpHintExample-9Py0wc-L.js";
88
88
  import { default as default11 } from "./components/LpShortcutsDialog.vue.js";
89
- import { _ as _63 } from "./chunks/LpSidebar-BoNLKgh-.js";
89
+ import { _ as _63 } from "./chunks/LpSidebar-CR_WKcn2.js";
90
90
  import { _ as _64 } from "./chunks/LpSkeleton-BB6XIV-w.js";
91
91
  import { _ as _65 } from "./chunks/LpSlider-DxYoCJJO.js";
92
92
  import { _ as _66 } from "./chunks/LpRangeSlider-CM-0ZITZ.js";
93
93
  import { _ as _67 } from "./chunks/LpVideoBackdrop-DS1wGv-X.js";
94
94
  import { formatClipTime, isNativeLoop, planLoop, seamProgress, seamReached } from "./components/videoBackdrop.js";
95
95
  import { _ as _68 } from "./chunks/LpSparkline-CExIRDb3.js";
96
- import { _ as _69 } from "./chunks/LpStat-BVf2UvEc.js";
97
- import { _ as _70 } from "./chunks/LpStepper-DpsocBNJ.js";
98
- import { _ as _71 } from "./chunks/LpSwitch-BnXd1llc.js";
96
+ import { _ as _69 } from "./chunks/LpStat-DXqKD1h5.js";
97
+ import { _ as _70 } from "./chunks/LpStepper-CKSTFzHE.js";
98
+ import { _ as _71 } from "./chunks/LpSwitch-DlZHmj-M.js";
99
99
  import { default as default12 } from "./components/LpTable.vue.js";
100
100
  import { compareValues } from "./components/tableSort.js";
101
101
  import { resolveRowMenu } from "./components/tableRowMenu.js";
102
- import { _ as _72 } from "./chunks/LpTableOfContents-CraicEs4.js";
102
+ import { _ as _72 } from "./chunks/LpTableOfContents-BN5zvpnx.js";
103
103
  import { default as default13 } from "./components/LpTabs.vue.js";
104
- import { _ as _73 } from "./chunks/LpBottomNav-CbNQQPuL.js";
105
- import { _ as _74 } from "./chunks/LpTagsInput-C-mDG648.js";
104
+ import { _ as _73 } from "./chunks/LpBottomNav-Dik-ZcHv.js";
105
+ import { _ as _74 } from "./chunks/LpTagsInput-CHoiid8u.js";
106
106
  import { addTags, splitTags } from "./components/tags.js";
107
- import { _ as _75 } from "./chunks/LpTextarea-TE-yZv2U.js";
108
- import { _ as _76 } from "./chunks/LpThemeSwitcher-BnEn_mvf.js";
107
+ import { _ as _75 } from "./chunks/LpTextarea-CU7C1pLO.js";
108
+ import { _ as _76 } from "./chunks/LpThemeSwitcher-BXJhQYGD.js";
109
109
  import { _ as _77 } from "./chunks/LpTilt-B7KHX1zH.js";
110
- import { _ as _78 } from "./chunks/LpToaster-IwTXjbbU.js";
110
+ import { _ as _78 } from "./chunks/LpToaster-C1D35SKk.js";
111
111
  import { _ as _79 } from "./chunks/LpTooltip-BcBvIx4z.js";
112
- import { _ as _80 } from "./chunks/LpTour-BZq-mUMv.js";
112
+ import { _ as _80 } from "./chunks/LpTour-XshJslGg.js";
113
113
  import { holeOf, openContextMenu, panelsAround } from "./components/tour.js";
114
- import { _ as _81 } from "./chunks/LpHintLayer-CgObdUsM.js";
115
- import { _ as _82 } from "./chunks/LpHints-Bz-BlaSl.js";
114
+ import { _ as _81 } from "./chunks/LpHintLayer-Db_s9_DL.js";
115
+ import { _ as _82 } from "./chunks/LpHints-Cxf5GXsx.js";
116
116
  import { HINT_SIGNALS, contextMenuTip, createHintSystem, dropzoneTip, hintKey, hintSummary, hotkeyTip, localHintStorage, noteDropzone, noteHotkeyTarget, paletteTip, provideHintSystem, runningHintSystem, useHintSystem } from "./composables/useHintSystem.js";
117
117
  import { _ as _83 } from "./chunks/LpTooltipProvider-CftjV7Xc.js";
118
- import { _ as _84 } from "./chunks/LpTopologyCanvas-B1gIbS0n.js";
119
- import { _ as _85 } from "./chunks/LpUptimeBar-DqNvvMBK.js";
118
+ import { _ as _84 } from "./chunks/LpTopologyCanvas-1OufbBtQ.js";
119
+ import { _ as _85 } from "./chunks/LpUptimeBar-qhvvmmoI.js";
120
120
  import { deserializeLayout, serializeLayout } from "./layout/tree.js";
121
121
  import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
122
122
  import { blockTitle, defineBlocks } from "./layout/registry.js";
@@ -124,7 +124,7 @@ import { layoutController, useLayout } from "./layout/useLayout.js";
124
124
  import { a as parseTheme, s as serializeTheme } from "./chunks/LpThemeProvider-8MG6yqU9.js";
125
125
  import { D, _ as _86, T, b as b2, c as c2, d, e, f, g, l, h, n, i as i2, j, p, r, k, t, m, o, q, u, v } from "./chunks/LpThemeProvider-8MG6yqU9.js";
126
126
  import { G, N, d as d2, i as i3, s } from "./chunks/backdrop-Dkf2vM81.js";
127
- import { _ as _87, a as a2, b as b3, c as c3, d as d3, S, e as e2, u as u2, w } from "./chunks/LpThemeStudio-BpORDvH6.js";
127
+ import { _ as _87, a as a2, b as b3, c as c3, d as d3, S, e as e2, u as u2, w } from "./chunks/LpThemeStudio-zyOH3dF2.js";
128
128
  import { useClipboard } from "./composables/useClipboard.js";
129
129
  import { activeShortcuts, appleModifierLabel, findHotkeyConflicts, hotkeyConflicts, hotkeySteps, isAppleKeyboard, matchesHotkey, mergeShortcutGroups, parseCombo, pendingHotkey, sequenceProgress, useAppleKeyboard, useHotkeys } from "./composables/useHotkeys.js";
130
130
  import { anyModalOpen, topModalElement, useModalLayer } from "./components/modalDepth.js";
@@ -163,10 +163,10 @@ import { useToast } from "./composables/useToast.js";
163
163
  import { _ as _88 } from "./chunks/LpFrame-BVQCQLDI.js";
164
164
  import { _ as _89 } from "./chunks/LpThemeStudioSection-B7bt8Iyg.js";
165
165
  import { isInternalHref, resolveLink } from "./components/link.js";
166
- import { _ as _90 } from "./chunks/LpRepeater-D7ZmnIYd.js";
166
+ import { _ as _90 } from "./chunks/LpRepeater-DghUJ3bc.js";
167
167
  import { _ as _91 } from "./chunks/LpGauge-KZaQ_FDR.js";
168
168
  import { _ as _92 } from "./chunks/LpQuota-Dk2MyI2Z.js";
169
- import { _ as _93 } from "./chunks/LpStatus-DglxttQ7.js";
169
+ import { _ as _93 } from "./chunks/LpStatus-DfZREVe0.js";
170
170
  import { _ as _94 } from "./chunks/LpRelativeTime-DwgCb5-8.js";
171
171
  import { _ as _95 } from "./chunks/LpActivityLine-CJvzrNpj.js";
172
172
  import { TONE_COLOR, TONE_SOFT, arcPath, capacityThresholds, meterFraction, toneAt } from "./components/meter.js";
@@ -175,20 +175,20 @@ import { formatAmount, quotaSegments, quotaState } from "./components/quota.js";
175
175
  import { compactNumber, decimalMark, formatBytes } from "./components/format.js";
176
176
  import { STATUS_LABELS, STATUS_LOOK } from "./components/status.js";
177
177
  import { formatRelativeTime } from "./components/relativeTime.js";
178
- import { _ as _96 } from "./chunks/LpPipeline-B3aOVOkV.js";
179
- import { _ as _97 } from "./chunks/LpStepMark-DpzKVyh8.js";
178
+ import { _ as _96 } from "./chunks/LpPipeline-QCZWWB0G.js";
179
+ import { _ as _97 } from "./chunks/LpStepMark-TaAyuNsH.js";
180
180
  import { childProgress, connectorFilled, currentStepIndex, isSettled, overallStatus, stepElapsed } from "./components/pipeline.js";
181
- import { _ as _98 } from "./chunks/LpLoader-DVwkOmt9.js";
181
+ import { _ as _98 } from "./chunks/LpLoader-DPTNhqLu.js";
182
182
  import { useLoadingGate } from "./composables/useLoadingGate.js";
183
183
  import { useNow } from "./composables/useNow.js";
184
- import { _ as _99 } from "./chunks/LpCountdown-DgeY3cLG.js";
184
+ import { _ as _99 } from "./chunks/LpCountdown-BNNhnQAK.js";
185
185
  import { DEFAULT_MILESTONES, DEFAULT_UNIT_LABELS, ceilToSecond, crossedMilestone, formatElapsed, formatTime, msToNextSecond, splitTime, timeParts } from "./components/timeSpan.js";
186
- import { _ as _100 } from "./chunks/LpCopyButton-xF7wFVoU.js";
187
- import { _ as _101 } from "./chunks/LpSecretField-BbmqM9ye.js";
188
- import { _ as _102 } from "./chunks/LpDataList-Jxyl9H5w.js";
189
- import { _ as _103 } from "./chunks/LpEditable-Cy6GLbFx.js";
190
- import { _ as _104 } from "./chunks/LpTruncate-CflAIwiJ.js";
191
- import { _ as _105 } from "./chunks/LpCodeDisplay-CE9TxCA4.js";
186
+ import { _ as _100 } from "./chunks/LpCopyButton-Cw-lF7Q3.js";
187
+ import { _ as _101 } from "./chunks/LpSecretField-osKPNdNT.js";
188
+ import { _ as _102 } from "./chunks/LpDataList-CcAA-rD9.js";
189
+ import { _ as _103 } from "./chunks/LpEditable-CSuJIPbG.js";
190
+ import { _ as _104 } from "./chunks/LpTruncate-qgKA-3K3.js";
191
+ import { _ as _105 } from "./chunks/LpCodeDisplay-BCghfBFC.js";
192
192
  import { formatRemaining, groupCode, middleTruncate, normalizeCode } from "./components/fieldText.js";
193
193
  import { _ as _106 } from "./chunks/LpHoverCard-spGxyw7T.js";
194
194
  import { _ as _107 } from "./chunks/LpIdenticon-BzdC_qz9.js";
@@ -200,7 +200,7 @@ import { pseudoLocalize } from "./composables/pseudoLocale.js";
200
200
  import { _ as _110 } from "./chunks/LpTextReveal-CtAnOUkp.js";
201
201
  import { _ as _111 } from "./chunks/LpShinyText-DX2ajtWq.js";
202
202
  import { _ as _112 } from "./chunks/LpTypewriter-BCL_a6wE.js";
203
- import { _ as _113 } from "./chunks/LpScramble-Bn9OoOtC.js";
203
+ import { _ as _113 } from "./chunks/LpScramble-DeT_D36q.js";
204
204
  import { useScramble } from "./composables/useScramble.js";
205
205
  import { useInView } from "./composables/useInView.js";
206
206
  import { SCRAMBLE_GLYPHS, graphemes, planScramble, randomGlyph, scrambleFrame, seededRandom, settledCells } from "./components/scramble.js";
@@ -217,7 +217,7 @@ import { spotlightPoint, useSpotlight } from "./composables/useSpotlight.js";
217
217
  import { _ as _119 } from "./chunks/LpCommandScope-CtUhJe8T.js";
218
218
  import { createPaletteRegistry, mergePaletteCommands, usePaletteCommands, vPalette } from "./composables/usePaletteCommands.js";
219
219
  import { u as u3 } from "./chunks/palette-Co8tSASo.js";
220
- import { _ as _120 } from "./chunks/LpPaletteTheme-DiLYKQiZ.js";
220
+ import { _ as _120 } from "./chunks/LpPaletteTheme-CWAIjBCi.js";
221
221
  const UI_CONFIG_VERSION = 1;
222
222
  function serializeConfig(config) {
223
223
  const out = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.72.8",
3
+ "version": "0.73.4",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -33,6 +33,7 @@ export default { inheritAttrs: false }
33
33
  */
34
34
  import { computed, onMounted, ref, useId, watch } from "vue"
35
35
  import { useLabels } from "../composables/useLpConfig"
36
+ import { useScrollMemory } from "../composables/scrollMemory"
36
37
  import LpBottomNav from "./LpBottomNav.vue"
37
38
  import LpButton from "./LpButton.vue"
38
39
  import LpIcon from "./LpIcon.vue"
@@ -59,8 +60,18 @@ const props = withDefaults(
59
60
  * The current route path. Enables the built-in prefix-match active test and
60
61
  * the derived page title without the host wiring its own `isActive`. Pass
61
62
  * `route.path`. Ignored when a custom `isActive` is provided.
63
+ *
64
+ * A new path also places the page in the shell's scroll region: back and
65
+ * forward return to where it was left, a `hash` lands on that anchor
66
+ * (honouring its `scroll-margin-top`), anything else starts at the top.
62
67
  */
63
68
  path?: string
69
+ /**
70
+ * The current route hash (`route.hash`). With `path` it lands a page on
71
+ * its anchor; a new hash on the same path glides there, after a dialog
72
+ * that started the jump has closed.
73
+ */
74
+ hash?: string
64
75
  /**
65
76
  * Custom active test (router-driven nav). Overrides `path`/`modelValue`.
66
77
  */
@@ -177,7 +188,8 @@ const text = useLabels(
177
188
  /*
178
189
  * The page scrolls inside the shell's own region, not the window, so the
179
190
  * router's scroll behaviour never reaches it: without this a route change from
180
- * a scrolled page landed mid-way down the next one.
191
+ * a scrolled page landed mid-way down the next one, and back never returned.
192
+ * A full-bleed page owns its scrolling, so the shell leaves it alone.
181
193
  */
182
194
  const scrollArea = ref<{ viewportEl: HTMLElement | null } | null>(null)
183
195
  const bleedId = `lp-shell-bleed-${useId()}`
@@ -195,9 +207,13 @@ watch(
195
207
  () => props.path,
196
208
  () => {
197
209
  if (drawerOpen.value) drawerOpen.value = false
198
- scrollArea.value?.viewportEl?.scrollTo({ top: 0, left: 0, behavior: "instant" })
199
210
  },
200
211
  )
212
+ useScrollMemory(
213
+ () => (bleeding.value ? null : scrollArea.value?.viewportEl),
214
+ () => props.path,
215
+ () => props.hash,
216
+ )
201
217
 
202
218
  function onSelect(item: SidebarItem) {
203
219
  emit("select", item)
@@ -304,8 +304,10 @@ defineExpose({ resizing })
304
304
  resizing ? 'overflow-hidden' : '',
305
305
  // While the box holds a width its content has outgrown, a flex row would
306
306
  // squeeze its children to fit (a button's keys folding into a column)
307
- // instead of letting the easing edge clip them.
308
- resizing && axis !== 'height' ? '[&>*]:shrink-0' : '',
307
+ // instead of letting the easing edge clip them. A single child that is
308
+ // itself a wrapping row is laid out at the width the box is heading for,
309
+ // or it would break onto a second line until the tween lands.
310
+ resizing && axis !== 'height' ? '[&>*]:shrink-0 [&>*:only-child]:min-w-(--lp-shift-to-w)' : '',
309
311
  resizing && anchor === 'end' ? 'justify-end' : '',
310
312
  ]"
311
313
  >
@@ -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">
@@ -12,7 +12,7 @@
12
12
  import { Motion } from "motion-v"
13
13
  import { computed, onBeforeUnmount, onMounted, ref, useId, watch } from "vue"
14
14
  import { useLabels } from "../composables/useLpConfig"
15
- import { atScrollEnd, scrollParent } from "../composables/scroll"
15
+ import { atScrollEnd, scrollMarginTop, scrollParent, scrollToElement } from "../composables/scroll"
16
16
  import { usePillTransition } from "../composables/usePillTransition"
17
17
 
18
18
  export interface TocLink {
@@ -170,7 +170,9 @@ function onClick(id: string, ev: MouseEvent) {
170
170
  ev.preventDefault()
171
171
  activeId.value = id // mark immediately so the bar jumps to the clicked item
172
172
  suppressSpyUntilScrollEnds(el)
173
- el.scrollIntoView({ behavior: "smooth", block: "start" })
173
+ // Only the nearest scrolling box moves: scrollIntoView also shifted every
174
+ // overflow-hidden ancestor, the shell itself among them.
175
+ scrollToElement(el, { block: "start", behavior: "smooth", margin: scrollMarginTop(el) })
174
176
  // Keep the router's own state: replacing it with null broke scroll
175
177
  // restoration and back/forward bookkeeping in vue-router apps.
176
178
  history.replaceState(history.state, "", `#${id}`)
@@ -46,6 +46,11 @@ export interface ScrollToElementOptions {
46
46
  }
47
47
 
48
48
  /** Scroll the nearest scroller, and only it, so that `el` is in view. */
49
+ /** The element's own `scroll-margin-top`, the gap it asks to keep above it when scrolled to. */
50
+ export function scrollMarginTop(el: Element): number {
51
+ return Number.parseFloat(getComputedStyle(el).scrollMarginTop) || 0
52
+ }
53
+
49
54
  export function scrollToElement(
50
55
  el: Element | null | undefined,
51
56
  { block = "nearest", behavior = "auto", margin = 0 }: ScrollToElementOptions = {},
@@ -0,0 +1,145 @@
1
+ import { nextTick, onBeforeUnmount, onMounted, watch } from "vue"
2
+ import { scrollMarginTop } from "./scroll"
3
+
4
+ /*
5
+ * Where a page lands when the shell's own scroll region changes page. The
6
+ * region is not the window, so a router's scrollBehavior never reaches it, and
7
+ * the shell has to answer the questions a browser answers for a plain site:
8
+ *
9
+ * - back and forward return to where the page was left;
10
+ * - an address with a hash lands on that anchor, and stays on it while the
11
+ * page is still growing (a lazily loaded body, images), until the reader
12
+ * scrolls or a moment has passed;
13
+ * - a new hash on the same page glides there, once a dialog that started the
14
+ * jump (a palette, a menu) has let go of the page;
15
+ * - anything else starts at the top.
16
+ *
17
+ * The router is not needed: what changed is read from the shell's `path` and
18
+ * `hash` props, history position from `popstate`, and positions are keyed by
19
+ * the address the browser shows.
20
+ */
21
+
22
+ const SETTLE_MS = 1500
23
+ const DIALOG_WAIT_MS = 2000
24
+ const SETTLE_STOP = ["wheel", "touchstart", "keydown", "pointerdown"] as const
25
+
26
+ const positions = new Map<string, number>()
27
+
28
+ function addressKey(): string {
29
+ return `${location.pathname}${location.search}`
30
+ }
31
+
32
+ export function useScrollMemory(
33
+ viewport: () => HTMLElement | null | undefined,
34
+ path: () => string | undefined,
35
+ hash: () => string | undefined,
36
+ ): void {
37
+ if (typeof window === "undefined") return
38
+ let current = addressKey()
39
+ let shown = path()
40
+ let travelled = false
41
+ let stopHold: (() => void) | undefined
42
+ let pending = 0
43
+
44
+ const onPop = () => {
45
+ travelled = true
46
+ }
47
+ window.addEventListener("popstate", onPop)
48
+
49
+ function anchor(value: string | undefined): HTMLElement | null {
50
+ if (!value || value === "#") return null
51
+ const id = decodeURIComponent(value.replace(/^#/, ""))
52
+ return document.getElementById(id)
53
+ }
54
+
55
+ function reach(value: string | undefined, behavior: ScrollBehavior): boolean {
56
+ const view = viewport()
57
+ const target = anchor(value)
58
+ if (!view || !target || !view.contains(target)) return false
59
+ const top = target.getBoundingClientRect().top - view.getBoundingClientRect().top + view.scrollTop - scrollMarginTop(target)
60
+ view.scrollTo({ top: Math.max(0, top), behavior })
61
+ return true
62
+ }
63
+
64
+ // Re-applies a landing while the page below it keeps changing size.
65
+ function hold(land: () => void) {
66
+ stopHold?.()
67
+ const view = viewport()
68
+ const content = view?.firstElementChild
69
+ land()
70
+ if (!content) return
71
+ const watcher = new ResizeObserver(land)
72
+ const timer = window.setTimeout(() => stop(), SETTLE_MS)
73
+ const stop = () => {
74
+ watcher.disconnect()
75
+ window.clearTimeout(timer)
76
+ for (const name of SETTLE_STOP) window.removeEventListener(name, stop, true)
77
+ if (stopHold === stop) stopHold = undefined
78
+ }
79
+ for (const name of SETTLE_STOP) window.addEventListener(name, stop, { capture: true, passive: true })
80
+ watcher.observe(content)
81
+ stopHold = stop
82
+ }
83
+
84
+ function arrive() {
85
+ const back = travelled
86
+ travelled = false
87
+ current = addressKey()
88
+ shown = path()
89
+ const view = viewport()
90
+ if (!view) return
91
+ const saved = positions.get(current)
92
+ if (back && saved !== undefined) {
93
+ hold(() => view.scrollTo({ top: saved, behavior: "instant" }))
94
+ return
95
+ }
96
+ if (hash()) {
97
+ hold(() => {
98
+ if (!reach(hash(), "instant")) view.scrollTo({ top: 0, behavior: "instant" })
99
+ })
100
+ return
101
+ }
102
+ stopHold?.()
103
+ view.scrollTo({ top: 0, left: 0, behavior: "instant" })
104
+ }
105
+
106
+ function afterDialogs(run: () => void) {
107
+ const since = performance.now()
108
+ const mine = ++pending
109
+ const tick = () => {
110
+ if (mine !== pending) return
111
+ const locked = document.querySelector('[role="dialog"]') || getComputedStyle(document.body).overflow === "hidden"
112
+ if (locked && performance.now() - since < DIALOG_WAIT_MS) requestAnimationFrame(tick)
113
+ else run()
114
+ }
115
+ requestAnimationFrame(tick)
116
+ }
117
+
118
+ watch(
119
+ path,
120
+ () => {
121
+ const view = viewport()
122
+ if (view) positions.set(current, view.scrollTop)
123
+ void nextTick(() => requestAnimationFrame(arrive))
124
+ },
125
+ { flush: "pre" },
126
+ )
127
+
128
+ watch(hash, (next, previous) => {
129
+ if (next === previous) return
130
+ if (path() !== shown) return
131
+ afterDialogs(() => {
132
+ travelled = false
133
+ if (!reach(next, "smooth")) viewport()?.scrollTo({ top: 0, behavior: "smooth" })
134
+ })
135
+ })
136
+
137
+ onMounted(() => {
138
+ if (hash()) requestAnimationFrame(() => hold(() => void reach(hash(), "instant")))
139
+ })
140
+
141
+ onBeforeUnmount(() => {
142
+ stopHold?.()
143
+ window.removeEventListener("popstate", onPop)
144
+ })
145
+ }
@@ -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