@net-advantage/nabs-ui-dropdownmenu 0.69.0 → 0.71.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.
@@ -0,0 +1,4 @@
1
+ import { DropdownMenuActionItem, DropdownMenuDividerItem, DropdownMenuItem } from './DropdownMenu.types';
2
+ export declare function isDividerItem(item: DropdownMenuItem | undefined): item is DropdownMenuDividerItem;
3
+ export declare function getItemLabel(item: DropdownMenuActionItem): string | number | bigint | boolean | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | null | undefined>;
4
+ export declare function getItemKey(item: DropdownMenuActionItem, index: number): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1 @@
1
- import { DropdownMenuActionItem, DropdownMenuDividerItem, DropdownMenuItem } from './DropdownMenu.types';
2
- export declare function isDividerItem(item: DropdownMenuItem | undefined): item is DropdownMenuDividerItem;
3
- export declare function getItemLabel(item: DropdownMenuActionItem): string | number | bigint | boolean | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<import('react').ReactNode> | null | undefined>;
4
- export declare function getItemKey(item: DropdownMenuActionItem, index: number): string;
1
+ export { isDividerItem, getItemLabel, getItemKey, } from './dropdownmenu.model';
@@ -0,0 +1,35 @@
1
+ import { DropdownMenuProps } from './DropdownMenu.types';
2
+ export interface DropdownMenuClassNameOptions {
3
+ className?: string;
4
+ triggerClassName?: string;
5
+ menuClassName?: string;
6
+ itemClassName?: string;
7
+ dividerClassName?: string;
8
+ }
9
+ export declare function buildDropdownMenuId(id: string | undefined, generatedId: string): string;
10
+ export declare function buildDropdownMenuClassNames(options: DropdownMenuClassNameOptions): {
11
+ classes: string;
12
+ triggerClasses: string;
13
+ menuClasses: string;
14
+ itemClasses: string;
15
+ dividerClasses: string;
16
+ };
17
+ export declare function buildDropdownMenuItemClassName(baseClassName: string, state: {
18
+ disabled?: boolean;
19
+ danger?: boolean;
20
+ }): string;
21
+ export declare function computeDropdownMenuPosition({ menuAlign, triggerRect, viewportWidth, viewportPadding, verticalGap, }: {
22
+ menuAlign: DropdownMenuProps["menuAlign"];
23
+ triggerRect: Pick<DOMRect, "left" | "right" | "bottom" | "width">;
24
+ viewportWidth: number;
25
+ viewportPadding?: number;
26
+ verticalGap?: number;
27
+ }): {
28
+ top: number;
29
+ left: number;
30
+ minWidth: number;
31
+ };
32
+ export declare function toDropdownMenuStateLabel({ open, itemCount, }: {
33
+ open: boolean;
34
+ itemCount: number;
35
+ }): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react-dom"),n=require("react/jsx-runtime");function r(e){return!!(e&&`type`in e&&e.type===`divider`)}function i(e){return e.label??e.children??`Menu item`}function a(e,t){return e.value??e.label??`item-${t}`}function o({label:o=`Menu`,triggerContent:s,triggerAriaLabel:c,menuAlign:l=`start`,items:u=[],className:d=``,triggerClassName:f=``,menuClassName:p=``,itemClassName:m=``,dividerClassName:h=``,id:g,onItemSelect:_,...v}){let[y,b]=(0,e.useState)(!1),x=(0,e.useRef)(null),S=(0,e.useRef)(null),C=(0,e.useRef)(null),w=(0,e.useId)(),T=g??`nabs-ui-dropdownmenu-${w.replace(/:/g,``)}`,E=[`nabs-ui-dropdownmenu`,d].filter(Boolean).join(` `),D=[`nabs-ui-dropdownmenu__trigger`,f].filter(Boolean).join(` `),O=[`nabs-ui-dropdownmenu__menu`,p].filter(Boolean).join(` `),k=[`nabs-ui-dropdownmenu__item`,m].filter(Boolean).join(` `),A=[`nabs-ui-dropdownmenu__divider`,h].filter(Boolean).join(` `);(0,e.useEffect)(()=>{let e=e=>{e.target instanceof Node&&x.current&&!x.current.contains(e.target)&&C.current&&!C.current.contains(e.target)&&b(!1)},t=e=>{e.key===`Escape`&&b(!1)};return document.addEventListener(`pointerdown`,e),document.addEventListener(`keydown`,t),()=>{document.removeEventListener(`pointerdown`,e),document.removeEventListener(`keydown`,t)}},[]),(0,e.useLayoutEffect)(()=>{if(!y||!S.current)return;let e=()=>{let e=S.current?.getBoundingClientRect(),t=C.current;if(!e||!t)return;let n=e.width,r=window.innerWidth-n-8,i=l===`end`?e.right-n:e.left,a=Math.max(8,Math.min(i,r)),o=Math.max(8,e.bottom+10);t.style.position=`fixed`,t.style.top=`${o}px`,t.style.left=`${a}px`,t.style.right=`auto`,t.style.minWidth=`${n}px`,t.style.visibility=`visible`};return C.current&&(C.current.style.visibility=`hidden`),e(),window.addEventListener(`resize`,e),window.addEventListener(`scroll`,e,!0),()=>{window.removeEventListener(`resize`,e),window.removeEventListener(`scroll`,e,!0)}},[l,y]);let j=(e,t)=>{if(b(!1),e.disabled){t.preventDefault();return}_?.(e,t),e.onSelect?.(e,t)};return(0,n.jsxs)(`div`,{ref:x,className:E,...v,children:[(0,n.jsx)(`button`,{ref:S,type:`button`,className:D,"aria-haspopup":`menu`,"aria-controls":T,"aria-label":c??o,onClick:()=>b(e=>!e),children:s??(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__triggerLabel`,children:o}),(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__triggerChevron`,"aria-hidden":`true`,children:`▾`})]})}),y&&typeof document<`u`?(0,t.createPortal)((0,n.jsx)(`div`,{ref:C,id:T,className:O,"aria-label":o,children:u.map((e,t)=>{if(r(e))return(0,n.jsx)(`div`,{className:A,role:`separator`,"aria-hidden":`true`},`divider-${t}`);let o=i(e),s=[k,e.disabled?`nabs-ui-dropdownmenu__item--disabled`:``,e.danger?`nabs-ui-dropdownmenu__item--danger`:``].filter(Boolean).join(` `),c={className:s,"aria-disabled":e.disabled||void 0};return e.href?(0,n.jsxs)(`a`,{...c,href:e.disabled?void 0:e.href,onClick:t=>j(e,t),children:[(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemLabel`,children:o}),e.description?(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemDescription`,children:e.description}):null]},a(e,t)):(0,n.jsxs)(`button`,{type:`button`,className:s,disabled:e.disabled,onClick:t=>j(e,t),children:[(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemLabel`,children:o}),e.description?(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemDescription`,children:e.description}):null]},a(e,t))})}),document.body):null]})}exports.DropdownMenu=o;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("react"),t=require("react-dom"),n=require("react/jsx-runtime");function r(e){return!!(e&&`type`in e&&e.type===`divider`)}function i(e){return e.label??e.children??`Menu item`}function a(e,t){return e.value??e.label??`item-${t}`}function o(e,t){return e||`nabs-ui-dropdownmenu-${t.replace(/:/g,``)}`}function s(e){return{classes:[`nabs-ui-dropdownmenu`,e.className??``].filter(Boolean).join(` `),triggerClasses:[`nabs-ui-dropdownmenu__trigger`,e.triggerClassName??``].filter(Boolean).join(` `),menuClasses:[`nabs-ui-dropdownmenu__menu`,e.menuClassName??``].filter(Boolean).join(` `),itemClasses:[`nabs-ui-dropdownmenu__item`,e.itemClassName??``].filter(Boolean).join(` `),dividerClasses:[`nabs-ui-dropdownmenu__divider`,e.dividerClassName??``].filter(Boolean).join(` `)}}function c(e,t){return[e,t.disabled?`nabs-ui-dropdownmenu__item--disabled`:``,t.danger?`nabs-ui-dropdownmenu__item--danger`:``].filter(Boolean).join(` `)}function l({menuAlign:e,triggerRect:t,viewportWidth:n,viewportPadding:r=8,verticalGap:i=10}){let a=t.width,o=n-a-r,s=e===`end`?t.right-a:t.left,c=Math.max(r,Math.min(s,o));return{top:Math.max(r,t.bottom+i),left:c,minWidth:a}}function u({label:u=`Menu`,triggerContent:d,triggerAriaLabel:f,menuAlign:p=`start`,items:m=[],className:h=``,triggerClassName:g=``,menuClassName:_=``,itemClassName:v=``,dividerClassName:y=``,id:b,onItemSelect:x,...S}){let[C,w]=(0,e.useState)(!1),T=(0,e.useRef)(null),E=(0,e.useRef)(null),D=(0,e.useRef)(null),O=o(b,(0,e.useId)()),{classes:k,triggerClasses:A,menuClasses:j,itemClasses:M,dividerClasses:N}=s({className:h,triggerClassName:g,menuClassName:_,itemClassName:v,dividerClassName:y});(0,e.useEffect)(()=>{let e=e=>{e.target instanceof Node&&T.current&&!T.current.contains(e.target)&&D.current&&!D.current.contains(e.target)&&w(!1)},t=e=>{e.key===`Escape`&&w(!1)};return document.addEventListener(`pointerdown`,e),document.addEventListener(`keydown`,t),()=>{document.removeEventListener(`pointerdown`,e),document.removeEventListener(`keydown`,t)}},[]),(0,e.useLayoutEffect)(()=>{if(!C||!E.current)return;let e=()=>{let e=E.current?.getBoundingClientRect(),t=D.current;if(!e||!t)return;let n=l({menuAlign:p,triggerRect:e,viewportWidth:window.innerWidth});t.style.position=`fixed`,t.style.top=`${n.top}px`,t.style.left=`${n.left}px`,t.style.right=`auto`,t.style.minWidth=`${n.minWidth}px`,t.style.visibility=`visible`};return D.current&&(D.current.style.visibility=`hidden`),e(),window.addEventListener(`resize`,e),window.addEventListener(`scroll`,e,!0),()=>{window.removeEventListener(`resize`,e),window.removeEventListener(`scroll`,e,!0)}},[p,C]);let P=(e,t)=>{if(w(!1),e.disabled){t.preventDefault();return}x?.(e,t),e.onSelect?.(e,t)};return(0,n.jsxs)(`div`,{ref:T,className:k,...S,children:[(0,n.jsx)(`button`,{ref:E,type:`button`,className:A,"aria-haspopup":`menu`,"aria-controls":O,"aria-label":f??u,onClick:()=>w(e=>!e),children:d??(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__triggerLabel`,children:u}),(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__triggerChevron`,"aria-hidden":`true`,children:`▾`})]})}),C&&typeof document<`u`?(0,t.createPortal)((0,n.jsx)(`div`,{ref:D,id:O,className:j,"aria-label":u,children:m.map((e,t)=>{if(r(e))return(0,n.jsx)(`div`,{className:N,role:`separator`,"aria-hidden":`true`},`divider-${t}`);let o=i(e),s=c(M,{disabled:e.disabled,danger:e.danger}),l={className:s,"aria-disabled":e.disabled||void 0};return e.href?(0,n.jsxs)(`a`,{...l,href:e.disabled?void 0:e.href,onClick:t=>P(e,t),children:[(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemLabel`,children:o}),e.description?(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemDescription`,children:e.description}):null]},a(e,t)):(0,n.jsxs)(`button`,{type:`button`,className:s,disabled:e.disabled,onClick:t=>P(e,t),children:[(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemLabel`,children:o}),e.description?(0,n.jsx)(`span`,{className:`nabs-ui-dropdownmenu__itemDescription`,children:e.description}):null]},a(e,t))})}),document.body):null]})}exports.DropdownMenu=u;
@@ -1,7 +1,7 @@
1
1
  import { useEffect as e, useId as t, useLayoutEffect as n, useRef as r, useState as i } from "react";
2
2
  import { createPortal as a } from "react-dom";
3
3
  import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
4
- //#region src/dropdownmenu.utils.ts
4
+ //#region src/dropdownmenu.model.ts
5
5
  function l(e) {
6
6
  return !!(e && "type" in e && e.type === "divider");
7
7
  }
@@ -12,80 +12,118 @@ function d(e, t) {
12
12
  return e.value ?? e.label ?? `item-${t}`;
13
13
  }
14
14
  //#endregion
15
+ //#region src/dropdownmenu.view-model.ts
16
+ function f(e, t) {
17
+ return e || `nabs-ui-dropdownmenu-${t.replace(/:/g, "")}`;
18
+ }
19
+ function p(e) {
20
+ return {
21
+ classes: ["nabs-ui-dropdownmenu", e.className ?? ""].filter(Boolean).join(" "),
22
+ triggerClasses: ["nabs-ui-dropdownmenu__trigger", e.triggerClassName ?? ""].filter(Boolean).join(" "),
23
+ menuClasses: ["nabs-ui-dropdownmenu__menu", e.menuClassName ?? ""].filter(Boolean).join(" "),
24
+ itemClasses: ["nabs-ui-dropdownmenu__item", e.itemClassName ?? ""].filter(Boolean).join(" "),
25
+ dividerClasses: ["nabs-ui-dropdownmenu__divider", e.dividerClassName ?? ""].filter(Boolean).join(" ")
26
+ };
27
+ }
28
+ function m(e, t) {
29
+ return [
30
+ e,
31
+ t.disabled ? "nabs-ui-dropdownmenu__item--disabled" : "",
32
+ t.danger ? "nabs-ui-dropdownmenu__item--danger" : ""
33
+ ].filter(Boolean).join(" ");
34
+ }
35
+ function h({ menuAlign: e, triggerRect: t, viewportWidth: n, viewportPadding: r = 8, verticalGap: i = 10 }) {
36
+ let a = t.width, o = n - a - r, s = e === "end" ? t.right - a : t.left, c = Math.max(r, Math.min(s, o));
37
+ return {
38
+ top: Math.max(r, t.bottom + i),
39
+ left: c,
40
+ minWidth: a
41
+ };
42
+ }
43
+ //#endregion
15
44
  //#region src/DropdownMenu.tsx
16
- function f({ label: f = "Menu", triggerContent: p, triggerAriaLabel: m, menuAlign: h = "start", items: g = [], className: _ = "", triggerClassName: v = "", menuClassName: y = "", itemClassName: b = "", dividerClassName: x = "", id: S, onItemSelect: C, ...w }) {
17
- let [T, E] = i(!1), D = r(null), O = r(null), k = r(null), A = t(), j = S ?? `nabs-ui-dropdownmenu-${A.replace(/:/g, "")}`, M = ["nabs-ui-dropdownmenu", _].filter(Boolean).join(" "), N = ["nabs-ui-dropdownmenu__trigger", v].filter(Boolean).join(" "), P = ["nabs-ui-dropdownmenu__menu", y].filter(Boolean).join(" "), F = ["nabs-ui-dropdownmenu__item", b].filter(Boolean).join(" "), I = ["nabs-ui-dropdownmenu__divider", x].filter(Boolean).join(" ");
45
+ function g({ label: g = "Menu", triggerContent: _, triggerAriaLabel: v, menuAlign: y = "start", items: b = [], className: x = "", triggerClassName: S = "", menuClassName: C = "", itemClassName: w = "", dividerClassName: T = "", id: E, onItemSelect: D, ...O }) {
46
+ let [k, A] = i(!1), j = r(null), M = r(null), N = r(null), P = f(E, t()), { classes: F, triggerClasses: I, menuClasses: L, itemClasses: R, dividerClasses: z } = p({
47
+ className: x,
48
+ triggerClassName: S,
49
+ menuClassName: C,
50
+ itemClassName: w,
51
+ dividerClassName: T
52
+ });
18
53
  e(() => {
19
54
  let e = (e) => {
20
- e.target instanceof Node && D.current && !D.current.contains(e.target) && k.current && !k.current.contains(e.target) && E(!1);
55
+ e.target instanceof Node && j.current && !j.current.contains(e.target) && N.current && !N.current.contains(e.target) && A(!1);
21
56
  }, t = (e) => {
22
- e.key === "Escape" && E(!1);
57
+ e.key === "Escape" && A(!1);
23
58
  };
24
59
  return document.addEventListener("pointerdown", e), document.addEventListener("keydown", t), () => {
25
60
  document.removeEventListener("pointerdown", e), document.removeEventListener("keydown", t);
26
61
  };
27
62
  }, []), n(() => {
28
- if (!T || !O.current) return;
63
+ if (!k || !M.current) return;
29
64
  let e = () => {
30
- let e = O.current?.getBoundingClientRect(), t = k.current;
65
+ let e = M.current?.getBoundingClientRect(), t = N.current;
31
66
  if (!e || !t) return;
32
- let n = e.width, r = window.innerWidth - n - 8, i = h === "end" ? e.right - n : e.left, a = Math.max(8, Math.min(i, r)), o = Math.max(8, e.bottom + 10);
33
- t.style.position = "fixed", t.style.top = `${o}px`, t.style.left = `${a}px`, t.style.right = "auto", t.style.minWidth = `${n}px`, t.style.visibility = "visible";
67
+ let n = h({
68
+ menuAlign: y,
69
+ triggerRect: e,
70
+ viewportWidth: window.innerWidth
71
+ });
72
+ t.style.position = "fixed", t.style.top = `${n.top}px`, t.style.left = `${n.left}px`, t.style.right = "auto", t.style.minWidth = `${n.minWidth}px`, t.style.visibility = "visible";
34
73
  };
35
- return k.current && (k.current.style.visibility = "hidden"), e(), window.addEventListener("resize", e), window.addEventListener("scroll", e, !0), () => {
74
+ return N.current && (N.current.style.visibility = "hidden"), e(), window.addEventListener("resize", e), window.addEventListener("scroll", e, !0), () => {
36
75
  window.removeEventListener("resize", e), window.removeEventListener("scroll", e, !0);
37
76
  };
38
- }, [h, T]);
39
- let L = (e, t) => {
40
- if (E(!1), e.disabled) {
77
+ }, [y, k]);
78
+ let B = (e, t) => {
79
+ if (A(!1), e.disabled) {
41
80
  t.preventDefault();
42
81
  return;
43
82
  }
44
- C?.(e, t), e.onSelect?.(e, t);
83
+ D?.(e, t), e.onSelect?.(e, t);
45
84
  };
46
85
  return /* @__PURE__ */ c("div", {
47
- ref: D,
48
- className: M,
49
- ...w,
86
+ ref: j,
87
+ className: F,
88
+ ...O,
50
89
  children: [/* @__PURE__ */ s("button", {
51
- ref: O,
90
+ ref: M,
52
91
  type: "button",
53
- className: N,
92
+ className: I,
54
93
  "aria-haspopup": "menu",
55
- "aria-controls": j,
56
- "aria-label": m ?? f,
57
- onClick: () => E((e) => !e),
58
- children: p ?? /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("span", {
94
+ "aria-controls": P,
95
+ "aria-label": v ?? g,
96
+ onClick: () => A((e) => !e),
97
+ children: _ ?? /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("span", {
59
98
  className: "nabs-ui-dropdownmenu__triggerLabel",
60
- children: f
99
+ children: g
61
100
  }), /* @__PURE__ */ s("span", {
62
101
  className: "nabs-ui-dropdownmenu__triggerChevron",
63
102
  "aria-hidden": "true",
64
103
  children: "▾"
65
104
  })] })
66
- }), T && typeof document < "u" ? a(/* @__PURE__ */ s("div", {
67
- ref: k,
68
- id: j,
69
- className: P,
70
- "aria-label": f,
71
- children: g.map((e, t) => {
105
+ }), k && typeof document < "u" ? a(/* @__PURE__ */ s("div", {
106
+ ref: N,
107
+ id: P,
108
+ className: L,
109
+ "aria-label": g,
110
+ children: b.map((e, t) => {
72
111
  if (l(e)) return /* @__PURE__ */ s("div", {
73
- className: I,
112
+ className: z,
74
113
  role: "separator",
75
114
  "aria-hidden": "true"
76
115
  }, `divider-${t}`);
77
- let n = u(e), r = [
78
- F,
79
- e.disabled ? "nabs-ui-dropdownmenu__item--disabled" : "",
80
- e.danger ? "nabs-ui-dropdownmenu__item--danger" : ""
81
- ].filter(Boolean).join(" "), i = {
116
+ let n = u(e), r = m(R, {
117
+ disabled: e.disabled,
118
+ danger: e.danger
119
+ }), i = {
82
120
  className: r,
83
121
  "aria-disabled": e.disabled || void 0
84
122
  };
85
123
  return e.href ? /* @__PURE__ */ c("a", {
86
124
  ...i,
87
125
  href: e.disabled ? void 0 : e.href,
88
- onClick: (t) => L(e, t),
126
+ onClick: (t) => B(e, t),
89
127
  children: [/* @__PURE__ */ s("span", {
90
128
  className: "nabs-ui-dropdownmenu__itemLabel",
91
129
  children: n
@@ -97,7 +135,7 @@ function f({ label: f = "Menu", triggerContent: p, triggerAriaLabel: m, menuAlig
97
135
  type: "button",
98
136
  className: r,
99
137
  disabled: e.disabled,
100
- onClick: (t) => L(e, t),
138
+ onClick: (t) => B(e, t),
101
139
  children: [/* @__PURE__ */ s("span", {
102
140
  className: "nabs-ui-dropdownmenu__itemLabel",
103
141
  children: n
@@ -111,4 +149,4 @@ function f({ label: f = "Menu", triggerContent: p, triggerAriaLabel: m, menuAlig
111
149
  });
112
150
  }
113
151
  //#endregion
114
- export { f as DropdownMenu };
152
+ export { g as DropdownMenu };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@net-advantage/nabs-ui-dropdownmenu",
3
- "version": "0.69.0",
3
+ "version": "0.71.0",
4
4
  "description": "Reusable dropdown menu control for the Nabs UI library.",
5
5
  "type": "module",
6
6
  "main": "./dist/nabs-ui-dropdownmenu.cjs",
@@ -25,11 +25,16 @@
25
25
  "@types/react": "^19.0.0",
26
26
  "@types/react-dom": "^19.0.0",
27
27
  "@vitejs/plugin-react": "^6.0.1",
28
+ "@vitest/coverage-v8": "^3.2.4",
28
29
  "react-dom": "^19.2.6",
29
- "vite": "^8.0.12"
30
+ "vite": "^8.0.12",
31
+ "vitest": "^3.2.4"
30
32
  },
31
33
  "scripts": {
32
34
  "build": "vite build",
33
- "dev": "vite"
35
+ "dev": "vite",
36
+ "test:unit": "vitest run",
37
+ "test:unit:watch": "vitest",
38
+ "test:unit:coverage": "vitest run --coverage --coverage.reporter=html --coverage.reporter=json-summary --coverage.reporter=text-summary"
34
39
  }
35
40
  }