tekivex-ui 2.5.7 → 2.5.9
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/README.md +15 -1
- package/dist/TkxForm-2tGLCPH6.js +326 -0
- package/dist/TkxForm-C2VqR2wC.cjs +12 -0
- package/dist/charts.cjs +1 -1
- package/dist/charts.js +264 -251
- package/dist/headless.cjs +1 -1
- package/dist/headless.js +21 -20
- package/dist/index-BgHMZe4Z.js +66 -0
- package/dist/index-Bt5y50Qa.cjs +1 -0
- package/dist/index-DnqXtpwV.cjs +2 -0
- package/dist/{index-CAXjeKoK.js → index-iUUHRxqJ.js} +77 -75
- package/dist/index.cjs +21 -21
- package/dist/index.js +24 -25
- package/dist/quantum.cjs +2 -2
- package/dist/quantum.js +4 -4
- package/dist/realtime.cjs +8 -4
- package/dist/realtime.js +391 -377
- package/dist/{security-loRa1HyV.js → security-C-ZPGoyG.js} +1 -1
- package/dist/{security-DMqZVW5d.cjs → security-Uf0mjv8o.cjs} +1 -1
- package/dist/src/charts/TkxDonutChart.d.ts.map +1 -1
- package/dist/src/charts/TkxPieChart.d.ts.map +1 -1
- package/dist/src/components/TkxForm.d.ts +17 -16
- package/dist/src/components/TkxForm.d.ts.map +1 -1
- package/dist/src/components/TkxLiveMetrics.d.ts.map +1 -1
- package/dist/src/components/TkxPlayground.d.ts.map +1 -1
- package/dist/src/components/TkxProgress.d.ts.map +1 -1
- package/dist/src/themes/index.d.ts.map +1 -1
- package/dist/{tkx-C_AlpMt4.js → tkx-BtHzWKTl.js} +1 -1
- package/dist/{tkx-69KQ0myA.cjs → tkx-C7GvVUE9.cjs} +1 -1
- package/package.json +5 -5
- package/src/charts/TkxDonutChart.tsx +6 -2
- package/src/charts/TkxPieChart.tsx +7 -3
- package/src/charts/shared.ts +4 -4
- package/src/components/TkxForm.tsx +35 -32
- package/src/components/TkxLiveMetrics.tsx +24 -2
- package/src/components/TkxPlayground.tsx +40 -5
- package/src/components/TkxProgress.tsx +0 -2
- package/src/themes/index.ts +8 -1
- package/dist/TkxForm-BK5Sq7oj.cjs +0 -12
- package/dist/TkxForm-br3i_vo8.js +0 -388
- package/dist/index-DB_m7X7e.cjs +0 -2
|
@@ -28,16 +28,17 @@ export interface ValidationRule {
|
|
|
28
28
|
min?: number;
|
|
29
29
|
max?: number;
|
|
30
30
|
pattern?: RegExp;
|
|
31
|
-
validator
|
|
31
|
+
/** Custom validator. Receives the field value and all form values for cross-field validation (e.g. "confirm password" patterns). */
|
|
32
|
+
validator?: (value: unknown, allValues?: Record<string, unknown>) => string | null | Promise<string | null>;
|
|
32
33
|
message?: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
// ── Form Props ──────────────────────────────────────────────────────────────
|
|
36
37
|
|
|
37
|
-
export interface TkxFormProps {
|
|
38
|
-
onSubmit?: (values:
|
|
39
|
-
onValuesChange?: (changed:
|
|
40
|
-
initialValues?:
|
|
38
|
+
export interface TkxFormProps<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
39
|
+
onSubmit?: (values: T) => void | Promise<void>;
|
|
40
|
+
onValuesChange?: (changed: Partial<T>, all: T) => void;
|
|
41
|
+
initialValues?: Partial<T>;
|
|
41
42
|
layout?: 'vertical' | 'horizontal' | 'inline';
|
|
42
43
|
disabled?: boolean;
|
|
43
44
|
children: ReactNode;
|
|
@@ -48,7 +49,7 @@ export interface TkxFormProps {
|
|
|
48
49
|
* When provided, the form exposes its internal instance through this reference,
|
|
49
50
|
* enabling programmatic control from outside the component tree.
|
|
50
51
|
*/
|
|
51
|
-
form?: FormInstance
|
|
52
|
+
form?: FormInstance<T>;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
// ── Form Field Props ────────────────────────────────────────────────────────
|
|
@@ -66,16 +67,16 @@ export interface TkxFormFieldProps {
|
|
|
66
67
|
|
|
67
68
|
// ── Form Instance (programmatic API) ────────────────────────────────────────
|
|
68
69
|
|
|
69
|
-
export interface FormInstance {
|
|
70
|
-
getFieldValue: (name:
|
|
71
|
-
setFieldValue: (name:
|
|
72
|
-
getFieldsValue: () =>
|
|
73
|
-
setFieldsValue: (values:
|
|
74
|
-
validateFields: () => Promise<
|
|
75
|
-
validateField: (name: string) => Promise<boolean>;
|
|
70
|
+
export interface FormInstance<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
71
|
+
getFieldValue: <K extends keyof T>(name: K) => T[K] | undefined;
|
|
72
|
+
setFieldValue: <K extends keyof T>(name: K, value: T[K]) => void;
|
|
73
|
+
getFieldsValue: () => Partial<T>;
|
|
74
|
+
setFieldsValue: (values: Partial<T>) => void;
|
|
75
|
+
validateFields: () => Promise<T>;
|
|
76
|
+
validateField: (name: keyof T & string) => Promise<boolean>;
|
|
76
77
|
resetFields: () => void;
|
|
77
|
-
getFieldError: (name: string) => string | null;
|
|
78
|
-
isFieldTouched: (name: string) => boolean;
|
|
78
|
+
getFieldError: (name: keyof T & string) => string | null;
|
|
79
|
+
isFieldTouched: (name: keyof T & string) => boolean;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
// ── Internal State ──────────────────────────────────────────────────────────
|
|
@@ -85,18 +86,18 @@ interface FieldMeta {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
interface FormState {
|
|
88
|
-
values: Record<string,
|
|
89
|
+
values: Record<string, unknown>;
|
|
89
90
|
errors: Record<string, string | null>;
|
|
90
91
|
touched: Record<string, boolean>;
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
interface FormContextValue {
|
|
94
95
|
state: FormState;
|
|
95
|
-
initialValues: Record<string,
|
|
96
|
+
initialValues: Record<string, unknown>;
|
|
96
97
|
layout: 'vertical' | 'horizontal' | 'inline';
|
|
97
98
|
disabled: boolean;
|
|
98
99
|
fieldMeta: React.MutableRefObject<Record<string, FieldMeta>>;
|
|
99
|
-
setFieldValue: (name: string, value:
|
|
100
|
+
setFieldValue: (name: string, value: unknown) => void;
|
|
100
101
|
setFieldError: (name: string, error: string | null) => void;
|
|
101
102
|
setFieldTouched: (name: string) => void;
|
|
102
103
|
registerField: (name: string, meta: FieldMeta) => void;
|
|
@@ -223,18 +224,18 @@ export function useTkxForm(): FormInstance {
|
|
|
223
224
|
|
|
224
225
|
// Always call hooks unconditionally (Rules of Hooks).
|
|
225
226
|
// These refs back the standalone instance when used outside a TkxForm.
|
|
226
|
-
const valuesRef = useRef<Record<string,
|
|
227
|
+
const valuesRef = useRef<Record<string, unknown>>({});
|
|
227
228
|
const errorsRef = useRef<Record<string, string | null>>({});
|
|
228
229
|
const touchedRef = useRef<Record<string, boolean>>({});
|
|
229
230
|
|
|
230
231
|
const standaloneInstance = useMemo<FormInstance>(() => ({
|
|
231
232
|
getFieldValue: (name: string) => valuesRef.current[name],
|
|
232
|
-
setFieldValue: (name: string, value:
|
|
233
|
+
setFieldValue: (name: string, value: unknown) => { valuesRef.current[name] = value; },
|
|
233
234
|
getFieldsValue: () => ({ ...valuesRef.current }),
|
|
234
|
-
setFieldsValue: (values: Record<string,
|
|
235
|
+
setFieldsValue: (values: Record<string, unknown>) => {
|
|
235
236
|
Object.assign(valuesRef.current, values);
|
|
236
237
|
},
|
|
237
|
-
validateFields: () => Promise.resolve({ ...valuesRef.current }),
|
|
238
|
+
validateFields: () => Promise.resolve({ ...valuesRef.current } as Record<string, unknown>),
|
|
238
239
|
validateField: (_name: string) => Promise.resolve(true),
|
|
239
240
|
resetFields: () => {
|
|
240
241
|
valuesRef.current = {};
|
|
@@ -252,17 +253,17 @@ export function useTkxForm(): FormInstance {
|
|
|
252
253
|
|
|
253
254
|
// ── TkxForm Component ───────────────────────────────────────────────────────
|
|
254
255
|
|
|
255
|
-
export function TkxForm({
|
|
256
|
+
export function TkxForm<T extends Record<string, unknown> = Record<string, unknown>>({
|
|
256
257
|
onSubmit,
|
|
257
258
|
onValuesChange,
|
|
258
|
-
initialValues = {}
|
|
259
|
+
initialValues = {} as Partial<T>,
|
|
259
260
|
layout = 'vertical',
|
|
260
261
|
disabled = false,
|
|
261
262
|
children,
|
|
262
263
|
className,
|
|
263
264
|
style,
|
|
264
265
|
form: externalInstance,
|
|
265
|
-
}: TkxFormProps) {
|
|
266
|
+
}: TkxFormProps<T>) {
|
|
266
267
|
const theme = useTheme();
|
|
267
268
|
|
|
268
269
|
// ─── State ──────────────────────────────────────────────────────────────
|
|
@@ -287,13 +288,13 @@ export function TkxForm({
|
|
|
287
288
|
}, []);
|
|
288
289
|
|
|
289
290
|
// ─── Value Management ───────────────────────────────────────────────────
|
|
290
|
-
const setFieldValue = useCallback((name: string, value:
|
|
291
|
+
const setFieldValue = useCallback((name: string, value: unknown) => {
|
|
291
292
|
setState(prev => {
|
|
292
293
|
const next = {
|
|
293
294
|
...prev,
|
|
294
295
|
values: { ...prev.values, [name]: value },
|
|
295
296
|
};
|
|
296
|
-
onValuesChange?.({ [name]: value }
|
|
297
|
+
onValuesChange?.({ [name]: value } as Partial<T>, next.values as T);
|
|
297
298
|
return next;
|
|
298
299
|
});
|
|
299
300
|
}, [onValuesChange]);
|
|
@@ -327,7 +328,7 @@ export function TkxForm({
|
|
|
327
328
|
return error === null;
|
|
328
329
|
}, []);
|
|
329
330
|
|
|
330
|
-
const validateFields = useCallback(async (): Promise<
|
|
331
|
+
const validateFields = useCallback(async (): Promise<T> => {
|
|
331
332
|
const fieldNames = Object.keys(fieldMetaRef.current);
|
|
332
333
|
const results = await Promise.all(
|
|
333
334
|
fieldNames.map(async (name) => {
|
|
@@ -361,7 +362,7 @@ export function TkxForm({
|
|
|
361
362
|
return Promise.reject(errorMap);
|
|
362
363
|
}
|
|
363
364
|
|
|
364
|
-
return { ...stateRef.current.values };
|
|
365
|
+
return { ...stateRef.current.values } as T;
|
|
365
366
|
}, []);
|
|
366
367
|
|
|
367
368
|
const resetFields = useCallback(() => {
|
|
@@ -373,14 +374,16 @@ export function TkxForm({
|
|
|
373
374
|
}, []);
|
|
374
375
|
|
|
375
376
|
// ─── Form Instance ─────────────────────────────────────────────────────
|
|
377
|
+
// Cast to the base FormInstance type for context compatibility.
|
|
378
|
+
// The generic T is only relevant at the props/API boundary, not internally.
|
|
376
379
|
const instance = useMemo<FormInstance>(() => ({
|
|
377
380
|
getFieldValue: (name: string) => stateRef.current.values[name],
|
|
378
381
|
setFieldValue,
|
|
379
382
|
getFieldsValue: () => ({ ...stateRef.current.values }),
|
|
380
|
-
setFieldsValue: (values: Record<string,
|
|
383
|
+
setFieldsValue: (values: Record<string, unknown>) => {
|
|
381
384
|
setState(prev => {
|
|
382
385
|
const merged = { ...prev.values, ...values };
|
|
383
|
-
onValuesChange?.(values
|
|
386
|
+
onValuesChange?.(values as Partial<T>, merged as T);
|
|
384
387
|
return { ...prev, values: merged };
|
|
385
388
|
});
|
|
386
389
|
},
|
|
@@ -410,7 +413,7 @@ export function TkxForm({
|
|
|
410
413
|
// ─── Context Value ─────────────────────────────────────────────────────
|
|
411
414
|
// When an external form instance is provided via the `form` prop, expose it
|
|
412
415
|
// through the context so that useTkxForm() inside children returns it.
|
|
413
|
-
const activeInstance = externalInstance ?? instance;
|
|
416
|
+
const activeInstance: FormInstance = (externalInstance as FormInstance | undefined) ?? instance;
|
|
414
417
|
|
|
415
418
|
const contextValue = useMemo<FormContextValue>(() => ({
|
|
416
419
|
state,
|
|
@@ -53,6 +53,10 @@ function injectMetricsStyles() {
|
|
|
53
53
|
}
|
|
54
54
|
.tkx-metric-shimmer {
|
|
55
55
|
animation: tkx-metric-shimmer 0.6s ease-in-out;
|
|
56
|
+
}
|
|
57
|
+
@media (prefers-reduced-motion: reduce) {
|
|
58
|
+
.tkx-metric-shimmer { animation: none; }
|
|
59
|
+
.tkx-metric-pulse { animation: none; }
|
|
56
60
|
}
|
|
57
61
|
`.trim();
|
|
58
62
|
document.head.appendChild(el);
|
|
@@ -80,15 +84,33 @@ function getStatusColor(status: MetricItem['status'], theme: ReturnType<typeof u
|
|
|
80
84
|
|
|
81
85
|
// ── useCountUp ───────────────────────────────────────────────────────────────
|
|
82
86
|
|
|
87
|
+
function useReducedMotion(): boolean {
|
|
88
|
+
const [reduced, setReduced] = useState(() =>
|
|
89
|
+
typeof window !== 'undefined'
|
|
90
|
+
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
91
|
+
: false
|
|
92
|
+
);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (typeof window === 'undefined') return;
|
|
95
|
+
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
96
|
+
const handler = (e: MediaQueryListEvent) => setReduced(e.matches);
|
|
97
|
+
mq.addEventListener('change', handler);
|
|
98
|
+
return () => mq.removeEventListener('change', handler);
|
|
99
|
+
}, []);
|
|
100
|
+
return reduced;
|
|
101
|
+
}
|
|
102
|
+
|
|
83
103
|
function useCountUp(target: number, duration: number, enabled: boolean): number {
|
|
84
104
|
const [displayed, setDisplayed] = useState(target);
|
|
85
105
|
const startRef = useRef(target);
|
|
86
106
|
const startTimeRef = useRef<number | null>(null);
|
|
87
107
|
const rafRef = useRef<number | null>(null);
|
|
108
|
+
const reducedMotion = useReducedMotion();
|
|
88
109
|
|
|
89
110
|
useEffect(() => {
|
|
90
|
-
if (!enabled) {
|
|
111
|
+
if (!enabled || reducedMotion) {
|
|
91
112
|
setDisplayed(target);
|
|
113
|
+
startRef.current = target;
|
|
92
114
|
return;
|
|
93
115
|
}
|
|
94
116
|
const from = startRef.current;
|
|
@@ -113,7 +135,7 @@ function useCountUp(target: number, duration: number, enabled: boolean): number
|
|
|
113
135
|
return () => {
|
|
114
136
|
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
115
137
|
};
|
|
116
|
-
}, [target, duration, enabled]);
|
|
138
|
+
}, [target, duration, enabled, reducedMotion]);
|
|
117
139
|
|
|
118
140
|
return displayed;
|
|
119
141
|
}
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
// In-browser live component playground. Type JSX, see it render instantly.
|
|
3
3
|
// Uses new Function() evaluation, an error boundary, quantum component
|
|
4
4
|
// suggestions via AmplitudeAmplifier, and real-time render-time metrics.
|
|
5
|
+
//
|
|
6
|
+
// ⚠ SECURITY NOTICE — new Function() / arbitrary code execution
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// TkxPlayground intentionally evaluates user-supplied JSX using new Function().
|
|
9
|
+
// This is an INTENTIONAL design decision for a developer-facing live playground;
|
|
10
|
+
// it is NOT suitable for executing untrusted user content.
|
|
11
|
+
//
|
|
12
|
+
// Risk: new Function() does NOT provide a true sandbox. Code running inside it
|
|
13
|
+
// has access to the JavaScript global scope, including window, document, and
|
|
14
|
+
// fetch. A malicious actor with access to the playground input could exfiltrate
|
|
15
|
+
// data or execute arbitrary browser-side code.
|
|
16
|
+
//
|
|
17
|
+
// Mitigation in this component:
|
|
18
|
+
// • The playground is intended for developer tooling / docs sites only.
|
|
19
|
+
// • "use strict" is applied inside the evaluated function.
|
|
20
|
+
// • An ErrorBoundary catches runtime exceptions from bad code.
|
|
21
|
+
// • All output is rendered inside React's reconciler (no innerHTML).
|
|
22
|
+
//
|
|
23
|
+
// If you need a true sandbox (e.g. for user-facing code execution):
|
|
24
|
+
// • Use a sandboxed <iframe sandbox="allow-scripts"> with a restrictive CSP.
|
|
25
|
+
// • Or run evaluation in a Web Worker (no DOM access).
|
|
26
|
+
// • Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
28
|
|
|
6
29
|
import { useState, useCallback, useRef, useEffect, type ReactNode } from 'react';
|
|
7
30
|
import React from 'react';
|
|
@@ -184,21 +207,32 @@ function evalCode(
|
|
|
184
207
|
? `return (${trimmed});`
|
|
185
208
|
: trimmed; // assume the user wrote `const x = ...; return <jsx>;`
|
|
186
209
|
|
|
187
|
-
|
|
210
|
+
// Babel transforms the JSX source first, then we inject it into new Function().
|
|
211
|
+
// IMPORTANT: `return PlaygroundRoot` must NOT be in the Babel-transformed source —
|
|
212
|
+
// Babel treats the input as a module/script where top-level `return` is a syntax error.
|
|
213
|
+
// Instead, Babel only sees the function definition; the `return` is appended afterward
|
|
214
|
+
// as a plain JS string that never passes through Babel.
|
|
215
|
+
let jsxSource = `
|
|
188
216
|
function PlaygroundRoot() {
|
|
189
217
|
${componentBody}
|
|
190
218
|
}
|
|
191
|
-
return PlaygroundRoot;
|
|
192
219
|
`;
|
|
193
220
|
|
|
194
221
|
const babel = getBabel();
|
|
195
222
|
if (babel) {
|
|
196
|
-
|
|
223
|
+
jsxSource = babel.transform(jsxSource, {
|
|
197
224
|
presets: ['react'],
|
|
198
225
|
filename: 'playground.jsx',
|
|
199
|
-
}).code ??
|
|
226
|
+
}).code ?? jsxSource;
|
|
200
227
|
}
|
|
201
228
|
|
|
229
|
+
// SECURITY: new Function() is used intentionally for the live playground.
|
|
230
|
+
// This executes user-provided JSX in a sandboxed scope with only React and
|
|
231
|
+
// component imports available. No access to window, document, or globals.
|
|
232
|
+
// NOTE: new Function() does NOT create a true sandbox — window/document are
|
|
233
|
+
// still reachable via the global scope. A true sandbox would require an
|
|
234
|
+
// iframe with a restrictive CSP (e.g., Content-Security-Policy: script-src 'none').
|
|
235
|
+
// For production deployments, consider wrapping this in a sandboxed iframe.
|
|
202
236
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
203
237
|
const fn = new Function(
|
|
204
238
|
'React',
|
|
@@ -206,7 +240,8 @@ function evalCode(
|
|
|
206
240
|
`
|
|
207
241
|
"use strict";
|
|
208
242
|
const { ${Object.keys(importsObj).join(', ')} } = imports;
|
|
209
|
-
${
|
|
243
|
+
${jsxSource}
|
|
244
|
+
return PlaygroundRoot;
|
|
210
245
|
`,
|
|
211
246
|
) as (r: typeof React, i: Record<string, unknown>) => React.ComponentType;
|
|
212
247
|
const Component = fn(React, importsObj);
|
|
@@ -52,7 +52,6 @@ export const TkxProgress = forwardRef<HTMLDivElement, TkxProgressProps>(
|
|
|
52
52
|
aria-label={label ?? (isIndeterminate ? 'Loading' : `${clamped}%`)}
|
|
53
53
|
className={cx(tkx('inline-flex flex-col items-center gap-1'), className)}
|
|
54
54
|
style={style}
|
|
55
|
-
{...rest}
|
|
56
55
|
>
|
|
57
56
|
<svg width={px} height={px} viewBox={`0 0 ${px} ${px}`} aria-hidden="true">
|
|
58
57
|
<circle cx={px / 2} cy={px / 2} r={r} fill="none" stroke={theme.border} strokeWidth={strokeW} />
|
|
@@ -85,7 +84,6 @@ export const TkxProgress = forwardRef<HTMLDivElement, TkxProgressProps>(
|
|
|
85
84
|
aria-label={label ?? (isIndeterminate ? 'Loading' : `${clamped}%`)}
|
|
86
85
|
className={cx(tkx('flex flex-col gap-1 w-full'), className)}
|
|
87
86
|
style={style}
|
|
88
|
-
{...rest}
|
|
89
87
|
>
|
|
90
88
|
{label && (
|
|
91
89
|
<div className={tkx('flex justify-between text-sm')} style={{ color: theme.text }}>
|
package/src/themes/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createContext, useContext, useLayoutEffect, useEffect, type ReactNode, createElement } from 'react';
|
|
2
2
|
import { cssVar } from '../engine/css';
|
|
3
|
-
import { meetsAA } from '../engine/wcag';
|
|
3
|
+
import { meetsAA, meetsAAA } from '../engine/wcag';
|
|
4
4
|
|
|
5
5
|
// useLayoutEffect on client, useEffect on server (avoids SSR warning)
|
|
6
6
|
const useIsomorphicLayoutEffect =
|
|
@@ -81,6 +81,13 @@ export function createTheme(base: ThemeTokens, overrides?: Partial<ThemeTokens>)
|
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
if (!meetsAAA(theme.text, theme.bg)) {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.warn(
|
|
87
|
+
`[TekiVex] Theme contrast warning: text (${theme.text}) vs bg (${theme.bg}) does not meet WCAG AAA minimum (7:1)`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
return theme;
|
|
85
92
|
}
|
|
86
93
|
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";const n=require("react"),C=require("./index-DB_m7X7e.cjs"),l=require("react/jsx-runtime"),$=require("./security-DMqZVW5d.cjs"),d=require("./tkx-69KQ0myA.cjs");function G(){const[e,r]=n.useState(()=>typeof window>"u"?!1:C.prefersReducedMotion());return n.useEffect(()=>{if(typeof window>"u")return;const t=window.matchMedia("(prefers-reduced-motion: reduce)"),o=i=>r(i.matches);return t.addEventListener("change",o),()=>t.removeEventListener("change",o)},[]),e}function J(){const[e,r]=n.useState(()=>typeof window>"u"?!1:C.prefersHighContrast());return n.useEffect(()=>{if(typeof window>"u")return;const t=window.matchMedia("(forced-colors: active)"),o=i=>r(i.matches);return t.addEventListener("change",o),()=>t.removeEventListener("change",o)},[]),e}function Q(e){const r=n.useRef(null);return n.useEffect(()=>{if(!e||!r.current)return;const t=C.createFocusTrap(r.current);return t.activate(),()=>t.deactivate()},[e]),r}function U(){const e=n.useRef(null);return n.useEffect(()=>{if(!(typeof document>"u"))return e.current=C.createAnnouncer(),()=>{var r;(r=e.current)==null||r.destroy()}},[]),n.useCallback((r,t="polite")=>{var o;(o=e.current)==null||o.announce(r,t)},[])}function V(e,r=!0){const t=n.useRef(e);t.current=e,n.useEffect(()=>{if(!r||typeof document>"u")return;const o=i=>{i.key==="Escape"&&t.current()};return document.addEventListener("keydown",o),()=>document.removeEventListener("keydown",o)},[r])}function X(e,r){const t=n.useRef(r);t.current=r,n.useEffect(()=>{const o=i=>{!e.current||e.current.contains(i.target)||t.current()};return document.addEventListener("pointerdown",o),()=>document.removeEventListener("pointerdown",o)},[e])}const B=n.createContext(null);function Z(){const e=n.useContext(B);if(!e)throw new Error("TkxFormField must be used inside a <TkxForm>. Wrap your fields in a TkxForm component.");return e}async function K(e,r){for(const t of r){if(t.required&&(e==null||e===""||Array.isArray(e)&&e.length===0))return t.message??"This field is required";if(!(e==null||e==="")){if(t.min!==void 0){if(typeof e=="string"&&e.length<t.min)return t.message??`Must be at least ${t.min} characters`;if(typeof e=="number"&&e<t.min)return t.message??`Must be at least ${t.min}`}if(t.max!==void 0){if(typeof e=="string"&&e.length>t.max)return t.message??`Must be no more than ${t.max} characters`;if(typeof e=="number"&&e>t.max)return t.message??`Must be no more than ${t.max}`}if(t.pattern&&typeof e=="string"&&!t.pattern.test(e))return t.message??"Invalid format";if(t.validator){const o=await t.validator(e);if(o)return o}}}return null}function ee(e,r){const t=e?[...e]:[];return r&&!t.some(o=>o.required)&&t.unshift({required:!0,message:"This field is required"}),t}function te(e){return e.some(r=>r.required)}function re(){const e=n.useContext(B),r=n.useRef({}),t=n.useRef({}),o=n.useRef({}),i=n.useMemo(()=>({getFieldValue:a=>r.current[a],setFieldValue:(a,v)=>{r.current[a]=v},getFieldsValue:()=>({...r.current}),setFieldsValue:a=>{Object.assign(r.current,a)},validateFields:()=>Promise.resolve({...r.current}),validateField:a=>Promise.resolve(!0),resetFields:()=>{r.current={},t.current={},o.current={}},getFieldError:a=>t.current[a]??null,isFieldTouched:a=>o.current[a]??!1}),[]);return e?e.instance:i}function D({onSubmit:e,onValuesChange:r,initialValues:t={},layout:o="vertical",disabled:i=!1,children:a,className:v,style:M,form:b}){const x=C.useTheme(),[y,p]=n.useState({values:{...t},errors:{},touched:{}}),z=n.useRef(t),h=n.useRef({}),F=n.useRef(y);F.current=y;const T=n.useCallback((s,c)=>{h.current[s]=c},[]),q=n.useCallback(s=>{delete h.current[s]},[]),j=n.useCallback((s,c)=>{p(u=>{const f={...u,values:{...u.values,[s]:c}};return r==null||r({[s]:c},f.values),f})},[r]),N=n.useCallback((s,c)=>{p(u=>({...u,errors:{...u.errors,[s]:c}}))},[]),R=n.useCallback(s=>{p(c=>({...c,touched:{...c.touched,[s]:!0}}))},[]),w=n.useCallback(async s=>{const c=h.current[s];if(!c)return!0;const u=F.current.values[s],f=await K(u,c.rules);return p(g=>({...g,errors:{...g.errors,[s]:f},touched:{...g.touched,[s]:!0}})),f===null},[]),E=n.useCallback(async()=>{const s=Object.keys(h.current),c=await Promise.all(s.map(async m=>{const k=h.current[m],W=F.current.values[m],_=await K(W,k.rules);return{name:m,error:_}})),u={},f={};let g=!1;for(const{name:m,error:k}of c)u[m]=k,f[m]=!0,k&&(g=!0);if(p(m=>({...m,errors:{...m.errors,...u},touched:{...m.touched,...f}})),g){const m=Object.fromEntries(c.filter(k=>k.error).map(k=>[k.name,k.error]));return Promise.reject(m)}return{...F.current.values}},[]),I=n.useCallback(()=>{p({values:{...z.current},errors:{},touched:{}})},[]),S=n.useMemo(()=>({getFieldValue:s=>F.current.values[s],setFieldValue:j,getFieldsValue:()=>({...F.current.values}),setFieldsValue:s=>{p(c=>{const u={...c.values,...s};return r==null||r(s,u),{...c,values:u}})},validateFields:E,validateField:w,resetFields:I,getFieldError:s=>F.current.errors[s]??null,isFieldTouched:s=>!!F.current.touched[s]}),[j,E,w,I,r]),L=n.useCallback(async s=>{s.preventDefault();try{const c=await E();await(e==null?void 0:e(c))}catch{}},[E,e]),A=o==="inline"?d.tkx("flex flex-row flex-wrap items-end gap-4"):d.tkx("flex flex-col gap-5"),H=b??S,P=n.useMemo(()=>({state:y,initialValues:z.current,layout:o,disabled:i,fieldMeta:h,setFieldValue:j,setFieldError:N,setFieldTouched:R,registerField:T,unregisterField:q,validateField:w,instance:H}),[y,o,i,j,N,R,T,q,w,H]);return l.jsx(B.Provider,{value:P,children:l.jsx("form",{noValidate:!0,role:"form","aria-label":"Form",onSubmit:L,className:d.cx(A,v),style:{color:x.text,...M},children:a})})}D.displayName="TkxForm";function O({name:e,label:r,rules:t,help:o,required:i,children:a,className:v,style:M}){const b=C.useTheme(),x=Z(),{state:y,layout:p,disabled:z}=x,h=n.useMemo(()=>ee(t,i),[t,i]),F=te(h),T=n.useRef({rules:h});T.current.rules=h;const q=n.useRef(!1);q.current||(x.registerField(e,T.current),q.current=!0),n.useMemo(()=>{x.registerField(e,T.current)},[h,e,x]);const j=y.values[e],N=y.touched[e]?y.errors[e]??null:null,R=N?$.sanitizeString(N):null,w=r?$.sanitizeString(r):void 0,E=o?$.sanitizeString(o):void 0,I=n.useCallback(u=>{let f;if(u!==null&&typeof u=="object"&&"target"in u){const g=u.target;f=g.type==="checkbox"?g.checked:g.value}else f=u;x.setFieldValue(e,f)},[x,e]),S=n.useCallback(()=>{x.setFieldTouched(e),x.validateField(e)},[x,e]),L=n.cloneElement(a,{value:j??"",onChange:I,onBlur:S,error:R??void 0,isInvalid:!!R,isRequired:F,disabled:z||a.props.disabled,name:e}),A=p==="horizontal",H=p==="inline",P=l.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:l.jsx("path",{d:"M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"})}),s=w?l.jsxs("label",{className:d.tkx("text-sm font-medium font-sans",A?"min-w-[140px] pt-2.5":""),style:{color:b.text},children:[w,F&&l.jsx("span",{"aria-hidden":"true",className:d.tkx("ml-1"),style:{color:b.danger},children:"*"})]}):null,c=l.jsxs(l.Fragment,{children:[E&&!R&&l.jsx("span",{className:d.tkx("text-xs mt-0.5"),style:{color:b.textMuted},children:E}),R&&l.jsxs("span",{role:"alert",className:d.tkx("text-xs flex items-center gap-1 mt-0.5"),style:{color:b.danger,animation:"tkxFormErrorReveal 200ms ease-out"},children:[P,R]})]});return H?l.jsxs("div",{className:d.cx(d.tkx("flex flex-col gap-1"),v),style:M,children:[s,L,c]}):A?l.jsxs("div",{className:d.cx(d.tkx("flex flex-row gap-4 items-start"),v),style:M,children:[s,l.jsxs("div",{className:d.tkx("flex flex-col gap-1 flex-1 min-w-0"),children:[L,c]})]}):l.jsxs("div",{className:d.cx(d.tkx("flex flex-col gap-1"),v),style:M,children:[s,L,c]})}O.displayName="TkxFormField";let Y=!1;function ne(){if(Y||typeof document>"u")return;Y=!0;const e=document.createElement("style");e.setAttribute("data-tkx-form",""),e.textContent=`
|
|
2
|
-
@keyframes tkxFormErrorReveal {
|
|
3
|
-
from {
|
|
4
|
-
opacity: 0;
|
|
5
|
-
transform: translateY(-4px);
|
|
6
|
-
}
|
|
7
|
-
to {
|
|
8
|
-
opacity: 1;
|
|
9
|
-
transform: translateY(0);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
`,document.head.appendChild(e)}ne();exports.TkxForm=D;exports.TkxFormField=O;exports.useAnnounce=U;exports.useClickOutside=X;exports.useEscapeKey=V;exports.useFocusTrap=Q;exports.useHighContrast=J;exports.useReducedMotion=G;exports.useTkxForm=re;
|