@leavepulse/ui 0.17.2 → 0.18.1

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 (36) hide show
  1. package/dist/chunks/{LpAddressInput-ChT1JQg0.js → LpAddressInput-Dfyn88Dd.js} +1 -1
  2. package/dist/chunks/{LpAppShell-C6Mj82LS.js → LpAppShell-CiHAD6PN.js} +2 -2
  3. package/dist/chunks/{LpConfirmDialog-3_Qu4hnA.js → LpConfirmDialog-BxBU4Pj9.js} +1 -1
  4. package/dist/chunks/{LpDrawer-Fxk7ViQ-.js → LpDrawer-DaCsNuW3.js} +1 -1
  5. package/dist/chunks/{LpFileTree-CXg3KDth.js → LpFileTree-DXb3yKzH.js} +1 -1
  6. package/dist/chunks/{LpLogViewer-D7DNEYaG.js → LpLogViewer-tmKPNTuU.js} +1 -1
  7. package/dist/chunks/{LpModal-J-KRPO7F.js → LpModal-CYKAaSHO.js} +1 -1
  8. package/dist/chunks/{LpNotificationBell-D8sdQLgu.js → LpNotificationBell-DHze8wYN.js} +1 -1
  9. package/dist/chunks/{LpScrollArea-CKhOx0_l.js → LpScrollArea-DQ0Yudfu.js} +2 -2
  10. package/dist/chunks/{LpSidebar-Bk7SrINq.js → LpSidebar-BBG9NhAn.js} +2 -2
  11. package/dist/chunks/{LpSidebarNav-Cpd-yHXb.js → LpSidebarNav-DSDa5mAY.js} +1 -1
  12. package/dist/chunks/{LpTable-X3ieZmbr.js → LpTable-DqZ-L4Au.js} +1 -1
  13. package/dist/chunks/{LpThemeSwitcher-DC6QygnU.js → LpThemeSwitcher-Ct-lnXMa.js} +16 -2
  14. package/dist/components/LpAddressInput.vue.js +1 -1
  15. package/dist/components/LpAppShell.vue.js +1 -1
  16. package/dist/components/LpCommandPalette.vue.js +1 -1
  17. package/dist/components/LpConfirmDialog.vue.js +1 -1
  18. package/dist/components/LpDrawer.vue.js +1 -1
  19. package/dist/components/LpFileTree.vue.js +1 -1
  20. package/dist/components/LpLogViewer.vue.js +1 -1
  21. package/dist/components/LpModal.vue.js +1 -1
  22. package/dist/components/LpNotificationBell.vue.js +1 -1
  23. package/dist/components/LpScrollArea.vue.js +1 -1
  24. package/dist/components/LpSidebar.vue.js +1 -1
  25. package/dist/components/LpSidebarNav.vue.js +1 -1
  26. package/dist/components/LpTable.vue.js +1 -1
  27. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +17 -15
  30. package/dist/theme/useTheme.d.ts +10 -0
  31. package/package.json +1 -1
  32. package/src/components/LpScrollArea.vue +8 -2
  33. package/src/index.ts +2 -0
  34. package/src/nuxt.ts +33 -7
  35. package/src/runtime/theme.plugin.ts +62 -0
  36. package/src/theme/useTheme.ts +41 -0
@@ -6,7 +6,7 @@ import { _ as _sfc_main$3 } from "./LpButton-5VXXCjwF.js";
6
6
  import { _ as _sfc_main$1 } from "./LpFormField-Bn-ZwC6z.js";
7
7
  import { _ as _sfc_main$4 } from "./LpIcon-CCnX5_2j.js";
8
8
  import { _ as _sfc_main$5 } from "./LpInput-BauZRyzm.js";
9
- import { _ as _sfc_main$7 } from "./LpModal-J-KRPO7F.js";
9
+ import { _ as _sfc_main$7 } from "./LpModal-CYKAaSHO.js";
10
10
  import { _ as _sfc_main$6 } from "./LpSelect-D5mU4cZO.js";
11
11
  const _hoisted_1 = { class: "flex flex-col gap-3" };
12
12
  const _hoisted_2 = { class: "flex items-stretch gap-2" };
@@ -1,8 +1,8 @@
1
1
  import { defineComponent, computed, watch, openBlock, createElementBlock, createVNode, createSlots, withCtx, renderSlot, normalizeProps, guardReactiveProps, createElementVNode, normalizeClass, toDisplayString, createCommentVNode, createBlock } from "vue";
2
2
  import { _ as _sfc_main$2 } from "./LpButton-5VXXCjwF.js";
3
3
  import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
4
- import { _ as _sfc_main$4 } from "./LpScrollArea-CKhOx0_l.js";
5
- import { _ as _sfc_main$1 } from "./LpSidebar-Bk7SrINq.js";
4
+ import { _ as _sfc_main$4 } from "./LpScrollArea-DQ0Yudfu.js";
5
+ import { _ as _sfc_main$1 } from "./LpSidebar-BBG9NhAn.js";
6
6
  const _hoisted_1 = { class: "flex h-dvh overflow-hidden bg-surface text-ink" };
7
7
  const _hoisted_2 = { class: "flex min-w-0 flex-1 flex-col" };
8
8
  const _hoisted_3 = { class: "flex h-16 shrink-0 items-center gap-3 border-b border-line bg-surface px-4 md:px-6" };
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, openBlock, createBlock, withCtx, renderSlot, createVNode, createTextVNode, toDisplayString } from "vue";
2
2
  import { _ as _sfc_main$2 } from "./LpButton-5VXXCjwF.js";
3
- import { _ as _sfc_main$1 } from "./LpModal-J-KRPO7F.js";
3
+ import { _ as _sfc_main$1 } from "./LpModal-CYKAaSHO.js";
4
4
  const _sfc_main = /* @__PURE__ */ defineComponent({
5
5
  __name: "LpConfirmDialog",
6
6
  props: {
@@ -3,7 +3,7 @@ import { usePointerSwipe } from "@vueuse/core";
3
3
  import { DrawerRoot, DrawerPortal, DrawerOverlay, DrawerContent, DrawerHandle, DrawerTitle, DrawerDescription, DrawerClose } from "vaul-vue";
4
4
  import { CLOSE_ICON } from "../components/dropdown.js";
5
5
  import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
6
- import { _ as _sfc_main$2 } from "./LpScrollArea-CKhOx0_l.js";
6
+ import { _ as _sfc_main$2 } from "./LpScrollArea-DQ0Yudfu.js";
7
7
  const _hoisted_1 = { class: "text-base font-semibold text-ink" };
8
8
  const _hoisted_2 = { class: "flex flex-col gap-1" };
9
9
  const _hoisted_3 = ["data-vaul-no-drag"];
@@ -2,7 +2,7 @@ import { defineComponent, ref, watch, computed, openBlock, createElementBlock, F
2
2
  import { formatSize, sortNodes, computeStats, subtreeIds, ancestorIds } from "../components/fileTree.js";
3
3
  import { _ as _sfc_main$1 } from "./LpEmptyState-CrfyRJUG.js";
4
4
  import { _ as _sfc_main$3 } from "./LpFileTreeNode-DFg4PhOK.js";
5
- import { _ as _sfc_main$2 } from "./LpScrollArea-CKhOx0_l.js";
5
+ import { _ as _sfc_main$2 } from "./LpScrollArea-DQ0Yudfu.js";
6
6
  const _hoisted_1 = {
7
7
  key: 0,
8
8
  class: "flex h-full w-full min-w-0 flex-col gap-1 overflow-hidden"
@@ -1,7 +1,7 @@
1
1
  import { defineComponent, computed, ref, watch, nextTick, onMounted, onBeforeUnmount, openBlock, createElementBlock, createVNode, normalizeStyle, withCtx, createElementVNode, toDisplayString, createCommentVNode, Fragment, createBlock, resolveDynamicComponent, TransitionGroup, mergeProps, renderList, normalizeClass, createTextVNode, Transition } from "vue";
2
2
  import { _ as _sfc_main$3 } from "./LpContextMenu-D0mzXqA7.js";
3
3
  import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
4
- import { _ as _sfc_main$1 } from "./LpScrollArea-CKhOx0_l.js";
4
+ import { _ as _sfc_main$1 } from "./LpScrollArea-DQ0Yudfu.js";
5
5
  const _hoisted_1 = { class: "relative overflow-hidden rounded-card border border-line bg-surface font-mono text-xs leading-relaxed" };
6
6
  const _hoisted_2 = {
7
7
  key: 0,
@@ -2,7 +2,7 @@ import { defineComponent, computed, useSlots, openBlock, createBlock, unref, wit
2
2
  import { DialogRoot, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription, DialogClose } from "reka-ui";
3
3
  import { CLOSE_ICON } from "../components/dropdown.js";
4
4
  import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
5
- import { _ as _sfc_main$2 } from "./LpScrollArea-CKhOx0_l.js";
5
+ import { _ as _sfc_main$2 } from "./LpScrollArea-DQ0Yudfu.js";
6
6
  const _hoisted_1 = { class: "fixed inset-0 z-(--z-modal) flex items-center justify-center pointer-events-none" };
7
7
  const _hoisted_2 = {
8
8
  key: 0,
@@ -5,7 +5,7 @@ import { _ as _sfc_main$4 } from "./LpContextMenu-D0mzXqA7.js";
5
5
  import { _ as _sfc_main$2 } from "./LpEmptyState-CrfyRJUG.js";
6
6
  import { _ as _sfc_main$5 } from "./LpIcon-CCnX5_2j.js";
7
7
  import { _ as _sfc_main$1 } from "./LpPopover-8268UmlC.js";
8
- import { _ as _sfc_main$3 } from "./LpScrollArea-CKhOx0_l.js";
8
+ import { _ as _sfc_main$3 } from "./LpScrollArea-DQ0Yudfu.js";
9
9
  const _hoisted_1 = { class: "flex items-center justify-between gap-2 px-3 py-2.5" };
10
10
  const _hoisted_2 = { class: "text-sm font-semibold" };
11
11
  const _hoisted_3 = {
@@ -30,7 +30,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
30
30
  style: normalizeStyle(__props.barInsetTop ? { marginTop: __props.barInsetTop } : void 0)
31
31
  }, {
32
32
  default: withCtx(() => [
33
- createVNode(unref(ScrollAreaThumb), { class: "w-1 rounded-l-pill bg-brand/40 transition-[width,background-color] duration-[var(--duration-fast)] group-hover:w-1.5 group-hover:bg-brand" })
33
+ createVNode(unref(ScrollAreaThumb), { class: "min-w-1 rounded-l-pill bg-brand/40 transition-[min-width,background-color] duration-[var(--duration-fast)] group-hover:min-w-1.5 group-hover:bg-brand" })
34
34
  ]),
35
35
  _: 1
36
36
  }, 8, ["style"]),
@@ -39,7 +39,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
39
39
  class: normalizeClass(["group flex h-3.5 flex-col touch-none select-none justify-end px-px pb-px pt-2.5", barFade])
40
40
  }, {
41
41
  default: withCtx(() => [
42
- createVNode(unref(ScrollAreaThumb), { class: "h-1 rounded-t-pill bg-brand/40 transition-[height,background-color] duration-[var(--duration-fast)] group-hover:h-1.5 group-hover:bg-brand" })
42
+ createVNode(unref(ScrollAreaThumb), { class: "min-h-1 rounded-t-pill bg-brand/40 transition-[min-height,background-color] duration-[var(--duration-fast)] group-hover:min-h-1.5 group-hover:bg-brand" })
43
43
  ]),
44
44
  _: 1
45
45
  }),
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, computed, useId, openBlock, createElementBlock, Fragment, createElementVNode, mergeProps, normalizeClass, renderSlot, createCommentVNode, createVNode, createSlots, withCtx, normalizeProps, guardReactiveProps, createBlock } from "vue";
2
- import { _ as _sfc_main$2 } from "./LpDrawer-Fxk7ViQ-.js";
3
- import { _ as _sfc_main$1 } from "./LpSidebarNav-Cpd-yHXb.js";
2
+ import { _ as _sfc_main$2 } from "./LpDrawer-DaCsNuW3.js";
3
+ import { _ as _sfc_main$1 } from "./LpSidebarNav-DSDa5mAY.js";
4
4
  const _hoisted_1 = {
5
5
  key: 1,
6
6
  class: "mt-2 flex shrink-0 flex-col gap-2"
@@ -4,7 +4,7 @@ import { usePillTransition } from "../composables/usePillTransition.js";
4
4
  import { _ as _sfc_main$4 } from "./LpBadge-CXzBPnwO.js";
5
5
  import { _ as _sfc_main$2 } from "./LpContextMenu-D0mzXqA7.js";
6
6
  import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
7
- import { _ as _sfc_main$1 } from "./LpScrollArea-CKhOx0_l.js";
7
+ import { _ as _sfc_main$1 } from "./LpScrollArea-DQ0Yudfu.js";
8
8
  const _hoisted_1 = {
9
9
  key: 0,
10
10
  class: "flex min-h-0 flex-1 flex-col gap-4"
@@ -2,7 +2,7 @@ import { defineComponent, computed, ref, watch, onBeforeUnmount, openBlock, crea
2
2
  import { _ as _sfc_main$2 } from "./LpCheckbox-BO0zuuPh.js";
3
3
  import { _ as _sfc_main$4 } from "./LpContextMenu-D0mzXqA7.js";
4
4
  import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
5
- import { _ as _sfc_main$1 } from "./LpScrollArea-CKhOx0_l.js";
5
+ import { _ as _sfc_main$1 } from "./LpScrollArea-DQ0Yudfu.js";
6
6
  const _hoisted_1 = { class: "w-full border-collapse text-sm" };
7
7
  const _hoisted_2 = { class: "border-b border-line bg-surface-soft" };
8
8
  const _hoisted_3 = {
@@ -429,6 +429,18 @@ function cacheTheme(theme) {
429
429
  localStorage.setItem(CACHE_KEY, serializeTheme(theme));
430
430
  } catch {
431
431
  }
432
+ writeThemeCookie(theme.name);
433
+ }
434
+ const THEME_COOKIE = "lp-theme";
435
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
436
+ function writeThemeCookie(name) {
437
+ if (typeof document === "undefined") return;
438
+ document.cookie = `${THEME_COOKIE}=${encodeURIComponent(name)}; path=/; max-age=${COOKIE_MAX_AGE}; samesite=lax`;
439
+ }
440
+ function themeFromCookie(cookieValue, themes) {
441
+ if (!cookieValue) return null;
442
+ const name = decodeURIComponent(cookieValue);
443
+ return themes.find((theme) => theme.name === name) ?? null;
432
444
  }
433
445
  function useTheme() {
434
446
  hydrateOnce();
@@ -588,6 +600,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
588
600
  });
589
601
  export {
590
602
  DEFAULT_SURFACE as D,
603
+ THEME_COOKIE as T,
591
604
  _sfc_main as _,
592
605
  parseTheme as a,
593
606
  amber as b,
@@ -597,13 +610,14 @@ export {
597
610
  dark as f,
598
611
  light as g,
599
612
  lime as h,
600
- themeToCssVars as i,
613
+ themeToCssRule as i,
614
+ themeToCssVars as j,
601
615
  leavepulse as l,
602
616
  nord as n,
603
617
  presets as p,
604
618
  rose as r,
605
619
  serializeTheme as s,
606
- themeToCssRule as t,
620
+ themeFromCookie as t,
607
621
  useTheme as u,
608
622
  violet as v
609
623
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAddressInput-ChT1JQg0.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAddressInput-Dfyn88Dd.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpAppShell-C6Mj82LS.js";
1
+ import { _ as _sfc_main } from "../chunks/LpAppShell-CiHAD6PN.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,7 +1,7 @@
1
1
  import { defineComponent, ref, computed, watch, nextTick, onMounted, onBeforeUnmount, openBlock, createBlock, unref, withCtx, createVNode, withModifiers, createTextVNode, createElementVNode, withDirectives, withKeys, vModelText, createElementBlock, toDisplayString, createCommentVNode, Fragment, renderList, normalizeClass } from "vue";
2
2
  import { DialogRoot, DialogPortal, DialogOverlay, DialogContent, DialogTitle, DialogDescription } from "reka-ui";
3
3
  import { _ as _sfc_main$1 } from "../chunks/LpIcon-CCnX5_2j.js";
4
- import { _ as _sfc_main$2 } from "../chunks/LpScrollArea-CKhOx0_l.js";
4
+ import { _ as _sfc_main$2 } from "../chunks/LpScrollArea-DQ0Yudfu.js";
5
5
  import '../assets/LpCommandPalette-D315B47-.css';const _hoisted_1 = { class: "flex shrink-0 items-center gap-2.5 border-b border-line px-4" };
6
6
  const _hoisted_2 = ["placeholder", "onKeydown"];
7
7
  const _hoisted_3 = {
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpConfirmDialog-3_Qu4hnA.js";
1
+ import { _ as _sfc_main } from "../chunks/LpConfirmDialog-BxBU4Pj9.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpDrawer-Fxk7ViQ-.js";
1
+ import { _ as _sfc_main } from "../chunks/LpDrawer-DaCsNuW3.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpFileTree-CXg3KDth.js";
1
+ import { _ as _sfc_main } from "../chunks/LpFileTree-DXb3yKzH.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpLogViewer-D7DNEYaG.js";
1
+ import { _ as _sfc_main } from "../chunks/LpLogViewer-tmKPNTuU.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpModal-J-KRPO7F.js";
1
+ import { _ as _sfc_main } from "../chunks/LpModal-CYKAaSHO.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpNotificationBell-D8sdQLgu.js";
1
+ import { _ as _sfc_main } from "../chunks/LpNotificationBell-DHze8wYN.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpScrollArea-CKhOx0_l.js";
1
+ import { _ as _sfc_main } from "../chunks/LpScrollArea-DQ0Yudfu.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebar-Bk7SrINq.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebar-BBG9NhAn.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebarNav-Cpd-yHXb.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebarNav-DSDa5mAY.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTable-X3ieZmbr.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTable-DqZ-L4Au.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-DC6QygnU.js";
1
+ import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-Ct-lnXMa.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
package/dist/index.d.ts CHANGED
@@ -96,7 +96,7 @@ export { blockTitle, defineBlocks } from './layout/registry';
96
96
  export type { BlockDef, BlockRegistry } from './layout/registry';
97
97
  export { useLayout } from './layout/useLayout';
98
98
  export type { UseLayout, UseLayoutOptions } from './layout/useLayout';
99
- export { applyTheme, applyThemeWithTransition, bootstrapTheme, parseTheme, serializeTheme, themeToCssRule, themeToCssVars, useTheme, } from './theme/useTheme';
99
+ export { applyTheme, applyThemeWithTransition, bootstrapTheme, parseTheme, serializeTheme, THEME_COOKIE, themeFromCookie, themeToCssRule, themeToCssVars, useTheme, } from './theme/useTheme';
100
100
  export type { RevealOrigin } from './theme/useTheme';
101
101
  export { DEFAULT_SURFACE } from './theme/tokens';
102
102
  export { defineTheme } from './theme/define';
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { _ } from "./chunks/LayoutCanvas-Dd20boUG.js";
2
- import { _ as _2 } from "./chunks/LpAddressInput-ChT1JQg0.js";
2
+ import { _ as _2 } from "./chunks/LpAddressInput-Dfyn88Dd.js";
3
3
  import { _ as _3 } from "./chunks/LpMapPicker-DFKrd1Ne.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
5
  import { _ as _4 } from "./chunks/LpAlert-BAYHDSS1.js";
6
- import { _ as _5 } from "./chunks/LpAppShell-C6Mj82LS.js";
6
+ import { _ as _5 } from "./chunks/LpAppShell-CiHAD6PN.js";
7
7
  import { _ as _6 } from "./chunks/LpAutocomplete-DIgXmtCA.js";
8
8
  import { _ as _7 } from "./chunks/LpAvatar-B1Ez0ZAx.js";
9
9
  import { _ as _8 } from "./chunks/LpBadge-CXzBPnwO.js";
@@ -14,15 +14,15 @@ import { _ as _12 } from "./chunks/LpCard-BmrTvWOe.js";
14
14
  import { _ as _13 } from "./chunks/LpCheckbox-BO0zuuPh.js";
15
15
  import { _ as _14 } from "./chunks/LpCodeBlock-CSiOSPs8.js";
16
16
  import { default as default2 } from "./components/LpCommandPalette.vue.js";
17
- import { _ as _15 } from "./chunks/LpConfirmDialog-3_Qu4hnA.js";
17
+ import { _ as _15 } from "./chunks/LpConfirmDialog-BxBU4Pj9.js";
18
18
  import { _ as _16 } from "./chunks/LpContextMenu-D0mzXqA7.js";
19
19
  import { _ as _17 } from "./chunks/LpDatePicker-dY2q4dr4.js";
20
20
  import { _ as _18 } from "./chunks/LpDisclosure-DP6yfgfM.js";
21
21
  import { _ as _19 } from "./chunks/LpDivider-CUX46KR0.js";
22
- import { _ as _20 } from "./chunks/LpDrawer-Fxk7ViQ-.js";
22
+ import { _ as _20 } from "./chunks/LpDrawer-DaCsNuW3.js";
23
23
  import { _ as _21 } from "./chunks/LpDropdownMenu-DJpoqRCb.js";
24
24
  import { _ as _22 } from "./chunks/LpEmptyState-CrfyRJUG.js";
25
- import { _ as _23 } from "./chunks/LpFileTree-CXg3KDth.js";
25
+ import { _ as _23 } from "./chunks/LpFileTree-DXb3yKzH.js";
26
26
  import { ancestorIds, checkStateOf, checkedCount, computeStats, fileIcon, formatModified, formatSize, sortNodes, subtreeIds, subtreeSize } from "./components/fileTree.js";
27
27
  import { _ as _24 } from "./chunks/LpFormField-Bn-ZwC6z.js";
28
28
  import { _ as _25 } from "./chunks/LpIcon-CCnX5_2j.js";
@@ -31,9 +31,9 @@ import { _ as _27 } from "./chunks/LpInput-BauZRyzm.js";
31
31
  import { _ as _28 } from "./chunks/LpLightbox-CWdIsthW.js";
32
32
  import { fileNameOf } from "./components/lightbox.js";
33
33
  import { _ as _29 } from "./chunks/LpLink-DHwOEp4e.js";
34
- import { _ as _30 } from "./chunks/LpLogViewer-D7DNEYaG.js";
35
- import { _ as _31 } from "./chunks/LpModal-J-KRPO7F.js";
36
- import { _ as _32 } from "./chunks/LpNotificationBell-D8sdQLgu.js";
34
+ import { _ as _30 } from "./chunks/LpLogViewer-tmKPNTuU.js";
35
+ import { _ as _31 } from "./chunks/LpModal-CYKAaSHO.js";
36
+ import { _ as _32 } from "./chunks/LpNotificationBell-DHze8wYN.js";
37
37
  import { _ as _33 } from "./chunks/LpNumberField-C5ar-OwE.js";
38
38
  import { _ as _34 } from "./chunks/LpOtpInput-DqaT0Z75.js";
39
39
  import { _ as _35 } from "./chunks/LpPagination-CeGuNe_q.js";
@@ -44,22 +44,22 @@ import { _ as _38 } from "./chunks/LpPopover-8268UmlC.js";
44
44
  import { _ as _39 } from "./chunks/LpProgress-BhI0IbjC.js";
45
45
  import { _ as _40 } from "./chunks/LpRadio-CL-pI0_O.js";
46
46
  import { _ as _41 } from "./chunks/LpRadioGroup-B4yDTmi6.js";
47
- import { _ as _42 } from "./chunks/LpScrollArea-CKhOx0_l.js";
47
+ import { _ as _42 } from "./chunks/LpScrollArea-DQ0Yudfu.js";
48
48
  import { _ as _43 } from "./chunks/LpSegmented-BmpanyAo.js";
49
49
  import { _ as _44 } from "./chunks/LpSelect-D5mU4cZO.js";
50
50
  import { _ as _45 } from "./chunks/LpServiceNode-Dc-Ut5RS.js";
51
- import { _ as _46 } from "./chunks/LpSidebar-Bk7SrINq.js";
51
+ import { _ as _46 } from "./chunks/LpSidebar-BBG9NhAn.js";
52
52
  import { _ as _47 } from "./chunks/LpSkeleton-eSFIo4kD.js";
53
53
  import { _ as _48 } from "./chunks/LpSlider-B1SF3pRV.js";
54
54
  import { _ as _49 } from "./chunks/LpStat-CdbEFOvn.js";
55
55
  import { _ as _50 } from "./chunks/LpStepper-5dFTrW21.js";
56
56
  import { _ as _51 } from "./chunks/LpSwitch-v6fnccSM.js";
57
- import { _ as _52 } from "./chunks/LpTable-X3ieZmbr.js";
57
+ import { _ as _52 } from "./chunks/LpTable-DqZ-L4Au.js";
58
58
  import { _ as _53 } from "./chunks/LpTableOfContents-CjvxPMpU.js";
59
59
  import { _ as _54 } from "./chunks/LpTabs-D7aTVPOb.js";
60
60
  import { _ as _55 } from "./chunks/LpTextarea-CRAkhemZ.js";
61
- import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-DC6QygnU.js";
62
- import { D, _ as _56, b, c, d, e, f, l, g, h, n, r, t, i, u, v } from "./chunks/LpThemeSwitcher-DC6QygnU.js";
61
+ import { p as presets, a as parseTheme, s as serializeTheme } from "./chunks/LpThemeSwitcher-Ct-lnXMa.js";
62
+ import { D, _ as _56, T, b, c, d, e, f, l, g, h, n, r, t, i, j, u, v } from "./chunks/LpThemeSwitcher-Ct-lnXMa.js";
63
63
  import { _ as _57 } from "./chunks/LpTilt-DuyKbpkA.js";
64
64
  import { _ as _58 } from "./chunks/LpToaster-gDB1EaU3.js";
65
65
  import { _ as _59 } from "./chunks/LpTooltip-CIwoRCag.js";
@@ -168,6 +168,7 @@ export {
168
168
  _60 as LpTopologyCanvas,
169
169
  _61 as LpUptimeBar,
170
170
  OSM_TILES,
171
+ T as THEME_COOKIE,
171
172
  UI_CONFIG_VERSION,
172
173
  addLeaf,
173
174
  addTab,
@@ -218,8 +219,9 @@ export {
218
219
  sortNodes,
219
220
  subtreeIds,
220
221
  subtreeSize,
221
- t as themeToCssRule,
222
- i as themeToCssVars,
222
+ t as themeFromCookie,
223
+ i as themeToCssRule,
224
+ j as themeToCssVars,
223
225
  tileUrl,
224
226
  unproject,
225
227
  useInputFilter,
@@ -15,6 +15,16 @@ export declare function themeToCssRule(theme: TokenSet, selector?: string): stri
15
15
  export declare function parseTheme(json: string): TokenSet;
16
16
  export declare function bootstrapTheme(fallback: TokenSet): TokenSet;
17
17
  export declare function cacheTheme(theme: TokenSet): void;
18
+ export declare const THEME_COOKIE = "lp-theme";
19
+ /**
20
+ * Resolve a theme name (as read from the `lp-theme` cookie) against a set of
21
+ * known themes. Returns `null` when the name is absent or unknown, so the caller
22
+ * can fall back to its own default.
23
+ *
24
+ * Server-side counterpart to `cacheTheme`: pair it with `themeToCssRule` to
25
+ * inline the right variables into `<head>` during SSR.
26
+ */
27
+ export declare function themeFromCookie(cookieValue: string | null | undefined, themes: readonly TokenSet[]): TokenSet | null;
18
28
  export interface UseTheme {
19
29
  /** The active theme, kept in sync by the kit. Null until hydrated (SSR/first
20
30
  * paint) — reactive, so a switcher binds straight to it. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.17.2",
3
+ "version": "0.18.1",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -83,8 +83,14 @@ const barFade =
83
83
  :class="barFade"
84
84
  :style="barInsetTop ? { marginTop: barInsetTop } : undefined"
85
85
  >
86
+ <!-- Sized with min-width, not width: reka sets an inline
87
+ `width: var(--reka-scroll-area-thumb-width)` on the thumb, and for a
88
+ VERTICAL bar it only ever defines the height variable — the width one
89
+ stays empty, so the inline rule resolves to 0 and beat a plain `w-1`,
90
+ leaving an invisible thumb. A minimum wins over that without fighting
91
+ the positioning reka owns. -->
86
92
  <ScrollAreaThumb
87
- class="w-1 rounded-l-pill bg-brand/40 transition-[width,background-color] duration-[var(--duration-fast)] group-hover:w-1.5 group-hover:bg-brand"
93
+ class="min-w-1 rounded-l-pill bg-brand/40 transition-[min-width,background-color] duration-[var(--duration-fast)] group-hover:min-w-1.5 group-hover:bg-brand"
88
94
  />
89
95
  </ScrollAreaScrollbar>
90
96
 
@@ -94,7 +100,7 @@ const barFade =
94
100
  :class="barFade"
95
101
  >
96
102
  <ScrollAreaThumb
97
- class="h-1 rounded-t-pill bg-brand/40 transition-[height,background-color] duration-[var(--duration-fast)] group-hover:h-1.5 group-hover:bg-brand"
103
+ class="min-h-1 rounded-t-pill bg-brand/40 transition-[min-height,background-color] duration-[var(--duration-fast)] group-hover:min-h-1.5 group-hover:bg-brand"
98
104
  />
99
105
  </ScrollAreaScrollbar>
100
106
 
package/src/index.ts CHANGED
@@ -158,6 +158,8 @@ export {
158
158
  bootstrapTheme,
159
159
  parseTheme,
160
160
  serializeTheme,
161
+ THEME_COOKIE,
162
+ themeFromCookie,
161
163
  themeToCssRule,
162
164
  themeToCssVars,
163
165
  useTheme,
package/src/nuxt.ts CHANGED
@@ -1,20 +1,32 @@
1
1
  /*
2
2
  * Nuxt module — auto-registers every kit component globally so consuming apps
3
3
  * use <LpButton> / <LpThemeSwitcher> / … with no per-app component list to
4
- * maintain. Add it to a Nuxt app's `modules`:
4
+ * maintain, and wires the SSR theme handoff. Add it to a Nuxt app's `modules`:
5
5
  *
6
6
  * export default defineNuxtConfig({ modules: ["@leavepulse/ui/nuxt"] })
7
7
  *
8
- * The list comes from the generated component-names file (kept in sync with the
9
- * actual components at build), so a NEW component is available everywhere with
10
- * no consumer change.
8
+ * The component list comes from the generated component-names file (kept in sync
9
+ * with the actual components at build), so a NEW component is available
10
+ * everywhere with no consumer change.
11
+ *
12
+ * Options (nuxt.config `leavepulseUi`):
13
+ * theme false to skip the theme plugin entirely (default: true)
14
+ * defaultTheme preset name used when the visitor has no saved theme
11
15
  */
12
- import { addComponent, defineNuxtModule } from "@nuxt/kit"
16
+ import { addComponent, addPlugin, createResolver, defineNuxtModule } from "@nuxt/kit"
13
17
  import { COMPONENT_NAMES } from "./component-names"
14
18
 
15
- export default defineNuxtModule({
19
+ export interface LeavepulseUiOptions {
20
+ /** Register the SSR theme plugin. Disable to wire theming yourself. */
21
+ theme?: boolean
22
+ /** Preset name to fall back to when nothing is saved (default "LeavePulse"). */
23
+ defaultTheme?: string
24
+ }
25
+
26
+ export default defineNuxtModule<LeavepulseUiOptions>({
16
27
  meta: { name: "@leavepulse/ui", configKey: "leavepulseUi" },
17
- setup() {
28
+ defaults: { theme: true },
29
+ setup(options: LeavepulseUiOptions, nuxt: { options: { runtimeConfig: { public: Record<string, unknown> } } }) {
18
30
  for (const name of COMPONENT_NAMES) {
19
31
  // Point each component at its own dist chunk rather than the barrel
20
32
  // ("@leavepulse/ui"). Importing through the barrel makes the bundler pull
@@ -27,5 +39,19 @@ export default defineNuxtModule({
27
39
  filePath: `@leavepulse/ui/dist/components/${name}.vue.js`,
28
40
  })
29
41
  }
42
+
43
+ if (options.theme === false) return
44
+
45
+ // The plugin reads its options from runtimeConfig rather than closing over
46
+ // them, so they survive the build into the server bundle.
47
+ nuxt.options.runtimeConfig.public.leavepulseUi = {
48
+ ...(nuxt.options.runtimeConfig.public.leavepulseUi ?? {}),
49
+ defaultTheme: options.defaultTheme,
50
+ }
51
+
52
+ addPlugin({
53
+ src: createResolver(import.meta.url).resolve("./runtime/theme.plugin"),
54
+ mode: "all",
55
+ })
30
56
  },
31
57
  })
@@ -0,0 +1,62 @@
1
+ /*
2
+ * Nuxt runtime plugin — paints the user's theme during SSR, so a reload shows
3
+ * the right colours in the first frame instead of flashing the default.
4
+ *
5
+ * Registered automatically by the kit's Nuxt module; a consuming app needs no
6
+ * plugin of its own. Opt out (or point it at custom themes) via nuxt.config:
7
+ *
8
+ * leavepulseUi: { theme: false } // no theme handling at all
9
+ * leavepulseUi: { defaultTheme: "Nord" } // different fallback preset
10
+ *
11
+ * How it avoids the flash: the kit mirrors the chosen theme's NAME into the
12
+ * `lp-theme` cookie (see cacheTheme), which unlike localStorage travels with the
13
+ * request. Here the server reads it, resolves it against the built-in presets,
14
+ * and inlines that theme's CSS variables into <head>. On the client
15
+ * bootstrapTheme() still runs before mount, so a localStorage choice — which
16
+ * holds the full token values, including themes the server cannot name — keeps
17
+ * winning.
18
+ */
19
+ // Imported through the package barrel on purpose. This file is shipped as
20
+ // source and compiled by the CONSUMING app, where "@leavepulse/ui" resolves to
21
+ // dist — the same module instance the kit's components use. A relative "../theme/…"
22
+ // would load a second copy of the theme singleton from src/, leaving components
23
+ // subscribed to a different reactive state than the one this plugin drives.
24
+ import {
25
+ bootstrapTheme,
26
+ leavepulse,
27
+ presets,
28
+ THEME_COOKIE,
29
+ themeFromCookie,
30
+ themeToCssRule,
31
+ type TokenSet,
32
+ } from "@leavepulse/ui"
33
+ // "#imports" is Nuxt's virtual module: it resolves in the consuming app, which
34
+ // is what compiles this file. (No @ts-expect-error here — the kit's own build
35
+ // excludes src/runtime, so the directive would be unused there and error in the
36
+ // consumer, where the import type-checks fine.)
37
+ import { defineNuxtPlugin, useCookie, useHead, useRuntimeConfig } from "#imports"
38
+
39
+ export default defineNuxtPlugin(() => {
40
+ const options = (useRuntimeConfig().public.leavepulseUi ?? {}) as {
41
+ defaultTheme?: string
42
+ }
43
+
44
+ const themes = Object.values(presets) as TokenSet[]
45
+ const fallback =
46
+ themes.find((theme) => theme.name === options.defaultTheme) ?? leavepulse
47
+
48
+ // Read-only here: the cookie is written by cacheTheme() in the browser, which
49
+ // is the one place a theme choice is recorded.
50
+ const cookie = useCookie<string | null>(THEME_COOKIE, { readonly: true })
51
+ const initial = themeFromCookie(cookie.value, themes) ?? fallback
52
+
53
+ useHead({
54
+ style: [{ id: "lp-ui-theme", innerHTML: themeToCssRule(initial) }],
55
+ })
56
+
57
+ // `initial` as the fallback (not the preset default) so a visitor who only
58
+ // has the cookie keeps the theme the server just painted.
59
+ if (import.meta.client) {
60
+ bootstrapTheme(initial)
61
+ }
62
+ })
@@ -220,6 +220,47 @@ export function cacheTheme(theme: TokenSet): void {
220
220
  } catch {
221
221
  /* storage unavailable — non-fatal */
222
222
  }
223
+ writeThemeCookie(theme.name)
224
+ }
225
+
226
+ /*
227
+ * SSR handoff. localStorage is invisible to the server, so an SSR app would
228
+ * render the default theme and only repaint after hydration — a visible flash on
229
+ * every reload. The cookie rides along with the request, letting the server
230
+ * inline the user's real theme in the first frame (see themeFromCookie).
231
+ *
232
+ * Only the NAME is stored: a full TokenSet is ~1KB of JSON that would be sent on
233
+ * every request. The server resolves the name against the themes it knows, and
234
+ * localStorage stays the client's source of truth for the full token values.
235
+ */
236
+ export const THEME_COOKIE = "lp-theme"
237
+
238
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 365
239
+
240
+ function writeThemeCookie(name: string): void {
241
+ if (typeof document === "undefined") return
242
+ // lax: the theme must survive a top-level navigation (which is what SSR
243
+ // renders) without riding cross-site subrequests.
244
+ document.cookie =
245
+ `${THEME_COOKIE}=${encodeURIComponent(name)}` +
246
+ `; path=/; max-age=${COOKIE_MAX_AGE}; samesite=lax`
247
+ }
248
+
249
+ /**
250
+ * Resolve a theme name (as read from the `lp-theme` cookie) against a set of
251
+ * known themes. Returns `null` when the name is absent or unknown, so the caller
252
+ * can fall back to its own default.
253
+ *
254
+ * Server-side counterpart to `cacheTheme`: pair it with `themeToCssRule` to
255
+ * inline the right variables into `<head>` during SSR.
256
+ */
257
+ export function themeFromCookie(
258
+ cookieValue: string | null | undefined,
259
+ themes: readonly TokenSet[],
260
+ ): TokenSet | null {
261
+ if (!cookieValue) return null
262
+ const name = decodeURIComponent(cookieValue)
263
+ return themes.find((theme) => theme.name === name) ?? null
223
264
  }
224
265
 
225
266
  export interface UseTheme {