@mohasinac/appkit 4.61.0 → 4.62.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/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +19 -3
- package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
- package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
- package/dist/_internal/server/jobs/core/paymentReconcile.js +87 -97
- package/dist/_internal/shared/actions/action-registry.js +149 -9
- package/dist/_internal/shared/media/limits.d.ts +10 -0
- package/dist/_internal/shared/media/limits.js +10 -0
- package/dist/_internal/shared/types/action-result.js +15 -1
- package/dist/client/api/surface-error.js +16 -2
- package/dist/client/api/useApiMutation.js +17 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/constants/api-endpoints.d.ts +3 -0
- package/dist/constants/api-endpoints.js +1 -0
- package/dist/constants/field-names.d.ts +17 -0
- package/dist/constants/field-names.js +23 -0
- package/dist/core/server-action.js +19 -3
- package/dist/errors/error-mapping.js +26 -1
- package/dist/features/account/components/UserOffersPanel.js +5 -3
- package/dist/features/admin/components/AdminActionIndexView.js +100 -5
- package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
- package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
- package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
- package/dist/features/admin/components/AdminCarouselView.js +96 -4
- package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
- package/dist/features/admin/components/AdminMediaView.js +36 -3
- package/dist/features/admin/components/AdminNavigationView.js +4 -1
- package/dist/features/admin/components/AdminOffersView.js +2 -1
- package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
- package/dist/features/admin/components/AdminProductEditorView.js +2 -2
- package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
- package/dist/features/admin/components/AdminSectionsView.js +486 -224
- package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
- package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
- package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
- package/dist/features/admin/repository/notification.repository.d.ts +15 -0
- package/dist/features/admin/repository/notification.repository.js +32 -0
- package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
- package/dist/features/admin/schemas/action-index-form.js +115 -0
- package/dist/features/auth/components/RegisterForm.js +2 -1
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
- package/dist/features/events/components/AdminEventEditorView.js +8 -6
- package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
- package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
- package/dist/features/orders/components/RefundRequestView.js +4 -4
- package/dist/features/orders/schemas/index.d.ts +6 -6
- package/dist/features/products/components/GroupMemberPicker.js +4 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
- package/dist/features/products/components/ProductDetailActions.js +2 -1
- package/dist/features/seller/components/FulfillmentView.js +3 -2
- package/dist/features/seller/components/SellerAddressesView.js +32 -4
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
- package/dist/features/seller/components/SellerBundlesView.js +53 -14
- package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
- package/dist/features/seller/components/SellerListingTypeView.js +56 -10
- package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
- package/dist/features/seller/components/SellerProductsView.js +5 -1
- package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
- package/dist/features/seller/components/SellerShippingView.js +2 -1
- package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
- package/dist/features/seller/components/SellerSupportView.js +69 -2
- package/dist/features/shell/SectionForm.js +20 -16
- package/dist/features/shell/build-sections.js +14 -1
- package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
- package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/next/routing/route-map.d.ts +4 -0
- package/dist/next/routing/route-map.js +9 -0
- package/dist/schemas/registry.d.ts +4 -4
- package/dist/schemas/sieve.d.ts +3 -3
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/ui/components/Checkbox.js +10 -2
- package/dist/ui/components/DateInput.js +8 -1
- package/dist/ui/components/Input.js +22 -2
- package/dist/ui/components/PaginatedSelect.d.ts +8 -19
- package/dist/ui/components/PaginatedSelect.js +43 -9
- package/dist/ui/components/Radio.js +8 -1
- package/dist/ui/components/Select.js +10 -2
- package/dist/ui/components/TagInput.d.ts +8 -1
- package/dist/ui/components/TagInput.js +4 -3
- package/dist/ui/components/Textarea.js +10 -2
- package/dist/ui/forms/FieldCheckbox.js +1 -1
- package/dist/ui/forms/FormErrorList.d.ts +0 -16
- package/dist/ui/forms/FormErrorList.js +34 -1
- package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
- package/dist/ui/forms/apply-zod-issues.js +23 -1
- package/package.json +1 -1
|
@@ -6,6 +6,14 @@ import { Label, Text } from "./Typography.js";
|
|
|
6
6
|
export function Select({ options, value, onChange, onValueChange, placeholder, label, error, helperText, disabled = false, required, className = "", id, variant = "default", bare = false, wrapperClassName, ...props }) {
|
|
7
7
|
const generatedId = React.useId();
|
|
8
8
|
const inputId = id ?? generatedId;
|
|
9
|
+
/*
|
|
10
|
+
* A caller's own `aria-invalid` wins. `{...props}` is spread first, so
|
|
11
|
+
* re-specifying the attribute afterwards used to overwrite an incoming
|
|
12
|
+
* `"true"` with `undefined` — and `FieldSelect` sets it itself while
|
|
13
|
+
* withholding `error` (to avoid a double message). See the full writeup in
|
|
14
|
+
* `Input.tsx`; the same bug existed here.
|
|
15
|
+
*/
|
|
16
|
+
const ariaInvalid = props["aria-invalid"] ?? (error ? "true" : undefined);
|
|
9
17
|
const handleChange = (event) => {
|
|
10
18
|
onChange?.(event);
|
|
11
19
|
onValueChange?.(event.target.value);
|
|
@@ -28,7 +36,7 @@ export function Select({ options, value, onChange, onValueChange, placeholder, l
|
|
|
28
36
|
.join(" ");
|
|
29
37
|
const optionsMarkup = (_jsxs(_Fragment, { children: [placeholder ? (_jsx("option", { value: "", disabled: true, children: placeholder })) : null, options.map((option) => (_jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, String(option.value))))] }));
|
|
30
38
|
if (bare) {
|
|
31
|
-
return (_jsx("select", { ...props, id: inputId, value: value, onChange: handleChange, disabled: disabled, "aria-invalid":
|
|
39
|
+
return (_jsx("select", { ...props, id: inputId, value: value, onChange: handleChange, disabled: disabled, "aria-invalid": ariaInvalid, className: selectClassName, children: optionsMarkup }));
|
|
32
40
|
}
|
|
33
|
-
return (_jsxs("div", { className: ["appkit-select", wrapperClassName].filter(Boolean).join(" "), "data-section": "select-div-592", children: [label && (_jsx(Label, { htmlFor: inputId, className: "appkit-select__label", required: required, children: label })), _jsxs("div", { className: "relative group", "data-section": "select-div-593", children: [_jsx("select", { ...props, id: inputId, value: value, onChange: handleChange, disabled: disabled, "aria-invalid":
|
|
41
|
+
return (_jsxs("div", { className: ["appkit-select", wrapperClassName].filter(Boolean).join(" "), "data-section": "select-div-592", children: [label && (_jsx(Label, { htmlFor: inputId, className: "appkit-select__label", required: required, children: label })), _jsxs("div", { className: "relative group", "data-section": "select-div-593", children: [_jsx("select", { ...props, id: inputId, value: value, onChange: handleChange, disabled: disabled, "aria-invalid": ariaInvalid, className: selectClassName, children: optionsMarkup }), _jsx(Div, { className: "pointer-events-none absolute inset-y-0 right-3 flex items-center text-[var(--appkit-color-text-faint)]", "data-section": "select-div-594", children: _jsx("svg", { className: "h-4 w-4", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) }) })] }), error ? (_jsx(Text, { size: "sm", variant: "error", className: "appkit-select__error", role: "alert", children: error })) : helperText ? (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-select__error", children: helperText })) : null] }));
|
|
34
42
|
}
|
|
@@ -7,5 +7,12 @@ export interface TagInputProps {
|
|
|
7
7
|
placeholder?: string;
|
|
8
8
|
className?: string;
|
|
9
9
|
helperText?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Validation message. This control had no error slot at all, so a schema
|
|
12
|
+
* failure on a tags field could only ever appear in the summary — the field
|
|
13
|
+
* itself showed nothing and was not announced as invalid. Mirrors the
|
|
14
|
+
* contract Input/Select/Textarea/Checkbox already have.
|
|
15
|
+
*/
|
|
16
|
+
error?: React.ReactNode;
|
|
10
17
|
}
|
|
11
|
-
export declare function TagInput({ value, onChange, disabled, label, placeholder, className, helperText, }: TagInputProps): React.JSX.Element;
|
|
18
|
+
export declare function TagInput({ value, onChange, disabled, label, placeholder, className, helperText, error, }: TagInputProps): React.JSX.Element;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useState } from "react";
|
|
3
|
+
import React, { useRef, useState } from "react";
|
|
4
4
|
import { Label, Span } from "./Typography.js";
|
|
5
|
-
export function TagInput({ value, onChange, disabled = false, label, placeholder = "Add a tag...", className = "", helperText = "Press Enter or comma to add a tag", }) {
|
|
5
|
+
export function TagInput({ value, onChange, disabled = false, label, placeholder = "Add a tag...", className = "", helperText = "Press Enter or comma to add a tag", error, }) {
|
|
6
6
|
const [draft, setDraft] = useState("");
|
|
7
7
|
const inputRef = useRef(null);
|
|
8
|
+
const errorId = React.useId();
|
|
8
9
|
const normalizeTag = (raw) => raw.trim().replace(/,+$/, "").trim();
|
|
9
10
|
const addTag = (raw) => {
|
|
10
11
|
const tag = normalizeTag(raw);
|
|
@@ -69,5 +70,5 @@ export function TagInput({ value, onChange, disabled = false, label, placeholder
|
|
|
69
70
|
disabled ? "appkit-tag-input__text--disabled" : "",
|
|
70
71
|
]
|
|
71
72
|
.filter(Boolean)
|
|
72
|
-
.join(" "), "aria-label": label ?? "Tag input" })] }), helperText
|
|
73
|
+
.join(" "), "aria-label": label ?? "Tag input", "aria-invalid": error ? "true" : undefined, "aria-describedby": error ? errorId : undefined })] }), error ? (_jsx(Span, { id: errorId, size: "xs", className: "appkit-tag-input__helper appkit-form-field__error", role: "alert", children: error })) : helperText ? (_jsx(Span, { size: "xs", className: "appkit-tag-input__helper", children: helperText })) : null] }));
|
|
73
74
|
}
|
|
@@ -15,12 +15,20 @@ export const Textarea = React.forwardRef(function Textarea({ label, error, helpe
|
|
|
15
15
|
: variant === "ghost"
|
|
16
16
|
? "border-transparent bg-transparent shadow-none"
|
|
17
17
|
: "";
|
|
18
|
+
/*
|
|
19
|
+
* A caller's own `aria-invalid` wins. `{...props}` is spread first, so
|
|
20
|
+
* re-specifying the attribute afterwards used to overwrite an incoming
|
|
21
|
+
* `"true"` with `undefined` — and `FieldTextarea` sets it itself while
|
|
22
|
+
* withholding `error` (to avoid a double message). See the full writeup in
|
|
23
|
+
* `Input.tsx`; the same bug existed here.
|
|
24
|
+
*/
|
|
25
|
+
const ariaInvalid = props["aria-invalid"] ?? (error ? "true" : undefined);
|
|
18
26
|
const textareaClassName = ["appkit-textarea", variantClass, className]
|
|
19
27
|
.filter(Boolean)
|
|
20
28
|
.join(" ");
|
|
21
29
|
if (bare) {
|
|
22
|
-
return (_jsx("textarea", { ...props, ref: ref, id: inputId, value: value, defaultValue: defaultValue, maxLength: maxLength, "aria-invalid":
|
|
30
|
+
return (_jsx("textarea", { ...props, ref: ref, id: inputId, value: value, defaultValue: defaultValue, maxLength: maxLength, "aria-invalid": ariaInvalid, className: textareaClassName }));
|
|
23
31
|
}
|
|
24
|
-
return (_jsxs("div", { className: "w-full", "data-section": "textarea-div-620", children: [label && (_jsx(Label, { htmlFor: inputId, required: required, children: label })), _jsx("textarea", { ...props, ref: ref, id: inputId, value: value, defaultValue: defaultValue, maxLength: maxLength, "aria-invalid":
|
|
32
|
+
return (_jsxs("div", { className: "w-full", "data-section": "textarea-div-620", children: [label && (_jsx(Label, { htmlFor: inputId, required: required, children: label })), _jsx("textarea", { ...props, ref: ref, id: inputId, value: value, defaultValue: defaultValue, maxLength: maxLength, "aria-invalid": ariaInvalid, className: textareaClassName }), (error || helperText || (showCharCount && maxLength)) && (_jsxs("div", { className: "mt-1.5 flex items-start justify-between gap-3", "data-section": "textarea-div-621", children: [_jsx("div", { className: "min-w-0 flex-1", "data-section": "textarea-div-622", children: error ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: error })) : helperText ? (_jsx(Text, { size: "sm", variant: "secondary", children: helperText })) : null }), showCharCount && maxLength ? (_jsxs(Text, { size: "xs", variant: charCount >= maxLength ? "error" : "muted", className: "shrink-0 tabular-nums", children: [charCount, "/", maxLength] })) : null] }))] }));
|
|
25
33
|
});
|
|
26
34
|
Textarea.displayName = "Textarea";
|
|
@@ -34,5 +34,5 @@ export function FieldCheckbox({ name, label, hint, checked, onChange, onBlur, di
|
|
|
34
34
|
ctx?.setFieldTouched(name);
|
|
35
35
|
onBlur?.();
|
|
36
36
|
}
|
|
37
|
-
return (_jsxs("div", { className: ["appkit-form-field", className].filter(Boolean).join(" "), children: [_jsx(Checkbox, { id: inputId, name: name, checked: checked, onChange: handleChange, onBlur: handleBlur, label: label, disabled: disabled, "aria-describedby": showError ? errorId : undefined }), !showError && hint && (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint })), showError && (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error }))] }));
|
|
37
|
+
return (_jsxs("div", { className: ["appkit-form-field", className].filter(Boolean).join(" "), children: [_jsx(Checkbox, { id: inputId, name: name, checked: checked, onChange: handleChange, onBlur: handleBlur, label: label, disabled: disabled, "aria-invalid": showError || undefined, "aria-describedby": showError ? errorId : undefined }), !showError && hint && (_jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: hint })), showError && (_jsx(Text, { id: errorId, size: "sm", variant: "error", className: "appkit-form-field__error", role: "alert", children: error }))] }));
|
|
38
38
|
}
|
|
@@ -9,21 +9,5 @@ export interface FormErrorListProps {
|
|
|
9
9
|
ctx?: FormShellContextValue;
|
|
10
10
|
className?: string;
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* The `<ul>` of current validation errors, with a jump link per entry when
|
|
14
|
-
* the form declares sections.
|
|
15
|
-
*
|
|
16
|
-
* WHY it is its own file: two surfaces render this exact list — the inline
|
|
17
|
-
* `<FormErrorSummary>` beside the submit button on desktop, and the mobile
|
|
18
|
-
* "Fix N issues" sheet in the bottom-chrome tier. Writing it twice would mean
|
|
19
|
-
* a jump-link fix, an empty-state fix or a wording fix landing in one and not
|
|
20
|
-
* the other, which is the bug-fix multiplier the Duplication Framework calls
|
|
21
|
-
* out. There are exactly two consumers and they are the same list, so this is
|
|
22
|
-
* a consolidation, not speculative abstraction.
|
|
23
|
-
*
|
|
24
|
-
* Renders nothing when there are no errors. It does NOT gate on
|
|
25
|
-
* `submitAttempted` — that is the *summary's* decision, not the list's, and
|
|
26
|
-
* the sheet needs to keep rendering while open.
|
|
27
|
-
*/
|
|
28
12
|
export declare function FormErrorList({ ctx: ctxProp, className }: FormErrorListProps): import("react").JSX.Element | null;
|
|
29
13
|
export default FormErrorList;
|
|
@@ -19,6 +19,39 @@ import { Span } from "../components/Typography.js";
|
|
|
19
19
|
* `submitAttempted` — that is the *summary's* decision, not the list's, and
|
|
20
20
|
* the sheet needs to keep rendering while open.
|
|
21
21
|
*/
|
|
22
|
+
/*
|
|
23
|
+
* 🛑 The two sides of this lookup use DIFFERENT key shapes, so an exact match
|
|
24
|
+
* silently loses every nested error.
|
|
25
|
+
*
|
|
26
|
+
* `errors` is keyed by the full dotted Zod path — `applyZodIssues` does
|
|
27
|
+
* `issue.path.map(String).join(".")`, giving `"address.line1"`. But
|
|
28
|
+
* `fieldToStepIndex` is built by `buildFieldToSectionIndex` from
|
|
29
|
+
* `SectionDef.fields`, which lists BARE field names — `"address"`. So
|
|
30
|
+
* `fieldToStepIndex["address.line1"]` was always `undefined`, the entry fell
|
|
31
|
+
* through to the plain `<Span>`, and the error that a section demonstrably
|
|
32
|
+
* owned rendered with no jump link.
|
|
33
|
+
*
|
|
34
|
+
* `SectionForm` already solved this for its own badge counts
|
|
35
|
+
* (`erroringSectionIds`, `errorCountFor`) with a prefix match. This is the same
|
|
36
|
+
* rule, so the list and the badges can no longer disagree about which section
|
|
37
|
+
* an error belongs to.
|
|
38
|
+
*
|
|
39
|
+
* Exact match is tried first: a field genuinely named `"address.line1"` must
|
|
40
|
+
* beat a prefix match on `"address"`.
|
|
41
|
+
*/
|
|
42
|
+
function resolveStepIndex(ctx, key) {
|
|
43
|
+
const map = ctx.fieldToStepIndex;
|
|
44
|
+
if (!map)
|
|
45
|
+
return undefined;
|
|
46
|
+
const exact = map[key];
|
|
47
|
+
if (exact != null)
|
|
48
|
+
return exact;
|
|
49
|
+
for (const [field, index] of Object.entries(map)) {
|
|
50
|
+
if (key.startsWith(`${field}.`))
|
|
51
|
+
return index;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
22
55
|
export function FormErrorList({ ctx: ctxProp, className }) {
|
|
23
56
|
const ambient = useContext(FormShellContext);
|
|
24
57
|
const ctx = ctxProp ?? ambient;
|
|
@@ -28,7 +61,7 @@ export function FormErrorList({ ctx: ctxProp, className }) {
|
|
|
28
61
|
if (entries.length === 0)
|
|
29
62
|
return null;
|
|
30
63
|
return (_jsx("ul", { className: ["appkit-formshell__error-summary__list", className].filter(Boolean).join(" "), children: entries.map(([key, message]) => {
|
|
31
|
-
const stepIndex = ctx
|
|
64
|
+
const stepIndex = resolveStepIndex(ctx, key);
|
|
32
65
|
const stepLabel = stepIndex != null ? ctx.steps[stepIndex]?.label : undefined;
|
|
33
66
|
return (_jsx("li", { children: stepLabel ? (_jsxs("button", { type: "button", className: "appkit-formshell__error-summary__step-link", onClick: () => ctx.goToStep(stepIndex), children: [_jsxs(Span, { weight: "semibold", children: [stepLabel, ":"] }), " ", message] })) : (_jsx(Span, { children: message })) }, key));
|
|
34
67
|
}) }));
|
|
@@ -22,7 +22,16 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Root-level `.refine()` issues (empty `path: []`, no owning field) are still
|
|
24
24
|
* skipped — there is no field to attach them to, so the caller must surface
|
|
25
|
-
* those some other way
|
|
25
|
+
* those some other way.
|
|
26
|
+
*
|
|
27
|
+
* 🛑 That last sentence used to end "(they fall through to a toast in
|
|
28
|
+
* `surfaceError`)", which was only true when EVERY issue was root-level.
|
|
29
|
+
* In the mixed case — one field error plus one cross-field `.refine()`, which
|
|
30
|
+
* is the common shape for "passwords must match" or "end date must be after
|
|
31
|
+
* start date" — `hasAttachableIssue` returned true, this function skipped the
|
|
32
|
+
* root issue, and `surfaceError` returned without a toast. The cross-field
|
|
33
|
+
* rule was enforced, the save was refused, and the user was told nothing
|
|
34
|
+
* about why. `rootLevelMessages` below is how a caller surfaces them.
|
|
26
35
|
*/
|
|
27
36
|
export declare function applyZodIssues(issues: ReadonlyArray<{
|
|
28
37
|
path?: ReadonlyArray<PropertyKey>;
|
|
@@ -32,3 +41,12 @@ export declare function applyZodIssues(issues: ReadonlyArray<{
|
|
|
32
41
|
export declare function hasAttachableIssue(issues: ReadonlyArray<{
|
|
33
42
|
path?: ReadonlyArray<PropertyKey>;
|
|
34
43
|
}> | undefined): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* The messages `applyZodIssues` cannot attach — the root-level `.refine()`
|
|
46
|
+
* ones with no owning field. A caller that has just run `applyZodIssues`
|
|
47
|
+
* should surface these separately, or they are silently discarded.
|
|
48
|
+
*/
|
|
49
|
+
export declare function rootLevelMessages(issues: ReadonlyArray<{
|
|
50
|
+
path?: ReadonlyArray<PropertyKey>;
|
|
51
|
+
message: string;
|
|
52
|
+
}> | undefined): string[];
|
|
@@ -22,7 +22,16 @@
|
|
|
22
22
|
*
|
|
23
23
|
* Root-level `.refine()` issues (empty `path: []`, no owning field) are still
|
|
24
24
|
* skipped — there is no field to attach them to, so the caller must surface
|
|
25
|
-
* those some other way
|
|
25
|
+
* those some other way.
|
|
26
|
+
*
|
|
27
|
+
* 🛑 That last sentence used to end "(they fall through to a toast in
|
|
28
|
+
* `surfaceError`)", which was only true when EVERY issue was root-level.
|
|
29
|
+
* In the mixed case — one field error plus one cross-field `.refine()`, which
|
|
30
|
+
* is the common shape for "passwords must match" or "end date must be after
|
|
31
|
+
* start date" — `hasAttachableIssue` returned true, this function skipped the
|
|
32
|
+
* root issue, and `surfaceError` returned without a toast. The cross-field
|
|
33
|
+
* rule was enforced, the save was refused, and the user was told nothing
|
|
34
|
+
* about why. `rootLevelMessages` below is how a caller surfaces them.
|
|
26
35
|
*/
|
|
27
36
|
export function applyZodIssues(issues, setFieldError) {
|
|
28
37
|
for (const issue of issues) {
|
|
@@ -36,3 +45,16 @@ export function applyZodIssues(issues, setFieldError) {
|
|
|
36
45
|
export function hasAttachableIssue(issues) {
|
|
37
46
|
return Boolean(issues?.some((i) => i.path && i.path.length > 0));
|
|
38
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The messages `applyZodIssues` cannot attach — the root-level `.refine()`
|
|
50
|
+
* ones with no owning field. A caller that has just run `applyZodIssues`
|
|
51
|
+
* should surface these separately, or they are silently discarded.
|
|
52
|
+
*/
|
|
53
|
+
export function rootLevelMessages(issues) {
|
|
54
|
+
if (!issues)
|
|
55
|
+
return [];
|
|
56
|
+
return issues
|
|
57
|
+
.filter((i) => !i.path || i.path.length === 0)
|
|
58
|
+
.map((i) => i.message)
|
|
59
|
+
.filter((m) => typeof m === "string" && m.length > 0);
|
|
60
|
+
}
|