@leavepulse/ui 0.80.1 → 0.81.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.
@@ -5,34 +5,34 @@
5
5
  * pane from its right edge as it grows. Animating the columns themselves made
6
6
  * every line of the list and the pane re-wrap on every frame.
7
7
  */
8
- .lp-cmd-dialog[data-v-63099a19] {
8
+ .lp-cmd-dialog[data-v-b72dc3e1] {
9
9
  --lp-cmd-list: min(92vw, 40rem);
10
10
  --lp-cmd-pane: max(0px, min(26rem, 96vw - var(--lp-cmd-list)));
11
11
  width: var(--lp-cmd-list);
12
12
  }
13
- .lp-cmd-split[data-v-63099a19] {
13
+ .lp-cmd-split[data-v-b72dc3e1] {
14
14
  transition:
15
15
  width 300ms var(--ease-emphasized, ease),
16
16
  min-height 300ms var(--ease-emphasized, ease);
17
17
  }
18
18
  @media (min-width: 768px) {
19
- .lp-cmd-split .lp-cmd-list[data-v-63099a19] {
19
+ .lp-cmd-split .lp-cmd-list[data-v-b72dc3e1] {
20
20
  flex: 0 0 auto;
21
21
  /* the dialog's 1px border on each side */
22
22
  width: calc(var(--lp-cmd-list) - 2px);
23
23
  }
24
- .lp-cmd-dialog[data-pane="open"][data-v-63099a19] {
24
+ .lp-cmd-dialog[data-pane="open"][data-v-b72dc3e1] {
25
25
  width: calc(var(--lp-cmd-list) + var(--lp-cmd-pane));
26
26
  min-height: min(76vh, 36rem);
27
27
  }
28
28
  }
29
- .lp-cmd-pane[data-v-63099a19] {
29
+ .lp-cmd-pane[data-v-b72dc3e1] {
30
30
  width: var(--lp-cmd-pane);
31
31
  }
32
- .lp-cmd-dialog[data-editing] .lp-cmd-pane[data-v-63099a19] {
32
+ .lp-cmd-dialog[data-editing] .lp-cmd-pane[data-v-b72dc3e1] {
33
33
  width: 100%;
34
34
  }
35
- .lp-cmd-dialog[data-editing][data-v-63099a19] {
35
+ .lp-cmd-dialog[data-editing][data-v-b72dc3e1] {
36
36
  width: var(--lp-cmd-list);
37
37
  min-height: 0;
38
38
  }
@@ -40,43 +40,43 @@
40
40
  * The pane rides in from the right edge while the dialog opens up to it, and
41
41
  * leaves the same way, for as long as the dialog takes to close over it.
42
42
  */
43
- .lp-cmd-pane-enter-active[data-v-63099a19],
44
- .lp-cmd-pane-leave-active[data-v-63099a19] {
43
+ .lp-cmd-pane-enter-active[data-v-b72dc3e1],
44
+ .lp-cmd-pane-leave-active[data-v-b72dc3e1] {
45
45
  transition:
46
46
  transform 300ms var(--ease-emphasized, ease),
47
47
  opacity 300ms ease;
48
48
  }
49
- .lp-cmd-pane-enter-from[data-v-63099a19],
50
- .lp-cmd-pane-leave-to[data-v-63099a19] {
49
+ .lp-cmd-pane-enter-from[data-v-b72dc3e1],
50
+ .lp-cmd-pane-leave-to[data-v-b72dc3e1] {
51
51
  opacity: 0;
52
52
  transform: translateX(40%);
53
53
  }
54
- .lp-cmd-para[data-v-63099a19] {
54
+ .lp-cmd-para[data-v-b72dc3e1] {
55
55
  --rise: 4px;
56
56
  animation: rise-in 260ms ease-out both;
57
57
  }
58
58
  @media (prefers-reduced-motion: reduce) {
59
- .lp-cmd-split[data-v-63099a19],
60
- .lp-cmd-pane-enter-active[data-v-63099a19],
61
- .lp-cmd-pane-leave-active[data-v-63099a19] {
59
+ .lp-cmd-split[data-v-b72dc3e1],
60
+ .lp-cmd-pane-enter-active[data-v-b72dc3e1],
61
+ .lp-cmd-pane-leave-active[data-v-b72dc3e1] {
62
62
  transition: none;
63
63
  }
64
- .lp-cmd-para[data-v-63099a19] {
64
+ .lp-cmd-para[data-v-b72dc3e1] {
65
65
  animation: none;
66
66
  }
67
67
  }
68
- .lp-cmd-excerpt[data-v-63099a19] {
68
+ .lp-cmd-excerpt[data-v-b72dc3e1] {
69
69
  display: -webkit-box;
70
70
  -webkit-box-orient: vertical;
71
71
  overflow: hidden;
72
72
  }
73
73
  /* Each result eases in; honours reduced motion. */
74
- .lp-cmd-item[data-v-63099a19] {
74
+ .lp-cmd-item[data-v-b72dc3e1] {
75
75
  --rise: 3px;
76
76
  animation: rise-in 140ms ease-out both;
77
77
  }
78
78
  @media (prefers-reduced-motion: reduce) {
79
- .lp-cmd-item[data-v-63099a19] {
79
+ .lp-cmd-item[data-v-b72dc3e1] {
80
80
  animation: none;
81
81
  }
82
82
  }
@@ -10,7 +10,7 @@ import { _ as _sfc_main$9 } from "./LpBottomNav-4I89sPDZ.js";
10
10
  import { _ as _sfc_main$2 } from "./LpContextSwitcher-CGr6ZAuq.js";
11
11
  import { _ as _sfc_main$3 } from "./LpButton-DSHioIfD.js";
12
12
  import { _ as _sfc_main$4 } from "./LpIcon-BCQLuHSP.js";
13
- import { _ as _sfc_main$8 } from "./LpPageScrollbar-D2O0xhJw.js";
13
+ import { _ as _sfc_main$8 } from "./LpPageScrollbar-C9sTeU-2.js";
14
14
  import { _ as _sfc_main$7 } from "./LpScrollArea-CqvwfZs2.js";
15
15
  import { _ as _sfc_main$5 } from "./LpShift-DAgS_d2x.js";
16
16
  import { _ as _sfc_main$1 } from "./LpSidebar-D3S8rYFf.js";
@@ -29,13 +29,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
29
29
  function scroller() {
30
30
  return doc?.scrollingElement ?? null;
31
31
  }
32
- function nativeBarWidth(d) {
33
- const probe = d.createElement("div");
34
- probe.style.cssText = "position:absolute;top:-9999px;width:100px;height:100px;overflow:scroll";
35
- d.body.append(probe);
36
- const width = probe.offsetWidth - probe.clientWidth;
37
- probe.remove();
38
- return width;
32
+ function finePointer(d) {
33
+ return nodeWindow(d).matchMedia?.("(pointer: fine)").matches ?? false;
39
34
  }
40
35
  function measure() {
41
36
  frame = 0;
@@ -107,7 +102,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
107
102
  onMounted(() => {
108
103
  const host = barEl.value ?? void 0;
109
104
  doc = host?.ownerDocument;
110
- if (!doc || nativeBarWidth(doc) === 0) return;
105
+ if (!doc || !finePointer(doc)) return;
111
106
  active.value = true;
112
107
  releaseNative = hideNativeBar(doc);
113
108
  const win = nodeWindow(doc);
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAppShell-InIK01Nn.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAppShell-BBINfN1v.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -26,7 +26,7 @@ import { parsePaletteQuery, parseCommandQuery, searchCommandItems, applyQueryFil
26
26
  import { PALETTE_MODES, modeChipLabel, settleFetched, fallbackRows, readRecent, resolveRecentItems, recentVisible, commandHasPreview, matchModePrefix, isSafeHref, absoluteHref, commandKey, recentKey, pushRecent, moreRowId, toRecentItem } from "./commandPalette.js";
27
27
  import { useView, usePortalTarget, useOutsideGuard, viewWindow } from "../composables/useView.js";
28
28
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
29
- import '../assets/LpCommandPalette-C1LRFzbN.css';const _hoisted_1 = { class: "relative flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
29
+ import '../assets/LpCommandPalette-DCZB4EAf.css';const _hoisted_1 = { class: "relative flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
30
30
  const _hoisted_2 = ["title", "onClick"];
31
31
  const _hoisted_3 = ["readonly", "aria-label", "aria-expanded", "aria-controls", "aria-activedescendant", "placeholder", "onKeydown"];
32
32
  const _hoisted_4 = { class: "shrink-0 rounded-small border border-line px-1.5 py-0.5 text-[10px] text-muted" };
@@ -1091,6 +1091,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1091
1091
  type: linkOf(c) ? void 0 : "button",
1092
1092
  role: "option",
1093
1093
  tabindex: "-1",
1094
+ "aria-label": c.description ? `${c.label} ${c.description}` : c.label,
1094
1095
  "aria-selected": flatIndexOf(c) === selectedIndex.value,
1095
1096
  "aria-disabled": c.disabled || void 0,
1096
1097
  "data-cmd-index": flatIndexOf(c),
@@ -1169,7 +1170,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1169
1170
  })) : createCommentVNode("", true)
1170
1171
  ]),
1171
1172
  _: 2
1172
- }, 1064, ["id", "href", "type", "aria-selected", "aria-disabled", "data-cmd-index", "disabled", "class", "onClick", "onPointerenter"]);
1173
+ }, 1064, ["id", "href", "type", "aria-label", "aria-selected", "aria-disabled", "data-cmd-index", "disabled", "class", "onClick", "onPointerenter"]);
1173
1174
  }), 128))
1174
1175
  ], 8, _hoisted_12);
1175
1176
  }), 128))
@@ -1363,7 +1364,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
1363
1364
  };
1364
1365
  }
1365
1366
  });
1366
- const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-63099a19"]]);
1367
+ const LpCommandPalette = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-b72dc3e1"]]);
1367
1368
  export {
1368
1369
  LpCommandPalette as default
1369
1370
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpPageScrollbar-D2O0xhJw.js";
1
+ import { _ as _sfc_main } from "../chunks/LpPageScrollbar-C9sTeU-2.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -205,7 +205,7 @@ function install() {
205
205
  window.addEventListener("popstate", afterPop);
206
206
  const push = history.pushState.bind(history);
207
207
  const replace = history.replaceState.bind(history);
208
- history.pushState = function(data, unused, url) {
208
+ const pushed = (data, unused, url) => {
209
209
  const before = location.href;
210
210
  push(data, unused, url);
211
211
  s.href = location.href;
@@ -214,12 +214,26 @@ function install() {
214
214
  if (pathOf(before) !== pathOf(location.href)) leavePage();
215
215
  mute();
216
216
  };
217
- history.replaceState = function(data, unused, url) {
217
+ const replaced = (data, unused, url) => {
218
218
  const mark = s.internal ? null : current();
219
219
  const keep = mark && data && typeof data === "object" && !markOf(data) ? { ...data, [KEY]: mark } : data;
220
220
  replace(keep, unused, url);
221
221
  s.href = location.href;
222
222
  };
223
+ history.pushState = function(data, unused, url) {
224
+ if (!s.internal && s.silent > 0) s.waiting.push(() => pushed(unmarked(data), unused, url));
225
+ else pushed(data, unused, url);
226
+ };
227
+ history.replaceState = function(data, unused, url) {
228
+ if (!s.internal && s.silent > 0) s.waiting.push(() => replaced(unmarked(data), unused, url));
229
+ else replaced(data, unused, url);
230
+ };
231
+ }
232
+ function unmarked(data) {
233
+ if (!markOf(data)) return data;
234
+ const copy = { ...data };
235
+ delete copy[KEY];
236
+ return copy;
223
237
  }
224
238
  function pushEntry(entry) {
225
239
  if (typeof window === "undefined") return;
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { _ as _3 } from "./chunks/LpMapPicker-DW2EnrCJ.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
5
  import { _ as _4 } from "./chunks/LpActionBar-C7g52Cyo.js";
6
6
  import { _ as _5 } from "./chunks/LpAlert-DWFeR82L.js";
7
- import { _ as _6 } from "./chunks/LpAppShell-InIK01Nn.js";
7
+ import { _ as _6 } from "./chunks/LpAppShell-BBINfN1v.js";
8
8
  import { _ as _7 } from "./chunks/LpAutocomplete-DY3LvpB9.js";
9
9
  import { _ as _8 } from "./chunks/LpAvatar-CEZmMcGh.js";
10
10
  import { _ as _9 } from "./chunks/LpBadge-C_VqLHtg.js";
@@ -83,7 +83,7 @@ import { _ as _54 } from "./chunks/LpRadio-CWA5Eb3M.js";
83
83
  import { _ as _55 } from "./chunks/LpRadioGroup-E3SP6j9h.js";
84
84
  import { _ as _56 } from "./chunks/LpRollingText-B54Pje7P.js";
85
85
  import { _ as _57 } from "./chunks/LpScrollArea-CqvwfZs2.js";
86
- import { _ as _58 } from "./chunks/LpPageScrollbar-D2O0xhJw.js";
86
+ import { _ as _58 } from "./chunks/LpPageScrollbar-C9sTeU-2.js";
87
87
  import { _ as _59 } from "./chunks/LpSegmented-DjvrHL_G.js";
88
88
  import { _ as _60 } from "./chunks/LpSection-CghNNxIv.js";
89
89
  import { _ as _61 } from "./chunks/LpSelect-DYPimzAZ.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.80.1",
3
+ "version": "0.81.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -1696,6 +1696,7 @@ const guardOutside = useOutsideGuard()
1696
1696
  :type="linkOf(c) ? undefined : 'button'"
1697
1697
  role="option"
1698
1698
  tabindex="-1"
1699
+ :aria-label="c.description ? `${c.label} ${c.description}` : c.label"
1699
1700
  :aria-selected="flatIndexOf(c) === selectedIndex"
1700
1701
  :aria-disabled="c.disabled || undefined"
1701
1702
  :data-cmd-index="flatIndexOf(c)"
@@ -1713,6 +1714,10 @@ const guardOutside = useOutsideGuard()
1713
1714
  @pointerenter="onPointerEnter(c)"
1714
1715
  @pointerleave="onPointerLeave"
1715
1716
  >
1717
+ <!-- Named from label and description, not from content: split
1718
+ into <mark> parts, a row's text was not always read as its
1719
+ name (BotEco's palette showed nameless options), and the
1720
+ shortcut or prefix kbd is not part of what the row is. -->
1716
1721
  <!-- A picture takes the icon's slot at the icon's footprint, with
1717
1722
  a negative margin so a 20px avatar does not make its row
1718
1723
  taller than its neighbours. -->
@@ -13,8 +13,10 @@
13
13
  * (the bar is not printed, and at rest it is not drawn). What the kit adds is
14
14
  * the drawing, a thumb that drags, and a press beside it that pages.
15
15
  *
16
- * Where the browser's bars already float over the content (phones, macOS set
17
- * to show them while scrolling) it draws nothing and leaves the browser's.
16
+ * With a mouse or touchpad it draws the bar, also where the browser's bars
17
+ * float over the content (macOS, GTK on Linux, Windows 11's overlay setting).
18
+ * With a finger as the main pointer (phones, tablets) it draws nothing and
19
+ * leaves the browser's.
18
20
  *
19
21
  * `top` and `bottom` keep the bar clear of what covers the window's edges: a
20
22
  * sticky header, a bottom bar. Any CSS length, a variable included, and a
@@ -51,14 +53,14 @@ function scroller(): HTMLElement | null {
51
53
  return (doc?.scrollingElement as HTMLElement | null) ?? null
52
54
  }
53
55
 
54
- /* The width the browser's bar takes from the layout: 0 where it floats over the content. */
55
- function nativeBarWidth(d: Document): number {
56
- const probe = d.createElement("div")
57
- probe.style.cssText = "position:absolute;top:-9999px;width:100px;height:100px;overflow:scroll"
58
- d.body.append(probe)
59
- const width = probe.offsetWidth - probe.clientWidth
60
- probe.remove()
61
- return width
56
+ /*
57
+ * Decided by the input, not by the browser's bar: desktop bars float over the
58
+ * content too (GTK's overlay bars on Linux, Windows 11's setting), and there the
59
+ * kit's bar is wanted beside the rest of the kit. A finger has no use for a thumb
60
+ * to drag, so a touch screen keeps the browser's.
61
+ */
62
+ function finePointer(d: Document): boolean {
63
+ return nodeWindow(d).matchMedia?.("(pointer: fine)").matches ?? false
62
64
  }
63
65
 
64
66
  function measure() {
@@ -138,7 +140,7 @@ function onThumbPointerDown(event: PointerEvent) {
138
140
  onMounted(() => {
139
141
  const host = barEl.value ?? undefined
140
142
  doc = host?.ownerDocument
141
- if (!doc || nativeBarWidth(doc) === 0) return
143
+ if (!doc || !finePointer(doc)) return
142
144
  active.value = true
143
145
  releaseNative = hideNativeBar(doc)
144
146
  const win = nodeWindow(doc)
@@ -31,6 +31,10 @@
31
31
  * entries keep working once Back returns past it.
32
32
  * - Someone else's `replaceState` on a kit entry keeps the kit's key, so a
33
33
  * router saving its scroll position does not orphan the entry.
34
+ * - Someone else's `pushState` or `replaceState` while a step the kit started
35
+ * is on its way (closing an overlay and following a link in it in one tick)
36
+ * is held until the step lands, then applied there: the address changes a
37
+ * traversal later than the router's own route does.
34
38
  * - An app that keeps an overlay in its query through `replace` rewrites the
35
39
  * address of the kit's entry when the overlay closes, and the entry under it
36
40
  * still holds the old one. Taking the entry out lands there, so the address
@@ -362,7 +366,7 @@ function install() {
362
366
 
363
367
  const push = history.pushState.bind(history)
364
368
  const replace = history.replaceState.bind(history)
365
- history.pushState = function (data: unknown, unused: string, url?: string | URL | null) {
369
+ const pushed = (data: unknown, unused: string, url?: string | URL | null) => {
366
370
  const before = location.href
367
371
  push(data, unused, url)
368
372
  s.href = location.href
@@ -371,12 +375,35 @@ function install() {
371
375
  if (pathOf(before) !== pathOf(location.href)) leavePage()
372
376
  mute()
373
377
  }
374
- history.replaceState = function (data: unknown, unused: string, url?: string | URL | null) {
378
+ const replaced = (data: unknown, unused: string, url?: string | URL | null) => {
375
379
  const mark = s.internal ? null : current()
376
380
  const keep = mark && data && typeof data === "object" && !markOf(data) ? { ...(data as object), [KEY]: mark } : data
377
381
  replace(keep, unused, url)
378
382
  s.href = location.href
379
383
  }
384
+ history.pushState = function (data: unknown, unused: string, url?: string | URL | null) {
385
+ if (!s.internal && s.silent > 0) s.waiting.push(() => pushed(unmarked(data), unused, url))
386
+ else pushed(data, unused, url)
387
+ }
388
+ history.replaceState = function (data: unknown, unused: string, url?: string | URL | null) {
389
+ if (!s.internal && s.silent > 0) s.waiting.push(() => replaced(unmarked(data), unused, url))
390
+ else replaced(data, unused, url)
391
+ }
392
+ }
393
+
394
+ /*
395
+ * A change written while a step the kit started is on its way (an overlay
396
+ * closed and a link inside it navigated in the same tick) waits for the step to
397
+ * land. Applied at once, the step would then go back from the new entry, and
398
+ * the page would end on the old path. A router builds its state from the entry
399
+ * it sat on, the kit's, so the kit's key is taken off: the entry it lands on is
400
+ * not the kit's any more.
401
+ */
402
+ function unmarked(data: unknown): unknown {
403
+ if (!markOf(data)) return data
404
+ const copy = { ...(data as Record<string, unknown>) }
405
+ delete copy[KEY]
406
+ return copy
380
407
  }
381
408
 
382
409
  /** Record an entry on top of the history, under the current address. */