@timeax/form-palette 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.scaffold-cache.json +537 -0
- package/package.json +42 -0
- package/src/.scaffold-cache.json +544 -0
- package/src/adapters/axios.ts +117 -0
- package/src/adapters/index.ts +91 -0
- package/src/adapters/inertia.ts +187 -0
- package/src/core/adapter-registry.ts +87 -0
- package/src/core/bound/bind-host.ts +14 -0
- package/src/core/bound/observe-bound-field.ts +172 -0
- package/src/core/bound/wait-for-bound-field.ts +57 -0
- package/src/core/context.ts +23 -0
- package/src/core/core-provider.tsx +818 -0
- package/src/core/core-root.tsx +72 -0
- package/src/core/core-shell.tsx +44 -0
- package/src/core/errors/error-strip.tsx +71 -0
- package/src/core/errors/index.ts +2 -0
- package/src/core/errors/map-error-bag.ts +51 -0
- package/src/core/errors/map-zod.ts +39 -0
- package/src/core/hooks/use-button.ts +220 -0
- package/src/core/hooks/use-core-context.ts +20 -0
- package/src/core/hooks/use-core-utility.ts +0 -0
- package/src/core/hooks/use-core.ts +13 -0
- package/src/core/hooks/use-field.ts +497 -0
- package/src/core/hooks/use-optional-field.ts +28 -0
- package/src/core/index.ts +0 -0
- package/src/core/registry/binder-registry.ts +82 -0
- package/src/core/registry/field-registry.ts +187 -0
- package/src/core/test.tsx +17 -0
- package/src/global.d.ts +14 -0
- package/src/index.ts +68 -0
- package/src/input/index.ts +4 -0
- package/src/input/input-field.tsx +854 -0
- package/src/input/input-layout-graph.ts +230 -0
- package/src/input/input-props.ts +190 -0
- package/src/lib/get-global-countries.ts +87 -0
- package/src/lib/utils.ts +6 -0
- package/src/presets/index.ts +0 -0
- package/src/presets/shadcn-preset.ts +0 -0
- package/src/presets/shadcn-variants/checkbox.tsx +849 -0
- package/src/presets/shadcn-variants/chips.tsx +756 -0
- package/src/presets/shadcn-variants/color.tsx +284 -0
- package/src/presets/shadcn-variants/custom.tsx +227 -0
- package/src/presets/shadcn-variants/date.tsx +796 -0
- package/src/presets/shadcn-variants/file.tsx +764 -0
- package/src/presets/shadcn-variants/keyvalue.tsx +556 -0
- package/src/presets/shadcn-variants/multiselect.tsx +1132 -0
- package/src/presets/shadcn-variants/number.tsx +176 -0
- package/src/presets/shadcn-variants/password.tsx +737 -0
- package/src/presets/shadcn-variants/phone.tsx +628 -0
- package/src/presets/shadcn-variants/radio.tsx +578 -0
- package/src/presets/shadcn-variants/select.tsx +956 -0
- package/src/presets/shadcn-variants/slider.tsx +622 -0
- package/src/presets/shadcn-variants/text.tsx +343 -0
- package/src/presets/shadcn-variants/textarea.tsx +66 -0
- package/src/presets/shadcn-variants/toggle.tsx +218 -0
- package/src/presets/shadcn-variants/treeselect.tsx +784 -0
- package/src/presets/ui/badge.tsx +46 -0
- package/src/presets/ui/button.tsx +60 -0
- package/src/presets/ui/calendar.tsx +214 -0
- package/src/presets/ui/checkbox.tsx +115 -0
- package/src/presets/ui/custom.tsx +0 -0
- package/src/presets/ui/dialog.tsx +141 -0
- package/src/presets/ui/field.tsx +246 -0
- package/src/presets/ui/input-mask.tsx +739 -0
- package/src/presets/ui/input-otp.tsx +77 -0
- package/src/presets/ui/input.tsx +1011 -0
- package/src/presets/ui/label.tsx +22 -0
- package/src/presets/ui/number.tsx +1370 -0
- package/src/presets/ui/popover.tsx +46 -0
- package/src/presets/ui/radio-group.tsx +43 -0
- package/src/presets/ui/scroll-area.tsx +56 -0
- package/src/presets/ui/select.tsx +190 -0
- package/src/presets/ui/separator.tsx +28 -0
- package/src/presets/ui/slider.tsx +61 -0
- package/src/presets/ui/switch.tsx +32 -0
- package/src/presets/ui/textarea.tsx +634 -0
- package/src/presets/ui/time-dropdowns.tsx +350 -0
- package/src/schema/adapter.ts +217 -0
- package/src/schema/core.ts +429 -0
- package/src/schema/field-map.ts +0 -0
- package/src/schema/field.ts +224 -0
- package/src/schema/index.ts +0 -0
- package/src/schema/input-field.ts +260 -0
- package/src/schema/presets.ts +0 -0
- package/src/schema/variant.ts +216 -0
- package/src/variants/core/checkbox.tsx +54 -0
- package/src/variants/core/chips.tsx +22 -0
- package/src/variants/core/color.tsx +16 -0
- package/src/variants/core/custom.tsx +18 -0
- package/src/variants/core/date.tsx +25 -0
- package/src/variants/core/file.tsx +9 -0
- package/src/variants/core/keyvalue.tsx +12 -0
- package/src/variants/core/multiselect.tsx +28 -0
- package/src/variants/core/number.tsx +115 -0
- package/src/variants/core/password.tsx +35 -0
- package/src/variants/core/phone.tsx +16 -0
- package/src/variants/core/radio.tsx +38 -0
- package/src/variants/core/select.tsx +15 -0
- package/src/variants/core/slider.tsx +55 -0
- package/src/variants/core/text.tsx +114 -0
- package/src/variants/core/textarea.tsx +22 -0
- package/src/variants/core/toggle.tsx +50 -0
- package/src/variants/core/treeselect.tsx +11 -0
- package/src/variants/helpers/selection-summary.tsx +236 -0
- package/src/variants/index.ts +75 -0
- package/src/variants/registry.ts +38 -0
- package/src/variants/select-shared.ts +0 -0
- package/src/variants/shared.ts +126 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import {
|
|
4
|
+
Select,
|
|
5
|
+
SelectTrigger,
|
|
6
|
+
SelectValue,
|
|
7
|
+
SelectContent,
|
|
8
|
+
SelectItem,
|
|
9
|
+
} from "@/presets/ui/select";
|
|
10
|
+
|
|
11
|
+
export interface TimeDropdownsProps {
|
|
12
|
+
/**
|
|
13
|
+
* Current date-time value.
|
|
14
|
+
* Only the time portion will be modified; date part is preserved.
|
|
15
|
+
*/
|
|
16
|
+
value: Date | undefined;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Called whenever any of the time parts change.
|
|
20
|
+
*/
|
|
21
|
+
onChange(next: Date | undefined): void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether to show seconds dropdown.
|
|
25
|
+
* Default: false.
|
|
26
|
+
*/
|
|
27
|
+
showSeconds?: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Step between minutes in the dropdown.
|
|
31
|
+
* Default: 5.
|
|
32
|
+
*/
|
|
33
|
+
minuteStep?: number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Step between seconds in the dropdown.
|
|
37
|
+
* Default: 5.
|
|
38
|
+
*/
|
|
39
|
+
secondStep?: number;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* If true, show 12-hour clock with AM/PM toggle.
|
|
43
|
+
* If false, show 24-hour clock (00–23).
|
|
44
|
+
* Default: false (24-hour).
|
|
45
|
+
*/
|
|
46
|
+
use12Hour?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Optional label to show above or beside the dropdowns.
|
|
50
|
+
*/
|
|
51
|
+
label?: React.ReactNode;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Custom className for the outer wrapper.
|
|
55
|
+
*/
|
|
56
|
+
className?: string;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Custom className for each SelectTrigger (hours/minutes/seconds).
|
|
60
|
+
*/
|
|
61
|
+
triggerClassName?: string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Compact / normal layout toggle.
|
|
65
|
+
* Just a quick spacing switch for now.
|
|
66
|
+
*/
|
|
67
|
+
density?: "compact" | "normal";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function pad2(n: number): string {
|
|
71
|
+
return n.toString().padStart(2, "0");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildMinuteOptions(step: number): string[] {
|
|
75
|
+
const items: string[] = [];
|
|
76
|
+
for (let m = 0; m < 60; m += step) {
|
|
77
|
+
items.push(pad2(m));
|
|
78
|
+
}
|
|
79
|
+
return items;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildSecondOptions(step: number): string[] {
|
|
83
|
+
const items: string[] = [];
|
|
84
|
+
for (let s = 0; s < 60; s += step) {
|
|
85
|
+
items.push(pad2(s));
|
|
86
|
+
}
|
|
87
|
+
return items;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildHourOptions24(): string[] {
|
|
91
|
+
const items: string[] = [];
|
|
92
|
+
for (let h = 0; h < 24; h++) {
|
|
93
|
+
items.push(pad2(h));
|
|
94
|
+
}
|
|
95
|
+
return items;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function buildHourOptions12(): string[] {
|
|
99
|
+
const items: string[] = [];
|
|
100
|
+
for (let h = 1; h <= 12; h++) {
|
|
101
|
+
items.push(h.toString());
|
|
102
|
+
}
|
|
103
|
+
return items;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Safely create a new Date instance with updated time parts,
|
|
108
|
+
* preserving the original date portion when possible.
|
|
109
|
+
*/
|
|
110
|
+
function withTime(
|
|
111
|
+
base: Date | undefined,
|
|
112
|
+
opts: { hours?: number; minutes?: number; seconds?: number },
|
|
113
|
+
): Date {
|
|
114
|
+
const src = base ? new Date(base.getTime()) : new Date();
|
|
115
|
+
const h = opts.hours ?? src.getHours();
|
|
116
|
+
const m = opts.minutes ?? src.getMinutes();
|
|
117
|
+
const s = opts.seconds ?? src.getSeconds();
|
|
118
|
+
src.setHours(h, m, s, 0);
|
|
119
|
+
return src;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Drop-in time dropdown cluster for use in the date popover.
|
|
124
|
+
*
|
|
125
|
+
* Renders hour / minute (and optionally second) Selects plus
|
|
126
|
+
* an AM/PM toggle when `use12Hour` is true.
|
|
127
|
+
*/
|
|
128
|
+
export const TimeDropdowns: React.FC<TimeDropdownsProps> = (props) => {
|
|
129
|
+
const {
|
|
130
|
+
value,
|
|
131
|
+
onChange,
|
|
132
|
+
showSeconds = false,
|
|
133
|
+
minuteStep = 5,
|
|
134
|
+
secondStep = 5,
|
|
135
|
+
use12Hour = false,
|
|
136
|
+
label,
|
|
137
|
+
className,
|
|
138
|
+
triggerClassName,
|
|
139
|
+
density = "normal",
|
|
140
|
+
} = props;
|
|
141
|
+
|
|
142
|
+
const minuteOptions = React.useMemo(
|
|
143
|
+
() => buildMinuteOptions(minuteStep),
|
|
144
|
+
[minuteStep],
|
|
145
|
+
);
|
|
146
|
+
const secondOptions = React.useMemo(
|
|
147
|
+
() => buildSecondOptions(secondStep),
|
|
148
|
+
[secondStep],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const hourOptions = React.useMemo(
|
|
152
|
+
() => (use12Hour ? buildHourOptions12() : buildHourOptions24()),
|
|
153
|
+
[use12Hour],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Derive current parts from value.
|
|
157
|
+
let hours24 = value ? value.getHours() : 0;
|
|
158
|
+
let minutes = value ? value.getMinutes() : 0;
|
|
159
|
+
let seconds = value ? value.getSeconds() : 0;
|
|
160
|
+
|
|
161
|
+
let hourDisplay: string;
|
|
162
|
+
let period: "am" | "pm" | null = null;
|
|
163
|
+
|
|
164
|
+
if (use12Hour) {
|
|
165
|
+
period = hours24 >= 12 ? "pm" : "am";
|
|
166
|
+
let h12 = hours24 % 12;
|
|
167
|
+
if (h12 === 0) h12 = 12;
|
|
168
|
+
hourDisplay = h12.toString();
|
|
169
|
+
} else {
|
|
170
|
+
hourDisplay = pad2(hours24);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const minuteDisplay = pad2(minutes);
|
|
174
|
+
const secondDisplay = pad2(seconds);
|
|
175
|
+
|
|
176
|
+
const baseTriggerClasses = cn(
|
|
177
|
+
"h-8 w-[4.2rem] px-2 py-0 text-xs",
|
|
178
|
+
"whitespace-nowrap",
|
|
179
|
+
"border-input bg-background",
|
|
180
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const triggerClasses = cn(
|
|
184
|
+
baseTriggerClasses,
|
|
185
|
+
triggerClassName,
|
|
186
|
+
density === "compact" && "h-7 text-[0.7rem] px-1.5",
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const gapClass = density === "compact" ? "gap-1" : "gap-2";
|
|
190
|
+
|
|
191
|
+
const handleHourChange = (hStr: string) => {
|
|
192
|
+
let nextHours24: number;
|
|
193
|
+
|
|
194
|
+
if (use12Hour) {
|
|
195
|
+
const h12 = parseInt(hStr, 10) || 0;
|
|
196
|
+
if (!period) {
|
|
197
|
+
// fallback: assume AM
|
|
198
|
+
nextHours24 = h12 % 12;
|
|
199
|
+
} else {
|
|
200
|
+
if (period === "am") {
|
|
201
|
+
nextHours24 = h12 % 12; // 12am → 0
|
|
202
|
+
} else {
|
|
203
|
+
nextHours24 = h12 % 12 + 12; // 12pm → 12, 1pm → 13
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
nextHours24 = parseInt(hStr, 10) || 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const nextDate = withTime(value, { hours: nextHours24 });
|
|
211
|
+
onChange(nextDate);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const handleMinuteChange = (mStr: string) => {
|
|
215
|
+
const m = parseInt(mStr, 10) || 0;
|
|
216
|
+
const nextDate = withTime(value, { minutes: m });
|
|
217
|
+
onChange(nextDate);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const handleSecondChange = (sStr: string) => {
|
|
221
|
+
const s = parseInt(sStr, 10) || 0;
|
|
222
|
+
const nextDate = withTime(value, { seconds: s });
|
|
223
|
+
onChange(nextDate);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const handlePeriodChange = (p: string) => {
|
|
227
|
+
if (!use12Hour) return;
|
|
228
|
+
const nextPeriod = p === "pm" ? "pm" : "am";
|
|
229
|
+
|
|
230
|
+
// Convert from current hours24 + new period.
|
|
231
|
+
let h12 = hours24 % 12;
|
|
232
|
+
if (h12 === 0) h12 = 12;
|
|
233
|
+
|
|
234
|
+
let nextHours24: number;
|
|
235
|
+
if (nextPeriod === "am") {
|
|
236
|
+
nextHours24 = h12 % 12; // 12am → 0
|
|
237
|
+
} else {
|
|
238
|
+
nextHours24 = h12 % 12 + 12; // 12pm → 12, etc.
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const nextDate = withTime(value, { hours: nextHours24 });
|
|
242
|
+
onChange(nextDate);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<div
|
|
247
|
+
className={cn(
|
|
248
|
+
"flex w-full flex-col gap-2",
|
|
249
|
+
density === "compact" && "gap-1",
|
|
250
|
+
className,
|
|
251
|
+
)}
|
|
252
|
+
data-slot="time-dropdowns"
|
|
253
|
+
>
|
|
254
|
+
{label && (
|
|
255
|
+
<div className="text-xs font-medium text-muted-foreground">
|
|
256
|
+
{label}
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
|
|
260
|
+
<div
|
|
261
|
+
className={cn(
|
|
262
|
+
"flex w-full items-center",
|
|
263
|
+
gapClass,
|
|
264
|
+
)}
|
|
265
|
+
data-slot="time-dropdowns-row"
|
|
266
|
+
>
|
|
267
|
+
{/* Hour */}
|
|
268
|
+
<Select value={hourDisplay} onValueChange={handleHourChange}>
|
|
269
|
+
<SelectTrigger
|
|
270
|
+
className={triggerClasses}
|
|
271
|
+
data-slot="time-hour"
|
|
272
|
+
>
|
|
273
|
+
<SelectValue placeholder="HH" />
|
|
274
|
+
</SelectTrigger>
|
|
275
|
+
<SelectContent>
|
|
276
|
+
{hourOptions.map((h) => (
|
|
277
|
+
<SelectItem key={h} value={h}>
|
|
278
|
+
{use12Hour ? h.padStart(2, " ") : pad2(Number(h))}
|
|
279
|
+
</SelectItem>
|
|
280
|
+
))}
|
|
281
|
+
</SelectContent>
|
|
282
|
+
</Select>
|
|
283
|
+
|
|
284
|
+
{/* Minute */}
|
|
285
|
+
<Select
|
|
286
|
+
value={minuteDisplay}
|
|
287
|
+
onValueChange={handleMinuteChange}
|
|
288
|
+
>
|
|
289
|
+
<SelectTrigger
|
|
290
|
+
className={triggerClasses}
|
|
291
|
+
data-slot="time-minute"
|
|
292
|
+
>
|
|
293
|
+
<SelectValue placeholder="MM" />
|
|
294
|
+
</SelectTrigger>
|
|
295
|
+
<SelectContent>
|
|
296
|
+
{minuteOptions.map((m) => (
|
|
297
|
+
<SelectItem key={m} value={m}>
|
|
298
|
+
{m}
|
|
299
|
+
</SelectItem>
|
|
300
|
+
))}
|
|
301
|
+
</SelectContent>
|
|
302
|
+
</Select>
|
|
303
|
+
|
|
304
|
+
{/* Second (optional) */}
|
|
305
|
+
{showSeconds && (
|
|
306
|
+
<Select
|
|
307
|
+
value={secondDisplay}
|
|
308
|
+
onValueChange={handleSecondChange}
|
|
309
|
+
>
|
|
310
|
+
<SelectTrigger
|
|
311
|
+
className={triggerClasses}
|
|
312
|
+
data-slot="time-second"
|
|
313
|
+
>
|
|
314
|
+
<SelectValue placeholder="SS" />
|
|
315
|
+
</SelectTrigger>
|
|
316
|
+
<SelectContent>
|
|
317
|
+
{secondOptions.map((s) => (
|
|
318
|
+
<SelectItem key={s} value={s}>
|
|
319
|
+
{s}
|
|
320
|
+
</SelectItem>
|
|
321
|
+
))}
|
|
322
|
+
</SelectContent>
|
|
323
|
+
</Select>
|
|
324
|
+
)}
|
|
325
|
+
|
|
326
|
+
{/* AM/PM (optional) */}
|
|
327
|
+
{use12Hour && (
|
|
328
|
+
<Select
|
|
329
|
+
value={period ?? "am"}
|
|
330
|
+
onValueChange={handlePeriodChange}
|
|
331
|
+
>
|
|
332
|
+
<SelectTrigger
|
|
333
|
+
className={cn(
|
|
334
|
+
triggerClasses,
|
|
335
|
+
"w-[3.8rem]",
|
|
336
|
+
)}
|
|
337
|
+
data-slot="time-period"
|
|
338
|
+
>
|
|
339
|
+
<SelectValue />
|
|
340
|
+
</SelectTrigger>
|
|
341
|
+
<SelectContent>
|
|
342
|
+
<SelectItem value="am">AM</SelectItem>
|
|
343
|
+
<SelectItem value="pm">PM</SelectItem>
|
|
344
|
+
</SelectContent>
|
|
345
|
+
</Select>
|
|
346
|
+
)}
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// src/schema/adapter.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HTTP methods supported by the core adapter layer.
|
|
5
|
+
*
|
|
6
|
+
* This matches the legacy Method union from the old types.ts.
|
|
7
|
+
*/
|
|
8
|
+
export type Method = 'post' | 'get' | 'delete' | 'put' | 'patch';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Lifecycle callbacks used by adapters to report events back to the core.
|
|
12
|
+
*
|
|
13
|
+
* @template Ok Type of the "successful" response payload (e.g. AxiosResponse).
|
|
14
|
+
* @template Err Type of the "error" payload (e.g. AxiosError, unknown).
|
|
15
|
+
*/
|
|
16
|
+
export interface AdapterCallbacks<Ok = unknown, Err = unknown> {
|
|
17
|
+
/**
|
|
18
|
+
* Called when the underlying request completes successfully.
|
|
19
|
+
* The adapter decides what "success" means (HTTP 2xx, no exception, etc.).
|
|
20
|
+
*/
|
|
21
|
+
onSuccess?(response: Ok): void;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Called when the underlying request fails.
|
|
25
|
+
* Adapters should pass the most informative error shape they have.
|
|
26
|
+
*/
|
|
27
|
+
onError?(error: Err): void;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Called at the end of the adapter lifecycle, whether success or error.
|
|
31
|
+
* Useful for clearing loading states, unlocking buttons, etc.
|
|
32
|
+
*/
|
|
33
|
+
onFinish?(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Result interface returned by an adapter.
|
|
38
|
+
*
|
|
39
|
+
* Generic evolution of the legacy AdapterResult:
|
|
40
|
+
*
|
|
41
|
+
* type AdapterResult = {
|
|
42
|
+
* submit(options?: unknown): void;
|
|
43
|
+
* send<T = unknown>(): Promise<AxiosResponse<T>>;
|
|
44
|
+
* run(options?: unknown): void;
|
|
45
|
+
* };
|
|
46
|
+
*
|
|
47
|
+
* Differences:
|
|
48
|
+
* - The success payload is generic (Ok) instead of hard-coded to AxiosResponse.
|
|
49
|
+
* - send() always returns Promise<Ok>.
|
|
50
|
+
* - run() may return either void or Promise<Ok>, depending on adapter.
|
|
51
|
+
*
|
|
52
|
+
* @template Ok Type of the "successful" response payload.
|
|
53
|
+
*/
|
|
54
|
+
export interface AdapterResult<Ok = unknown> {
|
|
55
|
+
/**
|
|
56
|
+
* Fire-and-forget trigger.
|
|
57
|
+
*
|
|
58
|
+
* Intended for flows where the caller does not care about the response
|
|
59
|
+
* object itself (e.g. SPA navigation).
|
|
60
|
+
*
|
|
61
|
+
* @param options Optional adapter-specific options.
|
|
62
|
+
*/
|
|
63
|
+
submit(options?: unknown): void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Promise-based trigger.
|
|
67
|
+
*
|
|
68
|
+
* Intended for flows where the caller wants to await the response object.
|
|
69
|
+
* Adapters should reject the promise when an error occurs.
|
|
70
|
+
*
|
|
71
|
+
* @param options Optional adapter-specific options.
|
|
72
|
+
*/
|
|
73
|
+
send(options?: unknown): Promise<Ok>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Convenience trigger.
|
|
77
|
+
*
|
|
78
|
+
* Adapters are free to implement this as:
|
|
79
|
+
* - submit(options) (returning void), or
|
|
80
|
+
* - send(options) (returning Promise<Ok>).
|
|
81
|
+
*
|
|
82
|
+
* Callers that need strict typing can prefer send();
|
|
83
|
+
* callers that just need "do the thing" can use run().
|
|
84
|
+
*
|
|
85
|
+
* @param options Optional adapter-specific options.
|
|
86
|
+
*/
|
|
87
|
+
run(options?: unknown): void | Promise<Ok>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Configuration passed from the core runtime to a concrete adapter factory.
|
|
92
|
+
*
|
|
93
|
+
* @template Body Type of the outbound payload (form values + extra data).
|
|
94
|
+
* @template Ok Type of the "successful" response payload.
|
|
95
|
+
* @template Err Type of the "error" payload.
|
|
96
|
+
*/
|
|
97
|
+
export interface AdapterConfig<Body = unknown, Ok = unknown, Err = unknown> {
|
|
98
|
+
/**
|
|
99
|
+
* HTTP method / intent used by the adapter.
|
|
100
|
+
*/
|
|
101
|
+
method: Method;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Fully-resolved URL or route string.
|
|
105
|
+
*
|
|
106
|
+
* The core is responsible for resolving named routes, base URLs, etc.,
|
|
107
|
+
* before handing control to the adapter.
|
|
108
|
+
*/
|
|
109
|
+
url: string;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Request body payload built by the core.
|
|
113
|
+
*
|
|
114
|
+
* Typically something like:
|
|
115
|
+
*
|
|
116
|
+
* { ...formValues, ...extra }
|
|
117
|
+
*/
|
|
118
|
+
data: Body;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Lifecycle callbacks provided by the core.
|
|
122
|
+
*
|
|
123
|
+
* The adapter should invoke these at the appropriate times; it must not
|
|
124
|
+
* swallow errors without calling onError (when provided).
|
|
125
|
+
*/
|
|
126
|
+
callbacks?: AdapterCallbacks<Ok, Err>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Factory function type for creating an adapter instance.
|
|
131
|
+
*
|
|
132
|
+
* Concrete implementations (Axios, Inertia, fetch, custom) can conform
|
|
133
|
+
* to this signature. The core runtime only knows about this type and does
|
|
134
|
+
* not depend on any adapter-specific details.
|
|
135
|
+
*
|
|
136
|
+
* @template Body Type of the outbound payload (form values + extra data).
|
|
137
|
+
* @template Ok Type of the "successful" response payload.
|
|
138
|
+
* @template Err Type of the "error" payload.
|
|
139
|
+
*/
|
|
140
|
+
export type AdapterFactory<
|
|
141
|
+
Body = unknown,
|
|
142
|
+
Ok = unknown,
|
|
143
|
+
Err = unknown,
|
|
144
|
+
> = (config: AdapterConfig<Body, Ok, Err>) => AdapterResult<Ok>;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Registry of adapter flavours.
|
|
148
|
+
*
|
|
149
|
+
* The library hard-codes a single built-in adapter flavour:
|
|
150
|
+
*
|
|
151
|
+
* - 'local' → host-handled, no transport semantics.
|
|
152
|
+
* .send() resolves to `{ data: Body }`.
|
|
153
|
+
*
|
|
154
|
+
* Hosts can extend this interface via module augmentation to add
|
|
155
|
+
* their own adapter flavours (e.g. 'axios', 'inertia', ...).
|
|
156
|
+
*/
|
|
157
|
+
export interface Adapters {
|
|
158
|
+
local: {
|
|
159
|
+
/**
|
|
160
|
+
* Type of the value produced by adapter.send() for this adapter flavour.
|
|
161
|
+
*/
|
|
162
|
+
ok: { data: unknown };
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Type of the error value passed into callbacks.onError for this adapter.
|
|
166
|
+
*/
|
|
167
|
+
err: unknown;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Union of all adapter keys known to the core.
|
|
173
|
+
*
|
|
174
|
+
* Hosts can extend this union by augmenting the Adapters interface.
|
|
175
|
+
*/
|
|
176
|
+
export type AdapterKey = keyof Adapters;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Helper: given an adapter key K, get its "ok" payload type.
|
|
180
|
+
*/
|
|
181
|
+
export type AdapterOk<K extends AdapterKey> = Adapters[K]['ok'];
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Helper: given an adapter key K, get its "error" payload type.
|
|
185
|
+
*/
|
|
186
|
+
export type AdapterError<K extends AdapterKey> = Adapters[K]['err'];
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Helper: what CoreProps.onSubmitted receives for adapter K.
|
|
190
|
+
*
|
|
191
|
+
* For now, this is the same as AdapterOk<K>. If a host wants a different
|
|
192
|
+
* shape, they can wrap/transform in their own components.
|
|
193
|
+
*/
|
|
194
|
+
export type AdapterSubmit<K extends AdapterKey> = AdapterOk<K>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* AdapterConfig specialised for a named adapter key K, using the
|
|
198
|
+
* registry's ok/err types for that key.
|
|
199
|
+
*
|
|
200
|
+
* @template K Adapter key.
|
|
201
|
+
* @template Body Outbound payload type.
|
|
202
|
+
*/
|
|
203
|
+
export type NamedAdapterConfig<
|
|
204
|
+
K extends AdapterKey,
|
|
205
|
+
Body = unknown,
|
|
206
|
+
> = AdapterConfig<Body, AdapterOk<K>, AdapterError<K>>;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* AdapterFactory specialised for a named adapter key K.
|
|
210
|
+
*
|
|
211
|
+
* @template K Adapter key.
|
|
212
|
+
* @template Body Outbound payload type.
|
|
213
|
+
*/
|
|
214
|
+
export type NamedAdapterFactory<
|
|
215
|
+
K extends AdapterKey,
|
|
216
|
+
Body = unknown,
|
|
217
|
+
> = (config: NamedAdapterConfig<K, Body>) => AdapterResult<AdapterOk<K>>;
|