@tappify/extension-sdk 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-2DBSVKAU.cjs +65 -0
  3. package/dist/chunk-2DBSVKAU.cjs.map +1 -0
  4. package/dist/chunk-3YWVYA42.cjs +142 -0
  5. package/dist/chunk-3YWVYA42.cjs.map +1 -0
  6. package/dist/chunk-4C5Z3CIK.mjs +26 -0
  7. package/dist/chunk-4C5Z3CIK.mjs.map +1 -0
  8. package/dist/chunk-5AAPD5WV.cjs +250 -0
  9. package/dist/chunk-5AAPD5WV.cjs.map +1 -0
  10. package/dist/chunk-7KIKOH5H.cjs +26 -0
  11. package/dist/chunk-7KIKOH5H.cjs.map +1 -0
  12. package/dist/chunk-BDCX6LRQ.mjs +250 -0
  13. package/dist/chunk-BDCX6LRQ.mjs.map +1 -0
  14. package/dist/chunk-CSTYBKZC.mjs +65 -0
  15. package/dist/chunk-CSTYBKZC.mjs.map +1 -0
  16. package/dist/chunk-IGK2AP6J.mjs +142 -0
  17. package/dist/chunk-IGK2AP6J.mjs.map +1 -0
  18. package/dist/chunk-MNDVYPEP.mjs +302 -0
  19. package/dist/chunk-MNDVYPEP.mjs.map +1 -0
  20. package/dist/chunk-OPPYEA7F.cjs +33 -0
  21. package/dist/chunk-OPPYEA7F.cjs.map +1 -0
  22. package/dist/chunk-PYJMJF44.cjs +302 -0
  23. package/dist/chunk-PYJMJF44.cjs.map +1 -0
  24. package/dist/chunk-QHM7XNRK.mjs +33 -0
  25. package/dist/chunk-QHM7XNRK.mjs.map +1 -0
  26. package/dist/chunk-RMUWRESK.cjs +365 -0
  27. package/dist/chunk-RMUWRESK.cjs.map +1 -0
  28. package/dist/chunk-ZT35X7KL.mjs +365 -0
  29. package/dist/chunk-ZT35X7KL.mjs.map +1 -0
  30. package/dist/codegen.cjs +300 -0
  31. package/dist/codegen.cjs.map +1 -0
  32. package/dist/codegen.d.cts +432 -0
  33. package/dist/codegen.d.ts +432 -0
  34. package/dist/codegen.mjs +272 -0
  35. package/dist/codegen.mjs.map +1 -0
  36. package/dist/constants-DJ_KgZpN.d.cts +449 -0
  37. package/dist/constants-DJ_KgZpN.d.ts +449 -0
  38. package/dist/context-Dt6xh_5t.d.cts +30 -0
  39. package/dist/context-Ml7k_V14.d.ts +30 -0
  40. package/dist/errors-COsBh7kd.d.cts +88 -0
  41. package/dist/errors-COsBh7kd.d.ts +88 -0
  42. package/dist/handler-CZIlZ5vu.d.ts +227 -0
  43. package/dist/handler-kU9Jf36K.d.cts +227 -0
  44. package/dist/host.cjs +24 -0
  45. package/dist/host.cjs.map +1 -0
  46. package/dist/host.d.cts +24 -0
  47. package/dist/host.d.ts +24 -0
  48. package/dist/host.mjs +24 -0
  49. package/dist/host.mjs.map +1 -0
  50. package/dist/index.cjs +698 -0
  51. package/dist/index.cjs.map +1 -0
  52. package/dist/index.d.cts +1016 -0
  53. package/dist/index.d.ts +1016 -0
  54. package/dist/index.mjs +698 -0
  55. package/dist/index.mjs.map +1 -0
  56. package/dist/manifest.cjs +872 -0
  57. package/dist/manifest.cjs.map +1 -0
  58. package/dist/manifest.d.cts +432 -0
  59. package/dist/manifest.d.ts +432 -0
  60. package/dist/manifest.mjs +872 -0
  61. package/dist/manifest.mjs.map +1 -0
  62. package/dist/matchers-Cz93AK1n.d.cts +74 -0
  63. package/dist/matchers-Cz93AK1n.d.ts +74 -0
  64. package/dist/server.cjs +210 -0
  65. package/dist/server.cjs.map +1 -0
  66. package/dist/server.d.cts +243 -0
  67. package/dist/server.d.ts +243 -0
  68. package/dist/server.mjs +210 -0
  69. package/dist/server.mjs.map +1 -0
  70. package/dist/styles.css +367 -0
  71. package/dist/testing/index.cjs +887 -0
  72. package/dist/testing/index.cjs.map +1 -0
  73. package/dist/testing/index.d.cts +512 -0
  74. package/dist/testing/index.d.ts +512 -0
  75. package/dist/testing/index.mjs +887 -0
  76. package/dist/testing/index.mjs.map +1 -0
  77. package/dist/testing/jest.cjs +17 -0
  78. package/dist/testing/jest.cjs.map +1 -0
  79. package/dist/testing/jest.d.cts +12 -0
  80. package/dist/testing/jest.d.ts +12 -0
  81. package/dist/testing/jest.mjs +17 -0
  82. package/dist/testing/jest.mjs.map +1 -0
  83. package/dist/testing/vitest.cjs +15 -0
  84. package/dist/testing/vitest.cjs.map +1 -0
  85. package/dist/testing/vitest.d.cts +10 -0
  86. package/dist/testing/vitest.d.ts +10 -0
  87. package/dist/testing/vitest.mjs +15 -0
  88. package/dist/testing/vitest.mjs.map +1 -0
  89. package/dist/types-DDAGLpov.d.ts +715 -0
  90. package/dist/types-DrpQSv_2.d.cts +715 -0
  91. package/dist/vite.cjs +1344 -0
  92. package/dist/vite.cjs.map +1 -0
  93. package/dist/vite.d.cts +434 -0
  94. package/dist/vite.d.ts +434 -0
  95. package/dist/vite.mjs +1314 -0
  96. package/dist/vite.mjs.map +1 -0
  97. package/package.json +268 -0
  98. package/schema/extension-v2.json +1500 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,698 @@
1
+ import {
2
+ dataQueryKey,
3
+ installPrefix,
4
+ procedurePrefix,
5
+ procedureQueryKey,
6
+ storageQueryKey,
7
+ useTapAuth,
8
+ useTapContext,
9
+ useTapFilters,
10
+ useTapParams,
11
+ useTapProject,
12
+ useTapQuery,
13
+ useTapServer,
14
+ useTapSize,
15
+ useTapState,
16
+ useTapStorage,
17
+ useTapTheme
18
+ } from "./chunk-BDCX6LRQ.mjs";
19
+ import {
20
+ useTap
21
+ } from "./chunk-QHM7XNRK.mjs";
22
+ import {
23
+ TAP_ERROR_CODES,
24
+ TapError,
25
+ TapServerError
26
+ } from "./chunk-CSTYBKZC.mjs";
27
+
28
+ // src/client/ui/cn.ts
29
+ import clsx from "clsx";
30
+ function cn(...values) {
31
+ return clsx(values);
32
+ }
33
+
34
+ // src/client/ui/primitives.tsx
35
+ import {
36
+ useId
37
+ } from "react";
38
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
39
+ function TapButton({
40
+ variant = "primary",
41
+ size = "md",
42
+ loading = false,
43
+ disabled,
44
+ className,
45
+ children,
46
+ ...rest
47
+ }) {
48
+ return /* @__PURE__ */ jsx(
49
+ "button",
50
+ {
51
+ type: "button",
52
+ ...rest,
53
+ disabled: disabled === true || loading,
54
+ "aria-busy": loading || void 0,
55
+ className: cn(
56
+ "tap-btn",
57
+ `tap-btn--${variant}`,
58
+ size === "sm" && "tap-btn--sm",
59
+ className
60
+ ),
61
+ children
62
+ }
63
+ );
64
+ }
65
+ function TapCard({
66
+ title,
67
+ action,
68
+ footer,
69
+ className,
70
+ children
71
+ }) {
72
+ return /* @__PURE__ */ jsxs("section", { className: cn("tap-card", className), children: [
73
+ (title !== void 0 || action !== void 0) && /* @__PURE__ */ jsxs("header", { className: "tap-card__head", children: [
74
+ /* @__PURE__ */ jsx("span", { children: title }),
75
+ action
76
+ ] }),
77
+ /* @__PURE__ */ jsx("div", { className: "tap-card__body", children }),
78
+ footer !== void 0 && /* @__PURE__ */ jsx("footer", { className: "tap-card__foot", children: footer })
79
+ ] });
80
+ }
81
+ function TapPageHeader({
82
+ title,
83
+ description,
84
+ actions
85
+ }) {
86
+ return /* @__PURE__ */ jsxs("div", { className: "tap-page-header", children: [
87
+ /* @__PURE__ */ jsxs("div", { children: [
88
+ /* @__PURE__ */ jsx("h2", { className: "tap-page-header__title", children: title }),
89
+ description !== void 0 && /* @__PURE__ */ jsx("p", { className: "tap-page-header__description", children: description })
90
+ ] }),
91
+ actions !== void 0 && /* @__PURE__ */ jsx("div", { className: "tap-row", children: actions })
92
+ ] });
93
+ }
94
+ function TapSkeleton({
95
+ lines = 1,
96
+ width,
97
+ className
98
+ }) {
99
+ return /* @__PURE__ */ jsx("div", { "aria-hidden": "true", children: Array.from({ length: lines }, (_, index) => /* @__PURE__ */ jsx(
100
+ "span",
101
+ {
102
+ className: cn("tap-skeleton", className),
103
+ style: { width: width ?? `${String(100 - index * 12)}%` }
104
+ },
105
+ index
106
+ )) });
107
+ }
108
+ function TapEmptyState({
109
+ title,
110
+ description,
111
+ action
112
+ }) {
113
+ return /* @__PURE__ */ jsxs("div", { className: "tap-state", children: [
114
+ /* @__PURE__ */ jsx("span", { className: "tap-state__title", children: title }),
115
+ description !== void 0 && /* @__PURE__ */ jsx("span", { children: description }),
116
+ action
117
+ ] });
118
+ }
119
+ function TapErrorState({
120
+ title,
121
+ description,
122
+ code,
123
+ onRetry
124
+ }) {
125
+ return /* @__PURE__ */ jsxs("div", { className: "tap-state tap-state--error", role: "alert", children: [
126
+ /* @__PURE__ */ jsx("span", { className: "tap-state__title", children: title }),
127
+ description !== void 0 && /* @__PURE__ */ jsx("span", { children: description }),
128
+ code !== void 0 && /* @__PURE__ */ jsx("span", { className: "tap-state__code", children: code }),
129
+ onRetry !== void 0 && /* @__PURE__ */ jsx(TapButton, { variant: "secondary", size: "sm", onClick: onRetry, children: "Retry" })
130
+ ] });
131
+ }
132
+ function TapStat({
133
+ label,
134
+ value,
135
+ delta,
136
+ hint
137
+ }) {
138
+ const percent = delta === void 0 ? void 0 : `${delta > 0 ? "+" : ""}${String(Math.round(delta * 100))}%`;
139
+ return /* @__PURE__ */ jsxs("div", { className: "tap-stat", children: [
140
+ /* @__PURE__ */ jsx("span", { className: "tap-stat__label", children: label }),
141
+ /* @__PURE__ */ jsx("span", { className: "tap-stat__value", children: value }),
142
+ percent !== void 0 && /* @__PURE__ */ jsx(
143
+ "span",
144
+ {
145
+ className: cn(
146
+ delta !== void 0 && delta < 0 ? "tap-stat__delta--down" : "tap-stat__delta--up"
147
+ ),
148
+ children: percent
149
+ }
150
+ ),
151
+ hint !== void 0 && /* @__PURE__ */ jsx("span", { className: "tap-field__hint", children: hint })
152
+ ] });
153
+ }
154
+ function FieldFrame({
155
+ id,
156
+ label,
157
+ hint,
158
+ error,
159
+ hintId,
160
+ errorId,
161
+ children
162
+ }) {
163
+ return /* @__PURE__ */ jsxs("div", { className: "tap-field", children: [
164
+ label !== void 0 && /* @__PURE__ */ jsx("label", { className: "tap-field__label", htmlFor: id, children: label }),
165
+ children,
166
+ hint !== void 0 && /* @__PURE__ */ jsx("span", { className: "tap-field__hint", id: hintId, children: hint }),
167
+ error !== void 0 && /* @__PURE__ */ jsx("span", { className: "tap-field__error", id: errorId, children: error })
168
+ ] });
169
+ }
170
+ function describedByOf(fieldId, hint, error) {
171
+ const value = cn(
172
+ hint !== void 0 && `${fieldId}-hint`,
173
+ error !== void 0 && `${fieldId}-error`
174
+ );
175
+ return value === "" ? void 0 : value;
176
+ }
177
+ function TapSingleLineInput({
178
+ label,
179
+ error,
180
+ hint,
181
+ className,
182
+ id,
183
+ ...rest
184
+ }) {
185
+ const generated = useId();
186
+ const fieldId = id ?? generated;
187
+ return /* @__PURE__ */ jsx(
188
+ FieldFrame,
189
+ {
190
+ id: fieldId,
191
+ label,
192
+ hint,
193
+ error,
194
+ hintId: `${fieldId}-hint`,
195
+ errorId: `${fieldId}-error`,
196
+ children: /* @__PURE__ */ jsx(
197
+ "input",
198
+ {
199
+ ...rest,
200
+ id: fieldId,
201
+ className: cn(
202
+ "tap-control",
203
+ error !== void 0 && "tap-control--invalid",
204
+ className
205
+ ),
206
+ "aria-invalid": error !== void 0 || void 0,
207
+ "aria-describedby": describedByOf(fieldId, hint, error)
208
+ }
209
+ )
210
+ }
211
+ );
212
+ }
213
+ function TapTextarea({
214
+ label,
215
+ error,
216
+ hint,
217
+ className,
218
+ id,
219
+ rows,
220
+ ...rest
221
+ }) {
222
+ const generated = useId();
223
+ const fieldId = id ?? generated;
224
+ return /* @__PURE__ */ jsx(
225
+ FieldFrame,
226
+ {
227
+ id: fieldId,
228
+ label,
229
+ hint,
230
+ error,
231
+ hintId: `${fieldId}-hint`,
232
+ errorId: `${fieldId}-error`,
233
+ children: /* @__PURE__ */ jsx(
234
+ "textarea",
235
+ {
236
+ ...rest,
237
+ id: fieldId,
238
+ rows: rows ?? 4,
239
+ className: cn(
240
+ "tap-control",
241
+ error !== void 0 && "tap-control--invalid",
242
+ className
243
+ ),
244
+ "aria-invalid": error !== void 0 || void 0,
245
+ "aria-describedby": describedByOf(fieldId, hint, error)
246
+ }
247
+ )
248
+ }
249
+ );
250
+ }
251
+ function TapInput(props) {
252
+ if (props.multiline === true) {
253
+ const { multiline: _multiline2, ...rest2 } = props;
254
+ return /* @__PURE__ */ jsx(TapTextarea, { ...rest2 });
255
+ }
256
+ const { multiline: _multiline, ...rest } = props;
257
+ return /* @__PURE__ */ jsx(TapSingleLineInput, { ...rest });
258
+ }
259
+ function TapSelect({
260
+ label,
261
+ error,
262
+ hint,
263
+ options,
264
+ className,
265
+ id,
266
+ ...rest
267
+ }) {
268
+ const generated = useId();
269
+ const fieldId = id ?? generated;
270
+ return /* @__PURE__ */ jsx(
271
+ FieldFrame,
272
+ {
273
+ id: fieldId,
274
+ label,
275
+ hint,
276
+ error,
277
+ hintId: `${fieldId}-hint`,
278
+ errorId: `${fieldId}-error`,
279
+ children: /* @__PURE__ */ jsx(
280
+ "select",
281
+ {
282
+ ...rest,
283
+ id: fieldId,
284
+ className: cn(
285
+ "tap-control",
286
+ error !== void 0 && "tap-control--invalid",
287
+ className
288
+ ),
289
+ "aria-invalid": error !== void 0 || void 0,
290
+ "aria-describedby": describedByOf(fieldId, hint, error),
291
+ children: options.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
292
+ }
293
+ )
294
+ }
295
+ );
296
+ }
297
+ function cellText(row, key) {
298
+ if (row === null || typeof row !== "object") return "";
299
+ const value = Reflect.get(row, key);
300
+ if (typeof value === "string") return value;
301
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
302
+ return String(value);
303
+ }
304
+ return "";
305
+ }
306
+ function TapTable({
307
+ columns,
308
+ rows,
309
+ rowKey,
310
+ empty,
311
+ onRowClick
312
+ }) {
313
+ if (rows.length === 0 && empty !== void 0) {
314
+ return /* @__PURE__ */ jsx(Fragment, { children: empty });
315
+ }
316
+ return /* @__PURE__ */ jsx("div", { className: "tap-table-wrap", children: /* @__PURE__ */ jsxs("table", { className: "tap-table", children: [
317
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx(
318
+ "th",
319
+ {
320
+ className: cn(column.align === "end" && "tap-table__end"),
321
+ scope: "col",
322
+ children: column.label
323
+ },
324
+ column.key
325
+ )) }) }),
326
+ /* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsx(
327
+ "tr",
328
+ {
329
+ "data-clickable": onRowClick !== void 0,
330
+ tabIndex: onRowClick === void 0 ? void 0 : 0,
331
+ onClick: onRowClick === void 0 ? void 0 : () => onRowClick(row),
332
+ onKeyDown: onRowClick === void 0 ? void 0 : (event) => {
333
+ if (event.key !== "Enter" && event.key !== " ") return;
334
+ event.preventDefault();
335
+ onRowClick(row);
336
+ },
337
+ children: columns.map((column) => /* @__PURE__ */ jsx(
338
+ "td",
339
+ {
340
+ className: cn(column.align === "end" && "tap-table__end"),
341
+ children: column.render ? column.render(row) : cellText(row, column.key)
342
+ },
343
+ column.key
344
+ ))
345
+ },
346
+ rowKey(row)
347
+ )) })
348
+ ] }) });
349
+ }
350
+
351
+ // src/client/ui/TapDialog.tsx
352
+ import {
353
+ useCallback,
354
+ useEffect,
355
+ useId as useId2,
356
+ useRef
357
+ } from "react";
358
+ import { createPortal } from "react-dom";
359
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
360
+ var DIALOG_CLOSE_EVENT = "tap:dialog-close";
361
+ var FOCUSABLE = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
362
+ function activeElementNear(node) {
363
+ const root = node?.getRootNode();
364
+ return root instanceof ShadowRoot ? root.activeElement : document.activeElement;
365
+ }
366
+ function TapDialog({
367
+ open,
368
+ title,
369
+ onClose,
370
+ footer,
371
+ children,
372
+ labelledBy
373
+ }) {
374
+ const tap = useTap();
375
+ const dialogRef = useRef(null);
376
+ const titleId = useId2();
377
+ const focusables = useCallback(() => {
378
+ const root = dialogRef.current;
379
+ if (root === null) return [];
380
+ return Array.from(root.querySelectorAll(FOCUSABLE));
381
+ }, []);
382
+ useEffect(() => {
383
+ if (!open) return;
384
+ const previous = activeElementNear(dialogRef.current);
385
+ focusables()[0]?.focus();
386
+ return () => {
387
+ if (previous instanceof HTMLElement) previous.focus();
388
+ };
389
+ }, [open, focusables]);
390
+ useEffect(() => {
391
+ const portal = tap.__portal;
392
+ if (!open || portal === null) return;
393
+ const close = () => {
394
+ onClose();
395
+ };
396
+ portal.addEventListener(DIALOG_CLOSE_EVENT, close);
397
+ return () => {
398
+ portal.removeEventListener(DIALOG_CLOSE_EVENT, close);
399
+ };
400
+ }, [open, tap, onClose]);
401
+ if (!open) return null;
402
+ if (tap.__portal === null) {
403
+ throw new TapError(
404
+ "TAP_NO_PORTAL",
405
+ "TapDialog needs the host portal node, which the mount sets inside its shadow root. Render this dialog from a contribution entry, or use renderWithTap() in tests."
406
+ );
407
+ }
408
+ const onKeyDown = (event) => {
409
+ if (event.key === "Escape") {
410
+ event.stopPropagation();
411
+ event.preventDefault();
412
+ onClose();
413
+ return;
414
+ }
415
+ if (event.key !== "Tab") return;
416
+ const items = focusables();
417
+ if (items.length === 0) return;
418
+ const first = items[0];
419
+ const last = items[items.length - 1];
420
+ const active = activeElementNear(dialogRef.current);
421
+ if (event.shiftKey && active === first) {
422
+ event.preventDefault();
423
+ last.focus();
424
+ return;
425
+ }
426
+ if (!event.shiftKey && active === last) {
427
+ event.preventDefault();
428
+ first.focus();
429
+ }
430
+ };
431
+ return createPortal(
432
+ /* @__PURE__ */ jsx2(
433
+ "div",
434
+ {
435
+ className: "tap-dialog-scrim",
436
+ onKeyDown,
437
+ onMouseDown: (event) => {
438
+ if (event.target === event.currentTarget) onClose();
439
+ },
440
+ children: /* @__PURE__ */ jsxs2(
441
+ "div",
442
+ {
443
+ ref: dialogRef,
444
+ className: "tap-dialog",
445
+ role: "dialog",
446
+ "aria-modal": "true",
447
+ "aria-labelledby": labelledBy ?? titleId,
448
+ children: [
449
+ /* @__PURE__ */ jsx2("header", { className: "tap-dialog__head", id: titleId, children: title }),
450
+ /* @__PURE__ */ jsx2("div", { className: "tap-dialog__body", children }),
451
+ footer !== void 0 && /* @__PURE__ */ jsx2("footer", { className: "tap-dialog__foot", children: footer })
452
+ ]
453
+ }
454
+ )
455
+ }
456
+ ),
457
+ tap.__portal
458
+ );
459
+ }
460
+
461
+ // src/client/ui/TapForm.tsx
462
+ import {
463
+ useMemo,
464
+ useState
465
+ } from "react";
466
+ import { z } from "zod";
467
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
468
+ function humanise(name) {
469
+ const spaced = name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
470
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
471
+ }
472
+ function readString(source, key) {
473
+ const value = source[key];
474
+ return typeof value === "string" ? value : void 0;
475
+ }
476
+ function kindOf(property) {
477
+ if (Array.isArray(property.enum)) return "enum";
478
+ const type = readString(property, "type");
479
+ if (type === "boolean") return "boolean";
480
+ if (type === "number" || type === "integer") return "number";
481
+ const format = readString(property, "format");
482
+ if (format === "textarea") return "textarea";
483
+ if (format === "date") return "date";
484
+ return "text";
485
+ }
486
+ function formFields(schema) {
487
+ const properties = schema.properties;
488
+ if (typeof properties !== "object" || properties === null) return [];
489
+ const required = new Set(
490
+ Array.isArray(schema.required) ? schema.required.filter(
491
+ (name) => typeof name === "string"
492
+ ) : []
493
+ );
494
+ return Object.entries(properties).flatMap(([name, raw]) => {
495
+ if (typeof raw !== "object" || raw === null) return [];
496
+ const property = { ...raw };
497
+ const kind = kindOf(property);
498
+ const field = {
499
+ name,
500
+ label: readString(property, "title") ?? humanise(name),
501
+ kind,
502
+ required: required.has(name)
503
+ };
504
+ const hint = readString(property, "description");
505
+ if (hint !== void 0) field.hint = hint;
506
+ if (kind === "enum" && Array.isArray(property.enum)) {
507
+ field.options = property.enum.filter((option) => typeof option === "string").map((option) => ({ value: option, label: option }));
508
+ }
509
+ return [field];
510
+ });
511
+ }
512
+ function requiredErrors(fields, values) {
513
+ const errors = {};
514
+ for (const field of fields) {
515
+ const value = values[field.name];
516
+ if (field.required && (value === void 0 || value === "")) {
517
+ errors[field.name] = `${field.label} is required.`;
518
+ }
519
+ }
520
+ return errors;
521
+ }
522
+ function compile(schema) {
523
+ try {
524
+ return z.fromJSONSchema(schema);
525
+ } catch {
526
+ return null;
527
+ }
528
+ }
529
+ function validate(fields, compiled, values) {
530
+ const errors = requiredErrors(fields, values);
531
+ if (compiled === null) return errors;
532
+ const result = compiled.safeParse(values);
533
+ if (result.success) return errors;
534
+ for (const issue of result.error.issues) {
535
+ const key = issue.path[0];
536
+ if (typeof key === "string" && errors[key] === void 0) {
537
+ errors[key] = issue.message;
538
+ }
539
+ }
540
+ return errors;
541
+ }
542
+ function clean(fields, values) {
543
+ const output = {};
544
+ for (const field of fields) {
545
+ const value = values[field.name];
546
+ if (value === void 0 || value === "") continue;
547
+ output[field.name] = value;
548
+ }
549
+ return output;
550
+ }
551
+ function TapForm({
552
+ schema,
553
+ value,
554
+ submitLabel = "Submit",
555
+ cancelLabel = "Cancel",
556
+ busy = false,
557
+ onSubmit,
558
+ onCancel
559
+ }) {
560
+ const fields = useMemo(() => formFields(schema), [schema]);
561
+ const compiled = useMemo(() => compile(schema), [schema]);
562
+ const [values, setValues] = useState(() => ({ ...value }));
563
+ const [errors, setErrors] = useState({});
564
+ const set = (name, next) => {
565
+ setValues((current) => ({ ...current, [name]: next }));
566
+ setErrors((current) => {
567
+ if (current[name] === void 0) return current;
568
+ const { [name]: _cleared, ...rest } = current;
569
+ return rest;
570
+ });
571
+ };
572
+ const submit = (event) => {
573
+ event.preventDefault();
574
+ const candidate = clean(fields, values);
575
+ const found = validate(fields, compiled, candidate);
576
+ setErrors(found);
577
+ if (Object.keys(found).length > 0) return;
578
+ void onSubmit(candidate);
579
+ };
580
+ return /* @__PURE__ */ jsxs3("form", { className: "tap-form", onSubmit: submit, noValidate: true, children: [
581
+ fields.map((field) => {
582
+ const raw = values[field.name];
583
+ if (field.kind === "enum") {
584
+ return /* @__PURE__ */ jsx3(
585
+ TapSelect,
586
+ {
587
+ label: field.label,
588
+ hint: field.hint,
589
+ error: errors[field.name],
590
+ required: field.required,
591
+ value: typeof raw === "string" ? raw : "",
592
+ options: [
593
+ { value: "", label: "Choose one" },
594
+ ...field.options ?? []
595
+ ],
596
+ onChange: (event) => {
597
+ set(field.name, event.target.value);
598
+ }
599
+ },
600
+ field.name
601
+ );
602
+ }
603
+ if (field.kind === "boolean") {
604
+ return /* @__PURE__ */ jsx3(
605
+ TapInput,
606
+ {
607
+ type: "checkbox",
608
+ label: field.label,
609
+ hint: field.hint,
610
+ error: errors[field.name],
611
+ className: "tap-control--checkbox",
612
+ checked: raw === true,
613
+ onChange: (event) => {
614
+ set(field.name, event.target.checked);
615
+ }
616
+ },
617
+ field.name
618
+ );
619
+ }
620
+ if (field.kind === "textarea") {
621
+ return /* @__PURE__ */ jsx3(
622
+ TapInput,
623
+ {
624
+ multiline: true,
625
+ label: field.label,
626
+ hint: field.hint,
627
+ error: errors[field.name],
628
+ required: field.required,
629
+ value: typeof raw === "string" ? raw : "",
630
+ onChange: (event) => {
631
+ set(field.name, event.target.value);
632
+ }
633
+ },
634
+ field.name
635
+ );
636
+ }
637
+ return /* @__PURE__ */ jsx3(
638
+ TapInput,
639
+ {
640
+ type: field.kind === "number" ? "number" : field.kind === "date" ? "date" : "text",
641
+ label: field.label,
642
+ hint: field.hint,
643
+ error: errors[field.name],
644
+ required: field.required,
645
+ value: typeof raw === "string" || typeof raw === "number" ? String(raw) : "",
646
+ onChange: (event) => {
647
+ set(
648
+ field.name,
649
+ field.kind === "number" ? event.target.value === "" ? "" : Number(event.target.value) : event.target.value
650
+ );
651
+ }
652
+ },
653
+ field.name
654
+ );
655
+ }),
656
+ /* @__PURE__ */ jsxs3("div", { className: "tap-form__actions", children: [
657
+ onCancel !== void 0 && /* @__PURE__ */ jsx3(TapButton, { variant: "secondary", onClick: onCancel, children: cancelLabel }),
658
+ /* @__PURE__ */ jsx3(TapButton, { type: "submit", loading: busy, children: submitLabel })
659
+ ] })
660
+ ] });
661
+ }
662
+ export {
663
+ TAP_ERROR_CODES,
664
+ TapButton,
665
+ TapCard,
666
+ TapDialog,
667
+ TapEmptyState,
668
+ TapError,
669
+ TapErrorState,
670
+ TapForm,
671
+ TapInput,
672
+ TapPageHeader,
673
+ TapSelect,
674
+ TapServerError,
675
+ TapSkeleton,
676
+ TapStat,
677
+ TapTable,
678
+ cn,
679
+ dataQueryKey,
680
+ formFields,
681
+ installPrefix,
682
+ procedurePrefix,
683
+ procedureQueryKey,
684
+ storageQueryKey,
685
+ useTap,
686
+ useTapAuth,
687
+ useTapContext,
688
+ useTapFilters,
689
+ useTapParams,
690
+ useTapProject,
691
+ useTapQuery,
692
+ useTapServer,
693
+ useTapSize,
694
+ useTapState,
695
+ useTapStorage,
696
+ useTapTheme
697
+ };
698
+ //# sourceMappingURL=index.mjs.map