tempest-react-sdk 0.80.0 → 0.82.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 +40 -10
- package/bin/create-tempest-app.e2e.test.mjs +31 -0
- package/bin/create-tempest-app.mjs +11 -1
- package/bin/lib/component/generate.test.mjs +13 -1
- package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
- package/bin/lib/doctor/vitest.mjs +120 -0
- package/bin/lib/doctor/vitest.test.mjs +139 -0
- package/bin/lib/fs/contain.mjs +7 -0
- package/bin/lib/openapi/escape.mjs +68 -4
- package/bin/lib/openapi/generate.hostile.test.mjs +150 -1
- package/bin/lib/openapi/generate.mjs +78 -13
- package/bin/lib/openapi/generate.test.mjs +13 -1
- package/bin/lib/openapi/pattern-risk.mjs +134 -0
- package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
- package/bin/tempest-gen.test.mjs +100 -0
- package/bin/tempest.mjs +24 -8
- package/dist/access/use-can.cjs +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/access/use-can.js +25 -28
- package/dist/access/use-can.js.map +1 -1
- 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 +234 -122
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/session-identity.cjs +2 -0
- package/dist/auth/session-identity.cjs.map +1 -0
- package/dist/auth/session-identity.js +12 -0
- package/dist/auth/session-identity.js.map +1 -0
- 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 +32 -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 +97 -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/Breadcrumbs/Breadcrumbs.cjs +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js +41 -40
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +38 -38
- 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/components/Form/Form.cjs +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js +30 -32
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/Layout/Layout.cjs +1 -1
- package/dist/components/Layout/Layout.cjs.map +1 -1
- package/dist/components/Layout/Layout.js +25 -24
- package/dist/components/Layout/Layout.js.map +1 -1
- package/dist/components/Layout/gap.cjs +2 -0
- package/dist/components/Layout/gap.cjs.map +1 -0
- package/dist/components/Layout/gap.js +15 -0
- package/dist/components/Layout/gap.js.map +1 -0
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.js +30 -29
- package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +1 -2
- package/dist/data/create-data-provider.js.map +1 -1
- 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/forms/validate-form.cjs +1 -1
- package/dist/forms/validate-form.cjs.map +1 -1
- package/dist/forms/validate-form.js +6 -1
- package/dist/forms/validate-form.js.map +1 -1
- package/dist/forms/zod-resolver.cjs +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js +8 -3
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +137 -116
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +33 -9
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/presigned-post.cjs.map +1 -1
- package/dist/http/presigned-post.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +140 -134
- 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 +17 -17
- package/dist/http/upload-with-progress.js.map +1 -1
- 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/oauth/GoogleSignIn.cjs +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js +5 -4
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- 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 +108 -68
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +54 -44
- package/dist/query/offline-persistence.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 +48 -41
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +1 -1
- package/dist/sse/ticket-event-stream.cjs.map +1 -1
- package/dist/sse/ticket-event-stream.js +48 -19
- package/dist/sse/ticket-event-stream.js.map +1 -1
- 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 +19 -14
- 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 +132 -112
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +36 -30
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/create-push-handler.cjs +1 -1
- package/dist/sw/create-push-handler.cjs.map +1 -1
- package/dist/sw/create-push-handler.js +20 -10
- package/dist/sw/create-push-handler.js.map +1 -1
- package/dist/sw.d.ts +93 -28
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +35 -16
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/manifest.cjs +1 -1
- package/dist/tabular/manifest.cjs.map +1 -1
- package/dist/tabular/manifest.js +33 -15
- package/dist/tabular/manifest.js.map +1 -1
- package/dist/tabular.d.ts +11 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +1057 -83
- package/dist/tempest-react-sdk.js +373 -365
- package/dist/theme/initial-theme.cjs +4 -4
- package/dist/theme/initial-theme.cjs.map +1 -1
- package/dist/theme/initial-theme.js +8 -5
- package/dist/theme/initial-theme.js.map +1 -1
- package/dist/utils/compressed-storage.cjs +1 -1
- package/dist/utils/compressed-storage.cjs.map +1 -1
- package/dist/utils/compressed-storage.js +13 -7
- package/dist/utils/compressed-storage.js.map +1 -1
- 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/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +13 -7
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/safe-url.cjs +1 -1
- package/dist/utils/safe-url.cjs.map +1 -1
- package/dist/utils/safe-url.js +4 -1
- package/dist/utils/safe-url.js.map +1 -1
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +7 -1
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +77 -61
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +4 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
- package/template-pwa/src/pwa.tsx +42 -0
- package/template-pwa/src/sw.ts +15 -10
|
@@ -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("../../utils/cn.cjs"),t=require("./Breadcrumbs.module.cjs");let
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../../utils/safe-url.cjs"),n=require("./Breadcrumbs.module.cjs");let r=require("react"),i=require("react/jsx-runtime");function a({items:a,separator:o=`/`,className:s}){return(0,i.jsx)(`nav`,{"aria-label":`breadcrumb`,className:e.cn(n.default.nav,s),children:a.map((e,s)=>{let c=s===a.length-1,l=t.vettedHref(e.href),u=!c&&(l||e.onClick);return(0,i.jsxs)(r.Fragment,{children:[(0,i.jsx)(`span`,{className:n.default.item,children:u?(0,i.jsx)(`a`,{href:l,onClick:t=>{e.onClick&&(t.preventDefault(),e.onClick())},className:n.default.link,children:e.label}):(0,i.jsx)(`span`,{"aria-current":c?`page`:void 0,className:c?n.default.current:void 0,children:e.label})}),!c&&(0,i.jsx)(`span`,{className:n.default.separator,"aria-hidden":!0,children:o})]},s)})})}exports.Breadcrumbs=a;
|
|
2
2
|
//# sourceMappingURL=Breadcrumbs.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Breadcrumbs.cjs","names":["cn","styles","Fragment","require_cn","require_Breadcrumbs_module","react","react_jsx_runtime","Breadcrumbs","className","items","isLast","index","
|
|
1
|
+
{"version":3,"file":"Breadcrumbs.cjs","names":["cn","styles","vettedHref","Fragment","require_cn","require_safe_url","require_Breadcrumbs_module","react","react_jsx_runtime","Breadcrumbs","className","items","isLast","index","href","item","interactive","event","separator"],"sources":["../../../src/components/Breadcrumbs/Breadcrumbs.tsx"],"sourcesContent":["import { Fragment } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { vettedHref } from \"@/utils/safe-url\";\nimport styles from \"./Breadcrumbs.module.css\";\n\nexport interface BreadcrumbItem {\n label: ReactNode;\n href?: string;\n onClick?: () => void;\n}\n\nexport interface BreadcrumbsProps {\n items: BreadcrumbItem[];\n separator?: ReactNode;\n className?: string;\n}\n\n/**\n * Hierarchical navigation breadcrumbs. The last item is rendered as plain\n * text with `aria-current=\"page\"`. Items with neither `href` nor `onClick`\n * also render as text (intermediate but non-clickable).\n *\n * Each `href` is vetted with `safeLinkUrl`'s allowlist: an item whose URL uses a scheme\n * outside `http:`/`https:`/`mailto:`/`tel:`/`sms:` loses the `href`, and renders\n * as text unless it also has an `onClick`. A trail built from API data — the\n * folder names of a document tree — would otherwise put a clickable\n * `javascript:` link on the page under React 18, which writes it verbatim.\n */\nexport function Breadcrumbs({ items, separator = \"/\", className }: BreadcrumbsProps) {\n return (\n <nav aria-label=\"breadcrumb\" className={cn(styles.nav, className)}>\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n const href = vettedHref(item.href);\n const interactive = !isLast && (href || item.onClick);\n return (\n <Fragment key={index}>\n <span className={styles.item}>\n {interactive ? (\n <a\n href={href}\n onClick={(event) => {\n if (item.onClick) {\n event.preventDefault();\n item.onClick();\n }\n }}\n className={styles.link}\n >\n {item.label}\n </a>\n ) : (\n <span\n aria-current={isLast ? \"page\" : undefined}\n className={isLast ? styles.current : undefined}\n >\n {item.label}\n </span>\n )}\n </span>\n {!isLast && (\n <span className={styles.separator} aria-hidden>\n {separator}\n </span>\n )}\n </Fragment>\n );\n })}\n </nav>\n );\n}\n"],"mappings":"A,wKA6BA,SAAgBS,EAAY,CAAE,QAAO,YAAY,IAAK,aAA+B,CACjF,OACI,EAAAD,EAAA,IAAA,CAAC,MAAD,CAAK,aAAW,aAAa,UAAWR,EAAAA,GAAGC,EAAAA,QAAO,IAAKS,CAAS,EAC3D,SAAAC,EAAM,KAAK,EAAM,IAAU,CACxB,IAAMC,EAASC,IAAUF,EAAM,OAAS,EAClCG,EAAOZ,EAAAA,WAAWa,EAAK,IAAI,EAC3BC,EAAc,CAACJ,IAAWE,GAAQC,EAAK,SAC7C,OACI,EAAAP,EAAA,KAAA,CAACL,EAAAA,SAAD,CAAA,SAAA,EACI,EAAAK,EAAA,IAAA,CAAC,OAAD,CAAM,UAAWP,EAAAA,QAAO,KACnB,SAAAe,GACG,EAAAR,EAAA,IAAA,CAAC,IAAD,CACU,OACN,QAAU,GAAU,CACZO,EAAK,UACLE,EAAM,eAAe,EACrBF,EAAK,QAAQ,EAErB,EACA,UAAWd,EAAAA,QAAO,KAEjB,SAAAc,EAAK,KACP,CAAA,GAEH,EAAAP,EAAA,IAAA,CAAC,OAAD,CACI,eAAcI,EAAS,OAAS,IAAA,GAChC,UAAWA,EAASX,EAAAA,QAAO,QAAU,IAAA,GAEpC,SAAAc,EAAK,KACJ,CAAA,CAER,CAAA,EACL,CAACH,IACE,EAAAJ,EAAA,IAAA,CAAC,OAAD,CAAM,UAAWP,EAAAA,QAAO,UAAW,cAAA,GAC9B,SAAAiB,CACC,CAAA,CAEJ,CAAA,EA7BKL,CA6BL,CAElB,CAAC,CACA,CAAA,CAEb,C"}
|
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import t from "
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { vettedHref as t } from "../../utils/safe-url.js";
|
|
3
|
+
import n from "./Breadcrumbs.module.js";
|
|
4
|
+
import { Fragment as r } from "react";
|
|
5
|
+
import { jsx as i, jsxs as a } from "react/jsx-runtime";
|
|
5
6
|
//#region src/components/Breadcrumbs/Breadcrumbs.tsx
|
|
6
|
-
function
|
|
7
|
-
return /* @__PURE__ */
|
|
7
|
+
function o({ items: o, separator: s = "/", className: c }) {
|
|
8
|
+
return /* @__PURE__ */ i("nav", {
|
|
8
9
|
"aria-label": "breadcrumb",
|
|
9
|
-
className: e(
|
|
10
|
-
children:
|
|
11
|
-
let
|
|
12
|
-
return /* @__PURE__ */
|
|
13
|
-
className:
|
|
14
|
-
children:
|
|
15
|
-
href:
|
|
10
|
+
className: e(n.nav, c),
|
|
11
|
+
children: o.map((e, c) => {
|
|
12
|
+
let l = c === o.length - 1, u = t(e.href), d = !l && (u || e.onClick);
|
|
13
|
+
return /* @__PURE__ */ a(r, { children: [/* @__PURE__ */ i("span", {
|
|
14
|
+
className: n.item,
|
|
15
|
+
children: d ? /* @__PURE__ */ i("a", {
|
|
16
|
+
href: u,
|
|
16
17
|
onClick: (t) => {
|
|
17
18
|
e.onClick && (t.preventDefault(), e.onClick());
|
|
18
19
|
},
|
|
19
|
-
className:
|
|
20
|
+
className: n.link,
|
|
20
21
|
children: e.label
|
|
21
|
-
}) : /* @__PURE__ */
|
|
22
|
-
"aria-current":
|
|
23
|
-
className:
|
|
22
|
+
}) : /* @__PURE__ */ i("span", {
|
|
23
|
+
"aria-current": l ? "page" : void 0,
|
|
24
|
+
className: l ? n.current : void 0,
|
|
24
25
|
children: e.label
|
|
25
26
|
})
|
|
26
|
-
}), !
|
|
27
|
-
className:
|
|
27
|
+
}), !l && /* @__PURE__ */ i("span", {
|
|
28
|
+
className: n.separator,
|
|
28
29
|
"aria-hidden": !0,
|
|
29
|
-
children:
|
|
30
|
-
})] },
|
|
30
|
+
children: s
|
|
31
|
+
})] }, c);
|
|
31
32
|
})
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
//#endregion
|
|
35
|
-
export {
|
|
36
|
+
export { o as Breadcrumbs };
|
|
36
37
|
|
|
37
38
|
//# sourceMappingURL=Breadcrumbs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Breadcrumbs.js","names":["styles","Breadcrumbs_module_default","Breadcrumbs","cn","className","items","isLast","index","
|
|
1
|
+
{"version":3,"file":"Breadcrumbs.js","names":["styles","Breadcrumbs_module_default","Breadcrumbs","cn","className","items","isLast","index","href","vettedHref","item","interactive","Fragment","event","separator"],"sources":["../../../src/components/Breadcrumbs/Breadcrumbs.tsx"],"sourcesContent":["import { Fragment } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { vettedHref } from \"@/utils/safe-url\";\nimport styles from \"./Breadcrumbs.module.css\";\n\nexport interface BreadcrumbItem {\n label: ReactNode;\n href?: string;\n onClick?: () => void;\n}\n\nexport interface BreadcrumbsProps {\n items: BreadcrumbItem[];\n separator?: ReactNode;\n className?: string;\n}\n\n/**\n * Hierarchical navigation breadcrumbs. The last item is rendered as plain\n * text with `aria-current=\"page\"`. Items with neither `href` nor `onClick`\n * also render as text (intermediate but non-clickable).\n *\n * Each `href` is vetted with `safeLinkUrl`'s allowlist: an item whose URL uses a scheme\n * outside `http:`/`https:`/`mailto:`/`tel:`/`sms:` loses the `href`, and renders\n * as text unless it also has an `onClick`. A trail built from API data — the\n * folder names of a document tree — would otherwise put a clickable\n * `javascript:` link on the page under React 18, which writes it verbatim.\n */\nexport function Breadcrumbs({ items, separator = \"/\", className }: BreadcrumbsProps) {\n return (\n <nav aria-label=\"breadcrumb\" className={cn(styles.nav, className)}>\n {items.map((item, index) => {\n const isLast = index === items.length - 1;\n const href = vettedHref(item.href);\n const interactive = !isLast && (href || item.onClick);\n return (\n <Fragment key={index}>\n <span className={styles.item}>\n {interactive ? (\n <a\n href={href}\n onClick={(event) => {\n if (item.onClick) {\n event.preventDefault();\n item.onClick();\n }\n }}\n className={styles.link}\n >\n {item.label}\n </a>\n ) : (\n <span\n aria-current={isLast ? \"page\" : undefined}\n className={isLast ? styles.current : undefined}\n >\n {item.label}\n </span>\n )}\n </span>\n {!isLast && (\n <span className={styles.separator} aria-hidden>\n {separator}\n </span>\n )}\n </Fragment>\n );\n })}\n </nav>\n );\n}\n"],"mappings":";;;;;;AA6BA,SAAgBE,EAAY,EAAE,UAAO,eAAY,KAAK,gBAA+B;CACjF,OACI,kBAAC,OAAD;EAAK,cAAW;EAAa,WAAWC,EAAGH,EAAO,KAAKI,CAAS;EAC3D,UAAAC,EAAM,KAAK,GAAM,MAAU;GACxB,IAAMC,IAASC,MAAUF,EAAM,SAAS,GAClCG,IAAOC,EAAWC,EAAK,IAAI,GAC3BC,IAAc,CAACL,MAAWE,KAAQE,EAAK;GAC7C,OACI,kBAACE,GAAD,EAAA,UAAA,CACI,kBAAC,QAAD;IAAM,WAAWZ,EAAO;IACnB,UAAAW,IACG,kBAAC,KAAD;KACU;KACN,UAAU,MAAU;MAChB,AAAID,EAAK,YACLG,EAAM,eAAe,GACrBH,EAAK,QAAQ;KAErB;KACA,WAAWV,EAAO;KAEjB,UAAAU,EAAK;IACP,CAAA,IAEH,kBAAC,QAAD;KACI,gBAAcJ,IAAS,SAAS,KAAA;KAChC,WAAWA,IAASN,EAAO,UAAU,KAAA;KAEpC,UAAAU,EAAK;IACJ,CAAA;GAER,CAAA,GACL,CAACJ,KACE,kBAAC,QAAD;IAAM,WAAWN,EAAO;IAAW,eAAA;IAC9B,UAAAc;GACC,CAAA,CAEJ,EAAA,GA7BKP,CA6BL;EAElB,CAAC;CACA,CAAA;AAEb"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./button-class.cjs"),
|
|
1
|
+
const e=require("../../utils/safe-url.cjs"),t=require("./button-class.cjs"),n=require("./ButtonContent.cjs"),r=require("./link-rel.cjs");let i=require("react"),a=require("react/jsx-runtime");var o=(0,i.forwardRef)(function(i,o){let{variant:s,size:c,loading:l=!1,disabled:u=!1,fullWidth:d,iconOnly:f,pill:p,leftIcon:m,rightIcon:h,className:g,children:_,href:v,target:y,rel:b,...x}=i,S=e.vettedHref(v),C=u||l||v!==void 0&&S===void 0,w=t.buttonClassName({variant:s,size:c,loading:l,fullWidth:d,iconOnly:f,pill:p},g),T=(0,a.jsx)(n.ButtonContent,{loading:l,leftIcon:m,rightIcon:h,children:_});return v===void 0?(0,a.jsx)(`button`,{...x,ref:o,className:w,disabled:C,"aria-busy":l||void 0,children:T}):(0,a.jsx)(`a`,{...x,ref:o,className:w,href:C?void 0:S,target:y,rel:r.resolveLinkRel(y,b),role:C?`link`:x.role,"aria-disabled":C||void 0,"aria-busy":l||void 0,tabIndex:C?-1:x.tabIndex,onClick:C?t.preventActivation:x.onClick,onAuxClick:C?t.preventActivation:x.onAuxClick,children:T})});exports.Button=o;
|
|
2
2
|
//# sourceMappingURL=Button.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.cjs","names":["forwardRef","buttonClassName","ButtonContent","resolveLinkRel","preventActivation","require_button_class","require_ButtonContent","require_link_rel","react","react_jsx_runtime","Button","rest","props","inactive","disabled","loading","classes","className","content","
|
|
1
|
+
{"version":3,"file":"Button.cjs","names":["forwardRef","vettedHref","buttonClassName","ButtonContent","resolveLinkRel","preventActivation","require_safe_url","require_button_class","require_ButtonContent","require_link_rel","react","react_jsx_runtime","Button","rest","props","safeHref","href","inactive","disabled","loading","classes","className","content","target","rel"],"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — this is the design system's button: variant, size,\n * loading, fullWidth, iconOnly, pill, leftIcon and rightIcon are the whole\n * vocabulary every screen composes from, and href only picks which element carries\n * it. Nine orthogonal knobs on the most-used component is the API working — the\n * count rule is looking for two components fused together, and there is only one\n * here.\n */\nimport { forwardRef } from \"react\";\nimport type {\n AnchorHTMLAttributes,\n ButtonHTMLAttributes,\n ForwardedRef,\n HTMLAttributes,\n ReactElement,\n ReactNode,\n Ref,\n RefAttributes,\n} from \"react\";\nimport { vettedHref } from \"@/utils/safe-url\";\nimport { buttonClassName, preventActivation } from \"./button-class\";\nimport { ButtonContent } from \"./ButtonContent\";\nimport { resolveLinkRel } from \"./link-rel\";\nimport type { ButtonBaseProps } from \"./types\";\n\nexport type { ButtonBaseProps, ButtonSize, ButtonVariant } from \"./types\";\n\n/** `Button` rendering a native `<button>` — the default, when `href` is not set. */\nexport interface ButtonAsButtonProps\n extends ButtonBaseProps, ButtonHTMLAttributes<HTMLButtonElement> {\n href?: undefined;\n}\n\n/**\n * `Button` rendering an `<a href>` — a call to action that navigates.\n *\n * `type` is a `<button>` attribute and is rejected here; `target` and `rel` are\n * forwarded, and `target=\"_blank\"` always gets `noopener noreferrer` added.\n */\nexport interface ButtonAsLinkProps\n extends ButtonBaseProps, Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\" | \"type\"> {\n /** Destination. Its presence is what makes the button an `<a>`. */\n href: string;\n type?: never;\n}\n\nexport type ButtonProps = ButtonAsButtonProps | ButtonAsLinkProps;\n\n/**\n * Each mode's props with the ref type of the element it renders, so a `ref` to an\n * `HTMLButtonElement` on a link is a type error.\n */\ntype ButtonPropsWithRef =\n | (ButtonAsButtonProps & RefAttributes<HTMLButtonElement>)\n | (ButtonAsLinkProps & RefAttributes<HTMLAnchorElement>);\n\n/** `Button`'s call signature: one props union, each member with its own ref type. */\ntype ButtonComponent = ((props: ButtonPropsWithRef) => ReactElement) & { displayName?: string };\n\n/** The flattened view the render function works on after the call site was type-checked. */\ntype ButtonRenderProps = ButtonBaseProps &\n HTMLAttributes<HTMLElement> & {\n href?: string;\n target?: string;\n rel?: string;\n };\n\n/**\n * Primary action button with variants, sizes and a loading state that\n * preserves layout via an absolutely-positioned spinner.\n *\n * Variants: `primary` (solid), `secondary` (neutral), `danger`, `success`,\n * `ghost` (transparent), `soft` (tinted), `outline` (bordered), `link`.\n *\n * Sizes are density-aware — they read from `--tempest-control-height-*`\n * tokens which respond to the `data-tempest-density` attribute.\n *\n * With `href` it renders an `<a>` with the same look: a call to action that\n * navigates keeps the middle click, Ctrl+click, \"open in new tab\" and the URL\n * preview, which a `<button>` calling `location.assign` loses. `target=\"_blank\"`\n * gets `noopener noreferrer`. An `<a>` has no `disabled`, so a disabled or loading\n * link gets `aria-disabled=\"true\"`, `role=\"link\"`, `tabIndex={-1}`, no `href`, and\n * its click and middle-click cancelled.\n *\n * The `href` is vetted first, with `safeLinkUrl`'s allowlist: `http:`, `https:`, `mailto:`,\n * `tel:`, `sms:` and relative URLs pass, anything else renders as the same inert,\n * `aria-disabled` link. React 19 blocks a `javascript:` href but the peer range\n * admits React 18, which writes it verbatim (#447), and a CTA built from API data\n * should not need the app to remember that. A link to a custom scheme (`whatsapp:`,\n * an app deep link) goes through {@link ButtonSlot} with the app's own `<a>`.\n *\n * To style another element — a react-router `Link` — use {@link ButtonSlot}. It is\n * a separate component, not an `asChild` prop, so an app that never uses it does\n * not ship the prop merge and ref composition it needs.\n *\n * The ref is typed per mode: `HTMLButtonElement`, or `HTMLAnchorElement` with\n * `href`. `forwardRef` takes one ref type for all props, so its result is re-typed\n * to the per-mode union — the runtime is plain `forwardRef`.\n *\n * @example\n * <Button onClick={save}>Salvar</Button>\n *\n * @example\n * <Button href=\"https://wa.me/5586999999999\" target=\"_blank\" rightIcon={<ExternalLink />}>\n * Fale conosco\n * </Button>\n */\nexport const Button = forwardRef<HTMLElement, ButtonProps>(function Button(\n props: ButtonProps,\n ref: ForwardedRef<HTMLElement>,\n): ReactElement {\n const {\n variant,\n size,\n loading = false,\n disabled = false,\n fullWidth,\n iconOnly,\n pill,\n leftIcon,\n rightIcon,\n className,\n children,\n href,\n target,\n rel,\n ...rest\n } = props as ButtonRenderProps;\n const safeHref = vettedHref(href);\n const inactive = disabled || loading || (href !== undefined && safeHref === undefined);\n const classes = buttonClassName(\n { variant, size, loading, fullWidth, iconOnly, pill },\n className,\n );\n const content: ReactNode = (\n <ButtonContent loading={loading} leftIcon={leftIcon} rightIcon={rightIcon}>\n {children}\n </ButtonContent>\n );\n\n if (href !== undefined) {\n return (\n <a\n {...rest}\n ref={ref as Ref<HTMLAnchorElement>}\n className={classes}\n href={inactive ? undefined : safeHref}\n target={target}\n rel={resolveLinkRel(target, rel)}\n role={inactive ? \"link\" : rest.role}\n aria-disabled={inactive || undefined}\n aria-busy={loading || undefined}\n tabIndex={inactive ? -1 : rest.tabIndex}\n onClick={inactive ? preventActivation : rest.onClick}\n onAuxClick={inactive ? preventActivation : rest.onAuxClick}\n >\n {content}\n </a>\n );\n }\n\n return (\n <button\n {...(rest as ButtonHTMLAttributes<HTMLButtonElement>)}\n ref={ref as Ref<HTMLButtonElement>}\n className={classes}\n disabled={inactive}\n aria-busy={loading || undefined}\n >\n {content}\n </button>\n );\n}) as unknown as ButtonComponent;\n"],"mappings":"A,+LA2GA,IAAaY,GAAA,EAASZ,EAAAA,WAAAA,CAAqC,SACvD,EACA,EACY,CACZ,GAAM,CACF,UACA,OACA,UAAU,GACV,WAAW,GACX,YACA,WACA,OACA,WACA,YACA,YACA,WACA,OACA,SACA,MACA,GAAGa,GACHC,EACEC,EAAWd,EAAAA,WAAWe,CAAI,EAC1BC,EAAWC,GAAYC,GAAYH,IAAS,IAAA,IAAaD,IAAa,IAAA,GACtEK,EAAUlB,EAAAA,gBACZ,CAAE,UAAS,OAAM,UAAS,YAAW,WAAU,MAAK,EACpDmB,CACJ,EACMC,GACF,EAAAX,EAAA,IAAA,CAACR,EAAAA,cAAD,CAAwB,UAAmB,WAAqB,YAC3D,UACU,CAAA,EAwBnB,OArBIa,IAAS,IAAA,IAsBT,EAAAL,EAAA,IAAA,CAAC,SAAD,CACI,GAAKE,EACA,MACL,UAAWO,EACX,SAAUH,EACV,YAAWE,GAAW,IAAA,GAErB,SAAAG,CACG,CAAA,GA5BJ,EAAAX,EAAA,IAAA,CAAC,IAAD,CACI,GAAIE,EACC,MACL,UAAWO,EACX,KAAMH,EAAW,IAAA,GAAYF,EACrB,SACR,IAAKX,EAAAA,eAAemB,EAAQC,CAAG,EAC/B,KAAMP,EAAW,OAASJ,EAAK,KAC/B,gBAAeI,GAAY,IAAA,GAC3B,YAAWE,GAAW,IAAA,GACtB,SAAUF,EAAW,GAAKJ,EAAK,SAC/B,QAASI,EAAWZ,EAAAA,kBAAoBQ,EAAK,QAC7C,WAAYI,EAAWZ,EAAAA,kBAAoBQ,EAAK,WAE/C,SAAAS,CACF,CAAA,CAef,CAAC,E"}
|
|
@@ -1,47 +1,48 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { vettedHref as e } from "../../utils/safe-url.js";
|
|
2
|
+
import { buttonClassName as t, preventActivation as n } from "./button-class.js";
|
|
3
|
+
import { ButtonContent as r } from "./ButtonContent.js";
|
|
4
|
+
import { resolveLinkRel as i } from "./link-rel.js";
|
|
5
|
+
import { forwardRef as a } from "react";
|
|
6
|
+
import { jsx as o } from "react/jsx-runtime";
|
|
6
7
|
//#region src/components/Button/Button.tsx
|
|
7
|
-
var
|
|
8
|
-
let { variant:
|
|
9
|
-
variant:
|
|
10
|
-
size:
|
|
11
|
-
loading:
|
|
12
|
-
fullWidth:
|
|
13
|
-
iconOnly:
|
|
14
|
-
pill:
|
|
15
|
-
},
|
|
16
|
-
loading:
|
|
17
|
-
leftIcon:
|
|
18
|
-
rightIcon:
|
|
19
|
-
children:
|
|
8
|
+
var s = a(function(a, s) {
|
|
9
|
+
let { variant: c, size: l, loading: u = !1, disabled: d = !1, fullWidth: f, iconOnly: p, pill: m, leftIcon: h, rightIcon: g, className: _, children: v, href: y, target: b, rel: x, ...S } = a, C = e(y), w = d || u || y !== void 0 && C === void 0, T = t({
|
|
10
|
+
variant: c,
|
|
11
|
+
size: l,
|
|
12
|
+
loading: u,
|
|
13
|
+
fullWidth: f,
|
|
14
|
+
iconOnly: p,
|
|
15
|
+
pill: m
|
|
16
|
+
}, _), E = /* @__PURE__ */ o(r, {
|
|
17
|
+
loading: u,
|
|
18
|
+
leftIcon: h,
|
|
19
|
+
rightIcon: g,
|
|
20
|
+
children: v
|
|
20
21
|
});
|
|
21
|
-
return
|
|
22
|
-
...
|
|
23
|
-
ref:
|
|
24
|
-
className:
|
|
25
|
-
disabled:
|
|
26
|
-
"aria-busy":
|
|
27
|
-
children:
|
|
28
|
-
}) : /* @__PURE__ */
|
|
29
|
-
...
|
|
30
|
-
ref:
|
|
31
|
-
className:
|
|
32
|
-
href:
|
|
33
|
-
target:
|
|
34
|
-
rel:
|
|
35
|
-
role:
|
|
36
|
-
"aria-disabled":
|
|
37
|
-
"aria-busy":
|
|
38
|
-
tabIndex:
|
|
39
|
-
onClick:
|
|
40
|
-
onAuxClick:
|
|
41
|
-
children:
|
|
22
|
+
return y === void 0 ? /* @__PURE__ */ o("button", {
|
|
23
|
+
...S,
|
|
24
|
+
ref: s,
|
|
25
|
+
className: T,
|
|
26
|
+
disabled: w,
|
|
27
|
+
"aria-busy": u || void 0,
|
|
28
|
+
children: E
|
|
29
|
+
}) : /* @__PURE__ */ o("a", {
|
|
30
|
+
...S,
|
|
31
|
+
ref: s,
|
|
32
|
+
className: T,
|
|
33
|
+
href: w ? void 0 : C,
|
|
34
|
+
target: b,
|
|
35
|
+
rel: i(b, x),
|
|
36
|
+
role: w ? "link" : S.role,
|
|
37
|
+
"aria-disabled": w || void 0,
|
|
38
|
+
"aria-busy": u || void 0,
|
|
39
|
+
tabIndex: w ? -1 : S.tabIndex,
|
|
40
|
+
onClick: w ? n : S.onClick,
|
|
41
|
+
onAuxClick: w ? n : S.onAuxClick,
|
|
42
|
+
children: E
|
|
42
43
|
});
|
|
43
44
|
});
|
|
44
45
|
//#endregion
|
|
45
|
-
export {
|
|
46
|
+
export { s as Button };
|
|
46
47
|
|
|
47
48
|
//# sourceMappingURL=Button.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","names":["Button","forwardRef","rest","props","inactive","disabled","loading","classes","buttonClassName","className","content","ButtonContent","
|
|
1
|
+
{"version":3,"file":"Button.js","names":["Button","forwardRef","rest","props","safeHref","vettedHref","href","inactive","disabled","loading","classes","buttonClassName","className","content","ButtonContent","resolveLinkRel","target","rel","preventActivation"],"sources":["../../../src/components/Button/Button.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — this is the design system's button: variant, size,\n * loading, fullWidth, iconOnly, pill, leftIcon and rightIcon are the whole\n * vocabulary every screen composes from, and href only picks which element carries\n * it. Nine orthogonal knobs on the most-used component is the API working — the\n * count rule is looking for two components fused together, and there is only one\n * here.\n */\nimport { forwardRef } from \"react\";\nimport type {\n AnchorHTMLAttributes,\n ButtonHTMLAttributes,\n ForwardedRef,\n HTMLAttributes,\n ReactElement,\n ReactNode,\n Ref,\n RefAttributes,\n} from \"react\";\nimport { vettedHref } from \"@/utils/safe-url\";\nimport { buttonClassName, preventActivation } from \"./button-class\";\nimport { ButtonContent } from \"./ButtonContent\";\nimport { resolveLinkRel } from \"./link-rel\";\nimport type { ButtonBaseProps } from \"./types\";\n\nexport type { ButtonBaseProps, ButtonSize, ButtonVariant } from \"./types\";\n\n/** `Button` rendering a native `<button>` — the default, when `href` is not set. */\nexport interface ButtonAsButtonProps\n extends ButtonBaseProps, ButtonHTMLAttributes<HTMLButtonElement> {\n href?: undefined;\n}\n\n/**\n * `Button` rendering an `<a href>` — a call to action that navigates.\n *\n * `type` is a `<button>` attribute and is rejected here; `target` and `rel` are\n * forwarded, and `target=\"_blank\"` always gets `noopener noreferrer` added.\n */\nexport interface ButtonAsLinkProps\n extends ButtonBaseProps, Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\" | \"type\"> {\n /** Destination. Its presence is what makes the button an `<a>`. */\n href: string;\n type?: never;\n}\n\nexport type ButtonProps = ButtonAsButtonProps | ButtonAsLinkProps;\n\n/**\n * Each mode's props with the ref type of the element it renders, so a `ref` to an\n * `HTMLButtonElement` on a link is a type error.\n */\ntype ButtonPropsWithRef =\n | (ButtonAsButtonProps & RefAttributes<HTMLButtonElement>)\n | (ButtonAsLinkProps & RefAttributes<HTMLAnchorElement>);\n\n/** `Button`'s call signature: one props union, each member with its own ref type. */\ntype ButtonComponent = ((props: ButtonPropsWithRef) => ReactElement) & { displayName?: string };\n\n/** The flattened view the render function works on after the call site was type-checked. */\ntype ButtonRenderProps = ButtonBaseProps &\n HTMLAttributes<HTMLElement> & {\n href?: string;\n target?: string;\n rel?: string;\n };\n\n/**\n * Primary action button with variants, sizes and a loading state that\n * preserves layout via an absolutely-positioned spinner.\n *\n * Variants: `primary` (solid), `secondary` (neutral), `danger`, `success`,\n * `ghost` (transparent), `soft` (tinted), `outline` (bordered), `link`.\n *\n * Sizes are density-aware — they read from `--tempest-control-height-*`\n * tokens which respond to the `data-tempest-density` attribute.\n *\n * With `href` it renders an `<a>` with the same look: a call to action that\n * navigates keeps the middle click, Ctrl+click, \"open in new tab\" and the URL\n * preview, which a `<button>` calling `location.assign` loses. `target=\"_blank\"`\n * gets `noopener noreferrer`. An `<a>` has no `disabled`, so a disabled or loading\n * link gets `aria-disabled=\"true\"`, `role=\"link\"`, `tabIndex={-1}`, no `href`, and\n * its click and middle-click cancelled.\n *\n * The `href` is vetted first, with `safeLinkUrl`'s allowlist: `http:`, `https:`, `mailto:`,\n * `tel:`, `sms:` and relative URLs pass, anything else renders as the same inert,\n * `aria-disabled` link. React 19 blocks a `javascript:` href but the peer range\n * admits React 18, which writes it verbatim (#447), and a CTA built from API data\n * should not need the app to remember that. A link to a custom scheme (`whatsapp:`,\n * an app deep link) goes through {@link ButtonSlot} with the app's own `<a>`.\n *\n * To style another element — a react-router `Link` — use {@link ButtonSlot}. It is\n * a separate component, not an `asChild` prop, so an app that never uses it does\n * not ship the prop merge and ref composition it needs.\n *\n * The ref is typed per mode: `HTMLButtonElement`, or `HTMLAnchorElement` with\n * `href`. `forwardRef` takes one ref type for all props, so its result is re-typed\n * to the per-mode union — the runtime is plain `forwardRef`.\n *\n * @example\n * <Button onClick={save}>Salvar</Button>\n *\n * @example\n * <Button href=\"https://wa.me/5586999999999\" target=\"_blank\" rightIcon={<ExternalLink />}>\n * Fale conosco\n * </Button>\n */\nexport const Button = forwardRef<HTMLElement, ButtonProps>(function Button(\n props: ButtonProps,\n ref: ForwardedRef<HTMLElement>,\n): ReactElement {\n const {\n variant,\n size,\n loading = false,\n disabled = false,\n fullWidth,\n iconOnly,\n pill,\n leftIcon,\n rightIcon,\n className,\n children,\n href,\n target,\n rel,\n ...rest\n } = props as ButtonRenderProps;\n const safeHref = vettedHref(href);\n const inactive = disabled || loading || (href !== undefined && safeHref === undefined);\n const classes = buttonClassName(\n { variant, size, loading, fullWidth, iconOnly, pill },\n className,\n );\n const content: ReactNode = (\n <ButtonContent loading={loading} leftIcon={leftIcon} rightIcon={rightIcon}>\n {children}\n </ButtonContent>\n );\n\n if (href !== undefined) {\n return (\n <a\n {...rest}\n ref={ref as Ref<HTMLAnchorElement>}\n className={classes}\n href={inactive ? undefined : safeHref}\n target={target}\n rel={resolveLinkRel(target, rel)}\n role={inactive ? \"link\" : rest.role}\n aria-disabled={inactive || undefined}\n aria-busy={loading || undefined}\n tabIndex={inactive ? -1 : rest.tabIndex}\n onClick={inactive ? preventActivation : rest.onClick}\n onAuxClick={inactive ? preventActivation : rest.onAuxClick}\n >\n {content}\n </a>\n );\n }\n\n return (\n <button\n {...(rest as ButtonHTMLAttributes<HTMLButtonElement>)}\n ref={ref as Ref<HTMLButtonElement>}\n className={classes}\n disabled={inactive}\n aria-busy={loading || undefined}\n >\n {content}\n </button>\n );\n}) as unknown as ButtonComponent;\n"],"mappings":";;;;;;;AA2GA,IAAaA,IAASC,EAAqC,SACvD,GACA,GACY;CACZ,IAAM,EACF,YACA,SACA,aAAU,IACV,cAAW,IACX,cACA,aACA,SACA,aACA,cACA,cACA,aACA,SACA,WACA,QACA,GAAGC,MACHC,GACEC,IAAWC,EAAWC,CAAI,GAC1BC,IAAWC,KAAYC,KAAYH,MAAS,KAAA,KAAaF,MAAa,KAAA,GACtEM,IAAUC,EACZ;EAAE;EAAS;EAAM;EAAS;EAAW;EAAU;CAAK,GACpDC,CACJ,GACMC,IACF,kBAACC,GAAD;EAAwB;EAAmB;EAAqB;EAC3D;CACU,CAAA;CAwBnB,OArBIR,MAAS,KAAA,IAsBT,kBAAC,UAAD;EACI,GAAKJ;EACA;EACL,WAAWQ;EACX,UAAUH;EACV,aAAWE,KAAW,KAAA;EAErB,UAAAI;CACG,CAAA,IA5BJ,kBAAC,KAAD;EACI,GAAIX;EACC;EACL,WAAWQ;EACX,MAAMH,IAAW,KAAA,IAAYH;EACrB;EACR,KAAKW,EAAeC,GAAQC,CAAG;EAC/B,MAAMV,IAAW,SAASL,EAAK;EAC/B,iBAAeK,KAAY,KAAA;EAC3B,aAAWE,KAAW,KAAA;EACtB,UAAUF,IAAW,KAAKL,EAAK;EAC/B,SAASK,IAAWW,IAAoBhB,EAAK;EAC7C,YAAYK,IAAWW,IAAoBhB,EAAK;EAE/C,UAAAW;CACF,CAAA;AAef,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/
|
|
1
|
+
const e=require("../../utils/safe-url.cjs"),t=require("../../utils/numbers.cjs"),n=require("../VideoPlayer/VideoPlayer.cjs"),r=require("../Lightbox/Lightbox.cjs"),i=require("./Chat.module.cjs"),a=require("../AudioPlayer/AudioPlayer.cjs");let o=require("react"),s=require("react/jsx-runtime");var c=Array.from({length:28},()=>.25);function l(e){let t=Math.round(e/1e3),n=Math.floor(t/60),r=t%60;return`${n}:${String(r).padStart(2,`0`)}`}function u({attachments:u,strings:d}){let f=u.filter(e=>e.kind===`image`),[p,m]=(0,o.useState)(null);return(0,s.jsxs)(`div`,{className:i.default.attachments,children:[u.map((r,o)=>{let u=`${r.kind}-${r.url}-${o}`;if(r.kind===`image`){let e=f.indexOf(r);return(0,s.jsx)(`button`,{type:`button`,className:i.default.imageAttachment,onClick:()=>m(e),children:(0,s.jsx)(`img`,{src:r.thumbnailUrl??r.url,alt:r.alt??r.name??``,loading:`lazy`})},u)}if(r.kind===`video`)return(0,s.jsx)(n.VideoPlayer,{src:r.url,durationMs:r.durationMs,poster:r.thumbnailUrl,className:i.default.videoAttachment},u);if(r.kind===`voice`)return(0,s.jsxs)(`div`,{className:i.default.voiceAttachment,children:[(0,s.jsx)(`div`,{className:i.default.waveform,"aria-hidden":`true`,children:(r.waveform??c).map((e,t)=>(0,s.jsx)(`span`,{style:{height:`${Math.max(8,Math.min(100,e*100))}%`}},t))}),(0,s.jsx)(a.AudioPlayer,{src:r.url,durationMs:r.durationMs,"aria-label":d.voiceNote})]},u);if(r.kind===`audio`)return(0,s.jsx)(a.AudioPlayer,{src:r.url,durationMs:r.durationMs},u);let p=e.safeAttachmentUrl(r.url),h=(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(`span`,{className:i.default.fileName,children:r.name??d.download}),(0,s.jsx)(`span`,{className:i.default.fileMeta,children:[r.sizeBytes===void 0?null:t.formatBytes(r.sizeBytes),r.durationMs===void 0?null:l(r.durationMs)].filter(Boolean).join(` · `)})]});return p?(0,s.jsx)(`a`,{className:i.default.fileAttachment,href:p,download:r.name??!0,children:h},u):(0,s.jsx)(`div`,{className:i.default.fileAttachment,children:h},u)}),f.length>0&&(0,s.jsx)(r.Lightbox,{items:f.map(e=>({src:e.url,alt:e.alt??e.name??``,thumbnail:e.thumbnailUrl})),open:p!==null,index:p??0,onIndexChange:m,onClose:()=>m(null)})]})}exports.ChatAttachments=u;
|
|
2
2
|
//# sourceMappingURL=ChatAttachments.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatAttachments.cjs","names":["useState","styles","VideoPlayer","AudioPlayer","safeAttachmentUrl","_Fragment","formatBytes","Lightbox","require_numbers","require_VideoPlayer","require_Lightbox","require_Chat_module","require_AudioPlayer","require_safe_url","react","react_jsx_runtime","FLAT_WAVEFORM","durationLabel","total","durationMs","minutes","seconds","ChatAttachments","images","attachments","attachment","lightboxIndex","setLightboxIndex","key","index","position","peak","bar","strings","href","row","image"],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\nimport { safeAttachmentUrl } from \"@/utils/safe-url\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * The file row is the one place a sender's URL becomes an `href`, so it goes\n * through {@link safeAttachmentUrl}: `http:`, `https:`, `blob:` and relative URLs\n * are linked, anything else renders the same row without a link (#447). Media\n * `src` is left as given — an `<img>`, `<video>` or `<audio>` does not run a\n * `javascript:` URL.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n const href = safeAttachmentUrl(attachment.url);\n const row = (\n <>\n <span className={styles.fileName}>\n {attachment.name ?? strings.download}\n </span>\n <span className={styles.fileMeta}>\n {[\n attachment.sizeBytes !== undefined\n ? formatBytes(attachment.sizeBytes)\n : null,\n attachment.durationMs !== undefined\n ? durationLabel(attachment.durationMs)\n : null,\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </>\n );\n return href ? (\n <a\n key={key}\n className={styles.fileAttachment}\n href={href}\n download={attachment.name ?? true}\n >\n {row}\n </a>\n ) : (\n <div key={key} className={styles.fileAttachment}>\n {row}\n </div>\n );\n })}\n\n {images.length > 0 && (\n <Lightbox\n items={images.map((image) => ({\n src: image.url,\n alt: image.alt ?? image.name ?? \"\",\n thumbnail: image.thumbnailUrl,\n }))}\n open={lightboxIndex !== null}\n index={lightboxIndex ?? 0}\n onIndexChange={setLightboxIndex}\n onClose={() => setLightboxIndex(null)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChatAttachments.cjs","names":["useState","styles","VideoPlayer","AudioPlayer","safeAttachmentUrl","_Fragment","formatBytes","Lightbox","require_safe_url","require_numbers","require_VideoPlayer","require_Lightbox","require_Chat_module","require_AudioPlayer","react","react_jsx_runtime","FLAT_WAVEFORM","durationLabel","total","durationMs","minutes","seconds","ChatAttachments","images","attachments","attachment","lightboxIndex","setLightboxIndex","key","index","position","peak","bar","strings","href","row","image"],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\nimport { safeAttachmentUrl } from \"@/utils/safe-url\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * The file row is the one place a sender's URL becomes an `href`, so it goes\n * through {@link safeAttachmentUrl}: `http:`, `https:`, `blob:` and relative URLs\n * are linked, anything else renders the same row without a link (#447). Media\n * `src` is left as given — an `<img>`, `<video>` or `<audio>` does not run a\n * `javascript:` URL.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n const href = safeAttachmentUrl(attachment.url);\n const row = (\n <>\n <span className={styles.fileName}>\n {attachment.name ?? strings.download}\n </span>\n <span className={styles.fileMeta}>\n {[\n attachment.sizeBytes !== undefined\n ? formatBytes(attachment.sizeBytes)\n : null,\n attachment.durationMs !== undefined\n ? durationLabel(attachment.durationMs)\n : null,\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </>\n );\n return href ? (\n <a\n key={key}\n className={styles.fileAttachment}\n href={href}\n download={attachment.name ?? true}\n >\n {row}\n </a>\n ) : (\n <div key={key} className={styles.fileAttachment}>\n {row}\n </div>\n );\n })}\n\n {images.length > 0 && (\n <Lightbox\n items={images.map((image) => ({\n src: image.url,\n alt: image.alt ?? image.name ?? \"\",\n thumbnail: image.thumbnailUrl,\n }))}\n open={lightboxIndex !== null}\n index={lightboxIndex ?? 0}\n onIndexChange={setLightboxIndex}\n onClose={() => setLightboxIndex(null)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import r from "
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
1
|
+
import { safeAttachmentUrl as e } from "../../utils/safe-url.js";
|
|
2
|
+
import { formatBytes as t } from "../../utils/numbers.js";
|
|
3
|
+
import { VideoPlayer as n } from "../VideoPlayer/VideoPlayer.js";
|
|
4
|
+
import { Lightbox as r } from "../Lightbox/Lightbox.js";
|
|
5
|
+
import i from "./Chat.module.js";
|
|
6
|
+
import { AudioPlayer as a } from "../AudioPlayer/AudioPlayer.js";
|
|
7
7
|
import { useState as o } from "react";
|
|
8
8
|
import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
9
9
|
//#region src/components/Chat/ChatAttachments.tsx
|
|
@@ -15,61 +15,61 @@ function d(e) {
|
|
|
15
15
|
function f({ attachments: f, strings: p }) {
|
|
16
16
|
let m = f.filter((e) => e.kind === "image"), [h, g] = o(null);
|
|
17
17
|
return /* @__PURE__ */ l("div", {
|
|
18
|
-
className:
|
|
19
|
-
children: [f.map((
|
|
20
|
-
let f = `${
|
|
21
|
-
if (
|
|
22
|
-
let e = m.indexOf(
|
|
18
|
+
className: i.attachments,
|
|
19
|
+
children: [f.map((r, o) => {
|
|
20
|
+
let f = `${r.kind}-${r.url}-${o}`;
|
|
21
|
+
if (r.kind === "image") {
|
|
22
|
+
let e = m.indexOf(r);
|
|
23
23
|
return /* @__PURE__ */ c("button", {
|
|
24
24
|
type: "button",
|
|
25
|
-
className:
|
|
25
|
+
className: i.imageAttachment,
|
|
26
26
|
onClick: () => g(e),
|
|
27
27
|
children: /* @__PURE__ */ c("img", {
|
|
28
|
-
src:
|
|
29
|
-
alt:
|
|
28
|
+
src: r.thumbnailUrl ?? r.url,
|
|
29
|
+
alt: r.alt ?? r.name ?? "",
|
|
30
30
|
loading: "lazy"
|
|
31
31
|
})
|
|
32
32
|
}, f);
|
|
33
33
|
}
|
|
34
|
-
if (
|
|
35
|
-
src:
|
|
36
|
-
durationMs:
|
|
37
|
-
poster:
|
|
38
|
-
className:
|
|
34
|
+
if (r.kind === "video") return /* @__PURE__ */ c(n, {
|
|
35
|
+
src: r.url,
|
|
36
|
+
durationMs: r.durationMs,
|
|
37
|
+
poster: r.thumbnailUrl,
|
|
38
|
+
className: i.videoAttachment
|
|
39
39
|
}, f);
|
|
40
|
-
if (
|
|
41
|
-
className:
|
|
40
|
+
if (r.kind === "voice") return /* @__PURE__ */ l("div", {
|
|
41
|
+
className: i.voiceAttachment,
|
|
42
42
|
children: [/* @__PURE__ */ c("div", {
|
|
43
|
-
className:
|
|
43
|
+
className: i.waveform,
|
|
44
44
|
"aria-hidden": "true",
|
|
45
|
-
children: (
|
|
46
|
-
}), /* @__PURE__ */ c(
|
|
47
|
-
src:
|
|
48
|
-
durationMs:
|
|
45
|
+
children: (r.waveform ?? u).map((e, t) => /* @__PURE__ */ c("span", { style: { height: `${Math.max(8, Math.min(100, e * 100))}%` } }, t))
|
|
46
|
+
}), /* @__PURE__ */ c(a, {
|
|
47
|
+
src: r.url,
|
|
48
|
+
durationMs: r.durationMs,
|
|
49
49
|
"aria-label": p.voiceNote
|
|
50
50
|
})]
|
|
51
51
|
}, f);
|
|
52
|
-
if (
|
|
53
|
-
src:
|
|
54
|
-
durationMs:
|
|
52
|
+
if (r.kind === "audio") return /* @__PURE__ */ c(a, {
|
|
53
|
+
src: r.url,
|
|
54
|
+
durationMs: r.durationMs
|
|
55
55
|
}, f);
|
|
56
|
-
let h =
|
|
57
|
-
className:
|
|
58
|
-
children:
|
|
56
|
+
let h = e(r.url), _ = /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("span", {
|
|
57
|
+
className: i.fileName,
|
|
58
|
+
children: r.name ?? p.download
|
|
59
59
|
}), /* @__PURE__ */ c("span", {
|
|
60
|
-
className:
|
|
61
|
-
children: [
|
|
60
|
+
className: i.fileMeta,
|
|
61
|
+
children: [r.sizeBytes === void 0 ? null : t(r.sizeBytes), r.durationMs === void 0 ? null : d(r.durationMs)].filter(Boolean).join(" · ")
|
|
62
62
|
})] });
|
|
63
63
|
return h ? /* @__PURE__ */ c("a", {
|
|
64
|
-
className:
|
|
64
|
+
className: i.fileAttachment,
|
|
65
65
|
href: h,
|
|
66
|
-
download:
|
|
66
|
+
download: r.name ?? !0,
|
|
67
67
|
children: _
|
|
68
68
|
}, f) : /* @__PURE__ */ c("div", {
|
|
69
|
-
className:
|
|
69
|
+
className: i.fileAttachment,
|
|
70
70
|
children: _
|
|
71
71
|
}, f);
|
|
72
|
-
}), m.length > 0 && /* @__PURE__ */ c(
|
|
72
|
+
}), m.length > 0 && /* @__PURE__ */ c(r, {
|
|
73
73
|
items: m.map((e) => ({
|
|
74
74
|
src: e.url,
|
|
75
75
|
alt: e.alt ?? e.name ?? "",
|
|
@@ -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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"}
|