tempest-react-sdk 0.79.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -4
- package/bin/lib/component/generate.test.mjs +13 -1
- package/bin/lib/openapi/generate.hostile.test.mjs +58 -5
- package/bin/lib/openapi/generate.mjs +145 -34
- package/bin/lib/openapi/generate.test.mjs +216 -5
- package/bin/lib/openapi/schema-to-zod.mjs +4 -3
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +172 -103
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/br/boleto.cjs +1 -1
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js +2 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/br.d.ts +22 -3
- package/dist/components/AuthForm/AuthFieldControl.cjs +2 -0
- package/dist/components/AuthForm/AuthFieldControl.cjs.map +1 -0
- package/dist/components/AuthForm/AuthFieldControl.js +85 -0
- package/dist/components/AuthForm/AuthFieldControl.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.cjs +2 -0
- package/dist/components/AuthForm/AuthForm.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.js +83 -0
- package/dist/components/AuthForm/AuthForm.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.cjs +3 -0
- package/dist/components/AuthForm/AuthForm.module.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.js +14 -0
- package/dist/components/AuthForm/AuthForm.module.js.map +1 -0
- package/dist/components/AuthForm/AuthLinks.cjs +2 -0
- package/dist/components/AuthForm/AuthLinks.cjs.map +1 -0
- package/dist/components/AuthForm/AuthLinks.js +29 -0
- package/dist/components/AuthForm/AuthLinks.js.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js +32 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/LoginForm.cjs +2 -0
- package/dist/components/AuthForm/LoginForm.cjs.map +1 -0
- package/dist/components/AuthForm/LoginForm.js +39 -0
- package/dist/components/AuthForm/LoginForm.js.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.js +39 -0
- package/dist/components/AuthForm/ResetPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/SignUpForm.cjs +2 -0
- package/dist/components/AuthForm/SignUpForm.cjs.map +1 -0
- package/dist/components/AuthForm/SignUpForm.js +32 -0
- package/dist/components/AuthForm/SignUpForm.js.map +1 -0
- package/dist/components/AuthForm/auth-defaults.cjs +2 -0
- package/dist/components/AuthForm/auth-defaults.cjs.map +1 -0
- package/dist/components/AuthForm/auth-defaults.js +54 -0
- package/dist/components/AuthForm/auth-defaults.js.map +1 -0
- package/dist/components/AuthForm/auth-schemas.cjs +2 -0
- package/dist/components/AuthForm/auth-schemas.cjs.map +1 -0
- package/dist/components/AuthForm/auth-schemas.js +26 -0
- package/dist/components/AuthForm/auth-schemas.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.cjs +2 -0
- package/dist/components/AuthPage/AuthPage.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.js +91 -0
- package/dist/components/AuthPage/AuthPage.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.cjs +3 -0
- package/dist/components/AuthPage/AuthPage.module.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.js +24 -0
- package/dist/components/AuthPage/AuthPage.module.js.map +1 -0
- package/dist/components/FilterBar/filter-apply.cjs +1 -1
- package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
- package/dist/components/FilterBar/filter-apply.js +7 -7
- package/dist/forms/index.js +7 -7
- package/dist/forms/masked-inputs.cjs +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +15 -15
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +104 -97
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/build-url.cjs +1 -1
- package/dist/http/build-url.cjs.map +1 -1
- package/dist/http/build-url.js +3 -1
- package/dist/http/build-url.js.map +1 -1
- package/dist/http/presigned-post.cjs +2 -0
- package/dist/http/presigned-post.cjs.map +1 -0
- package/dist/http/presigned-post.js +73 -0
- package/dist/http/presigned-post.js.map +1 -0
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +97 -97
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +12 -12
- package/dist/http/use-describe-api-error.cjs +1 -1
- package/dist/http/use-describe-api-error.cjs.map +1 -1
- package/dist/http/use-describe-api-error.js +9 -9
- package/dist/offline/create-offline-sync.cjs +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js +78 -56
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +44 -30
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +2 -0
- package/dist/sse/ticket-event-stream.cjs.map +1 -0
- package/dist/sse/ticket-event-stream.js +41 -0
- package/dist/sse/ticket-event-stream.js.map +1 -0
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +7 -4
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/styles/AuthForm.css +2 -0
- package/dist/styles/AuthPage.css +2 -0
- package/dist/styles/component/AuthForm.css +9 -0
- package/dist/styles/component/AuthPage.css +29 -0
- package/dist/styles/forms.css +36 -0
- package/dist/styles/manifest.json +63 -0
- package/dist/styles.css +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +124 -106
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw.d.ts +49 -15
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +809 -22
- package/dist/tempest-react-sdk.js +373 -363
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +25 -25
- package/dist/utils/numbers.cjs +1 -1
- package/dist/utils/numbers.cjs.map +1 -1
- package/dist/utils/numbers.js +7 -7
- package/package.json +1 -1
- package/template-pwa/src/sw.ts +10 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-schemas.js","names":["authEmail","z","authPassword","message","loginSchema","signUpSchema","forgotPasswordSchema","resetPasswordSchema","values"],"sources":["../../../src/components/AuthForm/auth-schemas.ts"],"sourcesContent":["import { z } from \"zod\";\n\n/**\n * An e-mail field: trimmed and lower-cased *before* it is validated.\n *\n * The order is the point. Validating first rejects `\" ana@x.com\"` pasted with a\n * trailing space from a password manager, and a backend that compares e-mails\n * case-sensitively creates a second account for `Ana@x.com`. An empty value gets\n * its own sentence instead of \"e-mail inválido\", which reads like an accusation\n * on a field the user simply has not reached yet.\n *\n * @returns A schema producing the normalized address.\n */\nexport function authEmail(): z.ZodPipe<z.ZodString, z.ZodEmail> {\n return z\n .string()\n .trim()\n .toLowerCase()\n .min(1, \"Informe seu e-mail\")\n .pipe(z.email(\"Informe um e-mail válido\"));\n}\n\n/**\n * A password field with the minimum length every Tempest backend enforces.\n *\n * @param message - Sentence shown below 8 characters.\n * @returns The password schema.\n */\nexport function authPassword(\n message: string = \"A senha deve ter no mínimo 8 caracteres\",\n): z.ZodString {\n return z.string().min(8, message);\n}\n\n/** Default schema of `LoginForm`: normalized e-mail + password of at least 8 characters. */\nexport const loginSchema = z.object({\n email: authEmail(),\n password: authPassword(),\n});\n\n/**\n * Default schema of `SignUpForm`: name, e-mail and password.\n *\n * Exported so an app adds its own fields with `.extend()` instead of rewriting\n * the three it shares with every other app — see the phone and terms example in\n * the docs.\n */\nexport const signUpSchema = z.object({\n name: z.string().trim().min(3, \"Informe seu nome completo\"),\n email: authEmail(),\n password: authPassword(),\n});\n\n/** Default schema of `ForgotPasswordForm`: the e-mail the reset link goes to. */\nexport const forgotPasswordSchema = z.object({\n email: authEmail(),\n});\n\n/**\n * Default schema of `ResetPasswordForm`: new password and its confirmation.\n *\n * The mismatch is reported on `confirmPassword` (`path`), so the message lands\n * under the field the user has to fix rather than in a form-level alert.\n */\nexport const resetPasswordSchema = z\n .object({\n password: authPassword(),\n confirmPassword: z.string().min(1, \"Confirme a nova senha\"),\n })\n .refine((values) => values.password === values.confirmPassword, {\n message: \"As senhas não coincidem\",\n path: [\"confirmPassword\"],\n });\n"],"mappings":";;AAaA,SAAgBA,IAAgD;CAC5D,OAAOC,EACF,OAAO,CAAC,CACR,KAAK,CAAC,CACN,YAAY,CAAC,CACb,IAAI,GAAG,oBAAoB,CAAC,CAC5B,KAAKA,EAAE,MAAM,0BAA0B,CAAC;AACjD;AAQA,SAAgBC,EACZ,IAAkB,2CACP;CACX,OAAOD,EAAE,OAAO,CAAC,CAAC,IAAI,GAAGE,CAAO;AACpC;AAGA,IAAaC,IAAcH,EAAE,OAAO;CAChC,OAAOD,EAAU;CACjB,UAAUE,EAAa;AAC3B,CAAC,GASYG,IAAeJ,EAAE,OAAO;CACjC,MAAMA,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,2BAA2B;CAC1D,OAAOD,EAAU;CACjB,UAAUE,EAAa;AAC3B,CAAC,GAGYI,IAAuBL,EAAE,OAAO,EACzC,OAAOD,EAAU,EACrB,CAAC,GAQYO,IAAsBN,EAC9B,OAAO;CACJ,UAAUC,EAAa;CACvB,iBAAiBD,EAAE,OAAO,CAAC,CAAC,IAAI,GAAG,uBAAuB;AAC9D,CAAC,CAAC,CACD,QAAQ,MAAWO,EAAO,aAAaA,EAAO,iBAAiB;CAC5D,SAAS;CACT,MAAM,CAAC,iBAAiB;AAC5B,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-media-query.cjs"),n=require("./AuthPage.module.cjs");let r=require("react"),i=require("react/jsx-runtime"),a=require("react-router");var o=`Voltar`;function s({back:e}){let t=typeof e==`string`?{to:e}:e,r=(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(`svg`,{"aria-hidden":!0,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,children:(0,i.jsx)(`path`,{d:`M19 12H5m6-7-7 7 7 7`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`})}),(0,i.jsx)(`span`,{children:t.label??o})]});return t.to===void 0?(0,i.jsx)(`button`,{type:`button`,onClick:t.onClick,className:n.default.back,children:r}):(0,i.jsx)(a.Link,{to:t.to,onClick:t.onClick,className:n.default.back,children:r})}function c({brand:a,title:o,subtitle:c,aside:l,variant:u=`split`,breakpoint:d=768,back:f,footer:p,headingLevel:m=1,as:h=`main`,children:g,className:_,style:v,...y}){let b=t.useMediaQuery(`(min-width: ${d}px)`),x=u===`card`?`card`:b?`split`:`stacked`,S={"--tempest-auth-page-aside-bg":l?.background,"--tempest-auth-page-aside-fg":l?.color,...v},C=o===void 0?null:(0,r.createElement)(`h${m}`,{className:n.default.title},o);return(0,r.createElement)(h,{...y,className:e.cn(n.default.root,n.default[x],_),style:S,"data-layout":x},x!==`card`&&(0,i.jsxs)(`div`,{className:n.default.aside,children:[f!==void 0&&(0,i.jsx)(s,{back:f}),a!==void 0&&(0,i.jsx)(`div`,{className:n.default.brand,children:a}),l?.content!==void 0&&(0,i.jsx)(`div`,{className:n.default.asideContent,children:l.content})]}),x===`card`&&f!==void 0&&(0,i.jsx)(`div`,{className:n.default.cardBack,children:(0,i.jsx)(s,{back:f})}),(0,i.jsx)(`div`,{className:n.default.panel,children:(0,i.jsxs)(`div`,{className:n.default.sheet,children:[x===`card`&&a!==void 0&&(0,i.jsx)(`div`,{className:n.default.cardBrand,children:a}),(C||c!==void 0)&&(0,i.jsxs)(`div`,{className:n.default.header,children:[C,c!==void 0&&(0,i.jsx)(`p`,{className:n.default.subtitle,children:c})]}),g,p!==void 0&&(0,i.jsx)(`div`,{className:n.default.footer,children:p})]})}))}exports.AuthPage=c;
|
|
2
|
+
//# sourceMappingURL=AuthPage.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthPage.cjs","names":["_Fragment","Link","styles","useMediaQuery","createElement","cn","require_cn","require_use_media_query","require_AuthPage_module","react","react_jsx_runtime","react_router","DEFAULT_BACK_LABEL","BackAction","action","back","content","AuthPage","props","wide","breakpoint","layout","variant","rootStyle","aside","style","heading","title","headingLevel","as","className","brand","subtitle","children","footer"],"sources":["../../../src/components/AuthPage/AuthPage.tsx"],"sourcesContent":["import { createElement } from \"react\";\nimport type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport { Link } from \"react-router\";\nimport { useMediaQuery } from \"@/hooks/use-media-query\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AuthPage.module.css\";\n\n/** How the page is laid out: brand panel beside the form, or one centered card. */\nexport type AuthPageVariant = \"split\" | \"card\";\n\n/** Heading level of the page title. */\nexport type AuthPageHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\n/** The brand panel of a `split` page — the colored half on desktop, the colored top on mobile. */\nexport interface AuthPageAside {\n /**\n * Any CSS `background` value: a color, a token, a gradient, an image.\n * Defaults to `var(--tempest-primary)`, so a themed app gets its brand color\n * without passing anything.\n */\n background?: string;\n /**\n * Text color over that background. Defaults to `var(--tempest-text-on-primary)`,\n * which is only right while the background is the primary color — set it\n * together with a custom `background` so the back button and the app name\n * keep their contrast.\n */\n color?: string;\n /** Extra content under the brand on the panel: a tagline, an illustration. */\n content?: ReactNode;\n}\n\n/** The back action, as an object when the label or the behavior is not the default. */\nexport interface AuthPageBackAction {\n /** Route to navigate to, rendered as a router `Link`. */\n to?: string;\n /** Visible label (and accessible name). Default `\"Voltar\"`. */\n label?: string;\n /** Click handler — used alone (a `<button>`) or together with `to`. */\n onClick?: () => void;\n}\n\nexport interface AuthPageProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Logo and/or app name, shown on the brand panel (split) or at the top of the card. */\n brand?: ReactNode;\n /** Page title, rendered as a heading — e.g. `\"Bem-vindo!\"`. */\n title?: ReactNode;\n /** Line under the title — e.g. `\"Entre com sua conta para continuar\"`. */\n subtitle?: ReactNode;\n /** Brand panel colors and extra content. */\n aside?: AuthPageAside;\n /**\n * `split` (default): colored top + rounded white sheet below `breakpoint`,\n * 50/50 brand panel and form at or above it. `card`: one centered card at\n * every width — the internal-panel login.\n */\n variant?: AuthPageVariant;\n /**\n * Viewport width, in px, at which `split` turns into two columns. Default\n * `768`, the width every hand-built auth screen this replaces switched at —\n * deliberately not the `1024` of `useBreakpoint`, which is about navigation\n * chrome, not about whether a form fits beside a brand panel.\n */\n breakpoint?: number;\n /** Back action: a route string, or an object with `to`/`label`/`onClick`. Omit to hide it. */\n back?: string | AuthPageBackAction;\n /** Content under the form, inside the sheet or card — legal text, a secondary link. */\n footer?: ReactNode;\n /** Heading level of `title`. Default `1`, since the auth screen is usually the whole page. */\n headingLevel?: AuthPageHeadingLevel;\n /**\n * Root element. Default `\"main\"`, the page's main landmark. Use `\"div\"` when\n * the page is rendered inside a layout that already owns a `<main>` — two\n * nested main landmarks are an accessibility violation.\n */\n as?: \"main\" | \"div\";\n /** The form — usually an `AuthForm` or one of its presets. */\n children?: ReactNode;\n}\n\nconst DEFAULT_BACK_LABEL = \"Voltar\";\n\n/**\n * Render the back action, as a router link or a plain button.\n *\n * @param props - The `back` prop, already present.\n * @returns The link or button, labelled by its visible text.\n */\nfunction BackAction({ back }: { back: string | AuthPageBackAction }): ReactNode {\n const action: AuthPageBackAction = typeof back === \"string\" ? { to: back } : back;\n const content = (\n <>\n <svg aria-hidden width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M19 12H5m6-7-7 7 7 7\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n <span>{action.label ?? DEFAULT_BACK_LABEL}</span>\n </>\n );\n if (action.to !== undefined) {\n return (\n <Link to={action.to} onClick={action.onClick} className={styles.back}>\n {content}\n </Link>\n );\n }\n return (\n <button type=\"button\" onClick={action.onClick} className={styles.back}>\n {content}\n </button>\n );\n}\n\n/**\n * The layout shell of a sign-in, sign-up or password-recovery screen.\n *\n * Every app was hand-building the same page around its form: a colored top with\n * the logo and a back button over a white sheet with rounded top corners on\n * mobile, a 50/50 brand panel and form on desktop, a \"Bem-vindo!\" heading. This\n * component is that page, so the app passes only the brand and the form.\n *\n * The switch between the two layouts is a JavaScript media query, not a CSS\n * `@media` rule, because the breakpoint is a prop: a CSS media query cannot read a\n * custom property. `useMediaQuery` evaluates synchronously on the first render, so\n * a client-rendered page does not flash the wrong layout.\n *\n * @example\n * <AuthPage brand={<img src=\"/logo.svg\" alt=\"Acme\" />} back=\"/\" title=\"Bem-vindo!\">\n * <LoginForm onSubmit={signIn} links={{ forgotPassword: \"/recuperar\", signUp: \"/cadastro\" }} />\n * </AuthPage>\n */\nexport function AuthPage({\n brand,\n title,\n subtitle,\n aside,\n variant = \"split\",\n breakpoint = 768,\n back,\n footer,\n headingLevel = 1,\n as = \"main\",\n children,\n className,\n style,\n ...props\n}: AuthPageProps): ReactNode {\n const wide = useMediaQuery(`(min-width: ${breakpoint}px)`);\n const layout = variant === \"card\" ? \"card\" : wide ? \"split\" : \"stacked\";\n const rootStyle = {\n \"--tempest-auth-page-aside-bg\": aside?.background,\n \"--tempest-auth-page-aside-fg\": aside?.color,\n ...style,\n } as CSSProperties;\n const heading =\n title === undefined\n ? null\n : createElement(`h${headingLevel}`, { className: styles.title }, title);\n\n return createElement(\n as,\n {\n ...props,\n className: cn(styles.root, styles[layout], className),\n style: rootStyle,\n \"data-layout\": layout,\n },\n layout !== \"card\" && (\n <div className={styles.aside}>\n {back !== undefined && <BackAction back={back} />}\n {brand !== undefined && <div className={styles.brand}>{brand}</div>}\n {aside?.content !== undefined && (\n <div className={styles.asideContent}>{aside.content}</div>\n )}\n </div>\n ),\n layout === \"card\" && back !== undefined && (\n <div className={styles.cardBack}>\n <BackAction back={back} />\n </div>\n ),\n <div className={styles.panel}>\n <div className={styles.sheet}>\n {layout === \"card\" && brand !== undefined && (\n <div className={styles.cardBrand}>{brand}</div>\n )}\n {(heading || subtitle !== undefined) && (\n <div className={styles.header}>\n {heading}\n {subtitle !== undefined && <p className={styles.subtitle}>{subtitle}</p>}\n </div>\n )}\n {children}\n {footer !== undefined && <div className={styles.footer}>{footer}</div>}\n </div>\n </div>,\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { useMediaQuery as t } from "../../hooks/use-media-query.js";
|
|
3
|
+
import n from "./AuthPage.module.js";
|
|
4
|
+
import { createElement as r } from "react";
|
|
5
|
+
import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
import { Link as s } from "react-router";
|
|
7
|
+
//#region src/components/AuthPage/AuthPage.tsx
|
|
8
|
+
var c = "Voltar";
|
|
9
|
+
function l({ back: e }) {
|
|
10
|
+
let t = typeof e == "string" ? { to: e } : e, r = /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ a("svg", {
|
|
11
|
+
"aria-hidden": !0,
|
|
12
|
+
width: "20",
|
|
13
|
+
height: "20",
|
|
14
|
+
viewBox: "0 0 24 24",
|
|
15
|
+
fill: "none",
|
|
16
|
+
children: /* @__PURE__ */ a("path", {
|
|
17
|
+
d: "M19 12H5m6-7-7 7 7 7",
|
|
18
|
+
stroke: "currentColor",
|
|
19
|
+
strokeWidth: "2",
|
|
20
|
+
strokeLinecap: "round",
|
|
21
|
+
strokeLinejoin: "round"
|
|
22
|
+
})
|
|
23
|
+
}), /* @__PURE__ */ a("span", { children: t.label ?? c })] });
|
|
24
|
+
return t.to === void 0 ? /* @__PURE__ */ a("button", {
|
|
25
|
+
type: "button",
|
|
26
|
+
onClick: t.onClick,
|
|
27
|
+
className: n.back,
|
|
28
|
+
children: r
|
|
29
|
+
}) : /* @__PURE__ */ a(s, {
|
|
30
|
+
to: t.to,
|
|
31
|
+
onClick: t.onClick,
|
|
32
|
+
className: n.back,
|
|
33
|
+
children: r
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function u({ brand: i, title: s, subtitle: c, aside: u, variant: d = "split", breakpoint: f = 768, back: p, footer: m, headingLevel: h = 1, as: g = "main", children: _, className: v, style: y, ...b }) {
|
|
37
|
+
let x = t(`(min-width: ${f}px)`), S = d === "card" ? "card" : x ? "split" : "stacked", C = {
|
|
38
|
+
"--tempest-auth-page-aside-bg": u?.background,
|
|
39
|
+
"--tempest-auth-page-aside-fg": u?.color,
|
|
40
|
+
...y
|
|
41
|
+
}, w = s === void 0 ? null : r(`h${h}`, { className: n.title }, s);
|
|
42
|
+
return r(g, {
|
|
43
|
+
...b,
|
|
44
|
+
className: e(n.root, n[S], v),
|
|
45
|
+
style: C,
|
|
46
|
+
"data-layout": S
|
|
47
|
+
}, S !== "card" && /* @__PURE__ */ o("div", {
|
|
48
|
+
className: n.aside,
|
|
49
|
+
children: [
|
|
50
|
+
p !== void 0 && /* @__PURE__ */ a(l, { back: p }),
|
|
51
|
+
i !== void 0 && /* @__PURE__ */ a("div", {
|
|
52
|
+
className: n.brand,
|
|
53
|
+
children: i
|
|
54
|
+
}),
|
|
55
|
+
u?.content !== void 0 && /* @__PURE__ */ a("div", {
|
|
56
|
+
className: n.asideContent,
|
|
57
|
+
children: u.content
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
}), S === "card" && p !== void 0 && /* @__PURE__ */ a("div", {
|
|
61
|
+
className: n.cardBack,
|
|
62
|
+
children: /* @__PURE__ */ a(l, { back: p })
|
|
63
|
+
}), /* @__PURE__ */ a("div", {
|
|
64
|
+
className: n.panel,
|
|
65
|
+
children: /* @__PURE__ */ o("div", {
|
|
66
|
+
className: n.sheet,
|
|
67
|
+
children: [
|
|
68
|
+
S === "card" && i !== void 0 && /* @__PURE__ */ a("div", {
|
|
69
|
+
className: n.cardBrand,
|
|
70
|
+
children: i
|
|
71
|
+
}),
|
|
72
|
+
(w || c !== void 0) && /* @__PURE__ */ o("div", {
|
|
73
|
+
className: n.header,
|
|
74
|
+
children: [w, c !== void 0 && /* @__PURE__ */ a("p", {
|
|
75
|
+
className: n.subtitle,
|
|
76
|
+
children: c
|
|
77
|
+
})]
|
|
78
|
+
}),
|
|
79
|
+
_,
|
|
80
|
+
m !== void 0 && /* @__PURE__ */ a("div", {
|
|
81
|
+
className: n.footer,
|
|
82
|
+
children: m
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
})
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { u as AuthPage };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=AuthPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthPage.js","names":["_Fragment","styles","AuthPage_module_default","Fragment$1","DEFAULT_BACK_LABEL","BackAction","action","back","content","Link","AuthPage","props","wide","useMediaQuery","breakpoint","layout","variant","rootStyle","aside","style","heading","title","createElement","headingLevel","as","cn","className","brand","subtitle","children","footer"],"sources":["../../../src/components/AuthPage/AuthPage.tsx"],"sourcesContent":["import { createElement } from \"react\";\nimport type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport { Link } from \"react-router\";\nimport { useMediaQuery } from \"@/hooks/use-media-query\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./AuthPage.module.css\";\n\n/** How the page is laid out: brand panel beside the form, or one centered card. */\nexport type AuthPageVariant = \"split\" | \"card\";\n\n/** Heading level of the page title. */\nexport type AuthPageHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;\n\n/** The brand panel of a `split` page — the colored half on desktop, the colored top on mobile. */\nexport interface AuthPageAside {\n /**\n * Any CSS `background` value: a color, a token, a gradient, an image.\n * Defaults to `var(--tempest-primary)`, so a themed app gets its brand color\n * without passing anything.\n */\n background?: string;\n /**\n * Text color over that background. Defaults to `var(--tempest-text-on-primary)`,\n * which is only right while the background is the primary color — set it\n * together with a custom `background` so the back button and the app name\n * keep their contrast.\n */\n color?: string;\n /** Extra content under the brand on the panel: a tagline, an illustration. */\n content?: ReactNode;\n}\n\n/** The back action, as an object when the label or the behavior is not the default. */\nexport interface AuthPageBackAction {\n /** Route to navigate to, rendered as a router `Link`. */\n to?: string;\n /** Visible label (and accessible name). Default `\"Voltar\"`. */\n label?: string;\n /** Click handler — used alone (a `<button>`) or together with `to`. */\n onClick?: () => void;\n}\n\nexport interface AuthPageProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Logo and/or app name, shown on the brand panel (split) or at the top of the card. */\n brand?: ReactNode;\n /** Page title, rendered as a heading — e.g. `\"Bem-vindo!\"`. */\n title?: ReactNode;\n /** Line under the title — e.g. `\"Entre com sua conta para continuar\"`. */\n subtitle?: ReactNode;\n /** Brand panel colors and extra content. */\n aside?: AuthPageAside;\n /**\n * `split` (default): colored top + rounded white sheet below `breakpoint`,\n * 50/50 brand panel and form at or above it. `card`: one centered card at\n * every width — the internal-panel login.\n */\n variant?: AuthPageVariant;\n /**\n * Viewport width, in px, at which `split` turns into two columns. Default\n * `768`, the width every hand-built auth screen this replaces switched at —\n * deliberately not the `1024` of `useBreakpoint`, which is about navigation\n * chrome, not about whether a form fits beside a brand panel.\n */\n breakpoint?: number;\n /** Back action: a route string, or an object with `to`/`label`/`onClick`. Omit to hide it. */\n back?: string | AuthPageBackAction;\n /** Content under the form, inside the sheet or card — legal text, a secondary link. */\n footer?: ReactNode;\n /** Heading level of `title`. Default `1`, since the auth screen is usually the whole page. */\n headingLevel?: AuthPageHeadingLevel;\n /**\n * Root element. Default `\"main\"`, the page's main landmark. Use `\"div\"` when\n * the page is rendered inside a layout that already owns a `<main>` — two\n * nested main landmarks are an accessibility violation.\n */\n as?: \"main\" | \"div\";\n /** The form — usually an `AuthForm` or one of its presets. */\n children?: ReactNode;\n}\n\nconst DEFAULT_BACK_LABEL = \"Voltar\";\n\n/**\n * Render the back action, as a router link or a plain button.\n *\n * @param props - The `back` prop, already present.\n * @returns The link or button, labelled by its visible text.\n */\nfunction BackAction({ back }: { back: string | AuthPageBackAction }): ReactNode {\n const action: AuthPageBackAction = typeof back === \"string\" ? { to: back } : back;\n const content = (\n <>\n <svg aria-hidden width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M19 12H5m6-7-7 7 7 7\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n <span>{action.label ?? DEFAULT_BACK_LABEL}</span>\n </>\n );\n if (action.to !== undefined) {\n return (\n <Link to={action.to} onClick={action.onClick} className={styles.back}>\n {content}\n </Link>\n );\n }\n return (\n <button type=\"button\" onClick={action.onClick} className={styles.back}>\n {content}\n </button>\n );\n}\n\n/**\n * The layout shell of a sign-in, sign-up or password-recovery screen.\n *\n * Every app was hand-building the same page around its form: a colored top with\n * the logo and a back button over a white sheet with rounded top corners on\n * mobile, a 50/50 brand panel and form on desktop, a \"Bem-vindo!\" heading. This\n * component is that page, so the app passes only the brand and the form.\n *\n * The switch between the two layouts is a JavaScript media query, not a CSS\n * `@media` rule, because the breakpoint is a prop: a CSS media query cannot read a\n * custom property. `useMediaQuery` evaluates synchronously on the first render, so\n * a client-rendered page does not flash the wrong layout.\n *\n * @example\n * <AuthPage brand={<img src=\"/logo.svg\" alt=\"Acme\" />} back=\"/\" title=\"Bem-vindo!\">\n * <LoginForm onSubmit={signIn} links={{ forgotPassword: \"/recuperar\", signUp: \"/cadastro\" }} />\n * </AuthPage>\n */\nexport function AuthPage({\n brand,\n title,\n subtitle,\n aside,\n variant = \"split\",\n breakpoint = 768,\n back,\n footer,\n headingLevel = 1,\n as = \"main\",\n children,\n className,\n style,\n ...props\n}: AuthPageProps): ReactNode {\n const wide = useMediaQuery(`(min-width: ${breakpoint}px)`);\n const layout = variant === \"card\" ? \"card\" : wide ? \"split\" : \"stacked\";\n const rootStyle = {\n \"--tempest-auth-page-aside-bg\": aside?.background,\n \"--tempest-auth-page-aside-fg\": aside?.color,\n ...style,\n } as CSSProperties;\n const heading =\n title === undefined\n ? null\n : createElement(`h${headingLevel}`, { className: styles.title }, title);\n\n return createElement(\n as,\n {\n ...props,\n className: cn(styles.root, styles[layout], className),\n style: rootStyle,\n \"data-layout\": layout,\n },\n layout !== \"card\" && (\n <div className={styles.aside}>\n {back !== undefined && <BackAction back={back} />}\n {brand !== undefined && <div className={styles.brand}>{brand}</div>}\n {aside?.content !== undefined && (\n <div className={styles.asideContent}>{aside.content}</div>\n )}\n </div>\n ),\n layout === \"card\" && back !== undefined && (\n <div className={styles.cardBack}>\n <BackAction back={back} />\n </div>\n ),\n <div className={styles.panel}>\n <div className={styles.sheet}>\n {layout === \"card\" && brand !== undefined && (\n <div className={styles.cardBrand}>{brand}</div>\n )}\n {(heading || subtitle !== undefined) && (\n <div className={styles.header}>\n {heading}\n {subtitle !== undefined && <p className={styles.subtitle}>{subtitle}</p>}\n </div>\n )}\n {children}\n {footer !== undefined && <div className={styles.footer}>{footer}</div>}\n </div>\n </div>,\n );\n}\n"],"mappings":";;;;;;;AAgFA,IAAMI,IAAqB;AAQ3B,SAASC,EAAW,EAAE,WAA0D;CAC5E,IAAMC,IAA6B,OAAOC,KAAS,WAAW,EAAE,IAAIA,EAAK,IAAIA,GACvEC,IACF,kBAAAR,GAAA,EAAA,UAAA,CACI,kBAAC,OAAD;EAAK,eAAA;EAAY,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,MAAK;EAC7D,UAAA,kBAAC,QAAD;GACI,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;EAClB,CAAA;CACA,CAAA,GACL,kBAAC,QAAD,EAAA,UAAOM,EAAO,SAASF,EAAyB,CAAA,CAClD,EAAA,CAAA;CASN,OAPIE,EAAO,OAAO,KAAA,IAQd,kBAAC,UAAD;EAAQ,MAAK;EAAS,SAASA,EAAO;EAAS,WAAWL,EAAO;EAC5D,UAAAO;CACG,CAAA,IARJ,kBAACC,GAAD;EAAM,IAAIH,EAAO;EAAI,SAASA,EAAO;EAAS,WAAWL,EAAO;EAC3D,UAAAO;CACC,CAAA;AAQlB;AAoBA,SAAgBE,EAAS,EACrB,UACA,UACA,aACA,UACA,aAAU,SACV,gBAAa,KACb,SACA,WACA,kBAAe,GACf,QAAK,QACL,aACA,cACA,UACA,GAAGC,KACsB;CACzB,IAAMC,IAAOC,EAAc,eAAeC,EAAW,IAAI,GACnDC,IAASC,MAAY,SAAS,SAASJ,IAAO,UAAU,WACxDK,IAAY;EACd,gCAAgCC,GAAO;EACvC,gCAAgCA,GAAO;EACvC,GAAGC;CACP,GACMC,IACFC,MAAU,KAAA,IACJ,OACAC,EAAc,IAAIC,KAAgB,EAAE,WAAWtB,EAAO,MAAM,GAAGoB,CAAK;CAE9E,OAAOC,EACHE,GACA;EACI,GAAGb;EACH,WAAWc,EAAGxB,EAAO,MAAMA,EAAOc,IAASW,CAAS;EACpD,OAAOT;EACP,eAAeF;CACnB,GACAA,MAAW,UACP,kBAAC,OAAD;EAAK,WAAWd,EAAO;EAAvB,UAAA;GACKM,MAAS,KAAA,KAAa,kBAACF,GAAD,EAAkB,QAAO,CAAA;GAC/CsB,MAAU,KAAA,KAAa,kBAAC,OAAD;IAAK,WAAW1B,EAAO;IAAQ,UAAA0B;GAAW,CAAA;GACjET,GAAO,YAAY,KAAA,KAChB,kBAAC,OAAD;IAAK,WAAWjB,EAAO;IAAe,UAAAiB,EAAM;GAAa,CAAA;EAE5D;CAET,CAAA,GAAAH,MAAW,UAAUR,MAAS,KAAA,KAC1B,kBAAC,OAAD;EAAK,WAAWN,EAAO;EACnB,UAAA,kBAACI,GAAD,EAAkB,QAAO,CAAA;CACxB,CAAA,GAET,kBAAC,OAAD;EAAK,WAAWJ,EAAO;EACnB,UAAA,kBAAC,OAAD;GAAK,WAAWA,EAAO;GAAvB,UAAA;IACKc,MAAW,UAAUY,MAAU,KAAA,KAC5B,kBAAC,OAAD;KAAK,WAAW1B,EAAO;KAAY,UAAA0B;IAAW,CAAA;KAEhDP,KAAWQ,MAAa,KAAA,MACtB,kBAAC,OAAD;KAAK,WAAW3B,EAAO;KAAvB,UAAA,CACKmB,GACAQ,MAAa,KAAA,KAAa,kBAAC,KAAD;MAAG,WAAW3B,EAAO;MAAW,UAAA2B;KAAY,CAAA,CACtE;;IAERC;IACAC,MAAW,KAAA,KAAa,kBAAC,OAAD;KAAK,WAAW7B,EAAO;KAAS,UAAA6B;IAAY,CAAA;GACpE;;CACJ,CAAA,CACT;AACJ"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
require("../../styles/component/AuthPage.css");
|
|
2
|
+
var e=`tempest_root_7FM9A`,t=`tempest_aside_iQvZx`,n=`tempest_brand_ucf0I`,r=`tempest_asideContent_27MiH`,i=`tempest_back_wf1QC`,a=`tempest_panel_4yoaM`,o=`tempest_sheet_BV27x`,s=`tempest_header_fglsK`,c=`tempest_title_US-kY`,l=`tempest_subtitle_IHFzn`,u=`tempest_footer_n5iU8`,d=`tempest_stacked_r5S3c`,f=`tempest_split_Jd8Ru`,p=`tempest_card_DgL6v`,m=`tempest_cardBack_911m6`,h=`tempest_cardBrand_U9HuK`,g={root:e,aside:t,brand:n,asideContent:r,back:i,panel:a,sheet:o,header:s,title:c,subtitle:l,footer:u,stacked:d,split:f,card:p,cardBack:m,cardBrand:h};exports.aside=t,exports.asideContent=r,exports.back=i,exports.brand=n,exports.card=p,exports.cardBack=m,exports.cardBrand=h,exports.default=g,exports.footer=u,exports.header=s,exports.panel=a,exports.root=e,exports.sheet=o,exports.split=f,exports.stacked=d,exports.subtitle=l,exports.title=c;
|
|
3
|
+
//# sourceMappingURL=AuthPage.module.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthPage.module.cjs","names":["root","aside","brand","asideContent","back","panel","sheet","header","title","subtitle","footer","stacked","split","card","cardBack","cardBrand","AuthPage_module_default"],"sources":["../../../src/components/AuthPage/AuthPage.module.css"],"sourcesContent":["/* ============================================================\n * AuthPage — the shell around a sign-in / sign-up form.\n *\n * Three layouts, chosen in JS (the breakpoint is a prop, and a CSS\n * media query cannot read a custom property):\n *\n * stacked colored top with back + brand, then a sheet with large\n * rounded top corners that holds the form (mobile)\n * split brand panel and form side by side, 50/50 (desktop)\n * card one centered card at every width (internal panels)\n *\n * The brand color travels as two component-scoped properties set\n * inline from the `aside` prop, falling back to the primary token.\n * ============================================================ */\n.root {\n --tempest-auth-page-bg: var(--tempest-auth-page-aside-bg, var(--tempest-primary));\n --tempest-auth-page-fg: var(--tempest-auth-page-aside-fg, var(--tempest-text-on-primary));\n box-sizing: border-box;\n min-height: 100dvh;\n width: 100%;\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n}\n\n/* ---------- brand panel ---------- */\n.aside {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--tempest-space-4);\n color: var(--tempest-auth-page-fg);\n text-align: center;\n}\n\n.brand {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n font-size: var(--tempest-text-2xl);\n font-weight: var(--tempest-weight-bold);\n}\n\n.brand img,\n.brand svg {\n max-width: 100%;\n height: auto;\n}\n\n.asideContent {\n max-width: 28rem;\n font-size: var(--tempest-text-md);\n line-height: var(--tempest-leading-relaxed);\n}\n\n.back {\n display: inline-flex;\n align-items: center;\n gap: var(--tempest-space-2);\n min-height: 44px;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 0;\n border-radius: var(--tempest-radius-md);\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: var(--tempest-text-md);\n font-weight: var(--tempest-weight-medium);\n text-decoration: none;\n cursor: pointer;\n}\n\n.back:hover {\n text-decoration: underline;\n}\n\n.back:focus-visible {\n outline: var(--tempest-focus-ring-width) solid currentColor;\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n/* ---------- form panel ---------- */\n.panel {\n display: flex;\n justify-content: center;\n background: var(--tempest-bg);\n}\n\n.sheet {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-5);\n width: 100%;\n max-width: 28rem;\n}\n\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n}\n\n.title {\n margin: 0;\n color: var(--tempest-text);\n font-size: var(--tempest-text-3xl);\n font-weight: var(--tempest-weight-bold);\n line-height: var(--tempest-leading-tight);\n}\n\n.subtitle {\n margin: 0;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-md);\n}\n\n.footer {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n text-align: center;\n}\n\n/* ---------- stacked (below the breakpoint) ---------- */\n.stacked {\n display: flex;\n flex-direction: column;\n background: var(--tempest-auth-page-bg);\n}\n\n.stacked .aside {\n padding: calc(var(--tempest-space-16) + var(--tempest-safe-area-top, 0px))\n var(--tempest-space-5) var(--tempest-space-10);\n}\n\n.stacked .back {\n position: absolute;\n top: calc(var(--tempest-space-3) + var(--tempest-safe-area-top, 0px));\n left: var(--tempest-space-2);\n}\n\n.stacked .panel {\n flex: 1;\n padding: var(--tempest-space-8) var(--tempest-space-5)\n calc(var(--tempest-space-8) + var(--tempest-safe-area-bottom, 0px));\n border-top-left-radius: calc(var(--tempest-radius-2xl) * 2);\n border-top-right-radius: calc(var(--tempest-radius-2xl) * 2);\n}\n\n/* ---------- split (at or above the breakpoint) ---------- */\n.split {\n display: grid;\n grid-template-columns: 1fr 1fr;\n}\n\n.split .aside {\n padding: var(--tempest-space-12);\n background: var(--tempest-auth-page-bg);\n}\n\n.split .back {\n position: absolute;\n top: var(--tempest-space-5);\n left: var(--tempest-space-5);\n}\n\n.split .panel {\n align-items: center;\n padding: var(--tempest-space-12) var(--tempest-space-8);\n}\n\n/* ---------- card (every width) ---------- */\n.card {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-5);\n background: var(--tempest-auth-page-aside-bg, var(--tempest-surface));\n}\n\n.cardBack {\n width: 100%;\n max-width: 28rem;\n color: var(--tempest-auth-page-aside-fg, var(--tempest-text));\n}\n\n.card .panel {\n width: 100%;\n max-width: 28rem;\n padding: var(--tempest-space-8) var(--tempest-space-6);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-xl);\n box-shadow: var(--tempest-shadow-md);\n}\n\n.cardBrand {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n color: var(--tempest-primary);\n font-size: var(--tempest-text-2xl);\n font-weight: var(--tempest-weight-bold);\n text-align: center;\n}\n\n.card .header {\n text-align: center;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .back {\n transition: var(--tempest-transition-color);\n }\n}\n"],"mappings":";"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "../../styles/component/AuthPage.css";
|
|
2
|
+
//#region src/components/AuthPage/AuthPage.module.css
|
|
3
|
+
var e = "tempest_root_7FM9A", t = "tempest_aside_iQvZx", n = "tempest_brand_ucf0I", r = "tempest_asideContent_27MiH", i = "tempest_back_wf1QC", a = "tempest_panel_4yoaM", o = "tempest_sheet_BV27x", s = "tempest_header_fglsK", c = "tempest_title_US-kY", l = "tempest_subtitle_IHFzn", u = "tempest_footer_n5iU8", d = "tempest_stacked_r5S3c", f = "tempest_split_Jd8Ru", p = "tempest_card_DgL6v", m = "tempest_cardBack_911m6", h = "tempest_cardBrand_U9HuK", g = {
|
|
4
|
+
root: e,
|
|
5
|
+
aside: t,
|
|
6
|
+
brand: n,
|
|
7
|
+
asideContent: r,
|
|
8
|
+
back: i,
|
|
9
|
+
panel: a,
|
|
10
|
+
sheet: o,
|
|
11
|
+
header: s,
|
|
12
|
+
title: c,
|
|
13
|
+
subtitle: l,
|
|
14
|
+
footer: u,
|
|
15
|
+
stacked: d,
|
|
16
|
+
split: f,
|
|
17
|
+
card: p,
|
|
18
|
+
cardBack: m,
|
|
19
|
+
cardBrand: h
|
|
20
|
+
};
|
|
21
|
+
//#endregion
|
|
22
|
+
export { t as aside, r as asideContent, i as back, n as brand, p as card, m as cardBack, h as cardBrand, g as default, u as footer, s as header, a as panel, e as root, o as sheet, f as split, d as stacked, l as subtitle, c as title };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=AuthPage.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthPage.module.js","names":["root","aside","brand","asideContent","back","panel","sheet","header","title","subtitle","footer","stacked","split","card","cardBack","cardBrand","AuthPage_module_default"],"sources":["../../../src/components/AuthPage/AuthPage.module.css"],"sourcesContent":["/* ============================================================\n * AuthPage — the shell around a sign-in / sign-up form.\n *\n * Three layouts, chosen in JS (the breakpoint is a prop, and a CSS\n * media query cannot read a custom property):\n *\n * stacked colored top with back + brand, then a sheet with large\n * rounded top corners that holds the form (mobile)\n * split brand panel and form side by side, 50/50 (desktop)\n * card one centered card at every width (internal panels)\n *\n * The brand color travels as two component-scoped properties set\n * inline from the `aside` prop, falling back to the primary token.\n * ============================================================ */\n.root {\n --tempest-auth-page-bg: var(--tempest-auth-page-aside-bg, var(--tempest-primary));\n --tempest-auth-page-fg: var(--tempest-auth-page-aside-fg, var(--tempest-text-on-primary));\n box-sizing: border-box;\n min-height: 100dvh;\n width: 100%;\n color: var(--tempest-text);\n font-family: var(--tempest-font-sans);\n}\n\n/* ---------- brand panel ---------- */\n.aside {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--tempest-space-4);\n color: var(--tempest-auth-page-fg);\n text-align: center;\n}\n\n.brand {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n font-size: var(--tempest-text-2xl);\n font-weight: var(--tempest-weight-bold);\n}\n\n.brand img,\n.brand svg {\n max-width: 100%;\n height: auto;\n}\n\n.asideContent {\n max-width: 28rem;\n font-size: var(--tempest-text-md);\n line-height: var(--tempest-leading-relaxed);\n}\n\n.back {\n display: inline-flex;\n align-items: center;\n gap: var(--tempest-space-2);\n min-height: 44px;\n padding: var(--tempest-space-2) var(--tempest-space-3);\n border: 0;\n border-radius: var(--tempest-radius-md);\n background: transparent;\n color: inherit;\n font: inherit;\n font-size: var(--tempest-text-md);\n font-weight: var(--tempest-weight-medium);\n text-decoration: none;\n cursor: pointer;\n}\n\n.back:hover {\n text-decoration: underline;\n}\n\n.back:focus-visible {\n outline: var(--tempest-focus-ring-width) solid currentColor;\n outline-offset: var(--tempest-focus-ring-offset);\n}\n\n/* ---------- form panel ---------- */\n.panel {\n display: flex;\n justify-content: center;\n background: var(--tempest-bg);\n}\n\n.sheet {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-5);\n width: 100%;\n max-width: 28rem;\n}\n\n.header {\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-1);\n}\n\n.title {\n margin: 0;\n color: var(--tempest-text);\n font-size: var(--tempest-text-3xl);\n font-weight: var(--tempest-weight-bold);\n line-height: var(--tempest-leading-tight);\n}\n\n.subtitle {\n margin: 0;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-md);\n}\n\n.footer {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n text-align: center;\n}\n\n/* ---------- stacked (below the breakpoint) ---------- */\n.stacked {\n display: flex;\n flex-direction: column;\n background: var(--tempest-auth-page-bg);\n}\n\n.stacked .aside {\n padding: calc(var(--tempest-space-16) + var(--tempest-safe-area-top, 0px))\n var(--tempest-space-5) var(--tempest-space-10);\n}\n\n.stacked .back {\n position: absolute;\n top: calc(var(--tempest-space-3) + var(--tempest-safe-area-top, 0px));\n left: var(--tempest-space-2);\n}\n\n.stacked .panel {\n flex: 1;\n padding: var(--tempest-space-8) var(--tempest-space-5)\n calc(var(--tempest-space-8) + var(--tempest-safe-area-bottom, 0px));\n border-top-left-radius: calc(var(--tempest-radius-2xl) * 2);\n border-top-right-radius: calc(var(--tempest-radius-2xl) * 2);\n}\n\n/* ---------- split (at or above the breakpoint) ---------- */\n.split {\n display: grid;\n grid-template-columns: 1fr 1fr;\n}\n\n.split .aside {\n padding: var(--tempest-space-12);\n background: var(--tempest-auth-page-bg);\n}\n\n.split .back {\n position: absolute;\n top: var(--tempest-space-5);\n left: var(--tempest-space-5);\n}\n\n.split .panel {\n align-items: center;\n padding: var(--tempest-space-12) var(--tempest-space-8);\n}\n\n/* ---------- card (every width) ---------- */\n.card {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: var(--tempest-space-3);\n padding: var(--tempest-space-5);\n background: var(--tempest-auth-page-aside-bg, var(--tempest-surface));\n}\n\n.cardBack {\n width: 100%;\n max-width: 28rem;\n color: var(--tempest-auth-page-aside-fg, var(--tempest-text));\n}\n\n.card .panel {\n width: 100%;\n max-width: 28rem;\n padding: var(--tempest-space-8) var(--tempest-space-6);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-xl);\n box-shadow: var(--tempest-shadow-md);\n}\n\n.cardBrand {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--tempest-space-2);\n color: var(--tempest-primary);\n font-size: var(--tempest-text-2xl);\n font-weight: var(--tempest-weight-bold);\n text-align: center;\n}\n\n.card .header {\n text-align: center;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .back {\n transition: var(--tempest-transition-color);\n }\n}\n"],"mappings":";"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("
|
|
1
|
+
const e=require("../../utils/format.cjs"),t=require("./filter-model.cjs");var n=/^\d{4}-\d{2}-\d{2}$/,r=new Intl.Collator(void 0,{numeric:!0});function i(e,t){if(typeof e==`object`&&e)return e[t]}function a(t){return t instanceof Date?e.formatDateForInput(t)||null:typeof t!=`string`||t.trim()===``?null:e.formatDateForInput(t)||null}function o(e,t){if(e==null)return null;if(typeof e==`number`){let n=Number(t);return Number.isNaN(n)?null:e-n}if(typeof e==`boolean`){let n=t===`true`||t!==`false`&&null;return n===null?null:Number(e)-Number(n)}if(n.test(t)){let n=a(e);return n===null?null:n.localeCompare(t)}return r.compare(String(e),t)}function s(e,t){if(e===t)return!0;let n=o(e,t);return n===null?String(e??``)===t:n===0}function c(e){return e==null?!0:typeof e==`string`?e.trim()===``:Array.isArray(e)?e.length===0:!1}function l(e){return e.value===void 0?[]:Array.isArray(e.value)?[...e.value]:[String(e.value)]}function u(e,t,n){switch(e){case`empty`:return c(t);case`notEmpty`:return!c(t);case`eq`:return s(t,n[0]??``);case`ne`:return!s(t,n[0]??``);case`contains`:return String(t??``).toLowerCase().includes((n[0]??``).toLowerCase());case`in`:return n.some(e=>s(t,e));case`between`:{let[e=``,r=``]=n,i=o(t,e),a=o(t,r);return i===null||a===null?!1:i>=0&&a<=0}default:{let r=o(t,n[0]??``);return r===null?!1:e===`gt`?r>0:e===`gte`?r>=0:e===`lt`?r<0:r<=0}}}function d(e){let[t=``,i=``]=e;if(n.test(t)&&n.test(i))return t<=i?[t,i]:[i,t];let a=Number(t),o=Number(i);return!Number.isNaN(a)&&!Number.isNaN(o)?a<=o?[t,i]:[i,t]:r.compare(t,i)<=0?[t,i]:[i,t]}function f(e,n){let r=n.filter(t.isComplete).map(e=>({field:e.field,operator:e.operator,values:e.operator===`between`?d(l(e)):l(e)}));return r.length===0?[...e]:e.filter(e=>r.every(({field:t,operator:n,values:r})=>u(n,i(e,t),r)))}exports.applyFilters=f,exports.orderRange=d;
|
|
2
2
|
//# sourceMappingURL=filter-apply.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filter-apply.cjs","names":["formatDateForInput","isComplete","require_filter_model","require_format","DATE_ONLY","TEXT_COLLATOR","readField","item","field","dayKey","value","compare","left","parsed","right","key","equals","result","isEmptyValue","valuesOf","filter","matchesOperator","operator","values","first","second","low","high","orderRange","applyFilters","active","filters","items"],"sources":["../../../src/components/FilterBar/filter-apply.ts"],"sourcesContent":["// The other half of the filter model: evaluating a `Filter[]` instead of only\n// describing it. `FilterBar` produces filters, `applyFilters` runs them over an\n// in-memory list, and `filtersToQueryParams` (filter-query.ts) hands the same set\n// to a paginated backend. Without these two the model stops one step short of\n// useful and every app writes the eleven operator branches again.\n\nimport { formatDateForInput } from \"@/utils/format\";\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\n\n/** Matches a plain `yyyy-mm-dd`, the shape `<input type=\"date\">` produces. */\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * One collator for every text comparison in this module.\n *\n * `localeCompare(value, undefined, { numeric: true })` builds a collator on each\n * call — passing an options bag opts out of the engine's cached-default fast\n * path — and this comparison runs once per row per filter. Measured at 200k\n * comparisons: 453 ms through `localeCompare` against 25 ms through a hoisted\n * collator, i.e. ~23 ms of main thread per keystroke on a 10k-row list.\n */\nconst TEXT_COLLATOR = new Intl.Collator(undefined, { numeric: true });\n\n/**\n * Read a property off a row without asserting the row's shape.\n *\n * `Filter.field` is a string chosen by the app's field list, so it cannot be a\n * `keyof T`; the lookup goes through an index signature and a missing property\n * yields `undefined`, which the operators treat as an empty value.\n *\n * @param item - The row being tested.\n * @param field - Property name from the filter.\n * @returns The property value, or `undefined` when the row has no such key.\n */\nfunction readField(item: unknown, field: string): unknown {\n if (item == null || typeof item !== \"object\") return undefined;\n return (item as Record<string, unknown>)[field];\n}\n\n/**\n * Local `yyyy-mm-dd` for a value that represents a date, or `null`.\n *\n * Delegates the formatting to {@link formatDateForInput}, which owns the rule\n * that the key is built from the local calendar parts rather than `toISOString`.\n * This adds only the narrowing from `unknown` and maps \"not a date\" to `null`\n * instead of to the empty string a date input wants.\n *\n * @param value - A `Date`, a date-ish string, or anything else.\n * @returns The day key, or `null` when the value does not denote a date.\n */\nfunction dayKey(value: unknown): string | null {\n if (value instanceof Date) return formatDateForInput(value) || null;\n if (typeof value !== \"string\" || value.trim() === \"\") return null;\n return formatDateForInput(value) || null;\n}\n\n/**\n * Compare a row value against a filter value, in the row value's own type.\n *\n * The filter always carries strings — it comes from form inputs and from the\n * URL — while the row carries whatever the API returned. Comparing them as text\n * gets the two common cases wrong: `10` would sort before `9`, and a date would\n * be compared by however it happens to be formatted. So the row value picks the\n * comparison, and the filter value is coerced into it.\n *\n * Dates compare by **day**, not by instant: a row stamped `2026-03-05T13:00:00Z`\n * is `>= 2026-03-05`, which is what the person picking a date in the UI means.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Negative, zero or positive, or `null` when the two cannot be compared.\n */\nfunction compare(left: unknown, right: string): number | null {\n if (left == null) return null;\n\n if (typeof left === \"number\") {\n const parsed = Number(right);\n return Number.isNaN(parsed) ? null : left - parsed;\n }\n\n if (typeof left === \"boolean\") {\n const parsed = right === \"true\" ? true : right === \"false\" ? false : null;\n return parsed === null ? null : Number(left) - Number(parsed);\n }\n\n if (DATE_ONLY.test(right)) {\n const key = dayKey(left);\n return key === null ? null : key.localeCompare(right);\n }\n\n return TEXT_COLLATOR.compare(String(left), right);\n}\n\n/**\n * Equality in the row value's type, falling back to exact text.\n *\n * Case-sensitive on purpose: `eq` maps to a plain `WHERE column = value` on the\n * server side, so a case-insensitive client would quietly disagree with the\n * backend for the same filter. `contains` is the case-insensitive operator, and\n * it is also the default one for text fields, so the friendly behaviour is what\n * people get without asking.\n *\n * Identical strings take a fast path out, since the row value is text in the\n * common case and the collator is the expensive part.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Whether the two are equal.\n */\nfunction equals(left: unknown, right: string): boolean {\n if (left === right) return true;\n const result = compare(left, right);\n if (result !== null) return result === 0;\n return String(left ?? \"\") === right;\n}\n\n/**\n * Is this row value empty?\n *\n * Empty means absent, blank text or an empty list — never falsy. `0` and `false`\n * are values somebody chose, and a filter that hid them would be reporting the\n * wrong count on every dashboard that tracks zeroes.\n *\n * @param value - The row value.\n * @returns Whether the value counts as empty.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * The filter's value as a list, whatever arity the operator uses.\n *\n * @param filter - The filter to read.\n * @returns Its values, trimmed of nothing — a leading space may be meaningful text.\n */\nfunction valuesOf(filter: Filter): string[] {\n if (filter.value === undefined) return [];\n return Array.isArray(filter.value) ? [...filter.value] : [String(filter.value)];\n}\n\n/**\n * Evaluate one operator against one row value.\n *\n * @param operator - The comparison to make.\n * @param left - The row value.\n * @param values - The filter values (one, two for `between`, many for `in`).\n * @returns Whether the row value satisfies the operator.\n */\nfunction matchesOperator(operator: FilterOperator, left: unknown, values: string[]): boolean {\n switch (operator) {\n case \"empty\":\n return isEmptyValue(left);\n case \"notEmpty\":\n return !isEmptyValue(left);\n case \"eq\":\n return equals(left, values[0] ?? \"\");\n case \"ne\":\n return !equals(left, values[0] ?? \"\");\n case \"contains\":\n return String(left ?? \"\")\n .toLowerCase()\n .includes((values[0] ?? \"\").toLowerCase());\n case \"in\":\n return values.some((value) => equals(left, value));\n case \"between\": {\n const [first = \"\", second = \"\"] = values;\n const low = compare(left, first);\n const high = compare(left, second);\n if (low === null || high === null) return false;\n return low >= 0 && high <= 0;\n }\n default: {\n const result = compare(left, values[0] ?? \"\");\n if (result === null) return false;\n if (operator === \"gt\") return result > 0;\n if (operator === \"gte\") return result >= 0;\n if (operator === \"lt\") return result < 0;\n return result <= 0;\n }\n }\n}\n\n/**\n * Order the two values of a `between` so the range is never empty by accident.\n *\n * A person who picks the later date first means the range between the two dates,\n * not \"no rows\". The server-side `BETWEEN` needs `(lo, hi)` in order too, which\n * is why the normalisation lives here and is shared by `filtersToQueryParams`.\n *\n * @param values - The raw pair.\n * @returns The pair, ascending.\n */\nexport function orderRange(values: readonly string[]): string[] {\n const [first = \"\", second = \"\"] = values;\n if (DATE_ONLY.test(first) && DATE_ONLY.test(second)) {\n return first <= second ? [first, second] : [second, first];\n }\n const low = Number(first);\n const high = Number(second);\n if (!Number.isNaN(low) && !Number.isNaN(high)) {\n return low <= high ? [first, second] : [second, first];\n }\n return TEXT_COLLATOR.compare(first, second) <= 0 ? [first, second] : [second, first];\n}\n\n/**\n * Run a filter set over an in-memory list.\n *\n * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies\n * them. Filters combine with `AND`, matching the flat model the bar builds, and\n * an incomplete filter is skipped rather than treated as a match of nothing — a\n * half-filled form should not empty the table underneath it.\n *\n * Comparison follows the row's type, not the filter's: numbers compare\n * numerically, dates compare by day, and everything else compares as text with\n * `numeric: true` so `\"item 2\"` lands before `\"item 10\"`.\n *\n * A few behaviours differ from the SQL the server-side twin produces, and the\n * difference is deliberate rather than accidental:\n *\n * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`\n * for a `NULL` column and the row drops out. Here \"is not paid\" shows the rows\n * with no status at all, which is what the chip claims.\n * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server\n * only matches `NULL`. A column that stores `\"\"` instead of `NULL` is where\n * the two disagree.\n *\n * @example\n * const visible = applyFilters(orders, filters);\n *\n * Each filter's values are normalised once, before the scan, rather than inside\n * the per-row predicate: the arity, the `between` ordering and the value array\n * depend only on the filter, so deriving them per row multiplied that work by\n * the row count. `empty`/`notEmpty` ignore their values entirely, which is why\n * the prepared shape does not need to distinguish them.\n *\n * @param items - The full list.\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns A new array with the rows that satisfy every complete filter.\n */\nexport function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[] {\n const active = filters.filter(isComplete).map((filter) => ({\n field: filter.field,\n operator: filter.operator,\n values: filter.operator === \"between\" ? orderRange(valuesOf(filter)) : valuesOf(filter),\n }));\n if (active.length === 0) return [...items];\n\n return items.filter((item) =>\n active.every(({ field, operator, values }) =>\n matchesOperator(operator, readField(item, field), values),\n ),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filter-apply.cjs","names":["formatDateForInput","isComplete","require_format","require_filter_model","DATE_ONLY","TEXT_COLLATOR","readField","item","field","dayKey","value","compare","left","parsed","right","key","equals","result","isEmptyValue","valuesOf","filter","matchesOperator","operator","values","first","second","low","high","orderRange","applyFilters","active","filters","items"],"sources":["../../../src/components/FilterBar/filter-apply.ts"],"sourcesContent":["// The other half of the filter model: evaluating a `Filter[]` instead of only\n// describing it. `FilterBar` produces filters, `applyFilters` runs them over an\n// in-memory list, and `filtersToQueryParams` (filter-query.ts) hands the same set\n// to a paginated backend. Without these two the model stops one step short of\n// useful and every app writes the eleven operator branches again.\n\nimport { formatDateForInput } from \"@/utils/format\";\nimport type { Filter, FilterOperator } from \"./filter-model\";\nimport { isComplete } from \"./filter-model\";\n\n/** Matches a plain `yyyy-mm-dd`, the shape `<input type=\"date\">` produces. */\nconst DATE_ONLY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * One collator for every text comparison in this module.\n *\n * `localeCompare(value, undefined, { numeric: true })` builds a collator on each\n * call — passing an options bag opts out of the engine's cached-default fast\n * path — and this comparison runs once per row per filter. Measured at 200k\n * comparisons: 453 ms through `localeCompare` against 25 ms through a hoisted\n * collator, i.e. ~23 ms of main thread per keystroke on a 10k-row list.\n */\nconst TEXT_COLLATOR = new Intl.Collator(undefined, { numeric: true });\n\n/**\n * Read a property off a row without asserting the row's shape.\n *\n * `Filter.field` is a string chosen by the app's field list, so it cannot be a\n * `keyof T`; the lookup goes through an index signature and a missing property\n * yields `undefined`, which the operators treat as an empty value.\n *\n * @param item - The row being tested.\n * @param field - Property name from the filter.\n * @returns The property value, or `undefined` when the row has no such key.\n */\nfunction readField(item: unknown, field: string): unknown {\n if (item == null || typeof item !== \"object\") return undefined;\n return (item as Record<string, unknown>)[field];\n}\n\n/**\n * Local `yyyy-mm-dd` for a value that represents a date, or `null`.\n *\n * Delegates the formatting to {@link formatDateForInput}, which owns the rule\n * that the key is built from the local calendar parts rather than `toISOString`.\n * This adds only the narrowing from `unknown` and maps \"not a date\" to `null`\n * instead of to the empty string a date input wants.\n *\n * @param value - A `Date`, a date-ish string, or anything else.\n * @returns The day key, or `null` when the value does not denote a date.\n */\nfunction dayKey(value: unknown): string | null {\n if (value instanceof Date) return formatDateForInput(value) || null;\n if (typeof value !== \"string\" || value.trim() === \"\") return null;\n return formatDateForInput(value) || null;\n}\n\n/**\n * Compare a row value against a filter value, in the row value's own type.\n *\n * The filter always carries strings — it comes from form inputs and from the\n * URL — while the row carries whatever the API returned. Comparing them as text\n * gets the two common cases wrong: `10` would sort before `9`, and a date would\n * be compared by however it happens to be formatted. So the row value picks the\n * comparison, and the filter value is coerced into it.\n *\n * Dates compare by **day**, not by instant: a row stamped `2026-03-05T13:00:00Z`\n * is `>= 2026-03-05`, which is what the person picking a date in the UI means.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Negative, zero or positive, or `null` when the two cannot be compared.\n */\nfunction compare(left: unknown, right: string): number | null {\n if (left == null) return null;\n\n if (typeof left === \"number\") {\n const parsed = Number(right);\n return Number.isNaN(parsed) ? null : left - parsed;\n }\n\n if (typeof left === \"boolean\") {\n const parsed = right === \"true\" ? true : right === \"false\" ? false : null;\n return parsed === null ? null : Number(left) - Number(parsed);\n }\n\n if (DATE_ONLY.test(right)) {\n const key = dayKey(left);\n return key === null ? null : key.localeCompare(right);\n }\n\n return TEXT_COLLATOR.compare(String(left), right);\n}\n\n/**\n * Equality in the row value's type, falling back to exact text.\n *\n * Case-sensitive on purpose: `eq` maps to a plain `WHERE column = value` on the\n * server side, so a case-insensitive client would quietly disagree with the\n * backend for the same filter. `contains` is the case-insensitive operator, and\n * it is also the default one for text fields, so the friendly behaviour is what\n * people get without asking.\n *\n * Identical strings take a fast path out, since the row value is text in the\n * common case and the collator is the expensive part.\n *\n * @param left - The row value.\n * @param right - The raw filter value.\n * @returns Whether the two are equal.\n */\nfunction equals(left: unknown, right: string): boolean {\n if (left === right) return true;\n const result = compare(left, right);\n if (result !== null) return result === 0;\n return String(left ?? \"\") === right;\n}\n\n/**\n * Is this row value empty?\n *\n * Empty means absent, blank text or an empty list — never falsy. `0` and `false`\n * are values somebody chose, and a filter that hid them would be reporting the\n * wrong count on every dashboard that tracks zeroes.\n *\n * @param value - The row value.\n * @returns Whether the value counts as empty.\n */\nfunction isEmptyValue(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * The filter's value as a list, whatever arity the operator uses.\n *\n * @param filter - The filter to read.\n * @returns Its values, trimmed of nothing — a leading space may be meaningful text.\n */\nfunction valuesOf(filter: Filter): string[] {\n if (filter.value === undefined) return [];\n return Array.isArray(filter.value) ? [...filter.value] : [String(filter.value)];\n}\n\n/**\n * Evaluate one operator against one row value.\n *\n * @param operator - The comparison to make.\n * @param left - The row value.\n * @param values - The filter values (one, two for `between`, many for `in`).\n * @returns Whether the row value satisfies the operator.\n */\nfunction matchesOperator(operator: FilterOperator, left: unknown, values: string[]): boolean {\n switch (operator) {\n case \"empty\":\n return isEmptyValue(left);\n case \"notEmpty\":\n return !isEmptyValue(left);\n case \"eq\":\n return equals(left, values[0] ?? \"\");\n case \"ne\":\n return !equals(left, values[0] ?? \"\");\n case \"contains\":\n return String(left ?? \"\")\n .toLowerCase()\n .includes((values[0] ?? \"\").toLowerCase());\n case \"in\":\n return values.some((value) => equals(left, value));\n case \"between\": {\n const [first = \"\", second = \"\"] = values;\n const low = compare(left, first);\n const high = compare(left, second);\n if (low === null || high === null) return false;\n return low >= 0 && high <= 0;\n }\n default: {\n const result = compare(left, values[0] ?? \"\");\n if (result === null) return false;\n if (operator === \"gt\") return result > 0;\n if (operator === \"gte\") return result >= 0;\n if (operator === \"lt\") return result < 0;\n return result <= 0;\n }\n }\n}\n\n/**\n * Order the two values of a `between` so the range is never empty by accident.\n *\n * A person who picks the later date first means the range between the two dates,\n * not \"no rows\". The server-side `BETWEEN` needs `(lo, hi)` in order too, which\n * is why the normalisation lives here and is shared by `filtersToQueryParams`.\n *\n * @param values - The raw pair.\n * @returns The pair, ascending.\n */\nexport function orderRange(values: readonly string[]): string[] {\n const [first = \"\", second = \"\"] = values;\n if (DATE_ONLY.test(first) && DATE_ONLY.test(second)) {\n return first <= second ? [first, second] : [second, first];\n }\n const low = Number(first);\n const high = Number(second);\n if (!Number.isNaN(low) && !Number.isNaN(high)) {\n return low <= high ? [first, second] : [second, first];\n }\n return TEXT_COLLATOR.compare(first, second) <= 0 ? [first, second] : [second, first];\n}\n\n/**\n * Run a filter set over an in-memory list.\n *\n * Closes the loop `FilterBar` opens: the bar produces `Filter[]`, this applies\n * them. Filters combine with `AND`, matching the flat model the bar builds, and\n * an incomplete filter is skipped rather than treated as a match of nothing — a\n * half-filled form should not empty the table underneath it.\n *\n * Comparison follows the row's type, not the filter's: numbers compare\n * numerically, dates compare by day, and everything else compares as text with\n * `numeric: true` so `\"item 2\"` lands before `\"item 10\"`.\n *\n * A few behaviours differ from the SQL the server-side twin produces, and the\n * difference is deliberate rather than accidental:\n *\n * - `ne` matches rows whose value is absent. In SQL, `column <> 'x'` is `NULL`\n * for a `NULL` column and the row drops out. Here \"is not paid\" shows the rows\n * with no status at all, which is what the chip claims.\n * - `empty` matches `NULL`, blank text and empty lists; `__isnull` on the server\n * only matches `NULL`. A column that stores `\"\"` instead of `NULL` is where\n * the two disagree.\n *\n * @example\n * const visible = applyFilters(orders, filters);\n *\n * Each filter's values are normalised once, before the scan, rather than inside\n * the per-row predicate: the arity, the `between` ordering and the value array\n * depend only on the filter, so deriving them per row multiplied that work by\n * the row count. `empty`/`notEmpty` ignore their values entirely, which is why\n * the prepared shape does not need to distinguish them.\n *\n * @param items - The full list.\n * @param filters - Applied filters; incomplete ones are ignored.\n * @returns A new array with the rows that satisfy every complete filter.\n */\nexport function applyFilters<T>(items: readonly T[], filters: readonly Filter[]): T[] {\n const active = filters.filter(isComplete).map((filter) => ({\n field: filter.field,\n operator: filter.operator,\n values: filter.operator === \"between\" ? orderRange(valuesOf(filter)) : valuesOf(filter),\n }));\n if (active.length === 0) return [...items];\n\n return items.filter((item) =>\n active.every(({ field, operator, values }) =>\n matchesOperator(operator, readField(item, field), values),\n ),\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { formatDateForInput as e } from "../../utils/format.js";
|
|
2
|
+
import { isComplete as t } from "./filter-model.js";
|
|
3
3
|
//#region src/components/FilterBar/filter-apply.ts
|
|
4
4
|
var n = /^\d{4}-\d{2}-\d{2}$/, r = new Intl.Collator(void 0, { numeric: !0 });
|
|
5
5
|
function i(e, t) {
|
|
6
6
|
if (typeof e == "object" && e) return e[t];
|
|
7
7
|
}
|
|
8
|
-
function a(
|
|
9
|
-
return
|
|
8
|
+
function a(t) {
|
|
9
|
+
return t instanceof Date ? e(t) || null : typeof t != "string" || t.trim() === "" ? null : e(t) || null;
|
|
10
10
|
}
|
|
11
11
|
function o(e, t) {
|
|
12
12
|
if (e == null) return null;
|
|
@@ -59,13 +59,13 @@ function d(e) {
|
|
|
59
59
|
let a = Number(t), o = Number(i);
|
|
60
60
|
return !Number.isNaN(a) && !Number.isNaN(o) ? a <= o ? [t, i] : [i, t] : r.compare(t, i) <= 0 ? [t, i] : [i, t];
|
|
61
61
|
}
|
|
62
|
-
function f(
|
|
63
|
-
let r = n.filter(
|
|
62
|
+
function f(e, n) {
|
|
63
|
+
let r = n.filter(t).map((e) => ({
|
|
64
64
|
field: e.field,
|
|
65
65
|
operator: e.operator,
|
|
66
66
|
values: e.operator === "between" ? d(l(e)) : l(e)
|
|
67
67
|
}));
|
|
68
|
-
return r.length === 0 ? [...
|
|
68
|
+
return r.length === 0 ? [...e] : e.filter((e) => r.every(({ field: t, operator: n, values: r }) => u(n, i(e, t), r)));
|
|
69
69
|
}
|
|
70
70
|
//#endregion
|
|
71
71
|
export { f as applyFilters, d as orderRange };
|
package/dist/forms/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { zodResolver as e } from "./zod-resolver.js";
|
|
2
|
+
import { useZodForm as t } from "./use-zod-form.js";
|
|
3
|
+
import { FormField as n } from "./FormField.js";
|
|
4
|
+
import { formatCEP as r, formatCNPJ as i, unmask as a, validateCNPJ as o, validateCPF as s } from "./br-validators.js";
|
|
5
|
+
import { CEPInput as c, CNPJInput as l, CPFInput as u, MoneyInput as d, PhoneInput as f } from "./masked-inputs.js";
|
|
6
|
+
import { validateForm as p } from "./validate-form.js";
|
|
7
7
|
import { useViaCEP as m } from "./use-viacep.js";
|
|
8
8
|
import { Controller as h, FormProvider as g, useFieldArray as _, useForm as v, useFormContext as y, useFormState as b, useWatch as x } from "react-hook-form";
|
|
9
|
-
export {
|
|
9
|
+
export { c as CEPInput, l as CNPJInput, u as CPFInput, h as Controller, n as FormField, g as FormProvider, d as MoneyInput, f as PhoneInput, r as formatCEP, i as formatCNPJ, a as unmask, _ as useFieldArray, v as useForm, y as useFormContext, b as useFormState, m as useViaCEP, x as useWatch, t as useZodForm, o as validateCNPJ, s as validateCPF, p as validateForm, e as zodResolver };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../
|
|
1
|
+
const e=require("../utils/intl-cache.cjs"),t=require("../components/Input/Input.cjs"),n=require("./br-validators.cjs"),r=require("../utils/format.cjs");let i=require("react"),a=require("react/jsx-runtime");function o(e,n=`numeric`){return(0,i.forwardRef)(function({value:r,onChange:i,...o},s){return(0,a.jsx)(t.Input,{...o,ref:s,value:e(r??``),inputMode:n,onChange:t=>i?.(e(t.target.value))})})}var s=o(r.formatCPF),c=o(n.formatCNPJ),l=o(r.formatPhone,`tel`),u=o(n.formatCEP);function d(t,n,r){return e.numberFormat(n,{style:`currency`,currency:r}).format(t/100)}function f(e){let t=e.replace(/\D/g,``);return t?Number.parseInt(t,10):0}var p=(0,i.forwardRef)(function({value:e,onChange:n,currency:r=`BRL`,locale:i=`pt-BR`,...o},s){return(0,a.jsx)(t.Input,{...o,ref:s,type:`text`,inputMode:`numeric`,value:d(e||0,i,r),onChange:e=>n(f(e.target.value))})});exports.CEPInput=u,exports.CNPJInput=c,exports.CPFInput=s,exports.MoneyInput=p,exports.PhoneInput=l;
|
|
2
2
|
//# sourceMappingURL=masked-inputs.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"masked-inputs.cjs","names":["forwardRef","Input","formatCPF","formatCNPJ","formatPhone","formatCEP","numberFormat","
|
|
1
|
+
{"version":3,"file":"masked-inputs.cjs","names":["forwardRef","Input","formatCPF","formatCNPJ","formatPhone","formatCEP","numberFormat","require_intl_cache","require_Input","require_br_validators","require_format","react","react_jsx_runtime","maskedInput","props","mask","value","onChange","event","CPFInput","CNPJInput","PhoneInput","CEPInput","formatCents","locale","cents","parseCents","digits","text","MoneyInput","currency"],"sources":["../../src/forms/masked-inputs.tsx"],"sourcesContent":["import { forwardRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { Input, type InputProps } from \"@/components/Input\";\nimport { formatCEP, formatCNPJ } from \"./br-validators\";\nimport { formatCPF, formatPhone } from \"@/utils/format\";\nimport { numberFormat } from \"@/utils/intl-cache\";\n\n/**\n * Props of every masked input.\n *\n * `value` and `onChange` are optional because the primary documented usage is\n * inside `<FormField>`, which supplies both through `cloneElement` — declaring\n * them required made `<FormField name=\"cpf\"><CPFInput /></FormField>` fail to\n * type-check while working perfectly at runtime. Standalone usage still passes\n * both; an uncontrolled input without `onChange` simply reports nothing.\n */\ntype MaskedFieldProps = Omit<InputProps, \"value\" | \"onChange\"> & {\n value?: string;\n onChange?: (value: string) => void;\n};\n\nfunction maskedInput(\n mask: (input: string) => string,\n inputMode: InputHTMLAttributes<HTMLInputElement>[\"inputMode\"] = \"numeric\",\n) {\n return forwardRef<HTMLInputElement, MaskedFieldProps>(function MaskedInput(\n { value, onChange, ...props },\n ref,\n ) {\n return (\n <Input\n {...props}\n ref={ref}\n value={mask(value ?? \"\")}\n inputMode={inputMode}\n onChange={(event) => onChange?.(mask(event.target.value))}\n />\n );\n });\n}\n\nexport const CPFInput = maskedInput(formatCPF);\nexport const CNPJInput = maskedInput(formatCNPJ);\nexport const PhoneInput = maskedInput(formatPhone, \"tel\");\nexport const CEPInput = maskedInput(formatCEP);\n\nexport interface MoneyInputProps extends Omit<InputProps, \"value\" | \"onChange\" | \"type\"> {\n /** Cents (integer). Internally treated as 1/100 of the currency unit. */\n value: number;\n onChange: (cents: number) => void;\n /** Currency code for `Intl.NumberFormat`. Default: `\"BRL\"`. */\n currency?: string;\n /** Locale for `Intl.NumberFormat`. Default: `\"pt-BR\"`. */\n locale?: string;\n}\n\nfunction formatCents(cents: number, locale: string, currency: string): string {\n return numberFormat(locale, { style: \"currency\", currency }).format(cents / 100);\n}\n\nfunction parseCents(text: string): number {\n const digits = text.replace(/\\D/g, \"\");\n if (!digits) return 0;\n return Number.parseInt(digits, 10);\n}\n\n/**\n * Currency-masked input. Stores the value as an integer number of cents to\n * avoid floating-point error. Suitable for `react-hook-form` once you adapt\n * the field to expose cents.\n */\nexport const MoneyInput = forwardRef<HTMLInputElement, MoneyInputProps>(function MoneyInput(\n { value, onChange, currency = \"BRL\", locale = \"pt-BR\", ...props },\n ref,\n) {\n return (\n <Input\n {...props}\n ref={ref}\n type=\"text\"\n inputMode=\"numeric\"\n value={formatCents(value || 0, locale, currency)}\n onChange={(event) => onChange(parseCents(event.target.value))}\n />\n );\n});\n"],"mappings":"A,8MAqBA,SAASa,EACL,EACA,EAAgE,UAClE,CACE,OAAA,EAAOb,EAAAA,WAAAA,CAA+C,SAClD,CAAE,QAAO,WAAU,GAAGc,GACtB,EACF,CACE,OACI,EAAAF,EAAA,IAAA,CAACX,EAAAA,MAAD,CACI,GAAIa,EACC,MACL,MAAOC,EAAKC,GAAS,EAAE,EACZ,YACX,SAAW,GAAUC,IAAWF,EAAKG,EAAM,OAAO,KAAK,CAAC,CAC3D,CAAA,CAET,CAAC,CACL,CAEA,IAAaC,EAAWN,EAAYX,EAAAA,SAAS,EAChCkB,EAAYP,EAAYV,EAAAA,UAAU,EAClCkB,EAAaR,EAAYT,EAAAA,YAAa,KAAK,EAC3CkB,EAAWT,EAAYR,EAAAA,SAAS,EAY7C,SAASkB,EAAY,EAAe,EAAgB,EAA0B,CAC1E,OAAOjB,EAAAA,aAAakB,EAAQ,CAAE,MAAO,WAAY,UAAS,CAAC,CAAC,CAAC,OAAOC,EAAQ,GAAG,CACnF,CAEA,SAASC,EAAW,EAAsB,CACtC,IAAMC,EAASC,EAAK,QAAQ,MAAO,EAAE,EAErC,OADKD,EACE,OAAO,SAASA,EAAQ,EAAE,EADb,CAExB,CAOA,IAAaE,GAAA,EAAa7B,EAAAA,WAAAA,CAA8C,SACpE,CAAE,QAAO,WAAU,WAAW,MAAO,SAAS,QAAS,GAAGc,GAC1D,EACF,CACE,OACI,EAAAF,EAAA,IAAA,CAACX,EAAAA,MAAD,CACI,GAAIa,EACC,MACL,KAAK,OACL,UAAU,UACV,MAAOS,EAAYP,GAAS,EAAGQ,EAAQM,CAAQ,EAC/C,SAAW,GAAUb,EAASS,EAAWR,EAAM,OAAO,KAAK,CAAC,CAC/D,CAAA,CAET,CAAC,E"}
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { numberFormat as e } from "../utils/intl-cache.js";
|
|
2
|
+
import { Input as t } from "../components/Input/Input.js";
|
|
3
|
+
import { formatCEP as n, formatCNPJ as r } from "./br-validators.js";
|
|
4
|
+
import { formatCPF as i, formatPhone as a } from "../utils/format.js";
|
|
5
5
|
import { forwardRef as o } from "react";
|
|
6
6
|
import { jsx as s } from "react/jsx-runtime";
|
|
7
7
|
//#region src/forms/masked-inputs.tsx
|
|
8
|
-
function c(
|
|
8
|
+
function c(e, n = "numeric") {
|
|
9
9
|
return o(function({ value: r, onChange: i, ...a }, o) {
|
|
10
|
-
return /* @__PURE__ */ s(
|
|
10
|
+
return /* @__PURE__ */ s(t, {
|
|
11
11
|
...a,
|
|
12
12
|
ref: o,
|
|
13
|
-
value:
|
|
13
|
+
value: e(r ?? ""),
|
|
14
14
|
inputMode: n,
|
|
15
|
-
onChange: (
|
|
15
|
+
onChange: (t) => i?.(e(t.target.value))
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
var l = c(
|
|
20
|
-
function p(
|
|
21
|
-
return
|
|
19
|
+
var l = c(i), u = c(r), d = c(a, "tel"), f = c(n);
|
|
20
|
+
function p(t, n, r) {
|
|
21
|
+
return e(n, {
|
|
22
22
|
style: "currency",
|
|
23
23
|
currency: r
|
|
24
|
-
}).format(
|
|
24
|
+
}).format(t / 100);
|
|
25
25
|
}
|
|
26
26
|
function m(e) {
|
|
27
27
|
let t = e.replace(/\D/g, "");
|
|
28
28
|
return t ? Number.parseInt(t, 10) : 0;
|
|
29
29
|
}
|
|
30
|
-
var h = o(function({ value:
|
|
31
|
-
return /* @__PURE__ */ s(
|
|
30
|
+
var h = o(function({ value: e, onChange: n, currency: r = "BRL", locale: i = "pt-BR", ...a }, o) {
|
|
31
|
+
return /* @__PURE__ */ s(t, {
|
|
32
32
|
...a,
|
|
33
33
|
ref: o,
|
|
34
34
|
type: "text",
|
|
35
35
|
inputMode: "numeric",
|
|
36
|
-
value: p(
|
|
36
|
+
value: p(e || 0, i, r),
|
|
37
37
|
onChange: (e) => n(m(e.target.value))
|
|
38
38
|
});
|
|
39
39
|
});
|
package/dist/http/api-client.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("
|
|
1
|
+
const e=require("./errors.cjs"),t=require("../utils/ids.cjs"),n=require("./build-url.cjs"),r=require("./credential-scope.cjs"),i=require("./csrf.cjs"),a=require("./decode-response.cjs"),o=require("./retry.cjs"),s=require("./timeout.cjs");var c=new Set([`GET`,`HEAD`,`OPTIONS`]);function l(t,n){return!c.has(n)||!(t instanceof e.TempestApiError)?!1:e.isRetriableStatus(t.status)}function u(e){return e?e===!0?{}:e:null}var d=15e3,f=3e5;function p(e){return typeof FormData<`u`&&e instanceof FormData}function m(e){return Math.round(performance.now()-e)}function h(e){return e.replace(/[?#][^]*/,``).replace(/^([^/]*\/\/)[^/]*@/,`$1`)}async function g(t,n){let r;try{r=await t.clone().json()}catch{try{r=await t.text()}catch{r=null}}return new e.TempestApiError(e.buildApiError(t.status,r,t.headers,n))}function _(c){let _=c.fetcher??globalThis.fetch.bind(globalThis);function v(e,t){if(t)return{};let n=c.getToken?.();return n?r.isTrustedCredentialTarget(e,c.baseURL,c.trustedOrigins)?{Authorization:`Bearer ${n}`}:(r.reportSuppressedCredential(e,c.baseURL),{}):{}}async function y(t,r,a,o){let{body:l,params:u,headers:m,signal:h,timeout:g,...y}=r,b=p(l),x=b?c.uploadTimeout:c.timeout,S=g===void 0?x===void 0?b?f:d:x:g,C=n.buildApiUrl(c.baseURL,t,{prefix:c.prefix,params:u}),w=m,T={...b?{}:{"Content-Type":`application/json`},...a?{"X-Request-ID":a}:{},...c.headers,...v(C,o),...i.csrfHeaders({method:y.method??`GET`,url:C,reference:c.baseURL,csrf:c.csrf,trustedOrigins:c.trustedOrigins,headers:{...c.headers,...w}}),...w},E=s.withTimeout(h,S),D={...y,signal:E.signal,headers:T,credentials:c.withCredentials?`include`:y.credentials,body:l==null?void 0:b?l:JSON.stringify(l)};try{return await _(C,D)}catch(t){throw E.timedOut()?new e.TempestApiError({status:0,detail:`A requisição excedeu ${S}ms e foi abandonada.`}):t}finally{E.dispose()}}async function b(e,t,n,r,i){let a=c.logger;if(!a)return y(e,t,n,i);let o=`${r} ${h(e)}`,s=performance.now();try{let r=await y(e,t,n,i),c={requestId:n,status:r.status,ms:m(s)},l=`${o} → ${r.status}`;return r.status>=400?a.warn(l,c):a.debug(l,c),r}catch(e){throw a.warn(`${o} → no response`,{requestId:n,ms:m(s),error:e}),e}}async function x(t,n,r){if(c.onUnauthorized){c.logger?.warn(`unauthorized — calling onUnauthorized`,{requestId:n,status:t.status});try{await c.onUnauthorized(t,r)}catch(r){if(r instanceof e.SessionSupersededError)throw r;c.logger?.warn(`onUnauthorized threw — keeping the original response error`,{requestId:n,status:t.status,error:r})}}}function S(e){return{token:e?null:c.getToken?.()??null,epoch:c.getSessionEpoch?.()??null}}async function C(n,r,i){let{skipAuth:a=!1,skipAuthRetry:o=!1,...s}=r,l=c.requestId?c.requestId():t.randomId(),u=(s.method??`GET`).toUpperCase(),d=S(a),f=await b(n,s,l,u,a);if(f.status===401&&!a&&!o){if(c.refresh){let t;try{await c.refresh(d),t=S(a),f=await b(n,s,l,u,a)}catch(t){throw t instanceof e.SessionSupersededError?t:(await x(f,l,d),await g(f,l))}f.status===401&&await x(f,l,t)}else await x(f,l,d)}if(!f.ok)throw await g(f,l);return i(f)}async function w(e,t,n){let{retry:r,...i}=t,a=u(r===void 0?c.retry:r);if(!a)return C(e,i,n);let s=(i.method??`GET`).toUpperCase();return o.retry(()=>C(e,i,n),{...a,shouldRetry:a.shouldRetry??(e=>l(e,s))})}async function T(e,t={}){return w(e,t,a.decodeByContentType)}async function E(e,t,n=`POST`,r){return T(e,{...r,method:n,body:t})}return{request:T,get:(e,t)=>T(e,{...t,method:`GET`}),post:(e,t)=>T(e,{...t,method:`POST`}),put:(e,t)=>T(e,{...t,method:`PUT`}),patch:(e,t)=>T(e,{...t,method:`PATCH`}),delete:(e,t)=>T(e,{...t,method:`DELETE`}),blob:(e,t)=>w(e,{...t},e=>e.blob()),arrayBuffer:(e,t)=>w(e,{...t},e=>e.arrayBuffer()),upload:E,url:(e,t)=>n.buildApiUrl(c.baseURL,e,{prefix:c.prefix,params:t})}}exports.createApiClient=_;
|
|
2
2
|
//# sourceMappingURL=api-client.cjs.map
|