@openzeppelin/ui-components 3.0.1 → 3.2.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/index.cjs CHANGED
@@ -38,7 +38,7 @@ let sonner = require("sonner");
38
38
  let next_themes = require("next-themes");
39
39
 
40
40
  //#region src/version.ts
41
- const VERSION = "3.0.1";
41
+ const VERSION = "3.2.0";
42
42
 
43
43
  //#endregion
44
44
  //#region src/components/ui/accordion.tsx
@@ -2952,6 +2952,457 @@ function AddressField({ id, label, placeholder, helperText, control, name, width
2952
2952
  }
2953
2953
  AddressField.displayName = "AddressField";
2954
2954
 
2955
+ //#endregion
2956
+ //#region src/components/fields/TextAreaField.tsx
2957
+ /**
2958
+ * Multi-line text input field component specifically designed for React Hook Form integration.
2959
+ *
2960
+ * Architecture flow:
2961
+ * 1. Form schemas are generated from contract functions using adapters
2962
+ * 2. TransactionForm renders the overall form structure with React Hook Form
2963
+ * 3. DynamicFormField selects the appropriate field component based on field type
2964
+ * 4. BaseField provides consistent layout and hook form integration
2965
+ * 5. This component handles textarea-specific rendering and validation
2966
+ *
2967
+ * The component includes:
2968
+ * - Integration with React Hook Form
2969
+ * - Resizable multi-line text input
2970
+ * - Character limit support
2971
+ * - Customizable validation through adapter integration
2972
+ * - Automatic error handling and reporting
2973
+ * - Full accessibility support with ARIA attributes
2974
+ * - Keyboard navigation with Escape to clear
2975
+ */
2976
+ function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea, readOnly }) {
2977
+ const isRequired = !!validation?.required;
2978
+ const errorId = `${id}-error`;
2979
+ const descriptionId = `${id}-description`;
2980
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2981
+ className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
2982
+ children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Label, {
2983
+ htmlFor: id,
2984
+ children: [
2985
+ label,
2986
+ " ",
2987
+ isRequired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2988
+ className: "text-destructive",
2989
+ children: "*"
2990
+ })
2991
+ ]
2992
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_hook_form.Controller, {
2993
+ control,
2994
+ name,
2995
+ rules: { validate: (value) => {
2996
+ if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
2997
+ if (validateTextArea && value) {
2998
+ const validation$1 = validateTextArea(value);
2999
+ if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
3000
+ }
3001
+ if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
3002
+ return true;
3003
+ } },
3004
+ disabled: readOnly,
3005
+ render: ({ field, fieldState: { error, isTouched } }) => {
3006
+ const hasError = !!error;
3007
+ const shouldShowError = hasError && isTouched;
3008
+ const validationClasses = require_ErrorMessage.getValidationStateClasses(error, isTouched);
3009
+ const accessibilityProps = getAccessibilityProps({
3010
+ id,
3011
+ hasError,
3012
+ isRequired,
3013
+ hasHelperText: !!helperText
3014
+ });
3015
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
3016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Textarea, {
3017
+ ...field,
3018
+ id,
3019
+ placeholder,
3020
+ rows,
3021
+ maxLength,
3022
+ className: validationClasses,
3023
+ value: field.value ?? "",
3024
+ disabled: readOnly,
3025
+ ...accessibilityProps,
3026
+ "aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
3027
+ onKeyDown: handleEscapeKey((value) => {
3028
+ if (typeof field.onChange === "function") field.onChange(value);
3029
+ }, field.value)
3030
+ }),
3031
+ maxLength && typeof field.value === "string" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3032
+ className: "text-muted-foreground text-right text-xs",
3033
+ children: [
3034
+ field.value.length,
3035
+ "/",
3036
+ maxLength
3037
+ ]
3038
+ }),
3039
+ helperText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3040
+ id: descriptionId,
3041
+ className: "text-muted-foreground text-sm",
3042
+ children: helperText
3043
+ }),
3044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ErrorMessage.ErrorMessage, {
3045
+ error,
3046
+ id: errorId,
3047
+ message: shouldShowError ? error?.message : void 0
3048
+ })
3049
+ ] });
3050
+ }
3051
+ })]
3052
+ });
3053
+ }
3054
+ TextAreaField.displayName = "TextAreaField";
3055
+
3056
+ //#endregion
3057
+ //#region src/components/fields/AddressListField/labels.ts
3058
+ /** Default English labels for {@link AddressListField}. */
3059
+ const DEFAULT_ADDRESS_LIST_FIELD_LABELS = {
3060
+ addSingleAddress: "Add",
3061
+ invalidSingleAddress: "Enter a valid address",
3062
+ enableBulkEntry: "Bulk paste",
3063
+ enableSingleEntry: "Single entry",
3064
+ addAddresses: "Add addresses",
3065
+ addAddressCount: (count) => `Add ${count} address${count === 1 ? "" : "es"}`,
3066
+ invalidPrefix: "Invalid:",
3067
+ invalidMore: (extraCount) => `(+${extraCount} more)`,
3068
+ maxItemsReached: (maxItems) => `Maximum of ${maxItems} addresses reached.`,
3069
+ addressesAdded: (count) => `${count} address${count === 1 ? "" : "es"} added`,
3070
+ removeAddress: (index) => `Remove address ${index + 1}`,
3071
+ previewReadyToAdd: (count) => `${count} ready to add`,
3072
+ previewInvalid: (count) => `${count} invalid`,
3073
+ previewAlreadyListed: (count) => `${count} already listed`,
3074
+ previewDuplicateInPaste: (count) => `${count} duplicate in paste`,
3075
+ bulkNothingAdded: "No addresses were added. Check formatting and try again.",
3076
+ bulkAdded: (count) => `Added ${count} address${count === 1 ? "" : "es"}`,
3077
+ bulkInvalidFormats: (count) => `${count} invalid format${count === 1 ? "" : "s"}`,
3078
+ bulkAlreadyListed: (count) => `${count} already listed`,
3079
+ bulkDuplicatesInPaste: (count) => `${count} duplicate${count === 1 ? "" : "s"} in paste`
3080
+ };
3081
+ /** Merges {@link DEFAULT_ADDRESS_LIST_FIELD_LABELS} with optional overrides. */
3082
+ function resolveAddressListFieldLabels(overrides) {
3083
+ return {
3084
+ ...DEFAULT_ADDRESS_LIST_FIELD_LABELS,
3085
+ ...overrides
3086
+ };
3087
+ }
3088
+ /** Builds the inline helper preview shown while the user types in the draft textarea. */
3089
+ function buildAddressListPreviewSummary(classification, labels) {
3090
+ if (!classification) return null;
3091
+ const skipped = classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length;
3092
+ if (classification.accepted.length === 0 && skipped === 0) return null;
3093
+ const parts = [];
3094
+ if (classification.accepted.length > 0) parts.push(labels.previewReadyToAdd(classification.accepted.length));
3095
+ if (classification.invalid.length > 0) parts.push(labels.previewInvalid(classification.invalid.length));
3096
+ if (classification.alreadyListed.length > 0) parts.push(labels.previewAlreadyListed(classification.alreadyListed.length));
3097
+ if (classification.duplicatesInInput.length > 0) parts.push(labels.previewDuplicateInPaste(classification.duplicatesInInput.length));
3098
+ return parts.join(" · ");
3099
+ }
3100
+ /** Formats post-add feedback after the user commits parsed addresses to the list. */
3101
+ function formatAddressBulkSummary(classification, addedCount, labels) {
3102
+ if (addedCount === 0 && classification.accepted.length === 0) {
3103
+ if (classification.invalid.length + classification.alreadyListed.length + classification.duplicatesInInput.length === 0) return null;
3104
+ return labels.bulkNothingAdded;
3105
+ }
3106
+ const parts = [];
3107
+ if (addedCount > 0) parts.push(labels.bulkAdded(addedCount));
3108
+ if (classification.invalid.length > 0) parts.push(labels.bulkInvalidFormats(classification.invalid.length));
3109
+ if (classification.alreadyListed.length > 0) parts.push(labels.bulkAlreadyListed(classification.alreadyListed.length));
3110
+ if (classification.duplicatesInInput.length > 0) parts.push(labels.bulkDuplicatesInPaste(classification.duplicatesInInput.length));
3111
+ if (parts.length === 0) return null;
3112
+ return parts.join(". ") + ".";
3113
+ }
3114
+
3115
+ //#endregion
3116
+ //#region src/components/fields/AddressListField/AddressListBulkEntry.tsx
3117
+ /** Bulk paste entry — info callout, textarea, and add action in a compact stack. */
3118
+ function AddressListBulkEntry({ fieldId, value, onAdd, placeholder, formatHint, addressing, maxItems, disabled = false, maxInvalidPreview = 3, labels, modeToggle }) {
3119
+ const atLimit = maxItems != null && value.length >= maxItems;
3120
+ const inputDisabled = disabled || atLimit;
3121
+ const [feedback, setFeedback] = (0, react.useState)(null);
3122
+ const { control, reset, watch } = (0, react_hook_form.useForm)({
3123
+ defaultValues: { input: "" },
3124
+ mode: "onChange"
3125
+ });
3126
+ const rawInput = watch("input");
3127
+ const classification = (0, react.useMemo)(() => {
3128
+ if (!rawInput.trim()) return null;
3129
+ return (0, _openzeppelin_ui_utils.classifyAddressCandidates)((0, _openzeppelin_ui_utils.parseDelimitedAddressInput)(rawInput), value, addressing, maxItems);
3130
+ }, [
3131
+ rawInput,
3132
+ value,
3133
+ addressing,
3134
+ maxItems
3135
+ ]);
3136
+ const previewSummary = (0, react.useMemo)(() => buildAddressListPreviewSummary(classification, labels), [classification, labels]);
3137
+ const statusMessage = rawInput.trim() ? previewSummary ?? void 0 : feedback ?? void 0;
3138
+ const handleAdd = (0, react.useCallback)(() => {
3139
+ if (!classification || classification.accepted.length === 0 || atLimit) return;
3140
+ onAdd(classification.accepted);
3141
+ reset({ input: "" });
3142
+ setFeedback(formatAddressBulkSummary(classification, classification.accepted.length, labels));
3143
+ }, [
3144
+ classification,
3145
+ atLimit,
3146
+ onAdd,
3147
+ reset,
3148
+ labels
3149
+ ]);
3150
+ const acceptedCount = classification?.accepted.length ?? 0;
3151
+ const addButtonLabel = acceptedCount > 0 ? labels.addAddressCount(acceptedCount) : labels.addAddresses;
3152
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3153
+ className: "space-y-3",
3154
+ children: [
3155
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3156
+ className: "flex items-start gap-2 rounded-lg border border-border/70 bg-muted/30 px-3 py-2.5",
3157
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Info, { className: "mt-0.5 size-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3158
+ className: "text-xs leading-relaxed text-muted-foreground",
3159
+ children: formatHint
3160
+ })]
3161
+ }),
3162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3163
+ className: "relative",
3164
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TextAreaField, {
3165
+ id: `address-list-bulk-${fieldId}`,
3166
+ name: "input",
3167
+ label: "",
3168
+ placeholder,
3169
+ helperText: statusMessage,
3170
+ control,
3171
+ rows: 4,
3172
+ validation: { required: false },
3173
+ readOnly: inputDisabled
3174
+ })
3175
+ }), modeToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3176
+ className: "flex justify-end pr-2",
3177
+ children: modeToggle
3178
+ }) : null] }),
3179
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3180
+ className: "flex flex-wrap items-center justify-between gap-2",
3181
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3182
+ className: "min-w-0 flex-1",
3183
+ children: [classification && classification.invalid.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
3184
+ className: "text-xs text-destructive",
3185
+ children: [
3186
+ labels.invalidPrefix,
3187
+ " ",
3188
+ classification.invalid.slice(0, maxInvalidPreview).join(", "),
3189
+ classification.invalid.length > maxInvalidPreview ? ` ${labels.invalidMore(classification.invalid.length - maxInvalidPreview)}` : ""
3190
+ ]
3191
+ }) : null, atLimit && maxItems != null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3192
+ className: "text-xs text-muted-foreground",
3193
+ children: labels.maxItemsReached(maxItems)
3194
+ }) : null]
3195
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
3196
+ type: "button",
3197
+ size: "sm",
3198
+ className: "shrink-0",
3199
+ disabled: inputDisabled || acceptedCount === 0,
3200
+ onClick: () => handleAdd(),
3201
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "mr-1 size-4" }), addButtonLabel]
3202
+ })]
3203
+ })
3204
+ ]
3205
+ });
3206
+ }
3207
+
3208
+ //#endregion
3209
+ //#region src/components/fields/AddressListField/AddressListEntries.tsx
3210
+ /** Renders committed addresses with remove actions. Shared by single and bulk entry modes. */
3211
+ function AddressListEntries({ value, getExplorerUrl, disabled = false, labels, onRemove }) {
3212
+ if (value.length === 0) return null;
3213
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3214
+ className: "space-y-1",
3215
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3216
+ className: "text-xs text-muted-foreground",
3217
+ children: labels.addressesAdded(value.length)
3218
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3219
+ className: "max-h-44 space-y-1 overflow-y-auto rounded-md border border-border/70 p-1",
3220
+ children: value.map((address, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3221
+ className: "flex items-center justify-between rounded bg-muted p-2",
3222
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressDisplay, {
3223
+ address,
3224
+ variant: "inline",
3225
+ truncateWhenLabeled: true,
3226
+ showCopyButton: true,
3227
+ explorerUrl: getExplorerUrl?.(address) ?? void 0
3228
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
3229
+ type: "button",
3230
+ variant: "ghost",
3231
+ size: "sm",
3232
+ className: "size-7 shrink-0 p-0 text-muted-foreground hover:text-destructive",
3233
+ onClick: () => onRemove(index),
3234
+ disabled,
3235
+ "aria-label": labels.removeAddress(index),
3236
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "size-3.5" })
3237
+ })]
3238
+ }, `${address}-${index}`))
3239
+ })]
3240
+ });
3241
+ }
3242
+
3243
+ //#endregion
3244
+ //#region src/components/fields/AddressListField/AddressListSingleEntry.tsx
3245
+ /** Single-address entry — inline {@link AddressField} with an Add button (address-book aware). */
3246
+ function AddressListSingleEntry({ fieldId, value, onAdd, placeholder, addressing, maxItems, disabled = false, labels, modeToggle }) {
3247
+ const atLimit = maxItems != null && value.length >= maxItems;
3248
+ const inputDisabled = disabled || atLimit;
3249
+ const { control, reset, getValues, handleSubmit } = (0, react_hook_form.useForm)({
3250
+ defaultValues: { draft: "" },
3251
+ mode: "onChange"
3252
+ });
3253
+ const trimmedDraft = ((0, react_hook_form.useWatch)({
3254
+ control,
3255
+ name: "draft"
3256
+ }) ?? "").trim();
3257
+ const validationState = (0, react.useMemo)(() => {
3258
+ if (!trimmedDraft) return {
3259
+ canAdd: false,
3260
+ showInvalid: false
3261
+ };
3262
+ if (value.includes(trimmedDraft)) return {
3263
+ canAdd: false,
3264
+ showInvalid: false
3265
+ };
3266
+ if (atLimit) return {
3267
+ canAdd: false,
3268
+ showInvalid: false
3269
+ };
3270
+ if (addressing && !addressing.isValidAddress(trimmedDraft)) return {
3271
+ canAdd: false,
3272
+ showInvalid: true
3273
+ };
3274
+ return {
3275
+ canAdd: true,
3276
+ showInvalid: false
3277
+ };
3278
+ }, [
3279
+ trimmedDraft,
3280
+ value,
3281
+ atLimit,
3282
+ addressing
3283
+ ]);
3284
+ const submitAdd = (0, react.useCallback)(() => {
3285
+ if (!validationState.canAdd) return;
3286
+ onAdd(getValues("draft").trim());
3287
+ reset({ draft: "" });
3288
+ }, [
3289
+ validationState.canAdd,
3290
+ onAdd,
3291
+ getValues,
3292
+ reset
3293
+ ]);
3294
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3295
+ className: "space-y-1",
3296
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3297
+ className: "flex items-start gap-2",
3298
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3299
+ className: "min-w-0 flex-1",
3300
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressField, {
3301
+ id: `address-list-single-${fieldId}`,
3302
+ name: "draft",
3303
+ label: "",
3304
+ placeholder,
3305
+ helperText: validationState.showInvalid ? labels.invalidSingleAddress : void 0,
3306
+ control,
3307
+ addressing,
3308
+ validation: { required: false },
3309
+ readOnly: inputDisabled
3310
+ }), modeToggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3311
+ className: "-mt-px flex justify-end pr-2",
3312
+ children: modeToggle
3313
+ }) : null]
3314
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Button, {
3315
+ type: "button",
3316
+ className: "h-10 shrink-0",
3317
+ disabled: inputDisabled || !validationState.canAdd,
3318
+ onClick: handleSubmit(submitAdd),
3319
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "mr-1 size-4" }), labels.addSingleAddress]
3320
+ })]
3321
+ }), atLimit && maxItems != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3322
+ className: "text-xs text-muted-foreground",
3323
+ children: labels.maxItemsReached(maxItems)
3324
+ })]
3325
+ });
3326
+ }
3327
+
3328
+ //#endregion
3329
+ //#region src/components/fields/AddressListField/AddressListField.tsx
3330
+ /**
3331
+ * Multi-address list field with single-entry default and optional bulk paste.
3332
+ */
3333
+ function AddressListField({ value, onChange, placeholder, bulkPlaceholder, formatHint, addressing, getExplorerUrl, label, helperText, maxItems, disabled = false, maxInvalidPreview = 3, className, defaultEntryMode = "single", allowModeToggle = true, labels: labelOverrides }) {
3334
+ const fieldId = (0, react.useId)();
3335
+ const labels = (0, react.useMemo)(() => resolveAddressListFieldLabels(labelOverrides), [labelOverrides]);
3336
+ const [entryMode, setEntryMode] = (0, react.useState)(defaultEntryMode);
3337
+ const resolvedBulkPlaceholder = bulkPlaceholder ?? placeholder;
3338
+ const handleRemove = (0, react.useCallback)((index) => {
3339
+ onChange(value.filter((_, currentIndex) => currentIndex !== index));
3340
+ }, [onChange, value]);
3341
+ const handleAddSingle = (0, react.useCallback)((address) => {
3342
+ onChange([...value, address]);
3343
+ }, [onChange, value]);
3344
+ const handleAddBulk = (0, react.useCallback)((addresses) => {
3345
+ onChange([...value, ...addresses]);
3346
+ }, [onChange, value]);
3347
+ const switchToBulk = (0, react.useCallback)(() => setEntryMode("bulk"), []);
3348
+ const switchToSingle = (0, react.useCallback)(() => setEntryMode("single"), []);
3349
+ const toggleClassName = "inline-flex items-center gap-1 rounded-b-md rounded-t-none border border-t-0 border-input bg-muted/40 px-2 py-1 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground";
3350
+ const modeToggle = !allowModeToggle ? void 0 : entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3351
+ type: "button",
3352
+ onClick: switchToBulk,
3353
+ className: toggleClassName,
3354
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ClipboardPaste, { className: "size-3" }), labels.enableBulkEntry]
3355
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3356
+ type: "button",
3357
+ onClick: switchToSingle,
3358
+ className: toggleClassName,
3359
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PencilLine, { className: "size-3" }), labels.enableSingleEntry]
3360
+ });
3361
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3362
+ className: (0, _openzeppelin_ui_utils.cn)("space-y-3", className),
3363
+ children: [
3364
+ label ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
3365
+ className: "text-sm font-medium leading-none",
3366
+ children: label
3367
+ }) : null,
3368
+ helperText && entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
3369
+ className: "text-sm leading-relaxed text-muted-foreground",
3370
+ children: helperText
3371
+ }) : null,
3372
+ entryMode === "single" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListSingleEntry, {
3373
+ fieldId,
3374
+ value,
3375
+ onAdd: handleAddSingle,
3376
+ placeholder,
3377
+ addressing,
3378
+ maxItems,
3379
+ disabled,
3380
+ labels,
3381
+ modeToggle
3382
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListBulkEntry, {
3383
+ fieldId,
3384
+ value,
3385
+ onAdd: handleAddBulk,
3386
+ placeholder: resolvedBulkPlaceholder,
3387
+ formatHint,
3388
+ addressing,
3389
+ maxItems,
3390
+ disabled,
3391
+ maxInvalidPreview,
3392
+ labels,
3393
+ modeToggle
3394
+ }),
3395
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AddressListEntries, {
3396
+ value,
3397
+ getExplorerUrl,
3398
+ disabled,
3399
+ labels,
3400
+ onRemove: handleRemove
3401
+ })
3402
+ ]
3403
+ });
3404
+ }
3405
+
2955
3406
  //#endregion
2956
3407
  //#region src/components/fields/address-suggestion/address-suggestion-context.tsx
2957
3408
  /**
@@ -5744,105 +6195,6 @@ function SelectGroupedField({ id, label, placeholder = "Select an option", helpe
5744
6195
  }
5745
6196
  SelectGroupedField.displayName = "SelectGroupedField";
5746
6197
 
5747
- //#endregion
5748
- //#region src/components/fields/TextAreaField.tsx
5749
- /**
5750
- * Multi-line text input field component specifically designed for React Hook Form integration.
5751
- *
5752
- * Architecture flow:
5753
- * 1. Form schemas are generated from contract functions using adapters
5754
- * 2. TransactionForm renders the overall form structure with React Hook Form
5755
- * 3. DynamicFormField selects the appropriate field component based on field type
5756
- * 4. BaseField provides consistent layout and hook form integration
5757
- * 5. This component handles textarea-specific rendering and validation
5758
- *
5759
- * The component includes:
5760
- * - Integration with React Hook Form
5761
- * - Resizable multi-line text input
5762
- * - Character limit support
5763
- * - Customizable validation through adapter integration
5764
- * - Automatic error handling and reporting
5765
- * - Full accessibility support with ARIA attributes
5766
- * - Keyboard navigation with Escape to clear
5767
- */
5768
- function TextAreaField({ id, label, placeholder, helperText, control, name, width = "full", validation, rows = 4, maxLength, validateTextArea }) {
5769
- const isRequired = !!validation?.required;
5770
- const errorId = `${id}-error`;
5771
- const descriptionId = `${id}-description`;
5772
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5773
- className: `flex flex-col gap-2 ${width === "full" ? "w-full" : width === "half" ? "w-1/2" : "w-1/3"}`,
5774
- children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Label, {
5775
- htmlFor: id,
5776
- children: [
5777
- label,
5778
- " ",
5779
- isRequired && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5780
- className: "text-destructive",
5781
- children: "*"
5782
- })
5783
- ]
5784
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_hook_form.Controller, {
5785
- control,
5786
- name,
5787
- rules: { validate: (value) => {
5788
- if (value === void 0 || value === null || value === "") return validation?.required ? "This field is required" : true;
5789
- if (validateTextArea && value) {
5790
- const validation$1 = validateTextArea(value);
5791
- if (validation$1 !== true && typeof validation$1 === "string") return validation$1;
5792
- }
5793
- if (maxLength && typeof value === "string" && value.length > maxLength) return `Maximum length is ${maxLength} characters`;
5794
- return true;
5795
- } },
5796
- render: ({ field, fieldState: { error, isTouched } }) => {
5797
- const hasError = !!error;
5798
- const shouldShowError = hasError && isTouched;
5799
- const validationClasses = require_ErrorMessage.getValidationStateClasses(error, isTouched);
5800
- const accessibilityProps = getAccessibilityProps({
5801
- id,
5802
- hasError,
5803
- isRequired,
5804
- hasHelperText: !!helperText
5805
- });
5806
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5807
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Textarea, {
5808
- ...field,
5809
- id,
5810
- placeholder,
5811
- rows,
5812
- maxLength,
5813
- className: validationClasses,
5814
- value: field.value ?? "",
5815
- ...accessibilityProps,
5816
- "aria-describedby": `${helperText ? descriptionId : ""} ${hasError ? errorId : ""}`,
5817
- onKeyDown: handleEscapeKey((value) => {
5818
- if (typeof field.onChange === "function") field.onChange(value);
5819
- }, field.value)
5820
- }),
5821
- maxLength && typeof field.value === "string" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5822
- className: "text-muted-foreground text-right text-xs",
5823
- children: [
5824
- field.value.length,
5825
- "/",
5826
- maxLength
5827
- ]
5828
- }),
5829
- helperText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5830
- id: descriptionId,
5831
- className: "text-muted-foreground text-sm",
5832
- children: helperText
5833
- }),
5834
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ErrorMessage.ErrorMessage, {
5835
- error,
5836
- id: errorId,
5837
- message: shouldShowError ? error?.message : void 0
5838
- })
5839
- ] });
5840
- }
5841
- })]
5842
- });
5843
- }
5844
- TextAreaField.displayName = "TextAreaField";
5845
-
5846
6198
  //#endregion
5847
6199
  //#region src/components/fields/TextField.tsx
5848
6200
  /**
@@ -6441,6 +6793,7 @@ exports.AccordionTrigger = AccordionTrigger;
6441
6793
  exports.AddressDisplay = AddressDisplay;
6442
6794
  exports.AddressField = AddressField;
6443
6795
  exports.AddressLabelProvider = AddressLabelProvider;
6796
+ exports.AddressListField = AddressListField;
6444
6797
  exports.AddressSuggestionProvider = AddressSuggestionProvider;
6445
6798
  exports.Alert = Alert;
6446
6799
  exports.AlertDescription = AlertDescription;
@@ -6462,6 +6815,7 @@ exports.CardFooter = CardFooter;
6462
6815
  exports.CardHeader = CardHeader;
6463
6816
  exports.CardTitle = CardTitle;
6464
6817
  exports.Checkbox = Checkbox;
6818
+ exports.DEFAULT_ADDRESS_LIST_FIELD_LABELS = DEFAULT_ADDRESS_LIST_FIELD_LABELS;
6465
6819
  exports.DateRangePicker = DateRangePicker;
6466
6820
  exports.DateTimeField = DateTimeField;
6467
6821
  exports.Dialog = Dialog;
@@ -6567,10 +6921,12 @@ exports.ViewContractStateButton = ViewContractStateButton;
6567
6921
  exports.WizardLayout = WizardLayout;
6568
6922
  exports.WizardNavigation = WizardNavigation;
6569
6923
  exports.WizardStepper = WizardStepper;
6924
+ exports.buildAddressListPreviewSummary = buildAddressListPreviewSummary;
6570
6925
  exports.buttonVariants = buttonVariants;
6571
6926
  exports.computeChildTouched = computeChildTouched;
6572
6927
  exports.createFocusManager = createFocusManager;
6573
6928
  exports.createValidationResult = require_ErrorMessage.createValidationResult;
6929
+ exports.formatAddressBulkSummary = formatAddressBulkSummary;
6574
6930
  exports.formatValidationError = require_ErrorMessage.formatValidationError;
6575
6931
  exports.getAccessibilityProps = getAccessibilityProps;
6576
6932
  exports.getDescribedById = getDescribedById;
@@ -6584,6 +6940,7 @@ exports.handleToggleKeys = handleToggleKeys;
6584
6940
  exports.handleValidationError = require_ErrorMessage.handleValidationError;
6585
6941
  exports.hasFieldError = require_ErrorMessage.hasFieldError;
6586
6942
  exports.isDuplicateMapKey = require_ErrorMessage.isDuplicateMapKey;
6943
+ exports.resolveAddressListFieldLabels = resolveAddressListFieldLabels;
6587
6944
  exports.useAddressLabel = useAddressLabel;
6588
6945
  exports.useAddressSuggestions = useAddressSuggestions;
6589
6946
  exports.useDuplicateKeyIndexes = useDuplicateKeyIndexes;