@timeax/form-palette 0.0.28 → 0.0.30

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.
@@ -0,0 +1,4337 @@
1
+ import * as React from 'react';
2
+ import React__default, { RefObject, ComponentType } from 'react';
3
+ import { z } from 'zod';
4
+ import { M as Method, b as AdapterResult, A as AdapterKey, f as AdapterProps, i as AdapterSubmit } from './adapter-CvjXO9Gi.mjs';
5
+ import * as react_jsx_runtime from 'react/jsx-runtime';
6
+ import { DayPicker } from 'react-day-picker';
7
+ import * as class_variance_authority_types from 'class-variance-authority/types';
8
+ import { VariantProps } from 'class-variance-authority';
9
+ import * as SwitchPrimitive from '@radix-ui/react-switch';
10
+
11
+ /**
12
+ * Imperative handle for a submit button registered with the core.
13
+ *
14
+ * This mirrors the legacy `ButtonRef` interface, but is aligned with the
15
+ * current CoreProvider implementation:
16
+ *
17
+ * - The core will try `setLoading(v)` / `setDisabled(v)` if available.
18
+ * - Otherwise, it will fall back to setting `loading` / `disabled` props.
19
+ */
20
+ interface ButtonRef {
21
+ /**
22
+ * Logical name of the button.
23
+ *
24
+ * Used by the core runtime to track the "active" button
25
+ * and to map behaviours to a specific action.
26
+ */
27
+ name: string;
28
+ /**
29
+ * Loading flag. The core may read or assign this directly if
30
+ * no setter is provided.
31
+ */
32
+ loading?: boolean;
33
+ /**
34
+ * Disabled flag. The core may read or assign this directly if
35
+ * no setter is provided.
36
+ */
37
+ disabled?: boolean;
38
+ /**
39
+ * Optional setter used by the core to toggle loading.
40
+ */
41
+ setLoading?(v: boolean): void;
42
+ /**
43
+ * Optional setter used by the core to toggle disabled state.
44
+ */
45
+ setDisabled?(v: boolean): void;
46
+ }
47
+ /**
48
+ * Runtime representation of a single field registered with the core.
49
+ *
50
+ * This is a direct, type-safe evolution of the legacy `Field` interface
51
+ * from the old `types.ts`, updated to match the new core + binder flow.
52
+ */
53
+ interface Field {
54
+ /**
55
+ * Primary field name, used in values, error bags, and schema mapping.
56
+ *
57
+ * May be omitted for purely bound/virtual fields that participate in
58
+ * binder flows but are not directly part of the value bag.
59
+ */
60
+ name?: string;
61
+ /**
62
+ * Internal binding identifier.
63
+ *
64
+ * Used by "bound" helpers (observe-bound-field, wait-for-bound-field)
65
+ * to locate shared/aliased fields without going through the name.
66
+ */
67
+ bindId?: string;
68
+ /**
69
+ * Optional explicit binding identifier.
70
+ * Use to bind to a specific field in a nested object that has bindId
71
+ */
72
+ bind?: string;
73
+ /**
74
+ * Ref to the underlying DOM element used for focus/scroll operations.
75
+ *
76
+ * Implementations typically point this at the outer wrapper of the field.
77
+ */
78
+ ref?: RefObject<HTMLElement> | null;
79
+ /**
80
+ * Whether this field is required.
81
+ *
82
+ * Variant-level and schema-level validation may use this.
83
+ */
84
+ required?: boolean;
85
+ /**
86
+ * Current error message for the field.
87
+ *
88
+ * Undefined or empty string means "no error".
89
+ */
90
+ error?: string;
91
+ /**
92
+ * Current value of the field, as seen by the core runtime.
93
+ *
94
+ * For formatted inputs, this may be the formatted representation.
95
+ */
96
+ value?: unknown;
97
+ /**
98
+ * Initial/default value for the field.
99
+ *
100
+ * This is typically the "un-touched" value coming from props or
101
+ * from a persisted value bag.
102
+ */
103
+ defaultValue?: unknown;
104
+ /**
105
+ * Original, unformatted value as first seen by the core.
106
+ *
107
+ * This allows callers to compare "what changed" relative to the
108
+ * original snapshot, independent of any display formatting.
109
+ */
110
+ originalValue?: unknown;
111
+ /**
112
+ * Whether this field is currently performing an async operation
113
+ * (e.g. remote validation).
114
+ */
115
+ loading?: boolean;
116
+ /**
117
+ * Optional group identifier used to group related fields together
118
+ * (e.g. radio groups, segmented inputs).
119
+ */
120
+ groupId?: string;
121
+ /**
122
+ * Optional alias for this field.
123
+ *
124
+ * Aliases allow mapping server error bags or schema keys that do
125
+ * not strictly match the `name` property.
126
+ */
127
+ alias?: string;
128
+ /**
129
+ * Marks this field as the "main" one in a group.
130
+ *
131
+ * Used by some variants/layouts to determine which field drives
132
+ * overall group state.
133
+ */
134
+ main?: boolean;
135
+ /**
136
+ * If true, this field will be ignored when building values or
137
+ * running certain validation flows.
138
+ */
139
+ ignore?: boolean;
140
+ /**
141
+ * Stable unique key (distinct from `name` and `bindId`).
142
+ *
143
+ * Used internally by registries and React lists.
144
+ */
145
+ key?: string;
146
+ /**
147
+ * Shared key for fields that share their value (e.g. custom views
148
+ * over the same underlying data).
149
+ *
150
+ * This is used by the core when building nested objects, e.g.:
151
+ * shared = "profile", name = "first_name"
152
+ * ⇒ values.profile.first_name
153
+ */
154
+ shared?: string;
155
+ /**
156
+ * Run validation for this field.
157
+ *
158
+ * @param report If true, the field should update its own error state;
159
+ * if false, it may simply return whether it is valid.
160
+ * @returns `true` if the field is currently valid, `false` otherwise.
161
+ */
162
+ validate?(report?: boolean): boolean;
163
+ /**
164
+ * Optional hook used by the core or higher-level utilities to retrieve
165
+ * the current value of the field.
166
+ *
167
+ * If omitted, the core will fall back to the `value` property.
168
+ */
169
+ getValue?(): unknown;
170
+ /**
171
+ * Optional hook used by the core or higher-level utilities to update
172
+ * the current value of the field.
173
+ *
174
+ * If omitted, the core will fall back to mutating the `value` property.
175
+ */
176
+ setValue?(value: unknown): void;
177
+ /**
178
+ * Optional hook used by the core to reset the field back to its
179
+ * default/original value.
180
+ */
181
+ reset?(): void;
182
+ /**
183
+ * Optional hook used by the core to set or clear the field error.
184
+ *
185
+ * If omitted, the core will fall back to assigning the `error` property.
186
+ */
187
+ setError?(message?: string): void;
188
+ /**
189
+ * Optional hook called whenever the field value changes.
190
+ *
191
+ * Used by binder utilities to propagate changes across bound fields.
192
+ *
193
+ * @param value New value.
194
+ * @param old Previous value.
195
+ * @param source Source tag responsible for the change
196
+ * (e.g. "variant", "util", "paste", "programmatic").
197
+ */
198
+ onChange?(value: unknown, old: unknown, source: string): void;
199
+ }
200
+
201
+ /**
202
+ * Minimal surface needed for bound helpers.
203
+ *
204
+ * CoreContext already satisfies this, and FieldRegistry can be made to
205
+ * satisfy it as well (via getBind).
206
+ */
207
+ interface BindHost<V extends Dict = Dict> {
208
+ getBind(id: string): Field | undefined;
209
+ controlButton?(): void;
210
+ }
211
+
212
+ /**
213
+ * BinderRegistry: bound-field utilities for a given host (CoreContext or FieldRegistry).
214
+ *
215
+ * - Hosts must satisfy BindHost (getBind + optional controlButton).
216
+ * - FieldRegistry already does (via getBind() we added).
217
+ * - CoreContext also does.
218
+ *
219
+ * You typically access this via:
220
+ * form.inputs.binding // where inputs is a FieldRegistry
221
+ */
222
+ declare class BinderRegistry<V extends Dict = Dict> {
223
+ private readonly host;
224
+ constructor(host: BindHost<V>);
225
+ /** Raw field access. */
226
+ get(bindId: string): Field | undefined;
227
+ has(bindId: string): boolean;
228
+ /** Read current value. */
229
+ value<T = unknown>(bindId: string): T | undefined;
230
+ /** Set value (and trigger controlButton / onChange). */
231
+ set<T = unknown>(bindId: string, value: T, variant?: string): boolean;
232
+ /** Set error message on the bound field. */
233
+ error(bindId: string, msg: string): boolean;
234
+ /** Run the field’s own validate(). */
235
+ validate(bindId: string, report?: boolean): boolean;
236
+ /** Observe a bound field’s value/error and liveness. */
237
+ observe<T = unknown>(bindId: string, handler: (evt: {
238
+ exists: boolean;
239
+ field?: Field;
240
+ value?: T;
241
+ error?: string;
242
+ }) => void, pollMs?: number): () => void;
243
+ /** Wait for a bound field to appear. */
244
+ wait(bindId: string, timeoutMs?: number): Promise<Field>;
245
+ }
246
+
247
+ /**
248
+ * Central store for all fields registered with the core runtime.
249
+ *
250
+ * Goals:
251
+ * - Keep stable Field references (no cloning).
252
+ * - Prefer mounted fields for all “get” operations.
253
+ * - Prune stale/detached fields opportunistically.
254
+ */
255
+ declare class FieldRegistry {
256
+ #private;
257
+ private list;
258
+ private getEl;
259
+ /** Mounted = has an element and that element is currently in the DOM. */
260
+ private isMounted;
261
+ /**
262
+ * Detached = has an element but it is NOT currently in the DOM.
263
+ * Note: if ref.current is null, we treat it as “unknown” (do NOT prune).
264
+ */
265
+ private isDetached;
266
+ /** Mounted-first stable ordering (does not mutate input). */
267
+ private sortMountedFirst;
268
+ /** Prefer the first mounted candidate; else fall back to first candidate. */
269
+ private pickPreferred;
270
+ /**
271
+ * Remove detached fields.
272
+ *
273
+ * IMPORTANT: We only remove entries that have a non-null ref.current AND are
274
+ * not in the DOM. We do not remove “unknown” (null-ref) fields because they
275
+ * may be in the process of mounting.
276
+ */
277
+ private pruneDetached;
278
+ /**
279
+ * Remove detached fields that conflict with an incoming field by identifier.
280
+ * This prevents stale “same-name / same-bindId / same-groupId” entries from
281
+ * hijacking lookups.
282
+ */
283
+ private pruneDetachedConflicts;
284
+ /**
285
+ * Whether this field should be tracked at all.
286
+ *
287
+ * We require at least one of: name, bindId, groupId.
288
+ */
289
+ hasIdentifier(field: Field): boolean;
290
+ /**
291
+ * Add a field to the registry if it has an identifier.
292
+ *
293
+ * Rules:
294
+ * - Opportunistically prune detached fields.
295
+ * - Prune detached conflicts (same name/bindId/groupId) before adding.
296
+ * - If the same field instance is already tracked, ignore.
297
+ * - If the same key already exists (rare), prefer mounted; otherwise replace.
298
+ */
299
+ add(field: Field): void;
300
+ /**
301
+ * Remove a field from the registry.
302
+ */
303
+ remove(field: Field): void;
304
+ /**
305
+ * Clear all tracked fields.
306
+ */
307
+ clear(): void;
308
+ /**
309
+ * All fields tracked by this registry (mounted-first).
310
+ */
311
+ all(): Field[];
312
+ /** All fields that have a non-empty name (mounted-first). */
313
+ getAllNamed(): Field[];
314
+ /** All fields that have a bindId (mounted-first). */
315
+ getAllBound(): Field[];
316
+ /** All fields that have a groupId (mounted-first). */
317
+ getAllGrouped(): Field[];
318
+ /**
319
+ * First field with a given name (exact, trimmed match).
320
+ *
321
+ * Behaviour:
322
+ * - Prefer a field whose ref is currently in the DOM.
323
+ * - If none are mounted, fall back to the first matching field.
324
+ */
325
+ getByName(name: string): Field | undefined;
326
+ /**
327
+ * All fields with a given name (exact, trimmed match), mounted-first.
328
+ */
329
+ getAllByName(name: string): Field[];
330
+ /** First field with the given groupId (prefer mounted). */
331
+ getByGroupId(id: string): Field | undefined;
332
+ /** All fields with the given groupId (mounted-first). */
333
+ getAllByGroupId(id: string): Field[];
334
+ /**
335
+ * All fields that share the given bindId (mounted-first).
336
+ */
337
+ getAllByBind(id: string): Field[];
338
+ /**
339
+ * First field with the given bindId (prefer mounted).
340
+ */
341
+ getByBind(id: string): Field | undefined;
342
+ getBind(id: string): Field | undefined;
343
+ get binding(): BinderRegistry;
344
+ }
345
+
346
+ /**
347
+ * Generic dictionary type used throughout the core.
348
+ *
349
+ * This matches the legacy Dict<T> from the old types.ts.
350
+ */
351
+ type Dict<T = unknown> = Record<string, T>;
352
+ /**
353
+ * If a Zod schema is present, infer the values from that schema;
354
+ * otherwise use the fallback V type. Ensured to be a Dict so it
355
+ * can safely be used as CoreContext's generic argument.
356
+ */
357
+ type InferFromSchema<S, V extends Dict> = S extends z.ZodType ? z.infer<S> & Dict : V;
358
+ /**
359
+ * Event object passed to onSubmit, matching the legacy SubmitEvent
360
+ * but kept transport-agnostic. The host decides how route/method/xhr
361
+ * are interpreted and which adapter is used.
362
+ *
363
+ * @template TValues Shape of the outbound data for this submit event.
364
+ */
365
+ type SubmitEvent<TValues extends Dict, K extends AdapterKey> = {
366
+ /**
367
+ * Prevent the default submit behavior.
368
+ *
369
+ * In practice this prevents the core from continuing with its
370
+ * normal submit/prepare flow.
371
+ */
372
+ preventDefault(): void;
373
+ /**
374
+ * Mutate the outbound data just before it is used.
375
+ *
376
+ * The callback may return a new data object or mutate in-place.
377
+ */
378
+ editData(cb: (data: TValues) => TValues | void): void;
379
+ /**
380
+ * Override the config for this adapter submission only.
381
+ *
382
+ * The core itself does not enforce any semantics here; the host
383
+ * is expected to interpret this when wiring submissions.
384
+ */
385
+ setConfig(props: Partial<AdapterProps<K>>): void;
386
+ setConfig(key: keyof AdapterProps<K>, value: any): void;
387
+ /**
388
+ * The button that triggered this submit, if any.
389
+ */
390
+ button?: ButtonRef;
391
+ /**
392
+ * The current outbound data snapshot (after any internal merges).
393
+ */
394
+ readonly formData: TValues;
395
+ /**
396
+ * The core context associated with this submit event.
397
+ */
398
+ form: CoreContext<TValues>;
399
+ /**
400
+ * If set to false, the core will abort the submit flow after
401
+ * this handler returns.
402
+ */
403
+ continue: boolean;
404
+ };
405
+ /**
406
+ * Shared base props for the core runtime, matching the spirit of
407
+ * the legacy BaseProps, but transport-agnostic.
408
+ *
409
+ * @template V Shape of the underlying value map (pre-schema).
410
+ * @template S Optional Zod schema type.
411
+ */
412
+ type BaseProps$7<V extends Dict, S extends z.ZodType | undefined, K extends AdapterKey> = {
413
+ /**
414
+ * Field names that should be ignored when building diffs or snapshots.
415
+ * Useful for excluding secrets like passwords from logs.
416
+ */
417
+ exceptions?: string[];
418
+ /**
419
+ * Whether the core should persist values to the provided valueBag.
420
+ */
421
+ persist?: boolean;
422
+ /**
423
+ * Optional logical name for the core instance.
424
+ */
425
+ name?: string;
426
+ /**
427
+ * If true, a button may be automatically marked as "active" when
428
+ * certain changes occur.
429
+ */
430
+ activateButtonOnChange?: boolean;
431
+ /**
432
+ * Called whenever a field changes.
433
+ *
434
+ * current is the field that changed; options carries any
435
+ * variant-specific metadata.
436
+ */
437
+ onChange?(form: CoreContext<InferFromSchema<S, V>>, current: Field, options: Dict): void;
438
+ /**
439
+ * Called when the overall values snapshot is considered "updated".
440
+ */
441
+ onUpdate?(values: InferFromSchema<S, V>): void;
442
+ /**
443
+ * If true, onChange may run before certain internal updates.
444
+ */
445
+ changeBefore?: boolean;
446
+ /**
447
+ * Optional ref to the core context instance, for imperative access.
448
+ */
449
+ formRef?: React__default.MutableRefObject<CoreContext<InferFromSchema<S, V>> | null>;
450
+ /**
451
+ * Initial value bag for hydration / persistence.
452
+ */
453
+ valueBag?: InferFromSchema<S, V>;
454
+ /**
455
+ * Optional hook used to transform a single value as it is being
456
+ * persisted or fed into the core.
457
+ */
458
+ valueFeed?: <K extends keyof InferFromSchema<S, V>>(name: K, value: InferFromSchema<S, V>[K], form: CoreContext<InferFromSchema<S, V>>) => InferFromSchema<S, V>[K] | undefined;
459
+ /**
460
+ * Called at the end of certain flows (legacy "finish" hook).
461
+ *
462
+ * Receives the core context so you can read values, errors, etc.
463
+ */
464
+ onFinish?(form: CoreContext<InferFromSchema<S, V>>): void;
465
+ /**
466
+ * Called after the core initializes.
467
+ */
468
+ init?(form: CoreContext<InferFromSchema<S, V>>): void;
469
+ /**
470
+ * Intercepts the submit event before the core proceeds.
471
+ *
472
+ * You can:
473
+ * - mutate data,
474
+ * - change route/method/xhr flags,
475
+ * - abort by setting e.continue = false.
476
+ */
477
+ onSubmit?<T extends Dict = InferFromSchema<S, V>>(e: SubmitEvent<T, K>): Promise<void> | void;
478
+ /**
479
+ * Optional Zod schema used for validation and value inference.
480
+ */
481
+ schema?: S;
482
+ };
483
+ /**
484
+ * Public core props, adapter-centric.
485
+ *
486
+ * - The library defines a built-in 'local' adapter flavour.
487
+ * AdapterSubmit<'local'> is `{ data: unknown }`.
488
+ * - Hosts can extend the Adapters interface (schema/adapter.ts) to add
489
+ * their own adapter flavours (axios, inertia, etc.) and then use
490
+ * those keys here.
491
+ *
492
+ * @template V Shape of the underlying value map (pre-schema).
493
+ * @template S Optional Zod schema type.
494
+ * @template K Adapter key; defaults to 'local'.
495
+ */
496
+ type CoreProps<V extends Dict, S extends z.ZodType | undefined, K extends AdapterKey = "local"> = BaseProps$7<V, S, K> & AdapterProps<K> & {
497
+ /**
498
+ * Which adapter flavour this core instance should use.
499
+ *
500
+ * - 'local' (default) → library-defined local submission (no URL/method semantics).
501
+ * - extended keys → host-defined adapters via Adapters augmentation.
502
+ */
503
+ adapter?: K;
504
+ /**
505
+ * Called after a submission completes. The payload type is derived from
506
+ * the selected adapter key via the adapter registry:
507
+ *
508
+ * AdapterSubmit<'local'> → { data: unknown }
509
+ * AdapterSubmit<'axios'> → host-defined type, etc.
510
+ */
511
+ onSubmitted?(form: CoreContext<InferFromSchema<S, V>>, payload: AdapterSubmit<K>, resolve?: () => void): void | Promise<void>;
512
+ };
513
+ /**
514
+ * Backwards-compatible alias for legacy naming, if you want it.
515
+ */
516
+ type FormProps<V extends Dict, S extends z.ZodType | undefined, K extends AdapterKey = "local"> = CoreProps<V, S, K>;
517
+ /**
518
+ * Result of a submit operation: values + validity flag.
519
+ */
520
+ type ValuesResult<V extends Dict> = {
521
+ values: V;
522
+ valid: boolean;
523
+ };
524
+ /**
525
+ * Query API for fields, similar to DOM helpers but scoped
526
+ * to the current form instance.
527
+ *
528
+ * "id" here refers to the field's groupId.
529
+ */
530
+ interface InputStore {
531
+ /** All registered inputs (with at least one identifier). */
532
+ all(): Field[];
533
+ /** All inputs that have a non-empty name. */
534
+ getAllNamed(): Field[];
535
+ /** All inputs that have a bindId. */
536
+ getAllBound(): Field[];
537
+ /** All inputs that have a groupId. */
538
+ getAllGrouped(): Field[];
539
+ /** First field matching an exact name. */
540
+ getByName(name: string): Field | undefined;
541
+ /** All fields matching an exact name. */
542
+ getAllByName(name: string): Field[];
543
+ /** First field with this groupId. */
544
+ getById(id: string): Field | undefined;
545
+ /** All fields with this groupId. */
546
+ getAllById(id: string): Field[];
547
+ /** First bound field with this bindId (prefers mounted fields). */
548
+ getByBind(id: string): Field | undefined;
549
+ /** All fields that share this bindId. */
550
+ getAllByBind(id: string): Field[];
551
+ }
552
+ /**
553
+ * Core runtime context, renamed from the legacy FormContext.
554
+ *
555
+ * @template V Shape of the values object produced by this core instance.
556
+ */
557
+ interface CoreContext<V extends Dict> {
558
+ /**
559
+ * Compute the current values snapshot from registered fields.
560
+ */
561
+ values(): V;
562
+ /**
563
+ * Run validation and return the values + validity flag.
564
+ */
565
+ submit(): ValuesResult<V>;
566
+ /**
567
+ * Lookup a field by its binding id.
568
+ */
569
+ getBind(id: string): Field | undefined;
570
+ /**
571
+ * Run validation across fields.
572
+ *
573
+ * @param report If true, fields should update their own error states.
574
+ * @returns true if all fields are valid, false otherwise.
575
+ */
576
+ validate(report?: boolean): boolean;
577
+ /**
578
+ * Register a new field with the core.
579
+ */
580
+ addField(field: Field): void;
581
+ /**
582
+ * Generic internal bucket for arbitrary metadata.
583
+ */
584
+ bucket: Dict;
585
+ /**
586
+ * Set a single field error or map an error bag.
587
+ */
588
+ error(name: string, msg: string): void;
589
+ error(bag: Record<string, string>): void;
590
+ /**
591
+ * Re-run button control logic (which button is active/disabled etc.).
592
+ */
593
+ controlButton(): void;
594
+ /**
595
+ * Prepare an adapter-backed request.
596
+ *
597
+ * This mirrors the legacy prepare method:
598
+ * - Builds a payload from values + extra.
599
+ * - May run validation / beforeSubmit hooks.
600
+ * - Returns an adapter result or undefined if aborted.
601
+ *
602
+ * The concrete adapter wiring is the host's responsibility.
603
+ */
604
+ prepare(type: Method, route: string, extra?: Partial<V>, ignoreForm?: boolean, autoErr?: boolean): Promise<AdapterResult<any> | undefined>;
605
+ /**
606
+ * Persist values to a provided data object, optionally transforming
607
+ * values via the feed function.
608
+ */
609
+ persist(data: Partial<V>, feed?: (name: string, value: unknown, original: unknown) => unknown): void;
610
+ /**
611
+ * Imperatively set a single value by field name.
612
+ */
613
+ setValue(name: string, value: unknown): void;
614
+ /**
615
+ * Kick off a submit flow using optional extra data.
616
+ */
617
+ go(data?: Partial<V>, ignoreForm?: boolean): void;
618
+ /**
619
+ * Reset specific inputs by name.
620
+ */
621
+ reset(inputs: string[]): void;
622
+ /**
623
+ * Register the current active button.
624
+ */
625
+ set button(v: ButtonRef);
626
+ /**
627
+ * Force a submit regardless of validation state.
628
+ */
629
+ forceSubmit(): Promise<void>;
630
+ /**
631
+ * All registered fields.
632
+ */
633
+ readonly fields: Field[];
634
+ /**
635
+ * Effective core props at runtime, excluding internal-only fields.
636
+ *
637
+ * Note: the adapter key parameter is erased here (set to any) because
638
+ * the runtime does not need the specific key for structural typing;
639
+ * hosts can still use more precise generics at the component level.
640
+ */
641
+ readonly props: Omit<CoreProps<V, z.ZodType | undefined, any>, "formRef" | "valueBag">;
642
+ /**
643
+ * Mark a button as active by name.
644
+ */
645
+ setActiveButton(name: string): void;
646
+ /**
647
+ * Return uncaught messages (errors that could not be mapped to a field).
648
+ *
649
+ * Typically used by an error strip component.
650
+ */
651
+ getUncaught(): readonly string[];
652
+ /**
653
+ * Field-query "DOM" for this form.
654
+ *
655
+ * Example:
656
+ * const email = form.inputs.getByName("email");
657
+ * const phoneFields = form.inputs.getAllById("phone-group");
658
+ * const bound = form.inputs.getByBind("shipping");
659
+ */
660
+ inputs: Omit<FieldRegistry, "add" | "remove">;
661
+ /**
662
+ * Checks if the form values have changed
663
+ */
664
+ isDirty(): boolean;
665
+ }
666
+
667
+ /**
668
+ * Size hint for field variants.
669
+ *
670
+ * Presets can interpret these however they like (font size, padding, etc.).
671
+ */
672
+ type FieldSize = "sm" | "md" | "lg";
673
+ /**
674
+ * Density hint for field variants.
675
+ *
676
+ * - "compact" → tight vertical spacing
677
+ * - "comfortable" → default spacing
678
+ * - "loose" → extra breathing room
679
+ */
680
+ type FieldDensity = "compact" | "comfortable" | "loose";
681
+ /**
682
+ * Logical source of a change event.
683
+ *
684
+ * Variants and utilities can tag changes to help the host reason
685
+ * about where a value came from.
686
+ */
687
+ type ChangeSource = "variant" | "paste" | "programmatic" | "util" | (string & {});
688
+ /**
689
+ * Additional context passed along with value changes.
690
+ */
691
+ interface ChangeDetail<TMeta = unknown, TRaw = unknown> {
692
+ /**
693
+ * Logical source for this change.
694
+ */
695
+ source: ChangeSource;
696
+ /**
697
+ * Optional raw input that produced this value.
698
+ *
699
+ * Example: original keyboard input or pasted string.
700
+ */
701
+ raw?: TRaw;
702
+ nativeEvent?: React__default.SyntheticEvent;
703
+ /**
704
+ * Variant-specific metadata (e.g. cursor position).
705
+ */
706
+ meta?: TMeta;
707
+ }
708
+ /**
709
+ * Base props shared by all variant components.
710
+ *
711
+ * Each variant module will extend this with its own props type.
712
+ */
713
+ interface VariantBaseProps<TValue> {
714
+ /**
715
+ * Current logical value for this field.
716
+ */
717
+ value?: TValue | undefined;
718
+ /**
719
+ * Called whenever the variant wants to update the value.
720
+ *
721
+ * The detail payload describes where the change came from.
722
+ */
723
+ onValue?(value: TValue | undefined, detail?: ChangeDetail): void;
724
+ /**
725
+ * State flags.
726
+ */
727
+ disabled?: boolean;
728
+ defaultValue?: any;
729
+ readOnly?: boolean;
730
+ required?: boolean;
731
+ alias?: string;
732
+ main?: boolean;
733
+ /**
734
+ * Current error message for this field, if any.
735
+ */
736
+ error?: string;
737
+ /**
738
+ * Size & density hints.
739
+ *
740
+ * Variants are free to ignore these, but presets (e.g. Shadcn)
741
+ * will typically honour them.
742
+ */
743
+ size?: FieldSize;
744
+ density?: FieldDensity;
745
+ }
746
+ interface Extras {
747
+ trailingIcons?: React__default.ReactNode[];
748
+ leadingIcons?: React__default.ReactNode[];
749
+ icon?: React__default.ReactNode;
750
+ iconGap?: number;
751
+ trailingIconSpacing?: number;
752
+ leadingIconSpacing?: number;
753
+ trailingControl?: React__default.ReactNode;
754
+ leadingControl?: React__default.ReactNode;
755
+ /**
756
+ * Optional className applied to the container that wraps the leading control.
757
+ * This does not affect the control node itself, only the wrapper div.
758
+ */
759
+ leadingControlClassName?: string;
760
+ /**
761
+ * Optional className applied to the container that wraps the trailing control.
762
+ * This does not affect the control node itself, only the wrapper div.
763
+ */
764
+ trailingControlClassName?: string;
765
+ px?: number;
766
+ py?: number;
767
+ pb?: number;
768
+ pe?: number;
769
+ ps?: number;
770
+ }
771
+ type ExtraFieldProps<Props> = Extras & Props;
772
+
773
+ /**
774
+ * Result type for validation hooks.
775
+ *
776
+ * Used by:
777
+ * - variant modules (`validate`)
778
+ * - per-field `onValidate` (InputField)
779
+ */
780
+ type ValidateResult = boolean | string | string[] | null | void;
781
+ /**
782
+ * Placement of the main label relative to the field control.
783
+ *
784
+ * This is a macro layout decision: where the label block lives
785
+ * compared to the input/control block.
786
+ */
787
+ type LabelPlacement = "top" | "left" | "right" | "hidden";
788
+ /**
789
+ * Shared placement for helper slots relative to their *root*.
790
+ *
791
+ * Example:
792
+ * - "above" → above the label root or input root
793
+ * - "below" → below the label root or input root
794
+ * - "left" → left of the label root or input root
795
+ * - "right" → right of the label root or input root
796
+ * - "hidden" → not rendered
797
+ */
798
+ type SlotPlacement = "left" | "right" | "above" | "below" | "hidden";
799
+ /**
800
+ * Placement of the sublabel relative to its root block.
801
+ */
802
+ type SublabelPlacement = SlotPlacement;
803
+ /**
804
+ * Placement for the longer description block.
805
+ */
806
+ type DescriptionPlacement = SlotPlacement;
807
+ /**
808
+ * Placement for helper text (typically small, subtle text).
809
+ */
810
+ type HelpTextPlacement = SlotPlacement;
811
+ /**
812
+ * Placement for explicit error text (visual error copy).
813
+ */
814
+ type ErrorTextPlacement = SlotPlacement;
815
+ /**
816
+ * Registry of all logical "slots" a field can render.
817
+ *
818
+ * Hosts can extend this via declaration merging, e.g.:
819
+ *
820
+ * declare module "@/schema/input-field" {
821
+ * interface FieldSlots {
822
+ * charCounter: true;
823
+ * }
824
+ * }
825
+ */
826
+ interface FieldSlots {
827
+ /** The main label text. */
828
+ label: true;
829
+ /** Optional smaller label text. */
830
+ sublabel: true;
831
+ /** Longer, usually multi-line description. */
832
+ description: true;
833
+ /** Small helper text, usually subtle. */
834
+ helpText: true;
835
+ /** Error text (validation message) when present. */
836
+ errorText: true;
837
+ /** The actual control/input element. */
838
+ input: true;
839
+ /**tags */
840
+ tags: true;
841
+ }
842
+ /**
843
+ * Registry of logical "roots" / anchor blocks.
844
+ *
845
+ * Other slots are positioned relative to one of these.
846
+ */
847
+ interface FieldRoots {
848
+ /** Label root block. */
849
+ label: true;
850
+ /** Input/control root block. */
851
+ input: true;
852
+ }
853
+ type FieldSlotId = keyof FieldSlots;
854
+ type FieldRootId = keyof FieldRoots;
855
+ /**
856
+ * Map of which root each *non-root* slot belongs to.
857
+ *
858
+ * Example:
859
+ * relativeRoots: {
860
+ * sublabel: "label",
861
+ * description: "input",
862
+ * helpText: "input",
863
+ * errorText: "input",
864
+ * }
865
+ */
866
+ type RelativeRootsMap = Partial<Record<Exclude<FieldSlotId, FieldRootId>, // non-root slots only
867
+ FieldRootId>>;
868
+ /**
869
+ * Relative ordering of *non-root* slots per root.
870
+ *
871
+ * This is *not* about placement; it only decides "who comes first"
872
+ * when multiple slots share the same:
873
+ * - root (label/input), and
874
+ * - placement (above/below/left/right)
875
+ *
876
+ * Example:
877
+ * ordering: {
878
+ * input: ["errorText", "description", "helpText"],
879
+ * }
880
+ *
881
+ * If description and helpText are both "below" the input, then the
882
+ * above config means:
883
+ * - errorText (below input) first,
884
+ * - then description (below input),
885
+ * - then helpText (below input).
886
+ */
887
+ type FieldOrdering = Partial<Record<FieldRootId, Exclude<FieldSlotId, FieldRootId>[]>>;
888
+ /**
889
+ * Layout defaults for a field/variant.
890
+ *
891
+ * Variants can provide these as defaults; InputField merges them
892
+ * with per-field overrides.
893
+ *
894
+ * The high-level placement props remain the main public API.
895
+ * `relativeRoots` and `ordering` provide a lower-level layout graph
896
+ * that InputField can use to render slots relative to "label" or
897
+ * "input" in a predictable order.
898
+ */
899
+ interface FieldLayoutConfig {
900
+ /**
901
+ * Where to render the main label relative to the control.
902
+ */
903
+ labelPlacement?: LabelPlacement;
904
+ /**
905
+ * Where to render the sublabel relative to its root.
906
+ */
907
+ sublabelPlacement?: SublabelPlacement;
908
+ /**
909
+ * Where to render the description block relative to its root.
910
+ */
911
+ descriptionPlacement?: DescriptionPlacement;
912
+ /**
913
+ * Where to render helper text relative to its root.
914
+ */
915
+ helpTextPlacement?: HelpTextPlacement;
916
+ /**
917
+ * Where to render error text (if any) relative to its root.
918
+ */
919
+ errorTextPlacement?: ErrorTextPlacement;
920
+ /**Where to render the tags (if any) relative to ites root */
921
+ tagPlacement?: SlotPlacement;
922
+ /**
923
+ * Hint that the field should render inline with other controls.
924
+ */
925
+ inline?: boolean;
926
+ /**
927
+ * Hint that the field should stretch to the full available width.
928
+ */
929
+ fullWidth?: boolean;
930
+ /**
931
+ * Optional default size/density hints.
932
+ *
933
+ * These are advisory; variants/presets may override them.
934
+ */
935
+ defaultSize?: FieldSize;
936
+ defaultDensity?: FieldDensity;
937
+ /**
938
+ * Which root each non-root slot is attached to.
939
+ *
940
+ * If omitted, InputField can infer reasonable defaults, e.g.:
941
+ * - sublabel → "label"
942
+ * - description → "input"
943
+ * - helpText → "input"
944
+ * - errorText → "input"
945
+ */
946
+ relativeRoots?: RelativeRootsMap;
947
+ /**
948
+ * Relative ordering of non-root slots per root.
949
+ *
950
+ * Used only when multiple slots share the same
951
+ * root + placement combination.
952
+ */
953
+ ordering?: FieldOrdering;
954
+ }
955
+ /**
956
+ * Effective layout for a field after merging:
957
+ * - variant defaults, and
958
+ * - per-field overrides.
959
+ */
960
+ interface EffectiveFieldLayout extends FieldLayoutConfig {
961
+ /**
962
+ * Concrete size/density after merging defaults + overrides.
963
+ */
964
+ size?: FieldSize;
965
+ density?: FieldDensity;
966
+ }
967
+ /**
968
+ * Context passed to a variant's layout resolver.
969
+ *
970
+ * - `defaults`: layout defaults defined by the variant module.
971
+ * - `overrides`: only the layout keys explicitly set on <InputField />.
972
+ * - `props`: the raw <InputField /> props for this field.
973
+ *
974
+ * The resolver MUST respect host overrides: if a key is present in
975
+ * `overrides`, it should not change it.
976
+ */
977
+ interface LayoutResolveContext<T = unknown> {
978
+ defaults: FieldLayoutConfig;
979
+ overrides: Partial<FieldLayoutConfig>;
980
+ props: T;
981
+ }
982
+ /**
983
+ * Variant-level layout resolver.
984
+ *
985
+ * This allows variants to implement mapping rules such as:
986
+ * - "if labelPlacement is left ⇒ inline=true, error below, etc."
987
+ * while still allowing host overrides to win.
988
+ *
989
+ * Variants may also fill in `relativeRoots` and `ordering` to define
990
+ * how slots are attached to "label" vs "input" and in what relative
991
+ * order they should render.
992
+ */
993
+ type LayoutResolver<T = unknown> = (ctx: LayoutResolveContext<T>) => FieldLayoutConfig;
994
+
995
+ type MaskMode = "raw" | "masked";
996
+ /**
997
+ * Mask-related props for the Shadcn text variant.
998
+ *
999
+ * These are forwarded to the underlying <Input>, which in turn wires
1000
+ * them into the InputMask implementation.
1001
+ */
1002
+ interface ShadcnTextMaskProps {
1003
+ /**
1004
+ * Mask pattern – Primereact style.
1005
+ * Example: "99/99/9999", "(999) 999-9999"
1006
+ */
1007
+ mask?: string;
1008
+ /**
1009
+ * Per-symbol definitions for slots.
1010
+ * Kept for future custom engine; not used by the current
1011
+ * react-input-mask implementation.
1012
+ */
1013
+ maskDefinitions?: Record<string, RegExp>;
1014
+ /**
1015
+ * Character used to visually represent an empty slot.
1016
+ * Default: "_".
1017
+ */
1018
+ slotChar?: string;
1019
+ /**
1020
+ * If true, when the value is effectively "empty" (no unmasked chars),
1021
+ * we emit an empty string "" instead of a fully-masked placeholder.
1022
+ *
1023
+ * NOTE: This behaviour is implemented in the variant, not Input,
1024
+ * so we preserve your existing semantics.
1025
+ */
1026
+ autoClear?: boolean;
1027
+ /**
1028
+ * Whether the *model* value is raw or masked.
1029
+ *
1030
+ * - "raw" or true → onValue receives unmasked value
1031
+ * - "masked" or false/undefined → onValue receives full masked string
1032
+ *
1033
+ * NOTE: detail.raw is **always** the masked string.
1034
+ */
1035
+ unmask?: MaskMode | boolean;
1036
+ /**
1037
+ * Placeholder for future caret-mode logic when we go back
1038
+ * to a custom engine. Currently unused, kept for API compatibility.
1039
+ */
1040
+ maskInsertMode?: "stream" | "caret";
1041
+ }
1042
+ /**
1043
+ * Extra UI props for the Shadcn text input (pure HTML-level).
1044
+ *
1045
+ * These are forwarded straight to the underlying <Input />.
1046
+ */
1047
+ type ShadcnTextUiProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "size"> & {
1048
+ /**
1049
+ * Extra classes applied only to the *inner* input element
1050
+ * (the actual <input>, not the wrapper box).
1051
+ */
1052
+ inputClassName?: string;
1053
+ /**
1054
+ * Fixed prefix rendered as part of the input value, NOT as an icon.
1055
+ * E.g. "₦", "ID: ".
1056
+ *
1057
+ * The underlying <Input> will:
1058
+ * - take the model value (without prefix),
1059
+ * - render prefix + value,
1060
+ * - expose the full visible string in event.target.value.
1061
+ */
1062
+ prefix?: string;
1063
+ /**
1064
+ * Fixed suffix rendered as part of the input value, NOT as an icon.
1065
+ * E.g. "%", "kg".
1066
+ */
1067
+ suffix?: string;
1068
+ /**
1069
+ * If true (default), we strip the prefix from the value
1070
+ * before emitting it via `onValue`.
1071
+ */
1072
+ stripPrefix?: boolean;
1073
+ /**
1074
+ * If true (default), we strip the suffix from the value
1075
+ * before emitting it via `onValue`.
1076
+ */
1077
+ stripSuffix?: boolean;
1078
+ } & ShadcnTextMaskProps;
1079
+ /**
1080
+ * Props for the Shadcn-based text variant.
1081
+ *
1082
+ * This is a *form* wrapper around the base <Input />:
1083
+ * - Handles value ↔ ChangeDetail mapping.
1084
+ * - Delegates all visual concerns (masking, affixes, icons, controls,
1085
+ * size, density) to the Input component.
1086
+ */
1087
+ type ShadcnTextVariantProps = ExtraFieldProps<VariantBaseProps<string | undefined>> & {
1088
+ /**
1089
+ * If true and there are controls, the input + controls share one box
1090
+ * (borders, radius, focus states).
1091
+ *
1092
+ * Delegated to the underlying <Input />.
1093
+ */
1094
+ joinControls?: boolean;
1095
+ /**
1096
+ * When joinControls is true, whether the box styling extends over controls
1097
+ * (true) or controls are visually separate (false).
1098
+ */
1099
+ extendBoxToControls?: boolean;
1100
+ } & ShadcnTextUiProps;
1101
+
1102
+ type InputRef = HTMLInputElement;
1103
+ interface InputNumberValueChangeEvent {
1104
+ originalEvent: React.SyntheticEvent<any> | null;
1105
+ value: number | null;
1106
+ stopPropagation(): void;
1107
+ preventDefault(): void;
1108
+ target: {
1109
+ name?: string | null;
1110
+ id?: string | null;
1111
+ value: number | null;
1112
+ };
1113
+ }
1114
+ interface InputNumberProps extends Omit<ShadcnTextVariantProps, 'min' | 'max' | 'value'>, Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "onInput" | "onKeyDown" | "onKeyUp" | "size" | 'max' | 'min'> {
1115
+ onKeyUp?(event: React.KeyboardEvent<HTMLInputElement>): unknown;
1116
+ onKeyDown?(event: React.KeyboardEvent<HTMLInputElement>): unknown;
1117
+ value?: number | null;
1118
+ /**
1119
+ * Emitted when the numeric value changes (Prime-style).
1120
+ */
1121
+ onValueChange?: (e: InputNumberValueChangeEvent) => void;
1122
+ /**
1123
+ * Optional simple change handler (numeric value).
1124
+ */
1125
+ onChange?: (e: {
1126
+ originalEvent: React.SyntheticEvent<any>;
1127
+ value: number | null;
1128
+ }) => void;
1129
+ locale?: string;
1130
+ localeMatcher?: Intl.NumberFormatOptions["localeMatcher"];
1131
+ mode?: "decimal" | "currency";
1132
+ currency?: string;
1133
+ currencyDisplay?: Intl.NumberFormatOptions["currencyDisplay"];
1134
+ useGrouping?: boolean;
1135
+ minFractionDigits?: number;
1136
+ maxFractionDigits?: number;
1137
+ roundingMode?: Intl.NumberFormatOptions["roundingMode"];
1138
+ min?: number | null;
1139
+ max?: number | null;
1140
+ step?: number;
1141
+ allowEmpty?: boolean;
1142
+ format?: boolean;
1143
+ inputId?: string;
1144
+ inputClassName?: string;
1145
+ inputStyle?: React.CSSProperties;
1146
+ inputRef?: React.Ref<InputRef> | null;
1147
+ /**
1148
+ * String prefix/suffix (like Prime). They are part of formatting logic.
1149
+ * You can ALSO forward them to your text variant as visual adornments.
1150
+ */
1151
+ prefix?: string;
1152
+ suffix?: string;
1153
+ size?: FieldSize;
1154
+ invalid?: boolean;
1155
+ }
1156
+ declare const InputNumber: React.MemoExoticComponent<React.ForwardRefExoticComponent<InputNumberProps & React.RefAttributes<HTMLInputElement>>>;
1157
+
1158
+ type ShadcnNumberVariantProps = Omit<InputNumberProps, "onValueChange" | "onChange" | "leadingControl" | "trailingControl"> & {
1159
+ /**
1160
+ * Show +/- buttons around the numeric field.
1161
+ * Defaults to false.
1162
+ */
1163
+ showButtons?: boolean;
1164
+ /**
1165
+ * How the step buttons are laid out when showButtons is true.
1166
+ *
1167
+ * - 'inline': "-" on the left, "+" on the right
1168
+ * - 'stacked': vertical +/- stack on the right
1169
+ */
1170
+ buttonLayout?: "inline" | "stacked";
1171
+ };
1172
+
1173
+ type BaseProps$6 = VariantBaseProps<string | undefined>;
1174
+ /**
1175
+ * Single-country phone config.
1176
+ *
1177
+ * - code: ISO 3166-1 alpha-2 ("NG", "US", "GB", ...)
1178
+ * - dial: dial code without "+" ("234", "1", "44", ...)
1179
+ * - mask: NATIONAL portion mask only (no dial), e.g. "999 999 9999"
1180
+ */
1181
+ interface PhoneCountry {
1182
+ code: string;
1183
+ label: string;
1184
+ dial: string;
1185
+ mask: string;
1186
+ flag?: React.ReactNode;
1187
+ }
1188
+ /**
1189
+ * How the variant emits the form value.
1190
+ *
1191
+ * - "masked" → "+234 801 234 5678"
1192
+ * - "e164" → "2348012345678" (dial + national digits, no "+")
1193
+ * - "national"→ "8012345678"
1194
+ */
1195
+ type PhoneValueMode = "masked" | "e164" | "national";
1196
+ interface PhoneSpecificProps {
1197
+ countries?: PhoneCountry[];
1198
+ defaultCountry?: string;
1199
+ onCountryChange?: (country: PhoneCountry) => void;
1200
+ showCountry?: boolean;
1201
+ countryPlaceholder?: string;
1202
+ showFlag?: boolean;
1203
+ showSelectedLabel?: boolean;
1204
+ showSelectedDial?: boolean;
1205
+ showDialInList?: boolean;
1206
+ /**
1207
+ * Controls how the emitted value is shaped.
1208
+ *
1209
+ * Default mirrors legacy autoUnmask=true + emitE164=true → "e164".
1210
+ */
1211
+ valueMode?: PhoneValueMode;
1212
+ /**
1213
+ * When true, the national mask keeps placeholder characters
1214
+ * for not-yet-filled positions. When false, trailing mask
1215
+ * fragments are omitted.
1216
+ */
1217
+ keepCharPositions?: boolean;
1218
+ /**
1219
+ * Style hooks for the internal country selector.
1220
+ */
1221
+ countrySelectClassName?: string;
1222
+ countryTriggerClassName?: string;
1223
+ countryValueClassName?: string;
1224
+ countryContentClassName?: string;
1225
+ countryItemClassName?: string;
1226
+ }
1227
+ type TextUiProps$4 = Omit<ShadcnTextVariantProps, "type" | "inputMode" | "leadingControl" | "value" | "onValue">;
1228
+ /**
1229
+ * Full props for the phone variant as seen by the form runtime.
1230
+ *
1231
+ * - Keeps the same `value`/`onValue` contract as other variants.
1232
+ * - Inherits visual/behavioural text props (size, density, className, etc.).
1233
+ * - Adds phone-specific configuration (countries, valueMode, etc.).
1234
+ */
1235
+ type ShadcnPhoneVariantProps = TextUiProps$4 & PhoneSpecificProps & Pick<BaseProps$6, "value" | "onValue">;
1236
+
1237
+ type BaseProps$5 = VariantBaseProps<string | undefined>;
1238
+ /**
1239
+ * Extra options specific to the color variant.
1240
+ */
1241
+ interface ColorSpecificProps {
1242
+ /**
1243
+ * If false, we hide the colour preview box.
1244
+ * Default: true
1245
+ */
1246
+ showPreview?: boolean;
1247
+ /**
1248
+ * If false, we hide the picker toggle control/icon.
1249
+ * Default: true
1250
+ */
1251
+ showPickerToggle?: boolean;
1252
+ /**
1253
+ * Size of the colour swatch in pixels.
1254
+ * Default: 18
1255
+ */
1256
+ previewSize?: number;
1257
+ /**
1258
+ * Optional className for the outer wrapper that hosts
1259
+ * the Input + hidden color input.
1260
+ */
1261
+ wrapperClassName?: string;
1262
+ /**
1263
+ * Optional className for the preview button itself (around the swatch).
1264
+ */
1265
+ previewButtonClassName?: string;
1266
+ /**
1267
+ * Optional className for the swatch box inside the preview button.
1268
+ */
1269
+ previewSwatchClassName?: string;
1270
+ /**
1271
+ * Optional className for the hidden `<input type="color">`.
1272
+ *
1273
+ * By default this input is visually hidden and only used
1274
+ * to invoke the browser/OS colour picker, but you can override
1275
+ * this class to make it visible and style it.
1276
+ */
1277
+ pickerInputClassName?: string;
1278
+ /**
1279
+ * Custom icon shown in the trailing control as the picker toggle.
1280
+ * If omitted, a tiny ▾ triangle is used.
1281
+ */
1282
+ pickerToggleIcon?: React.ReactNode;
1283
+ className?: string;
1284
+ }
1285
+ /**
1286
+ * We inherit the *visual/behavioural* props from ShadcnTextVariant,
1287
+ * but control value / onValue / type / inputMode / leadingControl / trailingControl ourselves.
1288
+ */
1289
+ type TextUiProps$3 = Omit<ShadcnTextVariantProps, "type" | "inputMode" | "leadingControl" | "trailingControl" | "value" | "onValue">;
1290
+ /**
1291
+ * Full props for the color variant as seen by the form runtime.
1292
+ */
1293
+ type ShadcnColorVariantProps = TextUiProps$3 & ColorSpecificProps & Pick<BaseProps$5, "value" | "onValue">;
1294
+
1295
+ type BaseProps$4 = VariantBaseProps<string | undefined>;
1296
+ /**
1297
+ * Options for the built-in password strength meter.
1298
+ *
1299
+ * NOTE: Score is always in the range 0–4 (inclusive).
1300
+ */
1301
+ interface StrengthOptions {
1302
+ /**
1303
+ * Custom scoring function.
1304
+ * Return a number in the range 0–4 (inclusive) where 0 = weakest, 4 = strongest.
1305
+ */
1306
+ calc?: (value: string) => number;
1307
+ /**
1308
+ * Labels for each score bucket (index 0..4).
1309
+ * Defaults to: ["Very weak", "Weak", "Okay", "Good", "Strong"]
1310
+ */
1311
+ labels?: [string, string, string, string, string];
1312
+ /**
1313
+ * Thresholds for score steps using a 0–100 bar.
1314
+ * Defaults to [0, 25, 50, 75, 100] mapping to scores 0..4 respectively.
1315
+ */
1316
+ thresholds?: [number, number, number, number, number];
1317
+ /**
1318
+ * Minimum score required to consider the password acceptable (0–4).
1319
+ * This is purely visual unless you enforce it in validate/onChange.
1320
+ * Default: 2
1321
+ */
1322
+ minScore?: number | 2;
1323
+ /**
1324
+ * Whether to show the textual label next to/under the bar.
1325
+ * Default: true
1326
+ */
1327
+ showLabel?: boolean;
1328
+ /**
1329
+ * Where to render the meter.
1330
+ * - "inline" → compact row under the input
1331
+ * - "block" → stacked with more spacing
1332
+ * Default: "inline"
1333
+ */
1334
+ display?: "inline" | "block";
1335
+ }
1336
+ interface PasswordRuleConfig {
1337
+ /**
1338
+ * Pattern used to decide if the rule passes.
1339
+ */
1340
+ pattern: RegExp;
1341
+ /**
1342
+ * If true, the rule is considered optional (recommendation).
1343
+ * Default: false unless the rule name is not prefixed with "!".
1344
+ */
1345
+ optional?: boolean;
1346
+ /**
1347
+ * Weight in the scoring (relative importance).
1348
+ * Default: 1, doubled if the use key is prefixed with "!".
1349
+ */
1350
+ weight?: number;
1351
+ /**
1352
+ * Short label for the rule (e.g. "At least 8 characters").
1353
+ * Defaults to the map key if omitted.
1354
+ */
1355
+ label?: string;
1356
+ /**
1357
+ * Longer description, used in detailed rule view.
1358
+ */
1359
+ description?: string;
1360
+ }
1361
+ /**
1362
+ * A definition entry can be:
1363
+ * - string → treated as a regex source
1364
+ * - RegExp → used directly
1365
+ * - full config
1366
+ */
1367
+ type PasswordRuleDefinition = string | RegExp | PasswordRuleConfig;
1368
+ /**
1369
+ * Map of alias/keys → definition entries.
1370
+ */
1371
+ type PasswordDefinitionMap = Record<string, PasswordRuleDefinition>;
1372
+ /**
1373
+ * Internal normalized state for a single rule.
1374
+ */
1375
+ interface NormalizedRuleState {
1376
+ key: string;
1377
+ label: string;
1378
+ description?: string;
1379
+ optional: boolean;
1380
+ required: boolean;
1381
+ weight: number;
1382
+ passed: boolean;
1383
+ }
1384
+ /**
1385
+ * Props passed to custom meter renderers.
1386
+ */
1387
+ interface PasswordMeterRenderProps {
1388
+ /** Raw password value. */
1389
+ value: string;
1390
+ /** Bucket score 0..4 based on percent + thresholds. */
1391
+ score: number;
1392
+ /** 0–100 progress used for the bar. */
1393
+ percent: number;
1394
+ /** Human label for the current score. */
1395
+ label: string;
1396
+ /** Whether score >= minScore. */
1397
+ passed: boolean;
1398
+ /** Effective minScore after normalization. */
1399
+ minScore: number;
1400
+ /** Effective thresholds used for bucketing. */
1401
+ thresholds: [number, number, number, number, number];
1402
+ /** Effective labels used. */
1403
+ labels: [string, string, string, string, string];
1404
+ /** Rule-level details when using a definition map. */
1405
+ rules: NormalizedRuleState[];
1406
+ }
1407
+ /**
1408
+ * Password-only props (on top of Shadcn text UI props & VariantBaseProps).
1409
+ *
1410
+ * This is what the form runtime sees as VariantPropsFor<"password">.
1411
+ */
1412
+ interface PasswordVariantProps {
1413
+ /** Maximum number of characters permitted. */
1414
+ maxLength?: number;
1415
+ /** Browser autocomplete hint (e.g., "current-password", "new-password"). */
1416
+ autoComplete?: string;
1417
+ /** Show an eye button to toggle between obscured/plain text. (default: true) */
1418
+ revealToggle?: boolean;
1419
+ /** Start in the revealed (plain text) state. */
1420
+ defaultRevealed?: boolean;
1421
+ /** Called whenever the reveal state changes. */
1422
+ onRevealChange?(revealed: boolean): void;
1423
+ /** Override the icons used for hide/show. */
1424
+ renderToggleIcon?(revealed: boolean): React.ReactNode;
1425
+ /** Accessible label for the toggle button. */
1426
+ toggleAriaLabel?(revealed: boolean): string;
1427
+ /**
1428
+ * Extra className for the reveal toggle button.
1429
+ */
1430
+ toggleButtonClassName?: string;
1431
+ /**
1432
+ * Enable the built-in strength meter (boolean or options).
1433
+ *
1434
+ * - false / undefined → no built-in meter is shown
1435
+ * - true → use defaults
1436
+ * - object → merge with defaults
1437
+ */
1438
+ strengthMeter?: boolean | StrengthOptions;
1439
+ /**
1440
+ * Optional rule definition map.
1441
+ */
1442
+ ruleDefinitions?: PasswordDefinitionMap;
1443
+ /**
1444
+ * Selection of rule aliases to apply.
1445
+ *
1446
+ * - "length" → use ruleDefinitions["length"] with default importance
1447
+ * - "!length" → same rule but treated as more important
1448
+ */
1449
+ ruleUses?: string[];
1450
+ /**
1451
+ * Built-in meter style:
1452
+ * - "simple" → single bar + label
1453
+ * - "rules" → bar + per-rule checklist
1454
+ * Default: "simple"
1455
+ */
1456
+ meterStyle?: "simple" | "rules";
1457
+ /**
1458
+ * Optional custom meter renderer.
1459
+ */
1460
+ renderMeter?(props: PasswordMeterRenderProps): React.ReactNode;
1461
+ /**
1462
+ * ClassNames for the meter and rules UI.
1463
+ */
1464
+ meterWrapperClassName?: string;
1465
+ meterContainerClassName?: string;
1466
+ meterBarClassName?: string;
1467
+ meterLabelClassName?: string;
1468
+ rulesWrapperClassName?: string;
1469
+ rulesHeadingClassName?: string;
1470
+ rulesListClassName?: string;
1471
+ ruleItemClassName?: string;
1472
+ ruleIconClassName?: string;
1473
+ ruleLabelClassName?: string;
1474
+ /**
1475
+ * Extra className for the outer field wrapper.
1476
+ */
1477
+ className?: string;
1478
+ }
1479
+ type TextUiProps$2 = Omit<ShadcnTextVariantProps, "type" | "inputMode" | "leadingControl" | "trailingControl" | "value" | "onValue">;
1480
+ /**
1481
+ * Full props for the Shadcn-based password variant.
1482
+ */
1483
+ type ShadcnPasswordVariantProps = TextUiProps$2 & PasswordVariantProps & Pick<BaseProps$4, "value" | "onValue" | "error">;
1484
+
1485
+ declare const buttonVariants: (props?: ({
1486
+ variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
1487
+ size?: "default" | "sm" | "lg" | "icon" | "icon-sm" | "icon-lg" | null | undefined;
1488
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
1489
+ declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
1490
+ asChild?: boolean;
1491
+ }): react_jsx_runtime.JSX.Element;
1492
+
1493
+ declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, formatters, components, ...props }: React.ComponentProps<typeof DayPicker> & {
1494
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"];
1495
+ }): react_jsx_runtime.JSX.Element;
1496
+
1497
+ type DateMode = "single" | "range";
1498
+ interface DateRange {
1499
+ from?: Date;
1500
+ to?: Date;
1501
+ }
1502
+ type DateValue = Date | DateRange | undefined;
1503
+ type BaseProps$3 = VariantBaseProps<DateValue>;
1504
+ type DisabledDays = React.ComponentProps<typeof Calendar>["disabled"];
1505
+ /**
1506
+ * Logical temporal "kind" for the field.
1507
+ *
1508
+ * This controls the default mask + formatting/parsing.
1509
+ *
1510
+ * - "date" → yyyy-MM-dd (default)
1511
+ * - "datetime" → yyyy-MM-dd HH:mm
1512
+ * - "time" → HH:mm
1513
+ * - "hour" → HH
1514
+ * - "monthYear" → MM/yyyy
1515
+ * - "year" → yyyy
1516
+ */
1517
+ type DateKind = "date" | "datetime" | "time" | "hour" | "monthYear" | "year" | (string & {});
1518
+ /**
1519
+ * Public props for the date variant (legacy + mask extensions).
1520
+ */
1521
+ interface DateVariantProps {
1522
+ mode?: DateMode;
1523
+ placeholder?: React.ReactNode;
1524
+ clearable?: boolean;
1525
+ minDate?: Date;
1526
+ maxDate?: Date;
1527
+ disabledDays?: DisabledDays;
1528
+ /**
1529
+ * Pattern for single dates.
1530
+ *
1531
+ * Tokens:
1532
+ * - yyyy → full year
1533
+ * - MM → month (01–12)
1534
+ * - dd → day (01–31)
1535
+ * - HH → hours (00–23)
1536
+ * - mm → minutes (00–59)
1537
+ *
1538
+ * Default depends on `kind`:
1539
+ * - date → "yyyy-MM-dd"
1540
+ * - datetime → "yyyy-MM-dd HH:mm"
1541
+ * - time → "HH:mm"
1542
+ * - hour → "HH"
1543
+ * - monthYear → "MM/yyyy"
1544
+ * - year → "yyyy"
1545
+ */
1546
+ formatSingle?: string;
1547
+ /**
1548
+ * String pattern or custom formatter for ranges.
1549
+ *
1550
+ * - string → same token rules as formatSingle, applied to both ends
1551
+ * - function → full control over display text
1552
+ */
1553
+ formatRange?: string | ((range: DateRange | undefined) => string);
1554
+ /**
1555
+ * Separator when formatRange is a string pattern.
1556
+ * Default: " – "
1557
+ */
1558
+ rangeSeparator?: string;
1559
+ /**
1560
+ * When true, keep the calendar open after a selection.
1561
+ *
1562
+ * For range mode, the picker also stays open until both
1563
+ * `from` and `to` are chosen.
1564
+ */
1565
+ stayOpenOnSelect?: boolean;
1566
+ /**
1567
+ * Controlled open state for the popover.
1568
+ */
1569
+ open?: boolean;
1570
+ onOpenChange?(o: boolean): void;
1571
+ /**
1572
+ * Temporal kind (controls default mask + formatting/parsing).
1573
+ *
1574
+ * Default: "date".
1575
+ */
1576
+ kind?: DateKind;
1577
+ /**
1578
+ * Optional explicit input mask pattern for the text input.
1579
+ *
1580
+ * If omitted, a sensible default based on `kind` is used.
1581
+ *
1582
+ * Mask tokens follow the same rules as the underlying Input mask:
1583
+ * 9 = digit, a = letter, * = alphanumeric.
1584
+ */
1585
+ inputMask?: string;
1586
+ /**
1587
+ * Whether to render the calendar popover.
1588
+ *
1589
+ * Defaults:
1590
+ * - true for `kind` = "date" | "datetime"
1591
+ * - false for time-only kinds ("time", "hour", "monthYear", "year")
1592
+ */
1593
+ showCalendar?: boolean;
1594
+ }
1595
+ /**
1596
+ * We still reuse the Shadcn text UI props (size, density, icons, etc.),
1597
+ * but we take over type/value/onValue and the controls.
1598
+ */
1599
+ type TextUiProps$1 = Omit<ShadcnTextVariantProps, "type" | "inputMode" | "leadingControl" | "trailingControl" | "value" | "onValue">;
1600
+ /**
1601
+ * Full props for the Shadcn-based date variant.
1602
+ */
1603
+ type ShadcnDateVariantProps = TextUiProps$1 & DateVariantProps & Pick<BaseProps$3, "value" | "onValue" | "error">;
1604
+
1605
+ type ChipsValue = string[] | undefined;
1606
+ type BaseProps$2 = VariantBaseProps<ChipsValue>;
1607
+ /**
1608
+ * How we split text into chips when committing.
1609
+ */
1610
+ type ChipsSeparator = string | RegExp | (string | RegExp)[];
1611
+ /**
1612
+ * Placement of chips relative to the entry control.
1613
+ *
1614
+ * - "inline" → inside the same visual box (Input) or in the textarea toolbox.
1615
+ * - "below" → chips rendered as a block underneath the field.
1616
+ */
1617
+ type ChipsPlacement = "inline" | "below";
1618
+ /**
1619
+ * Chips-only props, on top of the injected ones.
1620
+ */
1621
+ interface ChipsVariantProps {
1622
+ /**
1623
+ * Placeholder shown when there are no chips and input is empty.
1624
+ */
1625
+ placeholder?: string;
1626
+ /**
1627
+ * Separators used to split raw input into chips.
1628
+ *
1629
+ * - string → split on that string
1630
+ * - RegExp → split with regex
1631
+ * - array → try each in order
1632
+ *
1633
+ * Default: [",", ";"]
1634
+ */
1635
+ separators?: ChipsSeparator;
1636
+ /**
1637
+ * When true, pressing Enter commits the current input as chips.
1638
+ * Default: true
1639
+ */
1640
+ addOnEnter?: boolean;
1641
+ /**
1642
+ * When true, pressing Tab commits the current input as chips.
1643
+ * Default: true
1644
+ */
1645
+ addOnTab?: boolean;
1646
+ /**
1647
+ * When true, blurring the field commits any remaining input as chips.
1648
+ * Default: true
1649
+ */
1650
+ addOnBlur?: boolean;
1651
+ /**
1652
+ * When false, duplicate chips are ignored.
1653
+ * Default: false
1654
+ */
1655
+ allowDuplicates?: boolean;
1656
+ /**
1657
+ * Maximum number of chips allowed.
1658
+ * Undefined → unlimited.
1659
+ */
1660
+ maxChips?: number;
1661
+ /**
1662
+ * When true, Backspace on empty input removes the last chip.
1663
+ * Default: true
1664
+ */
1665
+ backspaceRemovesLast?: boolean;
1666
+ /**
1667
+ * Show a small clear-all button.
1668
+ * Default: false
1669
+ */
1670
+ clearable?: boolean;
1671
+ /**
1672
+ * Called when chips are added.
1673
+ */
1674
+ onAddChips?(added: string[], next: string[]): void;
1675
+ /**
1676
+ * Called when chips are removed.
1677
+ */
1678
+ onRemoveChips?(removed: string[], next: string[]): void;
1679
+ /**
1680
+ * Optional custom chip renderer.
1681
+ *
1682
+ * If provided, you are responsible for calling onRemove(index)
1683
+ * from your UI when you want to remove a chip.
1684
+ */
1685
+ renderChip?(chip: string, index: number, ctx: {
1686
+ remove(): void;
1687
+ chips: string[];
1688
+ }): React.ReactNode;
1689
+ /**
1690
+ * Optional custom overflow chip renderer.
1691
+ *
1692
+ * Receives the hidden count and the full chip list.
1693
+ */
1694
+ renderOverflowChip?(hiddenCount: number, chips: string[]): React.ReactNode;
1695
+ /**
1696
+ * Max number of chips to *render*.
1697
+ * Extra chips are summarized as "+N more".
1698
+ */
1699
+ maxVisibleChips?: number;
1700
+ /**
1701
+ * Max number of characters to *display* per chip.
1702
+ * The underlying value is not truncated.
1703
+ */
1704
+ maxChipChars?: number;
1705
+ /**
1706
+ * CSS max-width for chip labels (e.g. 160 or "12rem").
1707
+ */
1708
+ maxChipWidth?: number | string;
1709
+ /**
1710
+ * When true, the entry control is a Textarea instead of Input.
1711
+ * Good for comment-style chip entry.
1712
+ */
1713
+ textareaMode?: boolean;
1714
+ /**
1715
+ * Where chips are rendered relative to the entry.
1716
+ *
1717
+ * Default:
1718
+ * - Input mode → "inline"
1719
+ * - Textarea mode → "inline"
1720
+ */
1721
+ placement?: ChipsPlacement;
1722
+ className?: string;
1723
+ chipsClassName?: string;
1724
+ chipClassName?: string;
1725
+ chipLabelClassName?: string;
1726
+ chipRemoveClassName?: string;
1727
+ inputClassName?: string;
1728
+ }
1729
+ /**
1730
+ * We still type against ShadcnTextVariantProps so chips can reuse
1731
+ * size/density/icon props etc. We take control of:
1732
+ * - type / value / onValue
1733
+ * - leadingControl / trailingControl
1734
+ */
1735
+ type TextUiProps = Omit<ShadcnTextVariantProps, "type" | "inputMode" | "leadingControl" | "trailingControl" | "value" | "onValue">;
1736
+ /**
1737
+ * Full props for the Shadcn-based chips variant.
1738
+ */
1739
+ type ShadcnChipsVariantProps = TextUiProps & ChipsVariantProps & Pick<BaseProps$2, "value" | "onValue" | "error">;
1740
+
1741
+ interface TextareaIconControlProps {
1742
+ leadingIcons?: React.ReactNode[];
1743
+ trailingIcons?: React.ReactNode[];
1744
+ icon?: React.ReactNode;
1745
+ iconGap?: number;
1746
+ leadingIconSpacing?: number;
1747
+ trailingIconSpacing?: number;
1748
+ leadingControl?: React.ReactNode;
1749
+ trailingControl?: React.ReactNode;
1750
+ leadingControlClassName?: string;
1751
+ trailingControlClassName?: string;
1752
+ /**
1753
+ * If true, move the visual box (border, bg, radius, focus) from
1754
+ * `textarea-field` to `textarea-inner` so the side controls are
1755
+ * inside the same frame.
1756
+ *
1757
+ * Default: false (controls sit outside the border).
1758
+ */
1759
+ extendBoxToControls?: boolean;
1760
+ /**
1761
+ * If true, move the visual box all the way up to `textarea-box`,
1762
+ * so the upper toolbox and the inner row share a single frame.
1763
+ *
1764
+ * When this is true, it overrides `extendBoxToControls`.
1765
+ *
1766
+ * Default: false.
1767
+ */
1768
+ extendBoxToToolbox?: boolean;
1769
+ /**
1770
+ * Extra padding knobs (same semantics as Input).
1771
+ *
1772
+ * px → symmetric horizontal padding
1773
+ * py → symmetric vertical padding
1774
+ * ps/pe → logical start/end padding adjustments
1775
+ * pb → extra bottom padding (stacked with py)
1776
+ */
1777
+ px?: number;
1778
+ py?: number;
1779
+ ps?: number;
1780
+ pe?: number;
1781
+ pb?: number;
1782
+ /**
1783
+ * Extra classes merged into the raw <textarea>.
1784
+ * (The box padding/border live on the wrappers.)
1785
+ */
1786
+ inputClassName?: string;
1787
+ }
1788
+ interface TextareaSizeProps {
1789
+ size?: "sm" | "md" | "lg" | (string & {});
1790
+ density?: "compact" | "normal" | "relaxed" | "dense" | "loose" | (string & {});
1791
+ }
1792
+ interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "size">, TextareaIconControlProps, TextareaSizeProps {
1793
+ /**
1794
+ * Auto-resize based on content.
1795
+ * Default: true.
1796
+ */
1797
+ autoResize?: boolean;
1798
+ /**
1799
+ * Minimum number of visual rows.
1800
+ * Default: 1.
1801
+ */
1802
+ rows?: number;
1803
+ /**
1804
+ * Maximum number of visual rows.
1805
+ * Undefined → unlimited.
1806
+ */
1807
+ maxRows?: number;
1808
+ /**
1809
+ * Optional upper toolbox area.
1810
+ */
1811
+ upperControl?: React.ReactNode;
1812
+ upperControlClassName?: string;
1813
+ }
1814
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
1815
+
1816
+ type TextareaValue = string | undefined;
1817
+ type BaseProps$1 = VariantBaseProps<TextareaValue>;
1818
+ /**
1819
+ * Full props for the Shadcn-based textarea variant.
1820
+ *
1821
+ * - Reuses all UI-level behaviour from `Textarea` (autoResize, upperControl,
1822
+ * leading/trailing controls, icons, size/density, padding knobs, etc.).
1823
+ * - Takes over `value` / `onChange` so it can emit through `onValue` with
1824
+ * a `ChangeDetail`.
1825
+ */
1826
+ interface ShadcnTextareaVariantProps extends Omit<TextareaProps, "value" | "defaultValue" | "onChange">, Pick<BaseProps$1, "value" | "onValue" | "error"> {
1827
+ }
1828
+
1829
+ declare function Switch({ className, thumbClassName, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root> & {
1830
+ thumbClassName?: string;
1831
+ }): react_jsx_runtime.JSX.Element;
1832
+
1833
+ type ToggleValue = boolean | undefined;
1834
+ type BaseProps = VariantBaseProps<ToggleValue>;
1835
+ type Size = "sm" | "md" | "lg";
1836
+ type Density = "default" | "dense";
1837
+ /**
1838
+ * UI props specific to the Shadcn-based toggle.
1839
+ *
1840
+ * This uses Switch as the underlying control, but we keep
1841
+ * the API surface small and focused.
1842
+ */
1843
+ interface ShadcnToggleUiProps extends Omit<React.ComponentProps<typeof Switch>, "checked" | "onCheckedChange" | "className"> {
1844
+ /**
1845
+ * Visual size of the switch / text.
1846
+ * Default: "md".
1847
+ */
1848
+ size?: Size;
1849
+ /**
1850
+ * Row density (vertical padding & gap).
1851
+ * Default: "default".
1852
+ */
1853
+ density?: Density;
1854
+ /**
1855
+ * Place the switch on the left or right of the state text.
1856
+ * Default: "left".
1857
+ */
1858
+ controlPlacement?: "left" | "right";
1859
+ /**
1860
+ * Optional state text shown next to the control when ON.
1861
+ */
1862
+ onText?: React.ReactNode;
1863
+ /**
1864
+ * Optional state text shown next to the control when OFF.
1865
+ */
1866
+ offText?: React.ReactNode;
1867
+ /**
1868
+ * Wrapper class for the whole toggle row.
1869
+ */
1870
+ className?: string;
1871
+ /**
1872
+ * Extra classes for the Switch root.
1873
+ */
1874
+ switchClassName?: string;
1875
+ /**
1876
+ * Extra classes for the Switch thumb.
1877
+ * (Your patched Switch should support thumbClassName.)
1878
+ */
1879
+ switchThumbClassName?: string;
1880
+ }
1881
+ /**
1882
+ * Full props for the Shadcn-based toggle variant.
1883
+ *
1884
+ * We only pick value/onValue/error from the variant base props;
1885
+ * everything else (id, disabled, aria-*) flows via Switch props.
1886
+ */
1887
+ type ShadcnToggleVariantProps$1 = ShadcnToggleUiProps & Pick<BaseProps, "value" | "onValue" | "error">;
1888
+
1889
+ /**
1890
+ * Visual size of the radio UI.
1891
+ */
1892
+ type RadioSize = "sm" | "md" | "lg";
1893
+ /**
1894
+ * Vertical density of each radio row.
1895
+ *
1896
+ * Names aligned with your FieldDensity, but local to this variant.
1897
+ */
1898
+ type RadioDensity = "compact" | "comfortable" | "loose";
1899
+ /**
1900
+ * Layout mode for the group.
1901
+ *
1902
+ * - "list" → stacked rows
1903
+ * - "grid" → CSS grid with `columns`
1904
+ */
1905
+ type RadioLayoutMode = "list" | "grid";
1906
+ /**
1907
+ * Base radio item shape.
1908
+ */
1909
+ interface RadioItem<TValue> {
1910
+ value: TValue;
1911
+ label: React.ReactNode;
1912
+ description?: React.ReactNode;
1913
+ disabled?: boolean;
1914
+ key?: React.Key;
1915
+ /**
1916
+ * Option-level renderer (provided by the normaliser).
1917
+ * If present, it overrides the variant-level `renderOption` for this item.
1918
+ */
1919
+ render?: (ctx: RadioRenderOptionContext<TValue>) => React.ReactNode;
1920
+ }
1921
+ /**
1922
+ * Mapping functions used when TItem is not `RadioItem<TValue>`.
1923
+ */
1924
+ interface RadioMappers<TItem, TValue> {
1925
+ getValue: (item: TItem, index: number) => TValue;
1926
+ getLabel: (item: TItem, index: number) => React.ReactNode;
1927
+ getDescription?: (item: TItem, index: number) => React.ReactNode;
1928
+ isDisabled?: (item: TItem, index: number) => boolean;
1929
+ getKey?: (item: TItem, index: number) => React.Key;
1930
+ }
1931
+ /**
1932
+ * Context passed to a custom renderOption callback.
1933
+ */
1934
+ interface RadioRenderOptionContext<TValue> {
1935
+ item: RadioItem<TValue>;
1936
+ index: number;
1937
+ selected: boolean;
1938
+ disabled: boolean;
1939
+ size: RadioSize;
1940
+ density: RadioDensity;
1941
+ click(): void;
1942
+ /**
1943
+ * DOM id of this option (tied to the underlying RadioGroupItem).
1944
+ */
1945
+ optionId?: string;
1946
+ /**
1947
+ * Prebuilt radio control for convenience.
1948
+ * You can ignore this and render your own if you want.
1949
+ */
1950
+ radio: React.ReactNode;
1951
+ }
1952
+ /**
1953
+ * UI-specific radio props (independent of VariantBaseProps).
1954
+ */
1955
+ interface ShadcnRadioUiProps<TItem, TValue> {
1956
+ /**
1957
+ * Items to render as choices.
1958
+ *
1959
+ * Can be:
1960
+ * - `RadioItem<TValue>[]`, or
1961
+ * - any custom TItem[] when used with mapping functions
1962
+ * or optionValue/optionLabel keys.
1963
+ * - primitive arrays such as `string[]` or `number[]` (fallback).
1964
+ */
1965
+ items: readonly TItem[];
1966
+ /**
1967
+ * Mapping functions for TItem → value/label/etc.
1968
+ *
1969
+ * Takes precedence over optionValue/optionLabel if provided.
1970
+ */
1971
+ mappers?: RadioMappers<TItem, TValue>;
1972
+ /**
1973
+ * Property name on TItem that holds the **value**.
1974
+ *
1975
+ * Example:
1976
+ * items = [{ id: "free", title: "Free" }]
1977
+ * optionValue = "id"
1978
+ */
1979
+ optionValue?: keyof TItem | string;
1980
+ /**
1981
+ * Property name on TItem that holds the **label**.
1982
+ *
1983
+ * Example:
1984
+ * items = [{ id: "free", title: "Free" }]
1985
+ * optionLabel = "title"
1986
+ */
1987
+ optionLabel?: keyof TItem | string;
1988
+ /**
1989
+ * Optional custom renderer for each option.
1990
+ *
1991
+ * If provided, the default label/description layout is skipped and
1992
+ * this function is responsible for rendering the row.
1993
+ */
1994
+ renderOption?: (ctx: RadioRenderOptionContext<TValue>) => React.ReactNode;
1995
+ /**
1996
+ * Layout mode for the group.
1997
+ * Default: "list".
1998
+ */
1999
+ layout?: RadioLayoutMode;
2000
+ /**
2001
+ * Number of columns in grid mode.
2002
+ * Default: 2.
2003
+ */
2004
+ columns?: number;
2005
+ /**
2006
+ * Gap between items (list rows or grid cells) in px.
2007
+ * If omitted, Tailwind gaps/classes can handle spacing.
2008
+ */
2009
+ itemGapPx?: number;
2010
+ /**
2011
+ * Visual size of the radios.
2012
+ * Default: "md".
2013
+ */
2014
+ size?: RadioSize;
2015
+ /**
2016
+ * Vertical density (padding) of each row.
2017
+ * Default: "comfortable".
2018
+ */
2019
+ density?: RadioDensity;
2020
+ /**
2021
+ * When true, capitalizes the **first letter** of the label
2022
+ * (only applied when the label is a string).
2023
+ */
2024
+ autoCap?: boolean;
2025
+ /**
2026
+ * ARIA overrides for the group.
2027
+ */
2028
+ "aria-label"?: string;
2029
+ "aria-labelledby"?: string;
2030
+ /**
2031
+ * Wrapper class for the whole radio group.
2032
+ */
2033
+ groupClassName?: string;
2034
+ /**
2035
+ * Extra classes for each radio option row.
2036
+ */
2037
+ optionClassName?: string;
2038
+ /**
2039
+ * Extra classes for the option label node.
2040
+ */
2041
+ labelClassName?: string;
2042
+ /**
2043
+ * Extra classes for the description text under the label.
2044
+ */
2045
+ descriptionClassName?: string;
2046
+ }
2047
+ /**
2048
+ * Full props for the Shadcn-based radio variant.
2049
+ */
2050
+ type ShadcnRadioVariantProps<TValue, TItem = RadioItem<TValue>> = ShadcnRadioUiProps<TItem, TValue> & Pick<VariantBaseProps<TValue | undefined>, "value" | "onValue" | "error" | "disabled" | "required"> & {
2051
+ id?: string;
2052
+ name?: string;
2053
+ className?: string;
2054
+ "aria-describedby"?: string;
2055
+ };
2056
+
2057
+ type CheckboxSize = "sm" | "md" | "lg";
2058
+ type CheckboxDensity = "compact" | "comfortable" | "loose";
2059
+ type CheckboxLayoutMode = "list" | "grid";
2060
+ /**
2061
+ * Internal state we store in the value list.
2062
+ * "none" never goes into the external value.
2063
+ */
2064
+ type CheckboxTriStateValue = true | false;
2065
+ /**
2066
+ * Internal state we pass to the Shadcn checkbox.
2067
+ * "none" is used to represent "no stance yet".
2068
+ */
2069
+ type CheckboxInternalState = true | false | "none";
2070
+ interface CheckboxGroupEntry<TValue> {
2071
+ value: TValue;
2072
+ state: CheckboxTriStateValue;
2073
+ }
2074
+ type CheckboxGroupValue<TValue> = readonly CheckboxGroupEntry<TValue>[] | undefined;
2075
+ /**
2076
+ * Single checkbox value.
2077
+ * undefined → "none"
2078
+ */
2079
+ type CheckboxSingleValue = boolean | undefined;
2080
+ /**
2081
+ * Public union type for the variant's value.
2082
+ *
2083
+ * - In single mode: we expect CheckboxSingleValue
2084
+ * - In group mode: we expect CheckboxGroupValue<TValue>
2085
+ *
2086
+ * At the type level this is a union; at runtime we branch using `single`.
2087
+ */
2088
+ type CheckboxVariantValue<TValue> = CheckboxSingleValue | CheckboxGroupValue<TValue>;
2089
+ interface CheckboxItem<TValue> {
2090
+ value: TValue;
2091
+ label: React.ReactNode;
2092
+ description?: React.ReactNode;
2093
+ disabled?: boolean;
2094
+ key?: React.Key;
2095
+ /**
2096
+ * Option-level renderer (provided by the normaliser).
2097
+ * If present, it should override the variant-level `renderOption` for this item.
2098
+ */
2099
+ render?: (ctx: CheckboxRenderOptionContext<TValue>) => React.ReactNode;
2100
+ /**
2101
+ * Override tri-state behaviour for this item.
2102
+ * If undefined, variant-level `tristate` is used.
2103
+ */
2104
+ tristate?: boolean;
2105
+ }
2106
+ interface CheckboxMappers<TItem, TValue> {
2107
+ getValue: (item: TItem, index: number) => TValue;
2108
+ getLabel: (item: TItem, index: number) => React.ReactNode;
2109
+ getDescription?: (item: TItem, index: number) => React.ReactNode;
2110
+ isDisabled?: (item: TItem, index: number) => boolean;
2111
+ getKey?: (item: TItem, index: number) => React.Key;
2112
+ getTristate?: (item: TItem, index: number) => boolean | undefined;
2113
+ }
2114
+ interface CheckboxRenderOptionContext<TValue> {
2115
+ item: CheckboxItem<TValue>;
2116
+ index: number;
2117
+ state: CheckboxInternalState;
2118
+ effectiveTristate: boolean;
2119
+ disabled: boolean;
2120
+ size: CheckboxSize;
2121
+ density: CheckboxDensity;
2122
+ checkboxId?: string;
2123
+ click(): void;
2124
+ /**
2125
+ * Prebuilt Shadcn checkbox node.
2126
+ */
2127
+ checkbox: React.ReactNode;
2128
+ }
2129
+ /**
2130
+ * UI props for both single and group modes.
2131
+ */
2132
+ interface ShadcnCheckboxUiProps<TItem, TValue> {
2133
+ /**
2134
+ * Group mode:
2135
+ * - Required when `single` is not true.
2136
+ *
2137
+ * Single mode:
2138
+ * - Optional; if provided, `items[0]` can supply label/description.
2139
+ */
2140
+ items?: readonly TItem[];
2141
+ /**
2142
+ * Mapping functions for arbitrary item shapes.
2143
+ * Takes precedence over optionValue/optionLabel.
2144
+ */
2145
+ mappers?: CheckboxMappers<TItem, TValue>;
2146
+ /**
2147
+ * Property name that holds the value on each item.
2148
+ *
2149
+ * Example:
2150
+ * items = [{ id: "read", label: "Read" }]
2151
+ * optionValue = "id"
2152
+ */
2153
+ optionValue?: keyof TItem;
2154
+ /**
2155
+ * Property name that holds the label on each item.
2156
+ *
2157
+ * Example:
2158
+ * items = [{ id: "read", title: "Read" }]
2159
+ * optionLabel = "title"
2160
+ */
2161
+ optionLabel?: keyof TItem;
2162
+ /**
2163
+ * Custom renderer for each option row.
2164
+ */
2165
+ renderOption?: (ctx: CheckboxRenderOptionContext<TValue>) => React.ReactNode;
2166
+ /**
2167
+ * If true, treat this variant as a single checkbox instead of a group.
2168
+ *
2169
+ * Value is then CheckboxSingleValue (boolean | undefined).
2170
+ */
2171
+ single?: boolean;
2172
+ /**
2173
+ * Variant-level default tri-state behaviour.
2174
+ *
2175
+ * - In single mode: directly controls tri-state for the single checkbox.
2176
+ * - In group mode: default for all items, unless item.tristate overrides.
2177
+ */
2178
+ tristate?: boolean;
2179
+ /**
2180
+ * Layout mode in group mode: vertical list or CSS grid.
2181
+ */
2182
+ layout?: CheckboxLayoutMode;
2183
+ /**
2184
+ * Number of columns in grid mode.
2185
+ * Default: 2.
2186
+ */
2187
+ columns?: number;
2188
+ /**
2189
+ * Gap between items in px.
2190
+ */
2191
+ itemGapPx?: number;
2192
+ /**
2193
+ * Visual size of the checkbox / text.
2194
+ * Default: "md".
2195
+ */
2196
+ size?: CheckboxSize;
2197
+ /**
2198
+ * Vertical density of each row.
2199
+ * Default: "comfortable".
2200
+ */
2201
+ density?: CheckboxDensity;
2202
+ /**
2203
+ * When true, capitalizes the first letter of the label
2204
+ * (only applied when the label is a string).
2205
+ */
2206
+ autoCap?: boolean;
2207
+ /**
2208
+ * ARIA attributes for the group wrapper.
2209
+ */
2210
+ "aria-label"?: string;
2211
+ "aria-labelledby"?: string;
2212
+ /**
2213
+ * Wrapper class for the entire group (or single field).
2214
+ */
2215
+ groupClassName?: string;
2216
+ /**
2217
+ * Extra classes for each option row (group mode).
2218
+ */
2219
+ optionClassName?: string;
2220
+ /**
2221
+ * Extra classes for the option label text.
2222
+ */
2223
+ labelClassName?: string;
2224
+ /**
2225
+ * Extra classes for the option label text in group mode only.
2226
+ * This allows styling group item labels without affecting single mode labels.
2227
+ */
2228
+ optionLabelClassName?: string;
2229
+ /**
2230
+ * Extra classes for the option description text.
2231
+ */
2232
+ descriptionClassName?: string;
2233
+ /**
2234
+ * Single-mode inline label (if you want variant-level text).
2235
+ * Usually you'll rely on InputField's label instead.
2236
+ */
2237
+ singleLabel?: React.ReactNode;
2238
+ /**
2239
+ * Single-mode description text under the label.
2240
+ */
2241
+ singleDescription?: React.ReactNode;
2242
+ }
2243
+ /**
2244
+ * Full props for the Shadcn-based checkbox variant.
2245
+ *
2246
+ * TValue: primitive or object key
2247
+ * TItem: item shape used to build checkbox items
2248
+ */
2249
+ type ShadcnCheckboxVariantProps<TValue, TItem = CheckboxItem<TValue>> = ShadcnCheckboxUiProps<TItem, TValue> & Pick<VariantBaseProps<CheckboxVariantValue<TValue>>, "value" | "onValue" | "error" | "disabled" | "required"> & {
2250
+ id?: string;
2251
+ className?: string;
2252
+ name?: string;
2253
+ "aria-describedby"?: string;
2254
+ };
2255
+ /**
2256
+ * Default item value type for the checkbox variant.
2257
+ *
2258
+ * You can still use the generic ShadcnCheckboxVariantProps<TValue, TItem>
2259
+ * directly if you need a different TValue; the registry uses this alias.
2260
+ */
2261
+ type DefaultCheckboxItemValue = string | number;
2262
+ /**
2263
+ * Public "value" type for the checkbox variant used by the registry:
2264
+ *
2265
+ * - Single mode: boolean | undefined
2266
+ * - Group mode: CheckboxGroupEntry<DefaultCheckboxItemValue>[] | undefined
2267
+ *
2268
+ * In tri-state group mode, both `true` and `false` entries are present;
2269
+ * `"none"` never appears in this type.
2270
+ */
2271
+ type CheckboxVariantPublicValue = CheckboxVariantValue<DefaultCheckboxItemValue>;
2272
+ /**
2273
+ * Public props type for the checkbox variant used by the registry.
2274
+ *
2275
+ * This is ShadcnCheckboxVariantProps with TValue fixed to DefaultCheckboxItemValue.
2276
+ */
2277
+ type ShadcnCheckboxVariantPublicProps = ShadcnCheckboxVariantProps<DefaultCheckboxItemValue>;
2278
+
2279
+ type SelectPrimitive$1 = string | number;
2280
+ type MultiSelectOption = SelectPrimitive$1 | {
2281
+ label?: React.ReactNode;
2282
+ value?: SelectPrimitive$1;
2283
+ description?: React.ReactNode;
2284
+ disabled?: boolean;
2285
+ icon?: React.ReactNode;
2286
+ [key: string]: any;
2287
+ };
2288
+ type NormalizedMultiItem = {
2289
+ key: string;
2290
+ value: SelectPrimitive$1;
2291
+ labelNode: React.ReactNode;
2292
+ labelText: string;
2293
+ description?: React.ReactNode;
2294
+ disabled?: boolean;
2295
+ icon?: React.ReactNode;
2296
+ /** Option-level renderer (falls back to global renderOption) */
2297
+ render?: (...args: any[]) => React.ReactNode;
2298
+ raw: MultiSelectOption;
2299
+ };
2300
+ type MultiSelectBaseProps = Pick<VariantBaseProps<SelectPrimitive$1[] | undefined>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "size" | "density"> & {
2301
+ /**
2302
+ * Options for the multi-select.
2303
+ *
2304
+ * You can pass:
2305
+ * - primitives: ["ng", "gh", "ke"]
2306
+ * - objects: [{ label, value, ...extra }]
2307
+ */
2308
+ options?: MultiSelectOption[];
2309
+ /**
2310
+ * Automatically capitalise the first letter of the label
2311
+ * (when the resolved label is a string).
2312
+ */
2313
+ autoCap?: boolean;
2314
+ /**
2315
+ * How to read the label from each option.
2316
+ *
2317
+ * - string → key on the option object
2318
+ * - function → custom mapper
2319
+ * - omitted → tries `label`, else String(value)
2320
+ */
2321
+ optionLabel?: string | ((item: MultiSelectOption) => React.ReactNode);
2322
+ /**
2323
+ * How to read the value from each option.
2324
+ *
2325
+ * - string → key on the option object
2326
+ * - function → custom mapper
2327
+ * - omitted → uses `value`, or `id`, or `key`, or index
2328
+ */
2329
+ optionValue?: string | ((item: MultiSelectOption) => SelectPrimitive$1);
2330
+ /**
2331
+ * Optional description line under the label.
2332
+ */
2333
+ optionDescription?: string | ((item: MultiSelectOption) => React.ReactNode);
2334
+ /**
2335
+ * How to determine if an option is disabled.
2336
+ */
2337
+ optionDisabled?: string | ((item: MultiSelectOption) => boolean);
2338
+ /**
2339
+ * How to extract an icon for each option.
2340
+ *
2341
+ * - string → key on the option object (default "icon")
2342
+ * - function → custom mapper
2343
+ */
2344
+ optionIcon?: string | ((item: MultiSelectOption) => React.ReactNode);
2345
+ /**
2346
+ * How to compute the React key for each option.
2347
+ */
2348
+ optionKey?: string | ((item: MultiSelectOption, index: number) => React.Key);
2349
+ /**
2350
+ * Enable inline search inside the dropdown.
2351
+ */
2352
+ searchable?: boolean;
2353
+ /**
2354
+ * Placeholder for the search input.
2355
+ */
2356
+ searchPlaceholder?: string;
2357
+ /**
2358
+ * Text to show when search yields no results.
2359
+ */
2360
+ emptySearchText?: React.ReactNode;
2361
+ /**
2362
+ * Placeholder when nothing is selected.
2363
+ */
2364
+ placeholder?: React.ReactNode;
2365
+ /**
2366
+ * Show a small clear button in the trigger when any value is selected.
2367
+ */
2368
+ clearable?: boolean;
2369
+ /**
2370
+ * Whether to show a "Select all" row.
2371
+ */
2372
+ showSelectAll?: boolean;
2373
+ /**
2374
+ * Label for the "Select all" row.
2375
+ * Default: "Select all".
2376
+ */
2377
+ selectAllLabel?: React.ReactNode;
2378
+ /**
2379
+ * Where to place the "Select all" row.
2380
+ * Default: "top".
2381
+ */
2382
+ selectAllPosition?: "top" | "bottom";
2383
+ /**
2384
+ * Custom renderer for each option row (checkbox + label).
2385
+ */
2386
+ renderOption?: (ctx: {
2387
+ item: NormalizedMultiItem;
2388
+ selected: boolean;
2389
+ index: number;
2390
+ option: React.ReactNode;
2391
+ click(): void;
2392
+ }) => React.ReactNode;
2393
+ /**
2394
+ * Custom renderer for the trigger summary.
2395
+ */
2396
+ renderValue?: (ctx: {
2397
+ selectedItems: NormalizedMultiItem[];
2398
+ placeholder?: React.ReactNode;
2399
+ }) => React.ReactNode;
2400
+ /**
2401
+ * Custom renderer for the checkbox.
2402
+ *
2403
+ * - item: the option item (or null for "select all")
2404
+ * - selected: whether this row is currently fully selected
2405
+ * - indeterminate: partially selected (used for "select all")
2406
+ * - isSelectAll: true for the "select all" row
2407
+ */
2408
+ renderCheckbox?: (ctx: {
2409
+ item: NormalizedMultiItem | null;
2410
+ selected: boolean;
2411
+ indeterminate: boolean;
2412
+ isSelectAll: boolean;
2413
+ }) => React.ReactNode;
2414
+ /**
2415
+ * Max height (in px) for the dropdown list before scrolling.
2416
+ * Default: 260.
2417
+ */
2418
+ maxListHeight?: number;
2419
+ /**
2420
+ * Wrapper class for the whole variant.
2421
+ */
2422
+ className?: string;
2423
+ /**
2424
+ * Extra classes for the trigger button.
2425
+ */
2426
+ triggerClassName?: string;
2427
+ /**
2428
+ * Extra classes for the popover content.
2429
+ */
2430
+ contentClassName?: string;
2431
+ };
2432
+ type MultiSelectDefaultModeProps = {
2433
+ mode?: "default";
2434
+ leadingIcons?: React.ReactNode[];
2435
+ trailingIcons?: React.ReactNode[];
2436
+ icon?: React.ReactNode;
2437
+ iconGap?: number;
2438
+ leadingIconSpacing?: number;
2439
+ trailingIconSpacing?: number;
2440
+ leadingControl?: React.ReactNode;
2441
+ trailingControl?: React.ReactNode;
2442
+ leadingControlClassName?: string;
2443
+ trailingControlClassName?: string;
2444
+ joinControls?: boolean;
2445
+ extendBoxToControls?: boolean;
2446
+ button?: never;
2447
+ children?: never;
2448
+ selectedBadge?: never;
2449
+ selectedBadgeHiddenWhenZero?: never;
2450
+ selectedBadgeClassName?: never;
2451
+ selectedBadgePlacement?: never;
2452
+ };
2453
+ type MultiSelectButtonModeButton = React.ReactNode | ((ctx: {
2454
+ open: boolean;
2455
+ selectedItems: NormalizedMultiItem[];
2456
+ selectedCount: number;
2457
+ }) => React.ReactNode);
2458
+ type MultiSelectButtonModeProps = {
2459
+ mode: "button";
2460
+ /**
2461
+ * Used when mode="button". If provided, this is the trigger.
2462
+ * If not provided, `children` is used.
2463
+ */
2464
+ button?: MultiSelectButtonModeButton;
2465
+ children?: MultiSelectButtonModeButton;
2466
+ /**
2467
+ * Selected-count badge (mode="button" only)
2468
+ */
2469
+ selectedBadge?: boolean;
2470
+ selectedBadgeHiddenWhenZero?: boolean;
2471
+ selectedBadgeClassName?: string;
2472
+ selectedBadgePlacement?: "end" | "corner";
2473
+ leadingIcons?: never;
2474
+ trailingIcons?: never;
2475
+ icon?: never;
2476
+ iconGap?: never;
2477
+ leadingIconSpacing?: never;
2478
+ trailingIconSpacing?: never;
2479
+ leadingControl?: never;
2480
+ trailingControl?: never;
2481
+ leadingControlClassName?: never;
2482
+ trailingControlClassName?: never;
2483
+ joinControls?: never;
2484
+ extendBoxToControls?: never;
2485
+ };
2486
+ type ShadcnMultiSelectVariantProps = MultiSelectBaseProps & (MultiSelectDefaultModeProps | MultiSelectButtonModeProps);
2487
+
2488
+ type SliderValue$1 = number | undefined;
2489
+ interface ShadcnSliderVariantProps extends Pick<VariantBaseProps<SliderValue$1>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "size" | "density"> {
2490
+ /**
2491
+ * Minimum value for the slider.
2492
+ * Default: 0
2493
+ */
2494
+ min?: number;
2495
+ /**
2496
+ * Maximum value for the slider.
2497
+ * Default: 100
2498
+ */
2499
+ max?: number;
2500
+ /**
2501
+ * Step between values.
2502
+ * Default: 1
2503
+ */
2504
+ step?: number;
2505
+ /**
2506
+ * Show the current value as text next to the slider.
2507
+ * Default: true
2508
+ */
2509
+ showValue?: boolean;
2510
+ /**
2511
+ * Where to place the value label, relative to the slider.
2512
+ * - "end" → right of the slider (horizontal)
2513
+ * - "start" → left of the slider
2514
+ *
2515
+ * Default: "end"
2516
+ */
2517
+ valuePlacement?: "start" | "end";
2518
+ /**
2519
+ * Custom formatter for the numeric value.
2520
+ * If omitted, uses the raw number.
2521
+ */
2522
+ formatValue?: (value: SliderValue$1) => React.ReactNode;
2523
+ /**
2524
+ * Wrapper class for the entire slider field.
2525
+ */
2526
+ className?: string;
2527
+ /**
2528
+ * Extra classes for the Slider root.
2529
+ */
2530
+ sliderClassName?: string;
2531
+ /**
2532
+ * Extra classes for the value label.
2533
+ */
2534
+ valueClassName?: string;
2535
+ /**
2536
+ * One or more icons displayed inside the slider region, on the left.
2537
+ *
2538
+ * If not provided and `icon` is set, that single icon
2539
+ * is treated as `leadingIcons[0]`.
2540
+ */
2541
+ leadingIcons?: React.ReactNode[];
2542
+ /**
2543
+ * Icons displayed on the right side of the slider region
2544
+ * (before/after the value label depending on placement).
2545
+ */
2546
+ trailingIcons?: React.ReactNode[];
2547
+ /**
2548
+ * Convenience single-icon prop for the left side.
2549
+ */
2550
+ icon?: React.ReactNode;
2551
+ /**
2552
+ * Base gap between icons and slider/value.
2553
+ * Defaults to 4px-ish via `gap-1`.
2554
+ */
2555
+ iconGap?: number;
2556
+ /**
2557
+ * Extra spacing to apply between leading icons and the slider track.
2558
+ */
2559
+ leadingIconSpacing?: number;
2560
+ /**
2561
+ * Extra spacing to apply between trailing icons and the value label.
2562
+ */
2563
+ trailingIconSpacing?: number;
2564
+ /**
2565
+ * Arbitrary React node rendered before the slider (e.g. a button).
2566
+ */
2567
+ leadingControl?: React.ReactNode;
2568
+ /**
2569
+ * Arbitrary React node rendered after the slider (e.g. a button).
2570
+ */
2571
+ trailingControl?: React.ReactNode;
2572
+ /**
2573
+ * Extra classes for the leading control wrapper.
2574
+ */
2575
+ leadingControlClassName?: string;
2576
+ /**
2577
+ * Extra classes for the trailing control wrapper.
2578
+ */
2579
+ trailingControlClassName?: string;
2580
+ /**
2581
+ * If true and there are controls, the slider + controls share
2582
+ * a single visual box (borders, radius, focus states).
2583
+ * Default: true (to match text/select behaviour).
2584
+ */
2585
+ joinControls?: boolean;
2586
+ /**
2587
+ * When joinControls is true, whether the box styling extends over controls
2588
+ * (true) or controls are visually separate (false).
2589
+ * Default: true.
2590
+ */
2591
+ extendBoxToControls?: boolean;
2592
+ /**
2593
+ * Built-in +/- controls around the slider.
2594
+ *
2595
+ * - "none" → no built-in step buttons (default)
2596
+ * - "boxed" → +/- inside the same frame as the slider
2597
+ * - "edge" → loose layout: "- [ slider ] +"
2598
+ */
2599
+ controlVariant?: "none" | "boxed" | "edge";
2600
+ /**
2601
+ * Step used when clicking the +/- controls.
2602
+ * Defaults to `step`.
2603
+ */
2604
+ controlStep?: number;
2605
+ /**
2606
+ * Custom node for the decrement control. Default: "−".
2607
+ */
2608
+ controlDecrementIcon?: React.ReactNode;
2609
+ /**
2610
+ * Custom node for the increment control. Default: "+".
2611
+ */
2612
+ controlIncrementIcon?: React.ReactNode;
2613
+ }
2614
+
2615
+ /**
2616
+ * Slider value type:
2617
+ * - `number | undefined` for now (single-value slider).
2618
+ * If/when you add range support, this can be widened to [number, number].
2619
+ */
2620
+ type SliderValue = number | undefined;
2621
+
2622
+ type KeyValueMap = Record<string, string>;
2623
+ interface KV {
2624
+ key: string;
2625
+ value: string;
2626
+ }
2627
+ interface ShadcnKeyValueVariantProps extends Pick<VariantBaseProps<KeyValueMap | undefined>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "size" | "density"> {
2628
+ min?: number;
2629
+ max?: number;
2630
+ minVisible?: number;
2631
+ maxVisible?: number;
2632
+ showAddButton?: boolean;
2633
+ showMenuButton?: boolean;
2634
+ placeholder?: React.ReactNode;
2635
+ dialogTitle?: React.ReactNode;
2636
+ keyLabel?: React.ReactNode;
2637
+ valueLabel?: React.ReactNode;
2638
+ submitLabel?: React.ReactNode;
2639
+ moreLabel?: (count: number) => React.ReactNode;
2640
+ emptyLabel?: React.ReactNode;
2641
+ className?: string;
2642
+ chipsClassName?: string;
2643
+ chipClassName?: string;
2644
+ renderChip?: (ctx: {
2645
+ pair: KV;
2646
+ index: number;
2647
+ onEdit: () => void;
2648
+ onRemove: () => void;
2649
+ defaultChip: React.ReactNode;
2650
+ }) => React.ReactNode;
2651
+ }
2652
+
2653
+ /**
2654
+ * Props for the generic "custom" variant.
2655
+ *
2656
+ * - The only special props we define are:
2657
+ * - component: the React component to render
2658
+ * - valueProp / changeProp / disabledProp / readOnlyProp / errorProp
2659
+ * - idProp / nameProp / placeholderProp
2660
+ * - mapValue / mapDetail (optional hooks)
2661
+ *
2662
+ * - All other props are treated as "component props" and forwarded
2663
+ * directly to the underlying component.
2664
+ *
2665
+ * The underlying component is expected to:
2666
+ * - accept the mapped `valueProp`
2667
+ * - call the mapped `changeProp` with the next value (first argument)
2668
+ * - optionally use disabled/readOnly/error/id/name/placeholder via the mapped names
2669
+ */
2670
+ interface ShadcnCustomVariantProps<TValue = unknown> extends VariantBaseProps<TValue> {
2671
+ /**
2672
+ * The actual React component to render.
2673
+ *
2674
+ * Example:
2675
+ * component={MyToggle}
2676
+ */
2677
+ component: React.ComponentType<any>;
2678
+ /**
2679
+ * Prop name that carries the current value for the component.
2680
+ * Default: "value".
2681
+ */
2682
+ valueProp?: string;
2683
+ /**
2684
+ * Prop name for the change handler that the component will call.
2685
+ * Default: "onChange".
2686
+ *
2687
+ * The component is expected to call:
2688
+ * props[changeProp](nextValue, ...otherArgs?)
2689
+ *
2690
+ * The first argument is taken as the new value.
2691
+ */
2692
+ changeProp?: string;
2693
+ /**
2694
+ * Prop name for disabled state.
2695
+ * Default: "disabled".
2696
+ */
2697
+ disabledProp?: string;
2698
+ /**
2699
+ * Prop name for read-only state.
2700
+ * Default: "readOnly".
2701
+ */
2702
+ readOnlyProp?: string;
2703
+ /**
2704
+ * Prop name for passing error to the component (if it cares).
2705
+ * If provided, we pass the `error` field as-is.
2706
+ * Example values: "error", "isInvalid", "status".
2707
+ */
2708
+ errorProp?: string;
2709
+ /**
2710
+ * Prop name for the id attribute.
2711
+ * Default: "id".
2712
+ */
2713
+ idProp?: string;
2714
+ /**
2715
+ * Prop name for the name attribute.
2716
+ * Default: "name".
2717
+ */
2718
+ nameProp?: string;
2719
+ /**
2720
+ * Prop name for the placeholder attribute.
2721
+ * Default: "placeholder".
2722
+ */
2723
+ placeholderProp?: string;
2724
+ /**
2725
+ * Optional transform for the raw next value before it hits the field.
2726
+ *
2727
+ * Receives the first argument that the component passes to the change
2728
+ * handler, plus the full argument list for flexibility.
2729
+ */
2730
+ mapValue?: (raw: any, ...args: any[]) => TValue;
2731
+ /**
2732
+ * Optional builder for ChangeDetail, given the raw next value.
2733
+ *
2734
+ * If omitted, a default { source: "variant", raw } detail is used.
2735
+ */
2736
+ mapDetail?: (raw: any, ...args: any[]) => ChangeDetail;
2737
+ /**
2738
+ * Any other props are assumed to belong to the custom component.
2739
+ */
2740
+ [key: string]: unknown;
2741
+ }
2742
+
2743
+ type TreeKey = string | number;
2744
+ type TreeValue = TreeKey | TreeKey[] | undefined;
2745
+ type TreeSelectOption = TreeKey | {
2746
+ label?: React__default.ReactNode;
2747
+ value?: TreeKey;
2748
+ description?: React__default.ReactNode;
2749
+ disabled?: boolean;
2750
+ icon?: React__default.ReactNode;
2751
+ children?: TreeSelectOption[];
2752
+ [key: string]: any;
2753
+ };
2754
+ type NormalizedTreeItem = {
2755
+ key: string;
2756
+ value: TreeKey;
2757
+ labelNode: React__default.ReactNode;
2758
+ labelText: string;
2759
+ description?: React__default.ReactNode;
2760
+ disabled?: boolean;
2761
+ icon?: React__default.ReactNode;
2762
+ level: number;
2763
+ parentValue?: TreeKey;
2764
+ path: TreeKey[];
2765
+ hasChildren: boolean;
2766
+ children: NormalizedTreeItem[];
2767
+ raw: TreeSelectOption;
2768
+ };
2769
+
2770
+ type BadgeVariant$1 = "default" | "secondary" | "destructive" | "outline";
2771
+ type TreeSelectBaseProps = Pick<VariantBaseProps<TreeValue>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "size" | "density"> & {
2772
+ options?: TreeSelectOption[];
2773
+ /**
2774
+ * If true, allows multiple selection (checkboxes).
2775
+ * If false, allows single selection (no checkboxes, closes on select).
2776
+ * Default: true
2777
+ */
2778
+ multiple?: boolean;
2779
+ autoCap?: boolean;
2780
+ optionLabel?: string | ((item: TreeSelectOption) => React.ReactNode);
2781
+ optionValue?: string | ((item: TreeSelectOption) => TreeKey);
2782
+ optionDescription?: string | ((item: TreeSelectOption) => React.ReactNode);
2783
+ optionDisabled?: string | ((item: TreeSelectOption) => boolean);
2784
+ optionIcon?: string | ((item: TreeSelectOption) => React.ReactNode);
2785
+ optionKey?: string | ((item: TreeSelectOption, index: number) => React.Key);
2786
+ searchable?: boolean;
2787
+ searchPlaceholder?: string;
2788
+ emptyLabel?: React.ReactNode;
2789
+ emptySearchText?: React.ReactNode;
2790
+ clearable?: boolean;
2791
+ placeholder?: React.ReactNode;
2792
+ className?: string;
2793
+ triggerClassName?: string;
2794
+ contentClassName?: string;
2795
+ renderOption?: (ctx: {
2796
+ item: NormalizedTreeItem;
2797
+ selected: boolean;
2798
+ index: number;
2799
+ option: React.ReactNode;
2800
+ click(): void;
2801
+ }) => React.ReactNode;
2802
+ renderValue?: (ctx: {
2803
+ selectedItems: NormalizedTreeItem[];
2804
+ placeholder?: React.ReactNode;
2805
+ }) => React.ReactNode;
2806
+ expandAll?: boolean;
2807
+ defaultExpandedValues?: TreeKey[];
2808
+ leafOnly?: boolean;
2809
+ };
2810
+ type TreeSelectDefaultModeProps = {
2811
+ mode?: "default";
2812
+ leadingIcons?: React.ReactNode[];
2813
+ trailingIcons?: React.ReactNode[];
2814
+ icon?: React.ReactNode;
2815
+ iconGap?: number;
2816
+ leadingIconSpacing?: number;
2817
+ trailingIconSpacing?: number;
2818
+ leadingControl?: React.ReactNode;
2819
+ trailingControl?: React.ReactNode;
2820
+ leadingControlClassName?: string;
2821
+ trailingControlClassName?: string;
2822
+ joinControls?: boolean;
2823
+ extendBoxToControls?: boolean;
2824
+ button?: never;
2825
+ children?: never;
2826
+ selectedBadge?: never;
2827
+ selectedBadgeHiddenWhenZero?: never;
2828
+ selectedBadgeVariant?: never;
2829
+ selectedBadgeClassName?: never;
2830
+ selectedBadgePlacement?: never;
2831
+ };
2832
+ type TreeSelectButtonModeButton = React.ReactNode | ((ctx: {
2833
+ open: boolean;
2834
+ selectedItems: NormalizedTreeItem[];
2835
+ selectedCount: number;
2836
+ }) => React.ReactNode);
2837
+ type TreeSelectButtonModeProps = {
2838
+ mode: "button";
2839
+ /**
2840
+ * Used when mode="button". If provided, this is the trigger.
2841
+ * If not provided, `children` is used.
2842
+ */
2843
+ button?: TreeSelectButtonModeButton;
2844
+ children?: TreeSelectButtonModeButton;
2845
+ /**
2846
+ * Selected-count badge (mode="button" only)
2847
+ */
2848
+ selectedBadge?: boolean;
2849
+ selectedBadgeHiddenWhenZero?: boolean;
2850
+ selectedBadgeVariant?: BadgeVariant$1;
2851
+ selectedBadgeClassName?: string;
2852
+ selectedBadgePlacement?: "end" | "corner";
2853
+ leadingIcons?: never;
2854
+ trailingIcons?: never;
2855
+ icon?: never;
2856
+ iconGap?: never;
2857
+ leadingIconSpacing?: never;
2858
+ trailingIconSpacing?: never;
2859
+ leadingControl?: never;
2860
+ trailingControl?: never;
2861
+ leadingControlClassName?: never;
2862
+ trailingControlClassName?: never;
2863
+ joinControls?: never;
2864
+ extendBoxToControls?: never;
2865
+ };
2866
+ type ShadcnTreeSelectVariantProps = TreeSelectBaseProps & (TreeSelectDefaultModeProps | TreeSelectButtonModeProps);
2867
+
2868
+ declare const badgeVariants: (props?: ({
2869
+ variant?: "default" | "destructive" | "outline" | "secondary" | null | undefined;
2870
+ } & class_variance_authority_types.ClassProp) | undefined) => string;
2871
+ declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
2872
+ asChild?: boolean;
2873
+ }): react_jsx_runtime.JSX.Element;
2874
+
2875
+ type FileSourceKind = "native" | "path" | "url" | "custom";
2876
+ interface FileItem {
2877
+ id: string;
2878
+ kind: FileSourceKind;
2879
+ file?: File;
2880
+ path?: string;
2881
+ url?: string;
2882
+ name: string;
2883
+ size?: number;
2884
+ type?: string;
2885
+ status?: "idle" | "loading" | "done" | "failed";
2886
+ error?: string | null;
2887
+ meta?: any;
2888
+ }
2889
+ type FileLike = File | string | {
2890
+ id?: string;
2891
+ file?: File;
2892
+ path?: string;
2893
+ url?: string;
2894
+ name?: string;
2895
+ size?: number;
2896
+ type?: string;
2897
+ status?: FileItem["status"];
2898
+ error?: string | null;
2899
+ meta?: any;
2900
+ [key: string]: unknown;
2901
+ };
2902
+ type CustomFileLoaderResult = FileLike | FileLike[] | null | undefined;
2903
+ type CustomFileLoader = (ctx: {
2904
+ multiple: boolean;
2905
+ current: FileItem[];
2906
+ }) => Promise<CustomFileLoaderResult> | CustomFileLoaderResult;
2907
+ type BadgeVariant = React.ComponentProps<typeof Badge>["variant"];
2908
+ type FileVariantBaseProps = Pick<VariantBaseProps<FileItem[]>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "size" | "density"> & {
2909
+ multiple?: boolean;
2910
+ accept?: string | string[];
2911
+ maxFiles?: number;
2912
+ maxTotalSize?: number;
2913
+ showDropArea?: boolean;
2914
+ dropIcon?: React.ReactNode;
2915
+ dropTitle?: React.ReactNode;
2916
+ dropDescription?: React.ReactNode;
2917
+ renderDropArea?: (ctx: {
2918
+ openPicker: () => void;
2919
+ isDragging: boolean;
2920
+ }) => React.ReactNode;
2921
+ renderFileItem?: (ctx: {
2922
+ item: FileItem;
2923
+ index: number;
2924
+ selected: boolean;
2925
+ toggleSelected: () => void;
2926
+ remove: () => void;
2927
+ }) => React.ReactNode;
2928
+ showCheckboxes?: boolean;
2929
+ onFilesAdded?: (added: FileItem[], detail: ChangeDetail<{
2930
+ from: "input" | "drop" | "custom-loader";
2931
+ }>) => void;
2932
+ customLoader?: CustomFileLoader;
2933
+ mergeMode?: "append" | "replace";
2934
+ formatFileName?: (item: FileItem) => React.ReactNode;
2935
+ formatFileSize?: (size?: number) => React.ReactNode;
2936
+ placeholder?: string;
2937
+ className?: string;
2938
+ dropAreaClassName?: string;
2939
+ listClassName?: string;
2940
+ triggerClassName?: string;
2941
+ };
2942
+ type FileDefaultModeProps = {
2943
+ mode?: "default";
2944
+ leadingIcons?: React.ReactNode[];
2945
+ trailingIcons?: React.ReactNode[];
2946
+ icon?: React.ReactNode;
2947
+ leadingControl?: React.ReactNode;
2948
+ trailingControl?: React.ReactNode;
2949
+ leadingControlClassName?: string;
2950
+ trailingControlClassName?: string;
2951
+ joinControls?: boolean;
2952
+ extendBoxToControls?: boolean;
2953
+ button?: never;
2954
+ children?: never;
2955
+ selectedBadge?: never;
2956
+ selectedBadgeHiddenWhenZero?: never;
2957
+ selectedBadgeVariant?: never;
2958
+ selectedBadgeClassName?: never;
2959
+ selectedBadgePlacement?: never;
2960
+ };
2961
+ type FileButtonTrigger = React.ReactNode | ((ctx: {
2962
+ open: boolean;
2963
+ items: FileItem[];
2964
+ selectedCount: number;
2965
+ disabled: boolean;
2966
+ }) => React.ReactNode);
2967
+ type FileButtonModeProps = {
2968
+ mode: "button";
2969
+ /** Used when mode="button". If provided, this is the trigger. If not, `children` is used. */
2970
+ button?: FileButtonTrigger;
2971
+ children?: FileButtonTrigger;
2972
+ /** Selected-count badge (mode="button" only) */
2973
+ selectedBadge?: boolean;
2974
+ selectedBadgeHiddenWhenZero?: boolean;
2975
+ selectedBadgeVariant?: BadgeVariant;
2976
+ selectedBadgeClassName?: string;
2977
+ selectedBadgePlacement?: "end" | "corner";
2978
+ leadingIcons?: never;
2979
+ trailingIcons?: never;
2980
+ icon?: never;
2981
+ leadingControl?: never;
2982
+ trailingControl?: never;
2983
+ leadingControlClassName?: never;
2984
+ trailingControlClassName?: never;
2985
+ joinControls?: never;
2986
+ extendBoxToControls?: never;
2987
+ };
2988
+ type ShadcnFileVariantProps = FileVariantBaseProps & (FileDefaultModeProps | FileButtonModeProps);
2989
+
2990
+ type SelectPrimitive = string | number;
2991
+ type SelectOption = SelectPrimitive | {
2992
+ label?: React.ReactNode;
2993
+ value?: SelectPrimitive;
2994
+ description?: React.ReactNode;
2995
+ disabled?: boolean;
2996
+ [key: string]: any;
2997
+ };
2998
+ type NormalizedSelectItem = {
2999
+ key: string;
3000
+ value: SelectPrimitive;
3001
+ labelNode: React.ReactNode;
3002
+ labelText: string;
3003
+ description?: React.ReactNode;
3004
+ disabled?: boolean;
3005
+ icon?: React.ReactNode;
3006
+ /** Option-level renderer (falls back to global renderOption) */
3007
+ render?: (...args: any[]) => React.ReactNode;
3008
+ raw: SelectOption;
3009
+ };
3010
+ /**
3011
+ * Shadcn-based Select variant.
3012
+ */
3013
+ interface SelectBaseProps extends Pick<VariantBaseProps<SelectPrimitive | undefined>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "size" | "density"> {
3014
+ /**
3015
+ * Options for the select.
3016
+ *
3017
+ * You can pass:
3018
+ * - primitives: ["ng", "gh", "ke"]
3019
+ * - objects: [{ label, value, ...extra }]
3020
+ */
3021
+ options?: SelectOption[];
3022
+ /**
3023
+ * Automatically capitalise the first letter of the label
3024
+ * (when the resolved label is a string).
3025
+ */
3026
+ autoCap?: boolean;
3027
+ /**
3028
+ * How to read the label from each option.
3029
+ *
3030
+ * - string → key on the option object
3031
+ * - function → custom mapper
3032
+ * - omitted → tries `label`, else String(value)
3033
+ */
3034
+ optionLabel?: string | ((item: SelectOption) => React.ReactNode);
3035
+ /**
3036
+ * How to read the value from each option.
3037
+ *
3038
+ * - string → key on the option object
3039
+ * - function → custom mapper
3040
+ * - omitted → uses `value`, or `id`, or `key`, or index
3041
+ */
3042
+ optionValue?: string | ((item: SelectOption) => SelectPrimitive);
3043
+ /**
3044
+ * Optional description line under the label.
3045
+ */
3046
+ optionDescription?: string | ((item: SelectOption) => React.ReactNode);
3047
+ /**
3048
+ * How to determine if an option is disabled.
3049
+ */
3050
+ optionDisabled?: string | ((item: SelectOption) => boolean);
3051
+ /**
3052
+ * How to extract an icon for each option.
3053
+ *
3054
+ * - string → key on the option object (default "icon")
3055
+ * - function → custom mapper
3056
+ */
3057
+ optionIcon?: string | ((item: SelectOption) => React.ReactNode);
3058
+ /**
3059
+ * How to compute the React key for each option.
3060
+ */
3061
+ optionKey?: string | ((item: SelectOption, index: number) => React.Key);
3062
+ /**
3063
+ * Enable inline search inside the dropdown.
3064
+ */
3065
+ searchable?: boolean;
3066
+ /**
3067
+ * Placeholder for the search input.
3068
+ */
3069
+ searchPlaceholder?: string;
3070
+ /**
3071
+ * Label shown when there are no options available at all.
3072
+ *
3073
+ * If omitted, falls back to `emptySearchText` or a default message.
3074
+ */
3075
+ emptyLabel?: React.ReactNode;
3076
+ /**
3077
+ * Text to show when search yields no results
3078
+ * (but there *are* options in general).
3079
+ */
3080
+ emptySearchText?: React.ReactNode;
3081
+ /**
3082
+ * Show a small clear button in the trigger when a value is selected.
3083
+ */
3084
+ clearable?: boolean;
3085
+ /**
3086
+ * Placeholder when nothing is selected.
3087
+ */
3088
+ placeholder?: React.ReactNode;
3089
+ /**
3090
+ * Wrapper class for the whole variant.
3091
+ */
3092
+ className?: string;
3093
+ /**
3094
+ * Extra classes for the SelectTrigger.
3095
+ */
3096
+ triggerClassName?: string;
3097
+ /**
3098
+ * Extra classes for the SelectContent popover.
3099
+ */
3100
+ contentClassName?: string;
3101
+ /**
3102
+ * Custom renderer for each option row.
3103
+ */
3104
+ renderOption?: (ctx: {
3105
+ item: NormalizedSelectItem;
3106
+ selected: boolean;
3107
+ index: number;
3108
+ option: React.ReactNode;
3109
+ click(): void;
3110
+ }) => React.ReactNode;
3111
+ /**
3112
+ * Custom renderer for the trigger value.
3113
+ */
3114
+ renderValue?: (ctx: {
3115
+ selectedItem: NormalizedSelectItem | null;
3116
+ placeholder?: React.ReactNode;
3117
+ }) => React.ReactNode;
3118
+ /**
3119
+ * Icons displayed on the right side of the trigger,
3120
+ * near the clear button / chevron area.
3121
+ */
3122
+ trailingIcons?: React.ReactNode[];
3123
+ /**
3124
+ * Convenience single-icon prop for the left side.
3125
+ */
3126
+ icon?: React.ReactNode;
3127
+ /**
3128
+ * Base gap between icons and text.
3129
+ * Defaults to 4px-ish via `gap-1`.
3130
+ */
3131
+ iconGap?: number;
3132
+ /**
3133
+ * Extra spacing to apply between leading icons and the text.
3134
+ */
3135
+ leadingIconSpacing?: number;
3136
+ /**
3137
+ * Extra spacing to apply between trailing icons and the clear button.
3138
+ */
3139
+ trailingIconSpacing?: number;
3140
+ /**
3141
+ * Arbitrary React node rendered before the select (e.g. a button).
3142
+ */
3143
+ leadingControl?: React.ReactNode;
3144
+ /**
3145
+ * Arbitrary React node rendered after the select (e.g. a button).
3146
+ */
3147
+ trailingControl?: React.ReactNode;
3148
+ /**
3149
+ * Extra classes for the leading control wrapper.
3150
+ */
3151
+ leadingControlClassName?: string;
3152
+ /**
3153
+ * Extra classes for the trailing control wrapper.
3154
+ */
3155
+ trailingControlClassName?: string;
3156
+ /**
3157
+ * If true and there are controls, the select trigger + controls share
3158
+ * a single visual box (borders, radius, focus states).
3159
+ */
3160
+ joinControls?: boolean;
3161
+ /**
3162
+ * When joinControls is true, whether the box styling extends over controls
3163
+ * (true) or controls are visually separate (false).
3164
+ */
3165
+ extendBoxToControls?: boolean;
3166
+ /**
3167
+ * Enable incremental rendering for large option lists.
3168
+ *
3169
+ * When true, only a page of options is rendered initially,
3170
+ * and more are revealed as the user scrolls down.
3171
+ */
3172
+ virtualScroll?: boolean;
3173
+ /**
3174
+ * Number of options to render per "page" when virtualScroll is enabled.
3175
+ * Default: 50.
3176
+ */
3177
+ virtualScrollPageSize?: number;
3178
+ /**
3179
+ * Distance from the bottom (in px) at which the next page loads.
3180
+ * Default: 48px.
3181
+ */
3182
+ virtualScrollThreshold?: number;
3183
+ }
3184
+ type SelectDefaultModeProps = {
3185
+ mode?: "default";
3186
+ leadingIcons?: React.ReactNode[];
3187
+ trailingIcons?: React.ReactNode[];
3188
+ icon?: React.ReactNode;
3189
+ iconGap?: number;
3190
+ leadingIconSpacing?: number;
3191
+ trailingIconSpacing?: number;
3192
+ leadingControl?: React.ReactNode;
3193
+ trailingControl?: React.ReactNode;
3194
+ leadingControlClassName?: string;
3195
+ trailingControlClassName?: string;
3196
+ joinControls?: boolean;
3197
+ extendBoxToControls?: boolean;
3198
+ button?: never;
3199
+ children?: never;
3200
+ };
3201
+ type SelectButtonModeButton = React.ReactNode | ((ctx: {
3202
+ open: boolean;
3203
+ selectedItem: NormalizedSelectItem | null;
3204
+ selectedValue: SelectPrimitive | undefined;
3205
+ placeholder?: React.ReactNode;
3206
+ }) => React.ReactNode);
3207
+ type SelectButtonModeProps = {
3208
+ mode: "button";
3209
+ /**
3210
+ * Used when mode="button". If provided, this is the trigger.
3211
+ * If not provided, `children` is used.
3212
+ */
3213
+ button?: SelectButtonModeButton;
3214
+ children?: SelectButtonModeButton;
3215
+ leadingIcons?: never;
3216
+ trailingIcons?: never;
3217
+ icon?: never;
3218
+ iconGap?: never;
3219
+ leadingIconSpacing?: never;
3220
+ trailingIconSpacing?: never;
3221
+ leadingControl?: never;
3222
+ trailingControl?: never;
3223
+ leadingControlClassName?: never;
3224
+ trailingControlClassName?: never;
3225
+ joinControls?: never;
3226
+ extendBoxToControls?: never;
3227
+ };
3228
+ type ShadcnSelectVariantProps = SelectBaseProps & (SelectDefaultModeProps | SelectButtonModeProps);
3229
+
3230
+ type SelectVariantProps = ShadcnSelectVariantProps;
3231
+
3232
+ interface ToggleOption {
3233
+ label: React.ReactNode;
3234
+ value: string;
3235
+ icon?: React.ReactNode;
3236
+ disabled?: boolean;
3237
+ tooltip?: React.ReactNode;
3238
+ meta?: any;
3239
+ }
3240
+ /**
3241
+ * Allow primitive options as shorthand:
3242
+ * - "free" → { value: "free", label: "free" }
3243
+ */
3244
+ type ToggleOptionInput = ToggleOption | string | number | boolean;
3245
+ interface ShadcnToggleVariantProps extends Pick<VariantBaseProps<string | string[]>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "size" | "density"> {
3246
+ /**
3247
+ * Options for the toggle group.
3248
+ *
3249
+ * Can be:
3250
+ * - ToggleOption objects
3251
+ * - Primitive strings/numbers/booleans (shorthand)
3252
+ * - Objects using option* keys (optionValue, optionLabel, etc.)
3253
+ */
3254
+ options: ToggleOptionInput[];
3255
+ multiple?: boolean;
3256
+ variant?: "default" | "outline";
3257
+ layout?: "horizontal" | "vertical" | "grid";
3258
+ gridCols?: number;
3259
+ fillWidth?: boolean;
3260
+ /**
3261
+ * Property name to read the option value from, when using
3262
+ * custom option objects.
3263
+ *
3264
+ * If omitted, falls back to:
3265
+ * - obj.value
3266
+ * - or the primitive itself (for primitive options)
3267
+ */
3268
+ optionValue?: string;
3269
+ /**
3270
+ * Property name to read the option label from, when using
3271
+ * custom option objects.
3272
+ *
3273
+ * If omitted, falls back to:
3274
+ * - obj.label
3275
+ * - or String(value)
3276
+ */
3277
+ optionLabel?: string;
3278
+ /**
3279
+ * Property name to read an icon node from, when using
3280
+ * custom option objects.
3281
+ *
3282
+ * If omitted, falls back to obj.icon.
3283
+ */
3284
+ optionIcon?: string;
3285
+ /**
3286
+ * Property name to read disabled flag from, when using
3287
+ * custom option objects.
3288
+ *
3289
+ * If omitted, falls back to obj.disabled.
3290
+ */
3291
+ optionDisabled?: string;
3292
+ /**
3293
+ * Property name to read tooltip content from, when using
3294
+ * custom option objects.
3295
+ *
3296
+ * If omitted, falls back to obj.tooltip.
3297
+ */
3298
+ optionTooltip?: string;
3299
+ /**
3300
+ * Property name to read meta from, when using custom option objects.
3301
+ *
3302
+ * If omitted, falls back to obj.meta.
3303
+ */
3304
+ optionMeta?: string;
3305
+ /**
3306
+ * Optional custom renderer for each option.
3307
+ * Receives the normalized ToggleOption and selected state.
3308
+ */
3309
+ renderOption?: (option: ToggleOption, isSelected: boolean) => React.ReactNode;
3310
+ className?: string;
3311
+ /** Base class for all items */
3312
+ itemClassName?: string;
3313
+ /** Class applied ONLY to selected items (overrides/merges with default active styles) */
3314
+ activeClassName?: string;
3315
+ /**
3316
+ * When true, capitalizes the first letter of the label
3317
+ * (only applied when the label is a string).
3318
+ */
3319
+ autoCap?: boolean;
3320
+ /**
3321
+ * Gap between buttons in pixels.
3322
+ *
3323
+ * - Applies to both flex (horizontal/vertical) and grid layouts.
3324
+ * - If omitted, falls back to Tailwind gap classes.
3325
+ */
3326
+ gap?: number;
3327
+ }
3328
+
3329
+ /**
3330
+ * Host app MUST import Toast UI Editor CSS:
3331
+ * import "@toast-ui/editor/dist/toastui-editor.css";
3332
+ */
3333
+ type ToastToolbarItem = "heading" | "bold" | "italic" | "strike" | "hr" | "quote" | "ul" | "ol" | "task" | "indent" | "outdent" | "table" | "image" | "link" | "code" | "codeblock";
3334
+ type EditorFormat = "html" | "markdown";
3335
+ type EditorToolbar = "default" | "none" | ToastToolbarItem[][];
3336
+ interface ShadcnEditorVariantProps extends Pick<VariantBaseProps<string | undefined>, "value" | "onValue" | "error" | "disabled" | "readOnly" | "required" | "size" | "density"> {
3337
+ placeholder?: string;
3338
+ height?: string;
3339
+ previewStyle?: "vertical" | "tab";
3340
+ editType?: "wysiwyg" | "markdown";
3341
+ useCommandShortcut?: boolean;
3342
+ /** Which format to store in the form value */
3343
+ format?: EditorFormat;
3344
+ /**
3345
+ * Toolbar config:
3346
+ * - "default" uses Toast UI defaults
3347
+ * - "none" hides tools + mode switch
3348
+ * - array provides toolbarItems
3349
+ */
3350
+ toolbar?: EditorToolbar;
3351
+ /** If true, paste is intercepted and inserted as plain text only */
3352
+ pastePlainText?: boolean;
3353
+ className?: string;
3354
+ }
3355
+
3356
+ type JsonPrimitive = string | number | boolean | null;
3357
+ type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
3358
+ type JsonObject = Record<string, JsonValue>;
3359
+ type JsonPath = string;
3360
+ type JsonWildcard = string;
3361
+
3362
+ /**
3363
+ * A "variant spec" can be:
3364
+ * - a plain VariantKey ("text", "number", "toggle", ...)
3365
+ * - a variant key + props to pass into that variant
3366
+ */
3367
+ type JsonEditorVariantSpec = VariantKey | {
3368
+ variant: VariantKey;
3369
+ props?: VariantPropsFor<any>;
3370
+ };
3371
+ /**
3372
+ * Map a key-path (or wildcard) to a variant spec.
3373
+ *
3374
+ * Keys are matched against:
3375
+ * - full path: "config.apiEndpoint"
3376
+ * - leaf key: "apiEndpoint"
3377
+ *
3378
+ * Wild examples:
3379
+ * - "*api*" (segment contains "api")
3380
+ * - "config.*" (direct children)
3381
+ * - "config.**" (subtree)
3382
+ * - "**.*token*" (any route/leaf)
3383
+ */
3384
+ type JsonEditorFieldMap = Record<JsonWildcard, JsonEditorVariantSpec>;
3385
+ /**
3386
+ * Layout is scoped to a "page" (object route path).
3387
+ *
3388
+ * Each entry is a "row":
3389
+ * - string: render a single field row
3390
+ * - string[]: render these fields side-by-side (grid row)
3391
+ *
3392
+ * Example:
3393
+ * layout: {
3394
+ * "": [["projectName","version"], "description"],
3395
+ * "config": [["maxEntries","apiEndpoint"], "retry"]
3396
+ * }
3397
+ */
3398
+ type JsonEditorLayoutRow = string | string[];
3399
+ type JsonEditorLayoutMap = Record<JsonWildcard, JsonEditorLayoutRow[]>;
3400
+ interface JsonEditorFilters {
3401
+ /** Hide entire object routes/pages (navigation + rendering) */
3402
+ excludeRoutes?: JsonWildcard[];
3403
+ includeRoutes?: JsonWildcard[];
3404
+ /** Hide specific fields (by full path or leaf/wild patterns) */
3405
+ excludeFields?: JsonWildcard[];
3406
+ includeFields?: JsonWildcard[];
3407
+ /**
3408
+ * If true, excluding "config" also excludes "config.**".
3409
+ * Default: true
3410
+ */
3411
+ excludeRouteSubtree?: boolean;
3412
+ }
3413
+ /**
3414
+ * Default value for a newly created key (or a new array item).
3415
+ * Can be a constant JsonValue, or a function.
3416
+ */
3417
+ type JsonEditorDefaultValueSpec = JsonValue | ((ctx: {
3418
+ parentPath: JsonPath;
3419
+ key: string;
3420
+ current: JsonValue | undefined;
3421
+ }) => JsonValue);
3422
+ interface JsonEditorDefaults {
3423
+ /**
3424
+ * When adding a new array item, you can pick from allowed variants.
3425
+ * You can pass VariantKey or a {variant, props} spec.
3426
+ */
3427
+ array?: JsonEditorVariantSpec[];
3428
+ /**
3429
+ * Optional default values for new keys.
3430
+ * Keyed by wildcard path (full path / leaf / patterns).
3431
+ */
3432
+ values?: Record<JsonWildcard, JsonEditorDefaultValueSpec>;
3433
+ }
3434
+ type JsonEditorNavMode = "sidebar" | "tabs" | "drawer";
3435
+ interface JsonEditorNavOptions {
3436
+ mode?: JsonEditorNavMode;
3437
+ /** Show root "" as a page in navigation. Default: true */
3438
+ showRoot?: boolean;
3439
+ /** Initial active route/page. Default: "" */
3440
+ defaultRoute?: JsonPath;
3441
+ /** Optional label overrides for route nodes */
3442
+ routeLabels?: Record<JsonWildcard, React.ReactNode>;
3443
+ /** Max object nesting to generate routes for. Optional safety */
3444
+ maxDepth?: number;
3445
+ /**
3446
+ * Whether arrays containing objects can contribute routes.
3447
+ * - "none": arrays never create routes (default)
3448
+ * - "objects": array items that are objects become routes like "config.items.0"
3449
+ */
3450
+ arrayRoutes?: "none" | "objects";
3451
+ }
3452
+ interface JsonRouteNode {
3453
+ path: JsonPath;
3454
+ key: string;
3455
+ label: React.ReactNode;
3456
+ children: JsonRouteNode[];
3457
+ }
3458
+ type JsonEditorViewMode = "split" | "visual" | "raw";
3459
+ interface JsonEditorPermissions {
3460
+ canAdd?: boolean;
3461
+ canDelete?: boolean;
3462
+ canViewRaw?: boolean;
3463
+ canEditRaw?: boolean;
3464
+ /**
3465
+ * Keys/paths in this shape cannot be deleted even if canDelete is true.
3466
+ * (treated as "locked")
3467
+ */
3468
+ defaultShape?: JsonObject;
3469
+ /**
3470
+ * Optional finer-grain locks by wildcard.
3471
+ * If true, this key/path cannot be added/deleted/edited.
3472
+ */
3473
+ lockPaths?: JsonWildcard[];
3474
+ }
3475
+ type JsonEditorEditAction = "add" | "delete" | "edit" | "edit-raw";
3476
+ interface JsonEditorEditMeta {
3477
+ action: JsonEditorEditAction;
3478
+ /** the page (object route) currently being edited */
3479
+ route: JsonPath;
3480
+ /** the exact key path being changed (field path) */
3481
+ path: JsonPath;
3482
+ /** parent object path of the key */
3483
+ parent: JsonPath;
3484
+ /** leaf key (segment) */
3485
+ key: string;
3486
+ }
3487
+ interface JsonEditorCallbacks {
3488
+ onAdd?: (nextRoot: JsonObject, meta: JsonEditorEditMeta) => void;
3489
+ onDelete?: (nextRoot: JsonObject, meta: JsonEditorEditMeta) => void;
3490
+ onEdit?: (nextRoot: JsonObject, meta: JsonEditorEditMeta) => void;
3491
+ }
3492
+ interface JsonEditorResolvedField {
3493
+ path: JsonPath;
3494
+ key: string;
3495
+ value: JsonValue;
3496
+ valueType: "string" | "number" | "boolean" | "null" | "object" | "array";
3497
+ variant?: JsonEditorVariantSpec;
3498
+ hidden?: boolean;
3499
+ }
3500
+ /**
3501
+ * This is the "shared" props contract for the JSON editor variant UI.
3502
+ *
3503
+ * NOTE:
3504
+ * - `title` is purely UI (header text)
3505
+ * - `schema` is NOT a title — it’s a schema id/key for validation (later use)
3506
+ */
3507
+ interface ShadcnJsonEditorVariantProps extends Pick<VariantBaseProps<JsonObject | undefined>, "value" | "onValue" | "error" | "disabled" | "readOnly"> {
3508
+ /** Header title (UI only) */
3509
+ title?: React.ReactNode;
3510
+ /** Optional schema id/key or raw JSON Schema object for validation */
3511
+ schema?: string | JsonObject;
3512
+ /** Primary config */
3513
+ fieldMap?: JsonEditorFieldMap;
3514
+ layout?: JsonEditorLayoutMap;
3515
+ defaults?: JsonEditorDefaults;
3516
+ /** Navigation derived from JSON structure */
3517
+ nav?: JsonEditorNavOptions;
3518
+ /** include/exclude for routes + fields */
3519
+ filters?: JsonEditorFilters;
3520
+ /** permissions + locks */
3521
+ permissions?: JsonEditorPermissions;
3522
+ /** callbacks */
3523
+ callbacks?: JsonEditorCallbacks;
3524
+ /**
3525
+ * Page rendering mode:
3526
+ * - "accordion": page sections expand/collapse in main panel
3527
+ * - "popover": nested objects open as overlays (optional UX)
3528
+ */
3529
+ mode?: "accordion" | "popover";
3530
+ /**
3531
+ * Routing:
3532
+ * - route: controlled current page
3533
+ * - defaultRoute: uncontrolled initial page (overrides nav.defaultRoute)
3534
+ * - onRouteChange: called whenever the editor navigates
3535
+ */
3536
+ route?: JsonPath;
3537
+ defaultRoute?: JsonPath;
3538
+ onRouteChange?: (route: JsonPath) => void;
3539
+ /**
3540
+ * View mode (top toggle):
3541
+ * - "split": raw sidebar + visual editor (default)
3542
+ * - "visual": visual editor only
3543
+ * - "raw": raw editor only
3544
+ *
3545
+ * If viewMode is provided, it is controlled.
3546
+ * Otherwise, the defaultViewMode seeds the internal state.
3547
+ */
3548
+ viewMode?: JsonEditorViewMode;
3549
+ defaultViewMode?: JsonEditorViewMode;
3550
+ onViewModeChange?: (mode: JsonEditorViewMode) => void;
3551
+ /** Close button intent (optional). Actual close UI is controlled by the wrapper (index.tsx). */
3552
+ onClose?: () => void;
3553
+ /** Visual (editor-level styling) */
3554
+ className?: string;
3555
+ contentClassName?: string;
3556
+ navClassName?: string;
3557
+ /**
3558
+ * Optional hooks to customize nav + page rendering.
3559
+ */
3560
+ renderRouteLabel?: (ctx: {
3561
+ node: JsonRouteNode;
3562
+ active: boolean;
3563
+ }) => React.ReactNode;
3564
+ renderField?: (ctx: {
3565
+ field: JsonEditorResolvedField;
3566
+ route: JsonPath;
3567
+ }) => React.ReactNode;
3568
+ leadingIcons?: React.ReactNode[];
3569
+ trailingIcons?: React.ReactNode[];
3570
+ icon?: React.ReactNode;
3571
+ iconGap?: number;
3572
+ leadingIconSpacing?: number;
3573
+ trailingIconSpacing?: number;
3574
+ leadingControl?: React.ReactNode;
3575
+ trailingControl?: React.ReactNode;
3576
+ leadingControlClassName?: string;
3577
+ trailingControlClassName?: string;
3578
+ joinControls?: boolean;
3579
+ extendBoxToControls?: boolean;
3580
+ triggerClassName?: string;
3581
+ }
3582
+ /**
3583
+ * Wrapper mode:
3584
+ * - "popover": show trigger + popover
3585
+ * - "accordion": inline panel that can expand/collapse
3586
+ */
3587
+ type JsonEditorMode = "popover" | "accordion";
3588
+ /**
3589
+ * Typed to match your shadcn button variants/sizes.
3590
+ * If your project differs, change these unions here once.
3591
+ */
3592
+ type JsonEditorTriggerVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
3593
+ type JsonEditorTriggerSize = "default" | "sm" | "lg" | "icon";
3594
+ /**
3595
+ * Exposed ref handle from index.tsx wrapper (not the inner editor).
3596
+ * The wrapper controls popover open/close; it can also expose the inner editor ref.
3597
+ */
3598
+ interface JsonEditorIndexHandle {
3599
+ open: () => void;
3600
+ close: () => void;
3601
+ toggle: () => void;
3602
+ editor: React.RefObject<any>;
3603
+ }
3604
+ /**
3605
+ * Standalone wiring:
3606
+ * - caller provides root/onRoot directly
3607
+ */
3608
+ type JsonEditorStandaloneWiring = {
3609
+ root: JsonObject;
3610
+ onRoot: (nextRoot: JsonObject, detail?: any) => void;
3611
+ value?: never;
3612
+ onValue?: never;
3613
+ };
3614
+ /**
3615
+ * Variant wiring:
3616
+ * - InputField variant uses value/onValue
3617
+ */
3618
+ type JsonEditorVariantWiring = Pick<VariantBaseProps<JsonObject | undefined>, "value" | "onValue" | "disabled" | "readOnly" | "error" | "size" | "density"> & {
3619
+ root?: never;
3620
+ onRoot?: never;
3621
+ };
3622
+ /**
3623
+ * Props for the exported component (index.tsx):
3624
+ * - accepts standalone OR variant wiring
3625
+ * - wrapper owns mode/open/trigger UI
3626
+ * - editor-specific props are passed through, without redefining a second type list
3627
+ *
3628
+ * IMPORTANT:
3629
+ * - wrapper uses `wrapperClassName` (outer container)
3630
+ * - editor uses `className` (inner editor surface)
3631
+ */
3632
+ interface JsonEditorWrapperProps {
3633
+ /** Wrapper mode (popover vs accordion). */
3634
+ mode?: JsonEditorMode;
3635
+ /** Trigger UI (popover mode) */
3636
+ triggerLabel?: React.ReactNode;
3637
+ triggerVariant?: JsonEditorTriggerVariant;
3638
+ triggerSize?: JsonEditorTriggerSize;
3639
+ /** Popover sizing/skin */
3640
+ popoverClassName?: string;
3641
+ /** Inline/accordion container class */
3642
+ panelClassName?: string;
3643
+ /** Outer wrapper class */
3644
+ wrapperClassName?: string;
3645
+ /** Optional: controlled popover open state */
3646
+ open?: boolean;
3647
+ onOpenChange?: (open: boolean) => void;
3648
+ /** Accessibility (useful when rendered as an InputField variant) */
3649
+ id?: string;
3650
+ describedBy?: string;
3651
+ /** Called when the wrapper closes (popover close / accordion hide). */
3652
+ onClose?: () => void;
3653
+ }
3654
+ /**
3655
+ * Single source of truth for what index.tsx accepts:
3656
+ * - (standalone OR variant wiring)
3657
+ * - wrapper props
3658
+ * - editor props (minus a few keys owned by wrapper)
3659
+ */
3660
+ type ShadcnJsonEditorProps = (JsonEditorStandaloneWiring | JsonEditorVariantWiring) & JsonEditorWrapperProps & Omit<ShadcnJsonEditorVariantProps, "onValue" | "value" | "disabled" | "readOnly" | "error" | "size" | "density" | "onClose">;
3661
+
3662
+ type ListerId = string | number;
3663
+ type ListerMode = "single" | "multiple";
3664
+ type ListerSearchMode = "local" | "remote" | "hybrid";
3665
+ type ListerOpenReason = "apply" | "cancel" | "close" | "denied" | "error";
3666
+ type ListerSessionId = string;
3667
+ /** Extraction selector:
3668
+ * - string form is runtime-only (dot-path); not type-checked
3669
+ * - function form is typed and must return an array
3670
+ */
3671
+ type Selector<TRaw> = string | ((body: any) => TRaw[]);
3672
+ /** Resolver:
3673
+ * - string form is runtime-only (dot-path relative to raw item)
3674
+ * - function form is fully typed
3675
+ */
3676
+ type Resolver<TOut, TRaw, TCtx = any> = string | ((raw: TRaw, ctx: TCtx) => TOut);
3677
+ type OpenAnchor = {
3678
+ x: number;
3679
+ y: number;
3680
+ } | {
3681
+ clientX: number;
3682
+ clientY: number;
3683
+ } | any;
3684
+ type ListerChangeEvent = {
3685
+ preventDefault(): void;
3686
+ defaultPrevented: boolean;
3687
+ };
3688
+ type ListerLogLevel = "info" | "success" | "warning" | "error";
3689
+ type ListerLogCode = "lister.access_denied" | "lister.fetch_failed" | "lister.extract_not_array" | "lister.mapping_failed" | "lister.unknown_error";
3690
+ type ListerLogEntry = {
3691
+ level: ListerLogLevel;
3692
+ code: ListerLogCode;
3693
+ message: string;
3694
+ details?: Record<string, unknown>;
3695
+ ui?: {
3696
+ mode?: "toast" | "banner" | "dialog";
3697
+ group?: string;
3698
+ autoCloseMs?: number | null;
3699
+ };
3700
+ };
3701
+ type ListerPermissionCtx = {
3702
+ kind?: string;
3703
+ endpoint?: string;
3704
+ filters?: any;
3705
+ sessionId?: ListerSessionId;
3706
+ };
3707
+ interface ListerProviderHost {
3708
+ /** Host decides permission logic. Mandatory permissions end with '!' */
3709
+ can: (permissions: string[], ctx: ListerPermissionCtx) => boolean;
3710
+ /** Host decides notification/diagnostic surface */
3711
+ log: (entry: ListerLogEntry) => void;
3712
+ }
3713
+ type ListerOption<TRaw, TValue extends ListerId, TMeta = unknown> = {
3714
+ value: TValue;
3715
+ label: any;
3716
+ icon?: any;
3717
+ description?: any;
3718
+ disabled?: boolean;
3719
+ group?: string;
3720
+ meta?: TMeta;
3721
+ /** optional raw passthrough (implementation choice) */
3722
+ raw?: TRaw;
3723
+ };
3724
+ type ListerMapping<TRaw, TValue extends ListerId, TMeta = unknown, TCtx = any> = {
3725
+ optionValue: Resolver<TValue, TRaw, TCtx>;
3726
+ /** default: raw.label ?? String(value) */
3727
+ optionLabel?: Resolver<any, TRaw, TCtx>;
3728
+ optionIcon?: Resolver<any, TRaw, TCtx>;
3729
+ optionDescription?: Resolver<any, TRaw, TCtx>;
3730
+ optionDisabled?: Resolver<boolean, TRaw, TCtx>;
3731
+ optionGroup?: Resolver<string, TRaw, TCtx>;
3732
+ optionMeta?: Resolver<TMeta, TRaw, TCtx>;
3733
+ };
3734
+ type ListerSource<TFilters = unknown> = {
3735
+ endpoint: string;
3736
+ method?: "GET" | "POST";
3737
+ /** Optional custom mapping of filters/query/cursor into request params/body/headers */
3738
+ buildRequest?: (args: {
3739
+ filters?: TFilters;
3740
+ query?: string;
3741
+ cursor?: string | null;
3742
+ }) => {
3743
+ params?: any;
3744
+ body?: any;
3745
+ headers?: any;
3746
+ };
3747
+ };
3748
+ type ListerSearchSpec<TColumn extends string = string> = {
3749
+ /**
3750
+ * Columns the UI can offer as "Subject" (search in one column).
3751
+ * Example: ["name", "email", "status"]
3752
+ */
3753
+ subjects?: readonly TColumn[];
3754
+ /**
3755
+ * Columns the UI can offer as "Search only" (search across multiple columns).
3756
+ * Example: ["name", "email"]
3757
+ */
3758
+ only?: readonly TColumn[];
3759
+ /**
3760
+ * Allow user to type a column name that isn't in `subjects`.
3761
+ * (Your backend still decides whether to accept/reject it.)
3762
+ */
3763
+ allowCustomSubject?: boolean;
3764
+ /**
3765
+ * Allow user to add custom column names to the multi-field "only" list.
3766
+ * Useful if you want advanced users to target niche columns.
3767
+ */
3768
+ allowCustomOnly?: boolean;
3769
+ /**
3770
+ * Whether the UI can show a "Search all" option.
3771
+ * (Semantics depend on your backend: usually "all text columns".)
3772
+ */
3773
+ allowAll?: boolean;
3774
+ /**
3775
+ * Optional UI hints (totally optional, but nice for button labels/placeholders).
3776
+ */
3777
+ ui?: {
3778
+ placeholder?: string;
3779
+ subjectLabel?: string;
3780
+ allLabel?: string;
3781
+ onlyLabel?: string;
3782
+ customSubjectLabel?: string;
3783
+ };
3784
+ default?: string;
3785
+ };
3786
+ type ListerDefinition<TRaw, TValue extends ListerId, TFilters = unknown, TMeta = unknown, TCtx = any, TSearchColumn extends string = string> = {
3787
+ /** optional stable id used by presets */
3788
+ id?: string;
3789
+ source: ListerSource<TFilters>;
3790
+ /** If missing: default extraction uses body.data (runtime). Must produce an array. */
3791
+ selector?: Selector<TRaw>;
3792
+ /** How raw item maps into selectable option */
3793
+ mapping: ListerMapping<TRaw, TValue, TMeta, TCtx>;
3794
+ /**
3795
+ * Search configuration:
3796
+ * - defines which columns are searchable (subject + multi-field)
3797
+ * - optionally allows custom column names (advanced mode)
3798
+ */
3799
+ search?: ListerSearchSpec<TSearchColumn>;
3800
+ };
3801
+ type ListerFilterApplyMode = "replace" | "merge" | "unset";
3802
+ type ListerFilterApply<TFilters, K extends keyof TFilters & string> = {
3803
+ key: K;
3804
+ mode?: ListerFilterApplyMode;
3805
+ value?: TFilters[K];
3806
+ toggleable?: boolean;
3807
+ /** optional: clicking cycles through these states */
3808
+ cycle?: Array<{
3809
+ mode: "unset";
3810
+ } | {
3811
+ mode: "replace";
3812
+ value: TFilters[K];
3813
+ } | {
3814
+ mode: "merge";
3815
+ value: Partial<TFilters>;
3816
+ }>;
3817
+ };
3818
+ type ListerFilterCtx<TFilters> = {
3819
+ /** base filters passed to open() */
3820
+ base: TFilters | undefined;
3821
+ /** only what filter controls changed */
3822
+ patch: Partial<TFilters>;
3823
+ /** base + patch (merged) */
3824
+ effective: TFilters | undefined;
3825
+ set<K extends keyof TFilters & string>(key: K, value: TFilters[K]): void;
3826
+ merge(patch: Partial<TFilters>): void;
3827
+ unset<K extends keyof TFilters & string>(key: K): void;
3828
+ clear(): void;
3829
+ /** triggers refetch using current effective filters */
3830
+ refresh(): void;
3831
+ /** reads from effective (base overridden by patch) */
3832
+ get<K extends keyof TFilters & string>(key: K): TFilters[K] | undefined;
3833
+ };
3834
+ type ListerFilterBindKey<TFilters> = keyof TFilters & string;
3835
+ /**
3836
+ * OPTIONAL inline input filter config
3837
+ * (value comes from the input; not from option.value)
3838
+ */
3839
+ type ListerFilterInput<TFilters> = {
3840
+ /** If omitted, falls back to option.bindKey (or parent bindKey). */
3841
+ bindKey?: ListerFilterBindKey<TFilters>;
3842
+ variant: VariantKey;
3843
+ props?: VariantPropsFor<any>;
3844
+ mode?: "replace" | "merge";
3845
+ unsetOnEmpty?: boolean;
3846
+ };
3847
+ type FilterNodeBase<TFilters> = {
3848
+ /**
3849
+ * UI identity (must be unique in the tree)
3850
+ * Example: "status", "status.active", "pricing.minMax"
3851
+ */
3852
+ id: string | number;
3853
+ label?: any;
3854
+ icon?: any;
3855
+ description?: any;
3856
+ disabled?: boolean;
3857
+ /**
3858
+ * Column/reference key (DB filter key).
3859
+ * Example: "status"
3860
+ *
3861
+ * Typically set on a group node and inherited by children.
3862
+ */
3863
+ bindKey?: ListerFilterBindKey<TFilters>;
3864
+ /**
3865
+ * Optional custom render (advanced).
3866
+ * (Still works, but now you can also use kind="input" for most cases.)
3867
+ */
3868
+ render?: (args: {
3869
+ option: ListerFilterOption<TFilters>;
3870
+ ctx: ListerFilterCtx<TFilters>;
3871
+ state: {
3872
+ open: boolean;
3873
+ selected: boolean;
3874
+ };
3875
+ actions: {
3876
+ close(): void;
3877
+ };
3878
+ }) => any;
3879
+ };
3880
+ type ListerFilterGroupOption<TFilters> = FilterNodeBase<TFilters> & {
3881
+ kind: "group";
3882
+ children: Array<ListerFilterOption<TFilters>>;
3883
+ apply?: never;
3884
+ input?: never;
3885
+ value?: never;
3886
+ };
3887
+ type ListerFilterValueOption<TFilters, TValue = string | number> = FilterNodeBase<TFilters> & {
3888
+ kind: "value";
3889
+ /**
3890
+ * Actual DB value. Example: "active"
3891
+ * (NOT "status.active")
3892
+ */
3893
+ value: TValue;
3894
+ /**
3895
+ * Optional: clicking this item applies/unapplies it.
3896
+ * If apply.value is omitted => defaults to option.value
3897
+ */
3898
+ apply?: ListerFilterApply<TFilters, any>;
3899
+ children?: never;
3900
+ input?: never;
3901
+ };
3902
+ type ListerFilterInputOption<TFilters> = FilterNodeBase<TFilters> & {
3903
+ kind: "input";
3904
+ /**
3905
+ * Value comes from the input; binds to bindKey (option.bindKey or input.bindKey).
3906
+ */
3907
+ input: ListerFilterInput<TFilters>;
3908
+ children?: never;
3909
+ apply?: never;
3910
+ value?: never;
3911
+ };
3912
+ type ListerFilterOption<TFilters> = ListerFilterGroupOption<TFilters> | ListerFilterValueOption<TFilters> | ListerFilterInputOption<TFilters>;
3913
+ type ListerFilterSpec<TFilters> = {
3914
+ /** TreeSelect options */
3915
+ options: Array<ListerFilterOption<TFilters>>;
3916
+ /** Merge base + patch into effective */
3917
+ merge?: (base: TFilters | undefined, patch: Partial<TFilters>) => TFilters;
3918
+ /** Default: true. If true, any ctx.set/merge/unset triggers a fetch */
3919
+ autoFetch?: boolean;
3920
+ };
3921
+ type ListerValueForMode<TValue extends ListerId, TMode extends ListerMode> = TMode extends "multiple" ? TValue[] : TValue | null;
3922
+ type ListerRawForMode<TRaw, TMode extends ListerMode> = TMode extends "multiple" ? TRaw[] : TRaw | null;
3923
+ type ListerOptionsForMode<TRaw, TValue extends ListerId, TMeta, TMode extends ListerMode> = TMode extends "multiple" ? Array<ListerOption<TRaw, TValue, TMeta>> : ListerOption<TRaw, TValue, TMeta> | null;
3924
+ type ListerDetails<TRaw, TValue extends ListerId, TMeta, TMode extends ListerMode> = {
3925
+ /** Selected mapped options (array in multiple, single option/null in single) */
3926
+ options: ListerOptionsForMode<TRaw, TValue, TMeta, TMode>;
3927
+ /** Selected raw backend item(s) (array only in multiple mode) */
3928
+ raw: ListerRawForMode<TRaw, TMode>;
3929
+ /** Live change semantic while open */
3930
+ action: "select" | "deselect" | "clear" | "init";
3931
+ };
3932
+ type ListerOpenOptions<TRaw, TValue extends ListerId, TFilters, TMeta, TMode extends ListerMode = "single"> = {
3933
+ /** Mode defaults to "single" */
3934
+ mode?: TMode;
3935
+ /** Single-mode only: if true => Apply/Cancel UI, draft selection until Apply */
3936
+ confirm?: TMode extends "single" ? boolean : never;
3937
+ /** Initial selection when opened (draft seed) */
3938
+ defaultValue?: ListerValueForMode<TValue, TMode>;
3939
+ /** Permission entries; mandatory end with '!' */
3940
+ permissions?: string[];
3941
+ /** Search behaviour */
3942
+ searchMode?: ListerSearchMode;
3943
+ initialQuery?: string;
3944
+ /** UI */
3945
+ title?: string;
3946
+ draggable?: boolean;
3947
+ anchor?: OpenAnchor;
3948
+ /** Refresh */
3949
+ showRefresh?: boolean;
3950
+ refreshMode?: "preserve-selection" | "clear-missing" | "clear-all";
3951
+ /** Filters control (TreeSelect-native options; render supported) */
3952
+ filtersSpec?: ListerFilterSpec<TFilters>;
3953
+ /** Custom row renderer */
3954
+ renderOption?: (args: {
3955
+ option: ListerOption<TRaw, TValue, TMeta>;
3956
+ state: {
3957
+ selected: boolean;
3958
+ active: boolean;
3959
+ mode: TMode;
3960
+ };
3961
+ actions: {
3962
+ toggle(): void;
3963
+ select(): void;
3964
+ deselect(): void;
3965
+ };
3966
+ ctx: {
3967
+ query?: string;
3968
+ filters?: TFilters;
3969
+ };
3970
+ }) => any;
3971
+ /** Live change hook (sync + veto) */
3972
+ onChange?: (value: ListerValueForMode<TValue, TMode>, details: ListerDetails<TRaw, TValue, TMeta, TMode>, e: ListerChangeEvent) => void;
3973
+ };
3974
+ type ListerOpenResult<TRaw, TValue extends ListerId, TMeta, TMode extends ListerMode> = {
3975
+ reason: ListerOpenReason;
3976
+ value: ListerValueForMode<TValue, TMode>;
3977
+ details: {
3978
+ options: ListerOptionsForMode<TRaw, TValue, TMeta, TMode>;
3979
+ raw: ListerRawForMode<TRaw, TMode>;
3980
+ action: "apply" | "cancel" | "close" | "denied" | "error";
3981
+ errorCode?: string;
3982
+ /** Useful in multi-session UI: which popover resolved */
3983
+ sessionId?: ListerSessionId;
3984
+ };
3985
+ };
3986
+ type PresetMap = Record<string, ListerDefinition<any, any, any, any, any>>;
3987
+ type PresetRaw<P extends PresetMap, K extends keyof P> = P[K] extends ListerDefinition<infer R, any, any, any, any> ? R : never;
3988
+ type PresetValue<P extends PresetMap, K extends keyof P> = P[K] extends ListerDefinition<any, infer V, any, any, any> ? V : never;
3989
+ type PresetFilters<P extends PresetMap, K extends keyof P> = P[K] extends ListerDefinition<any, any, infer F, any, any> ? F : never;
3990
+ type PresetMeta<P extends PresetMap, K extends keyof P> = P[K] extends ListerDefinition<any, any, any, infer M, any> ? M : never;
3991
+ interface ListerApi<P extends PresetMap> {
3992
+ /** Fetch data without opening session */
3993
+ fetch<K extends keyof P>(kind: K, filters?: PresetFilters<P, K>, opts?: {
3994
+ query?: string;
3995
+ }): Promise<{
3996
+ raw: PresetRaw<P, K>[];
3997
+ options: Array<ListerOption<PresetRaw<P, K>, PresetValue<P, K>, PresetMeta<P, K>>>;
3998
+ }>;
3999
+ /** Fetch data without opening session (custom definition) */
4000
+ fetch<TRaw, TValue extends ListerId, TFilters = unknown, TMeta = unknown>(def: ListerDefinition<TRaw, TValue, TFilters, TMeta>, filters?: TFilters, opts?: {
4001
+ query?: string;
4002
+ }): Promise<{
4003
+ raw: TRaw[];
4004
+ options: Array<ListerOption<TRaw, TValue, TMeta>>;
4005
+ }>;
4006
+ /** Open via preset kind */
4007
+ open<K extends keyof P, TMode extends ListerMode = "single">(kind: K, filters?: PresetFilters<P, K>, opts?: ListerOpenOptions<PresetRaw<P, K>, PresetValue<P, K>, PresetFilters<P, K>, PresetMeta<P, K>, TMode> & {
4008
+ mode?: TMode;
4009
+ }): Promise<ListerOpenResult<PresetRaw<P, K>, PresetValue<P, K>, PresetMeta<P, K>, TMode>>;
4010
+ /** Open via custom definition */
4011
+ open<TRaw, TValue extends ListerId, TFilters = unknown, TMeta = unknown, TMode extends ListerMode = "single">(def: ListerDefinition<TRaw, TValue, TFilters, TMeta>, filters?: TFilters, opts?: ListerOpenOptions<TRaw, TValue, TFilters, TMeta, TMode> & {
4012
+ mode?: TMode;
4013
+ }): Promise<ListerOpenResult<TRaw, TValue, TMeta, TMode>>;
4014
+ /** Optional preset registry helpers */
4015
+ registerPreset?: (kind: string, def: ListerDefinition<any, any, any, any, any>) => void;
4016
+ getPreset?: (kind: string) => ListerDefinition<any, any, any, any, any> | undefined;
4017
+ }
4018
+ type ListerSessionState<TRaw, TValue extends ListerId, TFilters, TMeta, TMode extends ListerMode> = {
4019
+ sessionId: ListerSessionId;
4020
+ createdAt: number;
4021
+ isOpen: boolean;
4022
+ kind?: string;
4023
+ definition?: ListerDefinition<TRaw, TValue, TFilters, TMeta>;
4024
+ filters?: TFilters;
4025
+ permissions?: string[];
4026
+ mode: TMode;
4027
+ confirm: TMode extends "single" ? boolean : true;
4028
+ title?: string;
4029
+ draggable: boolean;
4030
+ position: {
4031
+ x: number;
4032
+ y: number;
4033
+ } | null;
4034
+ hasMoved: boolean;
4035
+ searchMode: ListerSearchMode;
4036
+ query: string;
4037
+ loading: boolean;
4038
+ refreshing: boolean;
4039
+ errorCode?: ListerLogCode | string;
4040
+ rawList: TRaw[];
4041
+ optionsList: Array<ListerOption<TRaw, TValue, TMeta>>;
4042
+ draftValue: ListerValueForMode<TValue, TMode>;
4043
+ refreshMode: "preserve-selection" | "clear-missing" | "clear-all";
4044
+ _resolve?: (result: ListerOpenResult<TRaw, TValue, TMeta, TMode>) => void;
4045
+ };
4046
+ type ListerRuntimeState<TRaw, TValue extends ListerId, TFilters, TMeta, TMode extends ListerMode> = ListerSessionState<TRaw, TValue, TFilters, TMeta, TMode> & {
4047
+ /** Used to revert on cancel/close (recommended behaviour) */
4048
+ initialDraftValue: ListerValueForMode<TValue, TMode>;
4049
+ /** Stored from open() opts for later UI + provider logic */
4050
+ onChange?: (value: ListerValueForMode<TValue, TMode>, details: ListerDetails<TRaw, TValue, TMeta, TMode>, e: ListerChangeEvent) => void;
4051
+ /** UI hook for later */
4052
+ renderOption?: ListerOpenOptions<TRaw, TValue, TFilters, TMeta, TMode>["renderOption"];
4053
+ /** UI flag for later */
4054
+ showRefresh?: boolean;
4055
+ /** Filters config + runtime patch/effective (provider-owned) */
4056
+ filtersSpec?: ListerFilterSpec<TFilters>;
4057
+ filtersPatch?: Partial<TFilters>;
4058
+ effectiveFilters?: TFilters;
4059
+ /** Optional UI convenience */
4060
+ selectedFilterValues?: Array<string | number>;
4061
+ /** Derived from def.search (used for UI to render subjects/only/all rules) */
4062
+ searchSpec?: ListerSearchSpec<string>;
4063
+ /** Persisted user selection (subject/all/only) */
4064
+ searchTarget?: ListerSearchTarget;
4065
+ };
4066
+ type ListerStoreState = {
4067
+ /** rendering order (last = topmost) */
4068
+ order: ListerSessionId[];
4069
+ /** active/focused session */
4070
+ activeId?: ListerSessionId;
4071
+ /** sessions registry */
4072
+ sessions: Record<ListerSessionId, ListerRuntimeState<any, any, any, any, any>>;
4073
+ };
4074
+ type ListerSearchTarget = {
4075
+ /**
4076
+ * "all" => backend receives `searchAll=true`
4077
+ * "subject"=> backend receives `subject=<col>`
4078
+ * "only" => backend receives `searchOnly=[...]`
4079
+ */
4080
+ mode: "all" | "subject" | "only";
4081
+ subject?: string | null;
4082
+ only?: Array<string | number> | null;
4083
+ };
4084
+ type ListerSearchPayload = {
4085
+ subject?: string;
4086
+ searchAll?: boolean;
4087
+ searchOnly?: Array<string | number>;
4088
+ };
4089
+
4090
+ type ListerFieldBaseProps<TValue> = {
4091
+ value: TValue;
4092
+ onValue: (next: TValue, detail?: any) => void;
4093
+ disabled?: boolean;
4094
+ readOnly?: boolean;
4095
+ className?: string;
4096
+ placeholder?: string;
4097
+ };
4098
+ /**
4099
+ * Key or function mapping.
4100
+ * IMPORTANT: ctx-aware because your engine mapping functions receive (raw, ctx).
4101
+ */
4102
+ type KeyOrFn<TRaw, TOut, TCtx = any> = (keyof TRaw & string) | ((raw: TRaw, ctx: TCtx) => TOut);
4103
+ type ListerSize = "sm" | "md" | "lg";
4104
+ type ListerDensity = "compact" | "comfortable" | "loose";
4105
+ type ListerTriggerRenderCtx<TRaw extends Record<string, any>, TValue extends ListerId, TMeta, TMode extends ListerMode> = {
4106
+ mode: TMode;
4107
+ value: any;
4108
+ selectedOptions: any[] | null;
4109
+ placeholder: string;
4110
+ maxDisplayItems: number;
4111
+ display: React.ReactNode;
4112
+ disabled?: boolean;
4113
+ readOnly?: boolean;
4114
+ isOpen: boolean;
4115
+ /** convenience */
4116
+ disabledTrigger: boolean;
4117
+ hasValue: boolean;
4118
+ /** convenience actions */
4119
+ clear(): void;
4120
+ open(): void;
4121
+ };
4122
+ type ListerVariantProps<P extends PresetMap = PresetMap, TRaw extends Record<string, any> = any, TValue extends ListerId = any, TFilters extends Record<string, any> = Record<string, any>, TMeta = any, TMode extends ListerMode = "single", TCtx = any, TSearchColumn extends string = string> = ListerFieldBaseProps<ListerValueForMode<TValue, TMode>> & {
4123
+ host?: ListerProviderHost;
4124
+ presets?: P;
4125
+ remoteDebounceMs?: number;
4126
+ /** Final/base definition */
4127
+ def?: ListerDefinition<TRaw, TValue, TFilters, TMeta, TCtx, TSearchColumn>;
4128
+ /** Inline overrides (any one can exist => inline exists) */
4129
+ endpoint?: string;
4130
+ method?: "GET" | "POST";
4131
+ buildRequest?: ListerSource<TFilters>["buildRequest"];
4132
+ selector?: Selector<TRaw>;
4133
+ /** ✅ Search spec override (feeds session.searchSpec) */
4134
+ search?: ListerDefinition<TRaw, TValue, TFilters, TMeta, TCtx, TSearchColumn>["search"];
4135
+ /**
4136
+ * ✅ Initial/seed search target (feeds session.searchTarget)
4137
+ * If omitted, provider will derive it from `search.default` (if present) or fallback.
4138
+ */
4139
+ searchTarget?: ListerSearchTarget;
4140
+ /** Inline mapping overrides (ctx-aware) */
4141
+ optionValue?: KeyOrFn<TRaw, TValue, TCtx>;
4142
+ optionLabel?: KeyOrFn<TRaw, string, TCtx>;
4143
+ optionIcon?: KeyOrFn<TRaw, any, TCtx>;
4144
+ optionDescription?: KeyOrFn<TRaw, string, TCtx>;
4145
+ optionDisabled?: KeyOrFn<TRaw, boolean, TCtx>;
4146
+ optionGroup?: KeyOrFn<TRaw, string, TCtx>;
4147
+ optionMeta?: KeyOrFn<TRaw, TMeta, TCtx>;
4148
+ filters?: TFilters;
4149
+ mode?: TMode;
4150
+ confirm?: TMode extends "single" ? boolean : never;
4151
+ permissions?: string[];
4152
+ title?: string;
4153
+ searchMode?: ListerOpenOptions<TRaw, TValue, TFilters, TMeta, TMode>["searchMode"];
4154
+ initialQuery?: string;
4155
+ showRefresh?: boolean;
4156
+ refreshMode?: ListerOpenOptions<TRaw, TValue, TFilters, TMeta, TMode>["refreshMode"];
4157
+ /** Filters */
4158
+ filtersSpec?: ListerFilterSpec<TFilters>;
4159
+ renderOption?: ListerOpenOptions<TRaw, TValue, TFilters, TMeta, TMode>["renderOption"];
4160
+ maxDisplayItems?: number;
4161
+ renderTrigger?: (ctx: ListerTriggerRenderCtx<TRaw, TValue, TMeta, TMode>) => React.ReactElement;
4162
+ size?: ListerSize;
4163
+ density?: ListerDensity;
4164
+ clearable?: boolean;
4165
+ leadingIcons?: React.ReactNode[];
4166
+ trailingIcons?: React.ReactNode[];
4167
+ icon?: React.ReactNode;
4168
+ iconGap?: number;
4169
+ leadingIconSpacing?: number;
4170
+ trailingIconSpacing?: number;
4171
+ leadingControl?: React.ReactNode;
4172
+ trailingControl?: React.ReactNode;
4173
+ leadingControlClassName?: string;
4174
+ trailingControlClassName?: string;
4175
+ joinControls?: boolean;
4176
+ extendBoxToControls?: boolean;
4177
+ panelClassName?: string;
4178
+ contentClassName?: string;
4179
+ };
4180
+
4181
+ /**
4182
+ * Helper type for a single variant registry entry.
4183
+ *
4184
+ * Keeps the shape consistent and easy to extend via declaration merging.
4185
+ */
4186
+ interface VariantEntry<TValue, TProps> {
4187
+ value: TValue;
4188
+ props: TProps;
4189
+ }
4190
+ /**
4191
+ * Base type-level variant registry.
4192
+ *
4193
+ * This is the **canonical mapping** used by:
4194
+ * - InputFieldProps<K>
4195
+ * - VariantModule<K>
4196
+ *
4197
+ * Hosts & presets extend it via declaration merging:
4198
+ *
4199
+ * declare module "@/schema/variant" {
4200
+ * interface Variants {
4201
+ * select: VariantEntry<SelectValuePublic, SelectPropsPublic>;
4202
+ * }
4203
+ * }
4204
+ */
4205
+ interface Variants<H = unknown> {
4206
+ /**
4207
+ * Built-in "text" variant.
4208
+ *
4209
+ * Shadcn-based implementation lives in presets/shadcn-variants/text.tsx
4210
+ */
4211
+ text: VariantEntry<string | undefined, ShadcnTextVariantProps>;
4212
+ /**
4213
+ * Example scalar variant.
4214
+ *
4215
+ * You can repurpose this for "custom" or drop it later.
4216
+ */
4217
+ number: VariantEntry<number | undefined, ShadcnNumberVariantProps>;
4218
+ phone: VariantEntry<string | number | undefined, ShadcnPhoneVariantProps>;
4219
+ color: VariantEntry<string | undefined, ShadcnColorVariantProps>;
4220
+ password: VariantEntry<string | undefined, ShadcnPasswordVariantProps>;
4221
+ date: VariantEntry<string | undefined, ShadcnDateVariantProps>;
4222
+ chips: VariantEntry<string[] | undefined, ShadcnChipsVariantProps>;
4223
+ textarea: VariantEntry<string | undefined, ShadcnTextareaVariantProps>;
4224
+ toggle: VariantEntry<boolean | undefined, ShadcnToggleVariantProps$1>;
4225
+ 'toggle-group': VariantEntry<any | undefined, ShadcnToggleVariantProps>;
4226
+ radio: VariantEntry<unknown | undefined, ShadcnRadioVariantProps<unknown, H>>;
4227
+ checkbox: VariantEntry<CheckboxVariantPublicValue, ShadcnCheckboxVariantPublicProps>;
4228
+ select: VariantEntry<string | number | undefined, SelectVariantProps>;
4229
+ 'multi-select': VariantEntry<Array<string | number> | undefined, ShadcnMultiSelectVariantProps>;
4230
+ slider: VariantEntry<SliderValue, ShadcnSliderVariantProps>;
4231
+ keyvalue: VariantEntry<KeyValueMap | undefined, ShadcnKeyValueVariantProps>;
4232
+ custom: VariantEntry<unknown | undefined, ShadcnCustomVariantProps>;
4233
+ treeselect: VariantEntry<string | number | undefined, ShadcnTreeSelectVariantProps>;
4234
+ file: VariantEntry<FileLike, ShadcnFileVariantProps>;
4235
+ editor: VariantEntry<string | undefined, ShadcnEditorVariantProps>;
4236
+ 'json-editor': VariantEntry<JsonObject | undefined, ShadcnJsonEditorProps>;
4237
+ lister: VariantEntry<any | undefined, ListerVariantProps>;
4238
+ }
4239
+ /**
4240
+ * Union of all variant keys.
4241
+ */
4242
+ type VariantKey = keyof Variants;
4243
+ /**
4244
+ * Value type for a given variant key.
4245
+ *
4246
+ * Strongly drives autocomplete:
4247
+ * - InputFieldProps<"text"> → TValue = string | undefined
4248
+ */
4249
+ type VariantValueFor<K extends VariantKey, H = unknown> = Variants<H>[K]["value"];
4250
+ /**
4251
+ * Props type for a given variant key.
4252
+ *
4253
+ * Strongly drives autocomplete:
4254
+ * - InputFieldProps<"text"> → props = TextVariantProps
4255
+ */
4256
+ type VariantPropsFor<K extends VariantKey, H = unknown> = Variants<H>[K]["props"];
4257
+ /**
4258
+ * Signature for variant-level validation functions.
4259
+ */
4260
+ type VariantValidateFn<TValue, TProps> = (value: TValue | undefined, ctx: {
4261
+ required?: boolean;
4262
+ props: TProps;
4263
+ field: Field;
4264
+ form: CoreContext<Dict>;
4265
+ }) => ValidateResult;
4266
+ /**
4267
+ * Layout defaults for a variant.
4268
+ *
4269
+ * This extends FieldLayoutConfig, so it automatically includes:
4270
+ * - placement props (labelPlacement, descriptionPlacement, etc.)
4271
+ * - layout hints (inline, fullWidth, defaultSize/density)
4272
+ * - layout graph (relativeRoots, ordering)
4273
+ */
4274
+ interface VariantLayoutDefaults extends FieldLayoutConfig {
4275
+ }
4276
+ /**
4277
+ * Runtime module definition for a variant.
4278
+ *
4279
+ * IMPORTANT:
4280
+ * - This is **tied directly** to the registry:
4281
+ * TValue = VariantValueFor<K>
4282
+ * TProps = VariantPropsFor<K>
4283
+ *
4284
+ * So if you change the entry in `Variants`, both:
4285
+ * - <InputField variant="..." /> props
4286
+ * - The Variant component in the module
4287
+ * will see the updated types and IntelliSense matches everywhere.
4288
+ *
4289
+ * - For complex variants (select/multiselect):
4290
+ * you model the relationship via unions in `Variants["select"]`.
4291
+ */
4292
+ interface VariantModule<K extends VariantKey = VariantKey> {
4293
+ /**
4294
+ * Unique key for this variant, e.g. "text", "number", "select".
4295
+ */
4296
+ variant: K;
4297
+ /**
4298
+ * React component that renders the control.
4299
+ *
4300
+ * It receives:
4301
+ * - VariantBaseProps<VariantValueFor<K>>
4302
+ * - VariantPropsFor<K>
4303
+ */
4304
+ Variant: ComponentType<VariantBaseProps<VariantValueFor<K>> & VariantPropsFor<K>>;
4305
+ /**
4306
+ * Optional validation logic specific to this variant.
4307
+ */
4308
+ validate?: VariantValidateFn<VariantValueFor<K>, VariantPropsFor<K>>;
4309
+ /**
4310
+ * Optional default layout hints for this variant.
4311
+ */
4312
+ defaults?: {
4313
+ layout?: VariantLayoutDefaults;
4314
+ };
4315
+ /**
4316
+ * Optional smart layout resolver.
4317
+ *
4318
+ * Must respect host overrides.
4319
+ */
4320
+ resolveLayout?: LayoutResolver<VariantPropsFor<K>>;
4321
+ /**
4322
+ * Optional metadata, useful for docs/inspectors.
4323
+ */
4324
+ meta?: {
4325
+ label?: string;
4326
+ description?: string;
4327
+ tags?: string[];
4328
+ };
4329
+ }
4330
+ /**
4331
+ * Convenience alias when you want to be explicit:
4332
+ *
4333
+ * const textModule: VariantModuleFor<"text"> = { ... }
4334
+ */
4335
+ type VariantModuleFor<K extends VariantKey> = VariantModule<K>;
4336
+
4337
+ export { type ListerDefinition as $, type Variants as A, type BaseProps$7 as B, type CoreProps as C, type Dict as D, type ErrorTextPlacement as E, type Field as F, type VariantValidateFn as G, type HelpTextPlacement as H, InputNumber as I, type VariantLayoutDefaults as J, type VariantModuleFor as K, type LabelPlacement as L, type ListerProviderHost as M, type ListerApi as N, type ListerStoreState as O, type PresetMap as P, type ListerRuntimeState as Q, type RelativeRootsMap as R, type SublabelPlacement as S, Textarea as T, type ListerSessionId as U, type VariantKey as V, type ListerId as W, type ListerSearchMode as X, type ListerSearchTarget as Y, type ListerSearchPayload as Z, type ListerFilterCtx as _, type CoreContext as a, type ListerMode as a0, type ShadcnJsonEditorProps as a1, type JsonEditorIndexHandle as a2, type ListerOpenReason as a3, type Selector as a4, type Resolver as a5, type OpenAnchor as a6, type ListerChangeEvent as a7, type ListerLogLevel as a8, type ListerLogCode as a9, type ListerLogEntry as aa, type ListerPermissionCtx as ab, type ListerOption as ac, type ListerMapping as ad, type ListerSource as ae, type ListerSearchSpec as af, type ListerFilterApplyMode as ag, type ListerFilterApply as ah, type ListerFilterBindKey as ai, type ListerFilterInput as aj, type ListerFilterGroupOption as ak, type ListerFilterValueOption as al, type ListerFilterInputOption as am, type ListerFilterOption as an, type ListerFilterSpec as ao, type ListerValueForMode as ap, type ListerRawForMode as aq, type ListerOptionsForMode as ar, type ListerDetails as as, type ListerOpenOptions as at, type ListerOpenResult as au, type PresetRaw as av, type PresetValue as aw, type PresetFilters as ax, type PresetMeta as ay, type ListerSessionState as az, type DescriptionPlacement as b, type SlotPlacement as c, type FieldSize as d, type FieldDensity as e, type ValidateResult as f, type ChangeDetail as g, type VariantValueFor as h, type VariantPropsFor as i, type VariantModule as j, type InferFromSchema as k, type SubmitEvent as l, type FormProps as m, type ValuesResult as n, type InputStore as o, type ButtonRef as p, type FieldSlots as q, type FieldRoots as r, type FieldSlotId as s, type FieldRootId as t, type FieldOrdering as u, type FieldLayoutConfig as v, type EffectiveFieldLayout as w, type LayoutResolveContext as x, type LayoutResolver as y, type VariantEntry as z };