@leavepulse/ui 0.53.0 → 0.54.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.
Files changed (62) hide show
  1. package/dist/chunks/{LpAppShell-BmMYcEVx.js → LpAppShell-DcSXik4a.js} +1 -1
  2. package/dist/chunks/{LpAvatar-Cb1jtOVv.js → LpAvatar-LTvy0OkE.js} +1 -1
  3. package/dist/chunks/{LpCard-BdxkMXjo.js → LpCard-DbjO5yaF.js} +1 -1
  4. package/dist/chunks/{LpCheckbox-Bkj13CbW.js → LpCheckbox-DvVYeOD-.js} +2 -2
  5. package/dist/chunks/{LpColorPicker-BLqPX5Vw.js → LpColorPicker-D1sQqtXS.js} +1 -1
  6. package/dist/chunks/{LpContextMenu-1cKew_Dc.js → LpContextMenu-EF_90u3g.js} +5 -1
  7. package/dist/chunks/{LpFileTree-bk5VjBey.js → LpFileTree-3E4w4Rjr.js} +1 -1
  8. package/dist/chunks/{LpFileTreeNode-BvMSBM7X.js → LpFileTreeNode-B4uB8Ks_.js} +2 -2
  9. package/dist/chunks/{LpLogViewer-BDFXzvcl.js → LpLogViewer-BfgtXVKm.js} +1 -1
  10. package/dist/chunks/{LpModalNav-BAxC9et7.js → LpModalNav-aWwc_5CD.js} +1 -1
  11. package/dist/chunks/{LpNotificationBell-DsqSyMwS.js → LpNotificationBell-wZ3meeim.js} +1 -1
  12. package/dist/chunks/LpOtpInput-PbeNJ5In.js +131 -0
  13. package/dist/chunks/{LpPie-BdSnta11.js → LpPie-B7ti5qCH.js} +1 -1
  14. package/dist/chunks/{LpRadio-B5AfqTpZ.js → LpRadio-CWA5Eb3M.js} +2 -2
  15. package/dist/chunks/{LpRadioGroup-DmEW0l78.js → LpRadioGroup-CCn-Q2_c.js} +2 -2
  16. package/dist/chunks/{LpSidebar-DpEiGi8L.js → LpSidebar-C2oolgwy.js} +1 -1
  17. package/dist/chunks/{LpSidebarNav-BUuxipX-.js → LpSidebarNav-C9dHu-2b.js} +1 -1
  18. package/dist/chunks/{LpSwitch-OrcOJX9m.js → LpSwitch-DG2AhXIL.js} +2 -2
  19. package/dist/chunks/{LpTableFilter-BV8PpLM9.js → LpTableFilter-CrW7_Rj4.js} +1 -1
  20. package/dist/chunks/{LpTabs-UM0FQmPA.js → LpTabs-BMSBB78h.js} +44 -17
  21. package/dist/chunks/{LpThemeSwitcher-BbYFFT5w.js → LpThemeSwitcher-CPmhYOjX.js} +1 -1
  22. package/dist/components/LpAppShell.vue.js +1 -1
  23. package/dist/components/LpAvatar.vue.js +1 -1
  24. package/dist/components/LpCard.vue.js +1 -1
  25. package/dist/components/LpChart.vue.js +1 -1
  26. package/dist/components/LpCheckbox.vue.js +1 -1
  27. package/dist/components/LpColorPicker.vue.js +1 -1
  28. package/dist/components/LpCommandPalette.vue.js +1 -1
  29. package/dist/components/LpContextMenu.vue.d.ts +2 -2
  30. package/dist/components/LpContextMenu.vue.js +1 -1
  31. package/dist/components/LpFileTree.vue.js +1 -1
  32. package/dist/components/LpFileTreeNode.vue.js +1 -1
  33. package/dist/components/LpLogViewer.vue.js +1 -1
  34. package/dist/components/LpModalNav.vue.js +1 -1
  35. package/dist/components/LpNotificationBell.vue.js +1 -1
  36. package/dist/components/LpOtpInput.vue.d.ts +17 -0
  37. package/dist/components/LpOtpInput.vue.js +1 -1
  38. package/dist/components/LpPaletteHeader.vue.js +1 -1
  39. package/dist/components/LpPie.vue.js +1 -1
  40. package/dist/components/LpRadio.vue.js +1 -1
  41. package/dist/components/LpRadioGroup.vue.js +1 -1
  42. package/dist/components/LpSidebar.vue.js +1 -1
  43. package/dist/components/LpSidebarNav.vue.js +1 -1
  44. package/dist/components/LpSwitch.vue.js +1 -1
  45. package/dist/components/LpTable.vue.js +3 -3
  46. package/dist/components/LpTableFilter.vue.js +1 -1
  47. package/dist/components/LpTabs.vue.d.ts +10 -2
  48. package/dist/components/LpTabs.vue.js +1 -1
  49. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  50. package/dist/components/otp.d.ts +2 -0
  51. package/dist/components/otp.js +17 -1
  52. package/dist/index.js +18 -18
  53. package/package.json +1 -1
  54. package/src/components/LpCheckbox.vue +2 -2
  55. package/src/components/LpContextMenu.vue +18 -1
  56. package/src/components/LpOtpInput.vue +84 -28
  57. package/src/components/LpRadio.vue +2 -2
  58. package/src/components/LpRadioGroup.vue +2 -2
  59. package/src/components/LpSwitch.vue +2 -2
  60. package/src/components/LpTabs.vue +60 -21
  61. package/src/components/otp.ts +29 -0
  62. package/dist/chunks/LpOtpInput-CziPF3Oq.js +0 -99
@@ -10,6 +10,14 @@ declare const __VLS_export: import("vue").DefineComponent<{
10
10
  mask?: boolean;
11
11
  /** Allow letters too (backup codes). Default: digits only (TOTP). */
12
12
  alphanumeric?: boolean;
13
+ /**
14
+ * Turn typed letters into capitals, and open a phone's keyboard in capitals.
15
+ * Default: on with `alphanumeric`, where codes are printed in capitals and
16
+ * compared without regard to case; pass `false` for a case-sensitive code.
17
+ */
18
+ uppercase?: boolean;
19
+ /** Draw a separator after every `group` cells (e.g. 3 for "123-456"). */
20
+ group?: number;
13
21
  /** Error state — tints the cells red (e.g. wrong code). */
14
22
  invalid?: boolean;
15
23
  size?: "sm" | "md" | "lg";
@@ -26,6 +34,14 @@ declare const __VLS_export: import("vue").DefineComponent<{
26
34
  mask?: boolean;
27
35
  /** Allow letters too (backup codes). Default: digits only (TOTP). */
28
36
  alphanumeric?: boolean;
37
+ /**
38
+ * Turn typed letters into capitals, and open a phone's keyboard in capitals.
39
+ * Default: on with `alphanumeric`, where codes are printed in capitals and
40
+ * compared without regard to case; pass `false` for a case-sensitive code.
41
+ */
42
+ uppercase?: boolean;
43
+ /** Draw a separator after every `group` cells (e.g. 3 for "123-456"). */
44
+ group?: number;
29
45
  /** Error state — tints the cells red (e.g. wrong code). */
30
46
  invalid?: boolean;
31
47
  size?: "sm" | "md" | "lg";
@@ -35,5 +51,6 @@ declare const __VLS_export: import("vue").DefineComponent<{
35
51
  }>, {
36
52
  size: "sm" | "md" | "lg";
37
53
  length: number;
54
+ uppercase: boolean;
38
55
  alphanumeric: boolean;
39
56
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpOtpInput-CziPF3Oq.js";
1
+ import { _ as _sfc_main } from "../chunks/LpOtpInput-PbeNJ5In.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, computed, openBlock, createElementBlock, createElementVNode, renderSlot, createBlock, createVNode, createCommentVNode, toDisplayString } from "vue";
2
- import { _ as _sfc_main$1 } from "../chunks/LpAvatar-Cb1jtOVv.js";
2
+ import { _ as _sfc_main$1 } from "../chunks/LpAvatar-LTvy0OkE.js";
3
3
  import { _ as _sfc_main$2 } from "../chunks/LpIcon-DYtaid0K.js";
4
4
  import { _ as _sfc_main$3 } from "../chunks/HighlightText-BvnYZkhi.js";
5
5
  import { _ as _export_sfc } from "../chunks/_plugin-vue_export-helper-1tPrXgE0.js";
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpPie-BdSnta11.js";
1
+ import { _ as _sfc_main } from "../chunks/LpPie-B7ti5qCH.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpRadio-B5AfqTpZ.js";
1
+ import { _ as _sfc_main } from "../chunks/LpRadio-CWA5Eb3M.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpRadioGroup-DmEW0l78.js";
1
+ import { _ as _sfc_main } from "../chunks/LpRadioGroup-CCn-Q2_c.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebar-DpEiGi8L.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebar-C2oolgwy.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSidebarNav-BUuxipX-.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSidebarNav-C9dHu-2b.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpSwitch-OrcOJX9m.js";
1
+ import { _ as _sfc_main } from "../chunks/LpSwitch-DG2AhXIL.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,13 +1,13 @@
1
1
  import { defineComponent, computed, useAttrs, ref, shallowRef, watch, onMounted, onBeforeUnmount, openBlock, createElementBlock, normalizeClass, createVNode, withCtx, createTextVNode, toDisplayString, createCommentVNode, Fragment, renderList, createElementVNode, Transition, renderSlot, mergeProps, normalizeStyle, createBlock, TransitionGroup, unref, withModifiers, nextTick } from "vue";
2
- import { _ as _sfc_main$7 } from "../chunks/LpCheckbox-Bkj13CbW.js";
3
- import { _ as _sfc_main$6 } from "../chunks/LpContextMenu-1cKew_Dc.js";
2
+ import { _ as _sfc_main$7 } from "../chunks/LpCheckbox-DvVYeOD-.js";
3
+ import { _ as _sfc_main$6 } from "../chunks/LpContextMenu-EF_90u3g.js";
4
4
  import { _ as _sfc_main$2 } from "../chunks/LpIcon-DYtaid0K.js";
5
5
  import { _ as _sfc_main$a } from "../chunks/LpPagination-BcDlo3r_.js";
6
6
  import { _ as _sfc_main$8 } from "../chunks/LpPopover-DCKeqiVn.js";
7
7
  import { _ as _sfc_main$5 } from "../chunks/LpScrollArea-Hz3-JWUG.js";
8
8
  import { _ as _sfc_main$4 } from "../chunks/LpRollingText-DB7upf-5.js";
9
9
  import { _ as _sfc_main$3 } from "../chunks/LpShift-DXNsauQZ.js";
10
- import { _ as _sfc_main$9 } from "../chunks/LpTableFilter-BV8PpLM9.js";
10
+ import { _ as _sfc_main$9 } from "../chunks/LpTableFilter-CrW7_Rj4.js";
11
11
  import { useAsyncTask } from "../composables/useAsyncTask.js";
12
12
  import { useHotkeys } from "../composables/useHotkeys.js";
13
13
  import { usePaintSelect } from "../composables/usePaintSelect.js";
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTableFilter-BV8PpLM9.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTableFilter-CrW7_Rj4.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -3,6 +3,8 @@ export interface TabItem {
3
3
  label: string;
4
4
  /** Optional iconify name (e.g. "lucide:palette"), shown left of the label. */
5
5
  icon?: string;
6
+ /** A small dot on the tab: something behind it wants a look (unread, a problem). */
7
+ dot?: boolean;
6
8
  }
7
9
  type __VLS_Props = {
8
10
  modelValue?: string;
@@ -15,12 +17,18 @@ type __VLS_Props = {
15
17
  * sliding pill marking the active one (for nav bars over a custom surface).
16
18
  * "underline" is a flat row over a hairline baseline, marking the active
17
19
  * tab with a sliding bottom bar instead of a pill (page-section nav style).
20
+ * "bottom" is a phone's bottom navigation: full width, equal-share tabs
21
+ * with the icon over the label, a sliding bar along the top edge marking
22
+ * the active one, and padding for the gesture strip and side insets of an
23
+ * edge-to-edge screen. Place it as the last row of the layout.
18
24
  */
19
- variant?: "contained" | "plain" | "underline";
25
+ variant?: "contained" | "plain" | "underline" | "bottom";
20
26
  /** Tint the active pill + label with the brand colour (nav-bar style). */
21
27
  accent?: boolean;
22
28
  /** Stretch the bar to full width with equal-share triggers. */
23
29
  block?: boolean;
30
+ /** Accessible name of the tab list, e.g. "Main" for an app's navigation. */
31
+ label?: string;
24
32
  };
25
33
  declare var __VLS_51: {
26
34
  value: string;
@@ -36,7 +44,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
36
44
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
37
45
  }>, {
38
46
  block: boolean;
39
- variant: "contained" | "plain" | "underline";
47
+ variant: "contained" | "plain" | "underline" | "bottom";
40
48
  accent: boolean;
41
49
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
42
50
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTabs-UM0FQmPA.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTabs-BMSBB78h.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-BbYFFT5w.js";
1
+ import { _ as _sfc_main } from "../chunks/LpThemeSwitcher-CPmhYOjX.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -3,3 +3,5 @@ export type OtpCells = (string | number | undefined)[];
3
3
  export declare function joinCells(cells: OtpCells): string;
4
4
  /** Cells for a code given from outside, one character each. */
5
5
  export declare function splitCode(code: string | undefined, length: number): string[];
6
+ export declare function upperCells<T extends OtpCells>(cells: T): T;
7
+ export declare function groupBreaks(length: number, group: number | undefined): Set<number>;
@@ -4,7 +4,23 @@ function joinCells(cells) {
4
4
  function splitCode(code, length) {
5
5
  return (code ?? "").split("").slice(0, length);
6
6
  }
7
+ function upperCells(cells) {
8
+ const out = [];
9
+ cells.forEach((c, i) => {
10
+ out[i] = typeof c === "string" ? c.toUpperCase() : c;
11
+ });
12
+ out.length = cells.length;
13
+ return out;
14
+ }
15
+ function groupBreaks(length, group) {
16
+ const breaks = /* @__PURE__ */ new Set();
17
+ if (!group || group < 1 || group >= length) return breaks;
18
+ for (let i = group; i < length; i += group) breaks.add(i - 1);
19
+ return breaks;
20
+ }
7
21
  export {
22
+ groupBreaks,
8
23
  joinCells,
9
- splitCode
24
+ splitCode,
25
+ upperCells
10
26
  };
package/dist/index.js CHANGED
@@ -4,21 +4,21 @@ import { _ as _3 } from "./chunks/LpMapPicker-Cc84LaM3.js";
4
4
  import { OSM_TILES, createNominatimProvider, emptyAddress, formatAddress, project, tileUrl, unproject } from "./components/address.js";
5
5
  import { _ as _4 } from "./chunks/LpActionBar-CnfFVIwC.js";
6
6
  import { _ as _5 } from "./chunks/LpAlert-B6ilrM7T.js";
7
- import { _ as _6 } from "./chunks/LpAppShell-BmMYcEVx.js";
7
+ import { _ as _6 } from "./chunks/LpAppShell-DcSXik4a.js";
8
8
  import { _ as _7 } from "./chunks/LpAutocomplete-C8IgSMsi.js";
9
- import { _ as _8 } from "./chunks/LpAvatar-Cb1jtOVv.js";
9
+ import { _ as _8 } from "./chunks/LpAvatar-LTvy0OkE.js";
10
10
  import { _ as _9 } from "./chunks/LpBadge-Fe0D1bYx.js";
11
11
  import { _ as _10 } from "./chunks/LpBreadcrumbs-D4TsoFju.js";
12
12
  import { _ as _11 } from "./chunks/LpButton-CKSwC6Mu.js";
13
13
  import { _ as _12 } from "./chunks/LpCalendar-DrA2McTU.js";
14
- import { _ as _13 } from "./chunks/LpCard-BdxkMXjo.js";
14
+ import { _ as _13 } from "./chunks/LpCard-DbjO5yaF.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-Bkj13CbW.js";
18
+ import { _ as _14 } from "./chunks/LpCheckbox-DvVYeOD-.js";
19
19
  import { _ as _15 } from "./chunks/LpCodeBlock-ca7ANFyW.js";
20
20
  import { tokenizeLines } from "./components/codeHighlight.js";
21
- import { _ as _16 } from "./chunks/LpColorPicker-BLqPX5Vw.js";
21
+ import { _ as _16 } from "./chunks/LpColorPicker-D1sQqtXS.js";
22
22
  import { DEFAULT_SWATCHES, clampHsv, formatColor, hsvToHsl, hsvToRgb, isLight, normalizeHue, opaqueCss, parseColor, parseHex, rgbToHex, rgbToHsv } from "./components/color.js";
23
23
  import { default as default3 } from "./components/LpCommandPalette.vue.js";
24
24
  import { applyQueryFilters, highlightParts, parseCommandQuery, parsePaletteQuery, rankCommandItems } from "./components/commandSearch.js";
@@ -33,7 +33,7 @@ import { comboKeys, comboSteps } from "./components/keys.js";
33
33
  import { _ as _18 } from "./chunks/LpConfirmDialog-Ca-CTlxT.js";
34
34
  import { _ as _19 } from "./chunks/LpConfirmHost-BAaSaT2b.js";
35
35
  import { confirm, useConfirm } from "./components/confirm.js";
36
- import { _ as _20 } from "./chunks/LpContextMenu-1cKew_Dc.js";
36
+ import { _ as _20 } from "./chunks/LpContextMenu-EF_90u3g.js";
37
37
  import { _ as _21 } from "./chunks/LpDatePicker-BrdFtSTy.js";
38
38
  import { _ as _22 } from "./chunks/LpDateRangePicker-Drd7EbIz.js";
39
39
  import { _ as _23 } from "./chunks/LpRangeCalendar-DZuaRV0W.js";
@@ -44,7 +44,7 @@ import { _ as _26 } from "./chunks/LpDrawer-CBFVD624.js";
44
44
  import { _ as _27 } from "./chunks/LpDropdownMenu-DrpJJBp0.js";
45
45
  import { default as default9 } from "./components/LpDropzone.vue.js";
46
46
  import { _ as _28 } from "./chunks/LpEmptyState-BJbZXC8D.js";
47
- import { _ as _29 } from "./chunks/LpFileTree-bk5VjBey.js";
47
+ import { _ as _29 } from "./chunks/LpFileTree-3E4w4Rjr.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-wtieLUTj.js";
50
50
  import { _ as _31 } from "./chunks/LpFormField-CtVYj6tO.js";
@@ -56,25 +56,25 @@ import { _ as _34 } from "./chunks/LpInput-D-YGQbEv.js";
56
56
  import { _ as _35 } from "./chunks/LpLightbox-EVXzAq62.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-BDFXzvcl.js";
59
+ import { _ as _37 } from "./chunks/LpLogViewer-BfgtXVKm.js";
60
60
  import { countLevels, matchPositions, matchesTerm, ringWindow, stepMatch } from "./components/logSearch.js";
61
61
  import { _ as _38 } from "./chunks/LpMediaGallery-BnlkN_1f.js";
62
62
  import { _ as _39 } from "./chunks/LpModal-JNa3B_Lr.js";
63
- import { _ as _40 } from "./chunks/LpModalNav-BAxC9et7.js";
64
- import { _ as _41 } from "./chunks/LpNotificationBell-DsqSyMwS.js";
63
+ import { _ as _40 } from "./chunks/LpModalNav-aWwc_5CD.js";
64
+ import { _ as _41 } from "./chunks/LpNotificationBell-wZ3meeim.js";
65
65
  import { _ as _42 } from "./chunks/LpNumberField-B77O-z28.js";
66
66
  import { _ as _43 } from "./chunks/LpTimeField-Cqe7fXnh.js";
67
67
  import { _ as _44 } from "./chunks/LpNumberFlow-z4WPZcZu.js";
68
- import { _ as _45 } from "./chunks/LpOtpInput-CziPF3Oq.js";
68
+ import { _ as _45 } from "./chunks/LpOtpInput-PbeNJ5In.js";
69
69
  import { _ as _46 } from "./chunks/LpPagination-BcDlo3r_.js";
70
70
  import { _ as _47 } from "./chunks/LpPasswordInput-eKzQhbQP.js";
71
- import { _ as _48 } from "./chunks/LpPie-BdSnta11.js";
71
+ import { _ as _48 } from "./chunks/LpPie-B7ti5qCH.js";
72
72
  import { _ as _49 } from "./chunks/LpPhoneInput-BCbrrhgE.js";
73
73
  import { filterCountries, flagEmoji, loadCountries, matchCountryByValue } from "./components/countries.js";
74
74
  import { _ as _50 } from "./chunks/LpPopover-DCKeqiVn.js";
75
75
  import { _ as _51 } from "./chunks/LpProgress-CBU1iI69.js";
76
- import { _ as _52 } from "./chunks/LpRadio-B5AfqTpZ.js";
77
- import { _ as _53 } from "./chunks/LpRadioGroup-DmEW0l78.js";
76
+ import { _ as _52 } from "./chunks/LpRadio-CWA5Eb3M.js";
77
+ import { _ as _53 } from "./chunks/LpRadioGroup-CCn-Q2_c.js";
78
78
  import { _ as _54 } from "./chunks/LpRollingText-DB7upf-5.js";
79
79
  import { _ as _55 } from "./chunks/LpScrollArea-Hz3-JWUG.js";
80
80
  import { _ as _56 } from "./chunks/LpSegmented-C09EEam2.js";
@@ -83,7 +83,7 @@ import { _ as _58 } from "./chunks/LpSelect-DNIL6MB9.js";
83
83
  import { _ as _59 } from "./chunks/LpServiceNode-D8uJ8sGk.js";
84
84
  import { _ as _60 } from "./chunks/LpShift-DXNsauQZ.js";
85
85
  import { default as default11 } from "./components/LpShortcutsDialog.vue.js";
86
- import { _ as _61 } from "./chunks/LpSidebar-DpEiGi8L.js";
86
+ import { _ as _61 } from "./chunks/LpSidebar-C2oolgwy.js";
87
87
  import { _ as _62 } from "./chunks/LpSkeleton-BB6XIV-w.js";
88
88
  import { _ as _63 } from "./chunks/LpSlider-Cuzmrd7p.js";
89
89
  import { _ as _64 } from "./chunks/LpRangeSlider-C1yF9-nP.js";
@@ -92,15 +92,15 @@ import { formatClipTime, isNativeLoop, planLoop, seamProgress, seamReached } fro
92
92
  import { _ as _66 } from "./chunks/LpSparkline-CB80N8SJ.js";
93
93
  import { _ as _67 } from "./chunks/LpStat-B-7udBRP.js";
94
94
  import { _ as _68 } from "./chunks/LpStepper-C7XiVKZj.js";
95
- import { _ as _69 } from "./chunks/LpSwitch-OrcOJX9m.js";
95
+ import { _ as _69 } from "./chunks/LpSwitch-DG2AhXIL.js";
96
96
  import { default as default12 } from "./components/LpTable.vue.js";
97
97
  import { compareValues } from "./components/tableSort.js";
98
98
  import { _ as _70 } from "./chunks/LpTableOfContents-BUcAfGPQ.js";
99
- import { _ as _71 } from "./chunks/LpTabs-UM0FQmPA.js";
99
+ import { _ as _71 } from "./chunks/LpTabs-BMSBB78h.js";
100
100
  import { _ as _72 } from "./chunks/LpTagsInput-D1SuqbhB.js";
101
101
  import { addTags, splitTags } from "./components/tags.js";
102
102
  import { _ as _73 } from "./chunks/LpTextarea-uuScWo6-.js";
103
- import { _ as _74 } from "./chunks/LpThemeSwitcher-BbYFFT5w.js";
103
+ import { _ as _74 } from "./chunks/LpThemeSwitcher-CPmhYOjX.js";
104
104
  import { _ as _75 } from "./chunks/LpTilt-B7KHX1zH.js";
105
105
  import { _ as _76 } from "./chunks/LpToaster-DTtWuKvq.js";
106
106
  import { _ as _77 } from "./chunks/LpTooltip-CXVKcOXT.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -35,7 +35,7 @@ const field = useFieldControl(() => ({ wrapped: false, id: props.id, disabled: p
35
35
 
36
36
  // The label sits a fixed distance from the box; a dense list of options needs that closer.
37
37
  const { class: rootClass, attrs: rest } = useMergedAttrs(
38
- "group inline-flex cursor-pointer items-center gap-2 text-sm text-ink",
38
+ "group/check inline-flex cursor-pointer items-center gap-2 text-sm text-ink",
39
39
  )
40
40
  </script>
41
41
 
@@ -49,7 +49,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
49
49
  :model-value="indeterminate ? 'indeterminate' : modelValue"
50
50
  :disabled="field.disabled"
51
51
  :aria-describedby="field.describedBy"
52
- class="flex size-[18px] items-center justify-center rounded-[5px] border border-line-strong bg-surface-soft outline-none transition-[color,background-color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:not-data-[state=indeterminate]:group-hover:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand data-[state=indeterminate]:border-transparent data-[state=indeterminate]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
52
+ class="flex size-[18px] items-center justify-center rounded-[5px] border border-line-strong bg-surface-soft outline-none transition-[color,background-color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:not-data-[state=indeterminate]:group-hover/check:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand data-[state=indeterminate]:border-transparent data-[state=indeterminate]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
53
53
  @update:model-value="onUpdate"
54
54
  >
55
55
  <CheckboxIndicator
@@ -79,6 +79,18 @@ function rememberField() {
79
79
  : null
80
80
  }
81
81
 
82
+ /*
83
+ * A right-click the menu answers goes no further than its trigger. reka opens
84
+ * the menu a tick later, and only if nothing has called preventDefault by then;
85
+ * an app that silences the browser's own menu with a window listener (most
86
+ * desktop shells do) got there first, so the menu never opened while a long
87
+ * press, which reka times itself, still did. reka cancels the native menu on
88
+ * its own once it opens.
89
+ */
90
+ function keepContextMenu(event: Event) {
91
+ if (enabled.value) event.stopPropagation()
92
+ }
93
+
82
94
  function returnFocus(event: Event) {
83
95
  const saved = field
84
96
  field = null
@@ -95,7 +107,12 @@ function returnFocus(event: Event) {
95
107
  late, losing its state and focus. A disabled trigger leaves the browser's
96
108
  own menu in place. -->
97
109
  <ContextMenuRoot>
98
- <ContextMenuTrigger as-child :disabled="!enabled" @contextmenu.capture="rememberField">
110
+ <ContextMenuTrigger
111
+ as-child
112
+ :disabled="!enabled"
113
+ @contextmenu.capture="rememberField"
114
+ @contextmenu="keepContextMenu"
115
+ >
99
116
  <slot />
100
117
  </ContextMenuTrigger>
101
118
  <ContextMenuPortal v-if="items.length">
@@ -14,12 +14,16 @@ export default { inheritAttrs: false }
14
14
  * The kit-facing model is a single STRING (the joined code), not reka's per-cell
15
15
  * array: `v-model` is "123456", and @complete fires that string once every cell
16
16
  * is filled. Digits-only by default (TOTP); flip `alphanumeric` for backup codes.
17
+ *
18
+ * The cells share the row rather than keeping a fixed size: each grows to its
19
+ * size's width and no further, and shrinks with the row when the container is
20
+ * narrower, so eight cells fit a phone instead of running off its edge.
17
21
  */
18
22
  import { PinInputInput, PinInputRoot } from "reka-ui"
19
23
  import { computed, ref, watch } from "vue"
20
24
  import { useFieldControl } from "../composables/useField"
21
25
  import { useMergedAttrs } from "../composables/useMergedClass"
22
- import { joinCells, splitCode, type OtpCells } from "./otp"
26
+ import { groupBreaks, joinCells, splitCode, upperCells, type OtpCells } from "./otp"
23
27
 
24
28
  const props = withDefaults(
25
29
  defineProps<{
@@ -32,11 +36,19 @@ const props = withDefaults(
32
36
  mask?: boolean
33
37
  /** Allow letters too (backup codes). Default: digits only (TOTP). */
34
38
  alphanumeric?: boolean
39
+ /**
40
+ * Turn typed letters into capitals, and open a phone's keyboard in capitals.
41
+ * Default: on with `alphanumeric`, where codes are printed in capitals and
42
+ * compared without regard to case; pass `false` for a case-sensitive code.
43
+ */
44
+ uppercase?: boolean
45
+ /** Draw a separator after every `group` cells (e.g. 3 for "123-456"). */
46
+ group?: number
35
47
  /** Error state — tints the cells red (e.g. wrong code). */
36
48
  invalid?: boolean
37
49
  size?: "sm" | "md" | "lg"
38
50
  }>(),
39
- { length: 6, size: "md", alphanumeric: false },
51
+ { length: 6, size: "md", alphanumeric: false, uppercase: undefined },
40
52
  )
41
53
 
42
54
  const emit = defineEmits<{
@@ -45,6 +57,8 @@ const emit = defineEmits<{
45
57
  (e: "complete", value: string): void
46
58
  }>()
47
59
 
60
+ const upper = computed(() => props.alphanumeric && (props.uppercase ?? true))
61
+
48
62
  // reka models the value as an array of per-cell strings; we bridge to a single
49
63
  // string at the boundary so callers never touch the array shape.
50
64
  //
@@ -52,7 +66,7 @@ const emit = defineEmits<{
52
66
  // string on every render: clearing a middle cell leaves a gap the string cannot
53
67
  // express, and re-splitting it shifted every later digit one cell left. The
54
68
  // cells are only rebuilt when the model changes from outside.
55
- const local = ref<string[]>(splitCode(props.modelValue, props.length))
69
+ const local = ref<OtpCells>(splitCode(props.modelValue, props.length))
56
70
  let emitted = props.modelValue ?? ""
57
71
  watch(
58
72
  () => props.modelValue ?? "",
@@ -68,11 +82,13 @@ watch(
68
82
  local.value = local.value.slice(0, length)
69
83
  },
70
84
  )
85
+ // Typed as reka's text-mode array; numeric mode holds numbers and holes, which
86
+ // the helpers in ./otp are written for.
71
87
  const cells = computed<string[]>({
72
- get: () => local.value,
88
+ get: () => local.value as string[],
73
89
  set: (arr) => {
74
- local.value = arr
75
- emitted = joinCells(arr)
90
+ local.value = upper.value ? upperCells(arr) : arr
91
+ emitted = joinCells(local.value)
76
92
  emit("update:modelValue", emitted)
77
93
  },
78
94
  })
@@ -84,23 +100,53 @@ const field = useFieldControl(() => ({
84
100
  }))
85
101
 
86
102
  function onComplete(arr: OtpCells) {
87
- emit("complete", joinCells(arr))
103
+ emit("complete", joinCells(upper.value ? upperCells(arr) : arr))
104
+ }
105
+
106
+ const breaks = computed(() => groupBreaks(props.length, props.group))
107
+
108
+ /*
109
+ * Backspace, as a phone's keyboard sends it.
110
+ *
111
+ * reka handles Backspace on keydown, but an on-screen keyboard reports most
112
+ * keys as "Unidentified", so its handler never ran there: the browser deleted
113
+ * the character itself, and reka read the now-empty cell as typed input and
114
+ * moved the caret FORWARD — and an empty cell could not be backed out of at
115
+ * all. `beforeinput` names the deletion on every keyboard, so it is done here
116
+ * the way a hardware Backspace does it: clear this cell, or step back into
117
+ * the previous one and clear that. A hardware key never reaches this, since
118
+ * reka's keydown handler cancels it first.
119
+ */
120
+ const root = ref<{ $el: HTMLElement } | null>(null)
121
+ function onBeforeInput(event: InputEvent, index: number) {
122
+ if (event.inputType !== "deleteContentBackward") return
123
+ event.preventDefault()
124
+ const target = event.target as HTMLInputElement
125
+ const next = [...local.value]
126
+ let clear = index
127
+ if (!target.value && index > 0) {
128
+ clear = index - 1
129
+ root.value?.$el.querySelectorAll("input")[clear]?.focus()
130
+ }
131
+ // Emptied the way reka empties a cell itself: numeric mode leaves a hole.
132
+ next[clear] = props.alphanumeric ? "" : undefined
133
+ cells.value = next as string[]
88
134
  }
89
135
 
90
136
  const cellSize = {
91
- sm: "size-9 text-base",
92
- md: "size-11 text-lg",
93
- lg: "size-13 text-xl",
137
+ sm: "max-w-9 text-base",
138
+ md: "max-w-11 text-lg",
139
+ lg: "max-w-13 text-xl",
94
140
  }
95
141
 
96
- // The boxes sit on a fixed gap that a narrow form has to tighten.
97
142
  const { class: rootClass, attrs: rest } = useMergedAttrs(
98
- "flex items-center gap-2",
143
+ "flex min-w-0 max-w-full items-center gap-1.5 sm:gap-2",
99
144
  )
100
145
  </script>
101
146
 
102
147
  <template>
103
148
  <PinInputRoot
149
+ ref="root"
104
150
  v-model="cells"
105
151
  :type="alphanumeric ? 'text' : 'number'"
106
152
  :mask="mask"
@@ -110,21 +156,31 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
110
156
  v-bind="rest"
111
157
  @complete="onComplete"
112
158
  >
113
- <PinInputInput
114
- v-for="i in length"
115
- :key="i"
116
- :index="i - 1"
117
- :id="i === 1 ? field.id : undefined"
118
- :aria-invalid="field.invalid || undefined"
119
- :aria-describedby="field.describedBy"
120
- :class="[
121
- cellSize[size],
122
- 'rounded-control border bg-surface-soft text-center font-semibold text-ink caret-brand',
123
- 'outline-none transition-colors duration-[var(--duration-fast)]',
124
- 'focus:border-brand focus:ring-2 focus:ring-ring',
125
- 'disabled:cursor-not-allowed disabled:opacity-55',
126
- field.invalid ? 'border-danger focus:border-danger focus:ring-danger-soft' : 'border-line',
127
- ]"
128
- />
159
+ <template v-for="i in length" :key="i">
160
+ <PinInputInput
161
+ :index="i - 1"
162
+ :id="i === 1 ? field.id : undefined"
163
+ :aria-invalid="field.invalid || undefined"
164
+ :aria-describedby="field.describedBy"
165
+ :autocapitalize="upper ? 'characters' : 'none'"
166
+ autocorrect="off"
167
+ :spellcheck="false"
168
+ :class="[
169
+ cellSize[size],
170
+ 'aspect-square h-auto min-w-0 flex-1 basis-0 p-0',
171
+ 'rounded-control border bg-surface-soft text-center font-semibold text-ink caret-brand',
172
+ 'outline-none transition-colors duration-[var(--duration-fast)]',
173
+ 'focus:border-brand focus:ring-2 focus:ring-ring',
174
+ 'disabled:cursor-not-allowed disabled:opacity-55',
175
+ field.invalid ? 'border-danger focus:border-danger focus:ring-danger-soft' : 'border-line',
176
+ ]"
177
+ @beforeinput="onBeforeInput($event as InputEvent, i - 1)"
178
+ />
179
+ <span
180
+ v-if="breaks.has(i - 1)"
181
+ aria-hidden="true"
182
+ class="h-0.5 w-2 shrink-0 rounded-full bg-muted opacity-60"
183
+ />
184
+ </template>
129
185
  </PinInputRoot>
130
186
  </template>
@@ -18,7 +18,7 @@ const props = defineProps<{ value: string; label?: string; disabled?: boolean }>
18
18
  // options needs to close up — `class="gap-1"` should win over the default.
19
19
  const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
20
20
  [
21
- "group inline-flex cursor-pointer items-center gap-2 text-sm text-ink",
21
+ "group/radio inline-flex cursor-pointer items-center gap-2 text-sm text-ink",
22
22
  props.disabled ? "cursor-not-allowed opacity-55" : "",
23
23
  ].join(" "),
24
24
  )
@@ -32,7 +32,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
32
32
  <RadioGroupItem
33
33
  :value="value"
34
34
  :disabled="disabled"
35
- class="flex size-[18px] shrink-0 items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
35
+ class="flex size-[18px] shrink-0 items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover/radio:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
36
36
  >
37
37
  <RadioGroupIndicator
38
38
  class="flex data-[state=checked]:animate-[indicator-in_140ms_var(--ease-emphasized)]"
@@ -45,11 +45,11 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
45
45
  <label
46
46
  v-for="opt in options"
47
47
  :key="opt.value"
48
- class="group inline-flex cursor-pointer items-center gap-2 text-sm text-ink"
48
+ class="group/radio inline-flex cursor-pointer items-center gap-2 text-sm text-ink"
49
49
  >
50
50
  <RadioGroupItem
51
51
  :value="opt.value"
52
- class="flex size-[18px] items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
52
+ class="flex size-[18px] items-center justify-center rounded-full border border-line-strong bg-surface-soft outline-none transition-[color,border-color,box-shadow,scale] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] not-data-[state=checked]:group-hover/radio:border-brand active:scale-90 motion-reduce:active:scale-100 focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-brand disabled:cursor-not-allowed disabled:opacity-55"
53
53
  >
54
54
  <RadioGroupIndicator
55
55
  class="flex data-[state=checked]:animate-[indicator-in_140ms_var(--ease-emphasized)]"
@@ -33,9 +33,9 @@ const slots = useSlots()
33
33
  const labelled = computed(() => Boolean(props.label || props.description || slots.default))
34
34
 
35
35
  const TRACK =
36
- "group inline-flex h-5 w-9 shrink-0 items-center rounded-pill border border-line bg-surface-soft px-0.5 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
36
+ "group/switch inline-flex h-5 w-9 shrink-0 items-center rounded-pill border border-line bg-surface-soft px-0.5 outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[state=checked]:border-transparent data-[state=checked]:bg-brand disabled:cursor-not-allowed disabled:opacity-55"
37
37
  const THUMB =
38
- "h-3.5 w-3.5 rounded-full bg-ink shadow transition-[translate,width,background-color] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-active:w-5 group-data-[state=checked]:translate-x-4 group-data-[state=checked]:bg-ink-inverse group-data-[state=checked]:group-active:translate-x-2.5 motion-reduce:group-active:w-3.5"
38
+ "h-3.5 w-3.5 rounded-full bg-ink shadow transition-[translate,width,background-color] duration-[var(--duration-fast)] ease-[var(--ease-emphasized)] group-active/switch:w-5 group-data-[state=checked]/switch:translate-x-4 group-data-[state=checked]/switch:bg-ink-inverse group-data-[state=checked]/switch:group-active/switch:translate-x-2.5 motion-reduce:group-active/switch:w-3.5"
39
39
 
40
40
  // Bare, the switch is the root, so a consumer asking for a bigger track must not
41
41
  // have to depend on stylesheet order to get it. With text, the row is the root