@xenosystem/components 0.2.1 → 0.3.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.
@@ -91,10 +91,23 @@ interface CredentialFormProps {
91
91
  before?: ReactNode;
92
92
  /** Rendered under the submit — "Forgot password?", terms, the other-mode link. */
93
93
  after?: ReactNode;
94
+ /** Live password rules (sign-up): shown while the field is focused or partly filled — never before,
95
+ * a list of things you have failed before you typed is a telling-off for a crime not yet committed. */
96
+ passwordRules?: {
97
+ label: string;
98
+ met: boolean;
99
+ }[];
100
+ /** Rendered beside the password label — the page's "Forgot?" link on sign-in. */
101
+ passwordAside?: ReactNode;
102
+ /** When false, the fields collapse (0fr grid) and leave the accessibility tree; the page's disclosure
103
+ * controls it. Default open. */
104
+ open?: boolean;
105
+ /** id for the collapsible region (matched by `EmailDisclosure.controls`). */
106
+ id?: string;
94
107
  /** Copy overrides; defaults are the platform page's. */
95
108
  labels?: Partial<Record<'name' | 'email' | 'password' | 'submitSignIn' | 'submitSignUp' | 'signin' | 'signup', string>>;
96
109
  }
97
- declare function CredentialForm({ mode, onModeChange, values, onChange, errors, submitting, onSubmit, title, body, before, after, labels }: CredentialFormProps): react.JSX.Element;
110
+ declare function CredentialForm({ mode, onModeChange, values, onChange, errors, submitting, onSubmit, title, body, before, after, labels, passwordRules, passwordAside, open, id }: CredentialFormProps): react.JSX.Element;
98
111
 
99
112
  type SocialProvider = 'google' | 'github' | 'x';
100
113
  interface SocialSignInProps {
@@ -109,6 +122,16 @@ declare function OrDivider({ children }: {
109
122
  children?: string;
110
123
  }): react.JSX.Element;
111
124
 
125
+ interface EmailDisclosureProps {
126
+ open: boolean;
127
+ onToggle: () => void;
128
+ /** id of the form region it controls. */
129
+ controls: string;
130
+ openLabel?: string;
131
+ closedLabel?: string;
132
+ }
133
+ declare function EmailDisclosure({ open, onToggle, controls, openLabel, closedLabel }: EmailDisclosureProps): react.JSX.Element;
134
+
112
135
  declare const STATES: {
113
136
  id: string;
114
137
  props: SignInFormProps;
@@ -120,4 +143,4 @@ declare const CREDENTIAL_STATES: {
120
143
  props: CredentialStateProps;
121
144
  }[];
122
145
 
123
- export { AuthDoor, type AuthDoorProps, AuthHero, type AuthHeroProps, type AuthHeroStat, CREDENTIAL_STATES, type CredentialErrors, CredentialForm, type CredentialFormProps, type CredentialMode, type CredentialValues, LicencePlate, OrDivider, STATES, type SignInAction, SignInForm, type SignInFormProps, type SocialProvider, SocialSignIn, type SocialSignInProps, XenoMark, ensureAuthStyles };
146
+ export { AuthDoor, type AuthDoorProps, AuthHero, type AuthHeroProps, type AuthHeroStat, CREDENTIAL_STATES, type CredentialErrors, CredentialForm, type CredentialFormProps, type CredentialMode, type CredentialValues, EmailDisclosure, type EmailDisclosureProps, LicencePlate, OrDivider, STATES, type SignInAction, SignInForm, type SignInFormProps, type SocialProvider, SocialSignIn, type SocialSignInProps, XenoMark, ensureAuthStyles };
@@ -1,4 +1,5 @@
1
1
  // src/auth/styles.ts
2
+ import { useInsertionEffect } from "react";
2
3
  var STYLE_ID = "xeno-components-auth";
3
4
  var CSS = `
4
5
  .xc-auth{position:absolute;inset:0;display:flex;background:var(--xeno-canvas);color:var(--xeno-text);font-family:var(--xeno-font-sans);-webkit-font-smoothing:antialiased;overflow:hidden}
@@ -43,7 +44,23 @@ var CSS = `
43
44
  .xc-cred{width:100%;max-width:400px;display:flex;flex-direction:column;gap:24px}
44
45
  .xc-cred__head .xc-form__h2{font-size:30px}
45
46
  .xc-cred__mode{width:100%}
47
+ .xc-cred__collapse{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows var(--xeno-dur-entrance) var(--xeno-ease),opacity var(--xeno-dur-entrance) var(--xeno-ease)}
48
+ .xc-cred__collapse[data-open=false]{grid-template-rows:0fr;opacity:0}
49
+ .xc-cred__collapseIn{overflow:hidden;min-height:0;display:flex;flex-direction:column;gap:24px}
46
50
  .xc-cred__fields{display:flex;flex-direction:column;gap:16px}
51
+ .xc-cred__labelRow{display:flex;justify-content:space-between;align-items:baseline}
52
+ .xc-cred__labelRow a{font-size:12px;color:var(--xeno-muted);text-decoration:none}
53
+ .xc-cred__labelRow a:hover{color:var(--xeno-text)}
54
+ .xc-cred__rules{list-style:none;margin:0;padding:0;display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--xeno-dur-entrance) var(--xeno-ease),opacity var(--xeno-dur-entrance) var(--xeno-ease)}
55
+ .xc-cred__rules[data-open=true]{grid-template-rows:1fr;opacity:1;margin-top:10px}
56
+ .xc-cred__rules > li{overflow:hidden;min-height:0}
57
+ .xc-cred__rule{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--xeno-muted);transition:color var(--xeno-dur-fast) var(--xeno-ease)}
58
+ .xc-cred__rule[data-met=true]{color:var(--xeno-text)}
59
+ .xc-cred__ruleMark{width:14px;height:14px;flex:none}
60
+ .xc-or__btn{border:0;cursor:pointer;display:inline-flex;align-items:center;gap:6px;font:inherit;font-size:11px;letter-spacing:.12em;text-transform:uppercase}
61
+ .xc-or__btn:hover{color:var(--xeno-text)}
62
+ .xc-or__caret{width:12px;height:12px;transition:transform var(--xeno-dur-fast) var(--xeno-ease)}
63
+ .xc-or__caret[data-open=true]{transform:rotate(180deg)}
47
64
  .xc-cred__reveal{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--xeno-dur-entrance) var(--xeno-ease),opacity var(--xeno-dur-entrance) var(--xeno-ease)}
48
65
  .xc-cred__reveal[data-open=true]{grid-template-rows:1fr;opacity:1}
49
66
  .xc-cred__reveal > *{overflow:hidden;min-height:0}
@@ -68,6 +85,11 @@ function ensureAuthStyles() {
68
85
  style.textContent = CSS;
69
86
  document.head.appendChild(style);
70
87
  }
88
+ function useAuthStyles() {
89
+ useInsertionEffect(() => {
90
+ ensureAuthStyles();
91
+ }, []);
92
+ }
71
93
 
72
94
  // src/auth/XenoMark.tsx
73
95
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -83,6 +105,7 @@ function XenoMark() {
83
105
  // src/auth/AuthHero.tsx
84
106
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
85
107
  function AuthHero({ headline, lead, stats, imageSrc, compact }) {
108
+ useAuthStyles();
86
109
  return /* @__PURE__ */ jsxs2("div", { className: "xc-hero", "data-xc": "auth-hero", children: [
87
110
  imageSrc && /* @__PURE__ */ jsx2("img", { className: "xc-hero__img", src: imageSrc, alt: "", draggable: false }),
88
111
  /* @__PURE__ */ jsxs2("div", { className: "xc-hero__in", children: [
@@ -112,6 +135,7 @@ import { useEffect, useState } from "react";
112
135
  import { Button } from "@xenosystem/elements-react";
113
136
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
114
137
  function SignInForm({ tag, title, body, actions, note, warning, identity, animate = true, phase }) {
138
+ useAuthStyles();
115
139
  const [visible, setVisible] = useState(!animate);
116
140
  useEffect(() => {
117
141
  if (!animate) return;
@@ -174,8 +198,11 @@ var L = {
174
198
  signin: "Sign In",
175
199
  signup: "Sign Up"
176
200
  };
177
- function CredentialForm({ mode, onModeChange, values, onChange, errors = {}, submitting = false, onSubmit, title, body, before, after, labels }) {
201
+ function CredentialForm({ mode, onModeChange, values, onChange, errors = {}, submitting = false, onSubmit, title, body, before, after, labels, passwordRules, passwordAside, open = true, id = "xc-credential-fields" }) {
202
+ useAuthStyles();
178
203
  const [showPassword, setShowPassword] = useState3(false);
204
+ const [passwordFocused, setPasswordFocused] = useState3(false);
205
+ const showRules = mode === "signup" && !!passwordRules?.length && (passwordFocused || values.password.length > 0);
179
206
  const t = { ...L, ...labels };
180
207
  const set = (k) => (e) => onChange({ ...values, [k]: e.target.value });
181
208
  const submit = (e) => {
@@ -199,28 +226,37 @@ function CredentialForm({ mode, onModeChange, values, onChange, errors = {}, sub
199
226
  }
200
227
  ),
201
228
  before,
202
- /* @__PURE__ */ jsxs5("div", { className: "xc-cred__fields", children: [
203
- /* @__PURE__ */ jsx5("div", { className: "xc-cred__reveal", "data-open": mode === "signup", "aria-hidden": mode !== "signup", children: /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
204
- /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.name }),
205
- /* @__PURE__ */ jsx5(TextInput, { size: "lg", value: values.name, onChange: set("name"), placeholder: "Your name", autoComplete: "name", tabIndex: mode === "signup" ? 0 : -1, "aria-invalid": !!errors.name, "data-field": "name" }),
206
- errors.name && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.name })
207
- ] }) }),
208
- /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
209
- /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.email }),
210
- /* @__PURE__ */ jsx5(TextInput, { size: "lg", type: "email", value: values.email, onChange: set("email"), placeholder: "you@example.com", autoComplete: "email", required: true, "aria-invalid": !!errors.email, "data-field": "email" }),
211
- errors.email && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.email })
212
- ] }),
213
- /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
214
- /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.password }),
215
- /* @__PURE__ */ jsxs5("span", { className: "xc-cred__pw", children: [
216
- /* @__PURE__ */ jsx5(TextInput, { size: "lg", type: showPassword ? "text" : "password", value: values.password, onChange: set("password"), placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: mode === "signup" ? "new-password" : "current-password", required: true, "aria-invalid": !!errors.password, "data-field": "password" }),
217
- /* @__PURE__ */ jsx5(Button2, { type: "button", variant: "ghost", size: "sm", className: "xc-cred__eye", onClick: () => setShowPassword((v) => !v), "aria-label": showPassword ? "Hide password" : "Show password", "aria-pressed": showPassword, children: showPassword ? "Hide" : "Show" })
229
+ /* @__PURE__ */ jsx5("div", { className: "xc-cred__collapse", "data-open": open, id, "aria-hidden": !open, children: /* @__PURE__ */ jsxs5("div", { className: "xc-cred__collapseIn", children: [
230
+ /* @__PURE__ */ jsxs5("div", { className: "xc-cred__fields", children: [
231
+ /* @__PURE__ */ jsx5("div", { className: "xc-cred__reveal", "data-open": mode === "signup", "aria-hidden": mode !== "signup", children: /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
232
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.name }),
233
+ /* @__PURE__ */ jsx5(TextInput, { size: "lg", value: values.name, onChange: set("name"), placeholder: "Your name", autoComplete: "name", tabIndex: mode === "signup" && open ? 0 : -1, "aria-invalid": !!errors.name, "data-field": "name" }),
234
+ errors.name && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.name })
235
+ ] }) }),
236
+ /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
237
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.email }),
238
+ /* @__PURE__ */ jsx5(TextInput, { size: "lg", type: "email", value: values.email, onChange: set("email"), placeholder: "you@example.com", autoComplete: "email", required: true, tabIndex: open ? 0 : -1, "aria-invalid": !!errors.email, "data-field": "email" }),
239
+ errors.email && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.email })
218
240
  ] }),
219
- errors.password && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.password })
220
- ] })
221
- ] }),
222
- errors.form && /* @__PURE__ */ jsx5("p", { className: "xc-form__warn", role: "alert", children: errors.form }),
223
- /* @__PURE__ */ jsx5("div", { className: "xc-form__actions", children: /* @__PURE__ */ jsx5(Button2, { type: "submit", variant: "primary", size: "lg", busy: submitting, "data-intent": "submit", children: mode === "signin" ? t.submitSignIn : t.submitSignUp }) }),
241
+ /* @__PURE__ */ jsxs5("label", { className: "xc-cred__field", children: [
242
+ /* @__PURE__ */ jsxs5("span", { className: "xc-cred__labelRow", children: [
243
+ /* @__PURE__ */ jsx5("span", { className: "xc-cred__label", children: t.password }),
244
+ passwordAside
245
+ ] }),
246
+ /* @__PURE__ */ jsxs5("span", { className: "xc-cred__pw", children: [
247
+ /* @__PURE__ */ jsx5(TextInput, { size: "lg", type: showPassword ? "text" : "password", value: values.password, onChange: set("password"), onFocus: () => setPasswordFocused(true), onBlur: () => setPasswordFocused(false), placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", tabIndex: open ? 0 : -1, autoComplete: mode === "signup" ? "new-password" : "current-password", required: true, "aria-invalid": !!errors.password, "data-field": "password" }),
248
+ /* @__PURE__ */ jsx5(Button2, { type: "button", variant: "ghost", size: "sm", className: "xc-cred__eye", tabIndex: open ? 0 : -1, onClick: () => setShowPassword((v) => !v), "aria-label": showPassword ? "Hide password" : "Show password", "aria-pressed": showPassword, children: showPassword ? "Hide" : "Show" })
249
+ ] }),
250
+ errors.password && /* @__PURE__ */ jsx5("span", { className: "xc-cred__error", role: "alert", children: errors.password }),
251
+ passwordRules && /* @__PURE__ */ jsx5("ul", { className: "xc-cred__rules", "data-open": showRules, "aria-live": "polite", children: passwordRules.map((r, i) => /* @__PURE__ */ jsxs5("li", { className: "xc-cred__rule", "data-met": r.met, style: { transitionDelay: `${i * 60}ms` }, children: [
252
+ /* @__PURE__ */ jsx5("svg", { className: "xc-cred__ruleMark", viewBox: "0 0 16 16", "aria-hidden": "true", children: r.met ? /* @__PURE__ */ jsx5("path", { d: "M3.5 8.5l3 3 6-6", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }) : /* @__PURE__ */ jsx5("rect", { x: "4", y: "7.25", width: "8", height: "1.5", fill: "currentColor" }) }),
253
+ /* @__PURE__ */ jsx5("span", { children: r.label })
254
+ ] }, r.label)) })
255
+ ] })
256
+ ] }),
257
+ errors.form && /* @__PURE__ */ jsx5("p", { className: "xc-form__warn", role: "alert", children: errors.form }),
258
+ /* @__PURE__ */ jsx5("div", { className: "xc-form__actions", children: /* @__PURE__ */ jsx5(Button2, { type: "submit", variant: "primary", size: "lg", busy: submitting, tabIndex: open ? 0 : -1, "data-intent": "submit", children: mode === "signin" ? t.submitSignIn : t.submitSignUp }) })
259
+ ] }) }),
224
260
  after && /* @__PURE__ */ jsx5("div", { className: "xc-cred__after", children: after })
225
261
  ] });
226
262
  }
@@ -240,15 +276,27 @@ function Mark({ p }) {
240
276
  return /* @__PURE__ */ jsx6("svg", { className: "xc-social__mark", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx6("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) });
241
277
  }
242
278
  function SocialSignIn({ providers = ["google", "github", "x"], onSelect, disabled }) {
279
+ useAuthStyles();
243
280
  return /* @__PURE__ */ jsx6("div", { className: "xc-social", "data-xc": "social-sign-in", role: "group", "aria-label": "Continue with", children: providers.map((p) => /* @__PURE__ */ jsx6(Button3, { variant: "quiet", size: "lg", disabled, onClick: () => onSelect(p), "aria-label": `Continue with ${NAMES[p]}`, "data-provider": p, children: /* @__PURE__ */ jsx6(Mark, { p }) }, p)) });
244
281
  }
245
282
 
246
283
  // src/auth/OrDivider.tsx
247
284
  import { jsx as jsx7 } from "react/jsx-runtime";
248
285
  function OrDivider({ children = "or continue with email" }) {
286
+ useAuthStyles();
249
287
  return /* @__PURE__ */ jsx7("div", { className: "xc-or", role: "separator", "aria-label": children, children: /* @__PURE__ */ jsx7("span", { className: "xc-or__label", children }) });
250
288
  }
251
289
 
290
+ // src/auth/EmailDisclosure.tsx
291
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
292
+ function EmailDisclosure({ open, onToggle, controls, openLabel = "hide email sign-in", closedLabel = "or continue with email" }) {
293
+ useAuthStyles();
294
+ return /* @__PURE__ */ jsx8("div", { className: "xc-or", "data-xc": "email-disclosure", children: /* @__PURE__ */ jsxs7("button", { type: "button", className: "xc-or__label xc-or__btn", onClick: onToggle, "aria-expanded": open, "aria-controls": controls, children: [
295
+ open ? openLabel : closedLabel,
296
+ /* @__PURE__ */ jsx8("svg", { className: "xc-or__caret", "data-open": open, viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "M4 6l4 4 4-4", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }) })
297
+ ] }) });
298
+ }
299
+
252
300
  // src/auth/states.ts
253
301
  var STATES = [
254
302
  { id: "sign-in", props: { title: "Welcome back", body: "Sign in to your XENO account to use XENO Canvas.", actions: [{ id: "signIn", label: "Sign in to XENO", variant: "primary" }], note: "Your browser opens at xenostudio.ai, where you can continue with Google, GitHub or email. XENO Canvas never sees your password.", animate: false } },
@@ -265,6 +313,8 @@ var CREDENTIAL_STATES = [
265
313
  { id: "filled", props: { mode: "signin", values: { name: "", email: "you@example.com", password: "hunter22" }, title: "Welcome back", body: "Enter your credentials to access your account" } },
266
314
  { id: "errors", props: { mode: "signup", values: { name: "", email: "not-an-email", password: "123" }, errors: { name: "Please enter your full name", email: "Please enter a valid email address", password: "Password must be at least 6 characters" }, title: "Get started", body: "Create your account and start creating" } },
267
315
  { id: "submitting", props: { mode: "signin", values: { name: "", email: "you@example.com", password: "hunter22" }, submitting: true, title: "Welcome back", body: "Enter your credentials to access your account" } },
316
+ { id: "rules", props: { mode: "signup", values: { name: "Ada", email: "ada@example.com", password: "abcdefgh1" }, passwordRules: [{ label: "Minimum 8 letters", met: true }, { label: "At least one number", met: true }, { label: "At least one special character", met: false }], title: "Get started", body: "Create your account and start creating" } },
317
+ { id: "collapsed", props: { mode: "signin", values: empty, open: false, title: "Welcome back", body: "Enter your credentials to access your account" } },
268
318
  { id: "rejected", props: { mode: "signin", values: { name: "", email: "you@example.com", password: "hunter22" }, errors: { form: "Invalid email or password." }, title: "Welcome back", body: "Enter your credentials to access your account" } }
269
319
  ];
270
320
  export {
@@ -272,6 +322,7 @@ export {
272
322
  AuthHero,
273
323
  CREDENTIAL_STATES,
274
324
  CredentialForm,
325
+ EmailDisclosure,
275
326
  LicencePlate,
276
327
  OrDivider,
277
328
  STATES,
package/dist/index.d.ts CHANGED
@@ -3,6 +3,6 @@
3
3
  * Families are subpaths: `@xenosystem/components/auth`, `/navigation`, `/feedback`, `/data`.
4
4
  * This root re-exports nothing on purpose: import the family you use, so a bundle carries only it.
5
5
  */
6
- declare const COMPONENTS_VERSION = "0.2.1";
6
+ declare const COMPONENTS_VERSION = "0.3.1";
7
7
 
8
8
  export { COMPONENTS_VERSION };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var COMPONENTS_VERSION = "0.2.1";
2
+ var COMPONENTS_VERSION = "0.3.1";
3
3
  export {
4
4
  COMPONENTS_VERSION
5
5
  };
package/package.json CHANGED
@@ -1,19 +1,29 @@
1
1
  {
2
2
  "name": "@xenosystem/components",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "XENO Components — rung 2 of the front ladder: molecules composed from @xenosystem/elements, with no host contract. Families as subpaths: /auth, /navigation, /feedback, /data.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "sideEffects": false,
8
8
  "exports": {
9
- ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
10
- "./auth": { "types": "./dist/auth/index.d.ts", "import": "./dist/auth/index.js" }
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./auth": {
14
+ "types": "./dist/auth/index.d.ts",
15
+ "import": "./dist/auth/index.js"
16
+ }
11
17
  },
12
- "files": ["dist", "README.md"],
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
13
22
  "scripts": {
14
23
  "build": "tsup",
15
24
  "typecheck": "tsc --noEmit",
16
- "test": "vitest run"
25
+ "test": "vitest run",
26
+ "prepublishOnly": "node scripts/verify-dist.mjs"
17
27
  },
18
28
  "peerDependencies": {
19
29
  "@xenosystem/elements-react": ">=0.0.1",
@@ -31,5 +41,7 @@
31
41
  "typescript": "^5.4.0",
32
42
  "vitest": "^4.1.0"
33
43
  },
34
- "publishConfig": { "access": "public" }
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
35
47
  }