tekivex-ui 2.5.7 → 2.5.8

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 (40) hide show
  1. package/dist/TkxForm-2tGLCPH6.js +326 -0
  2. package/dist/TkxForm-C2VqR2wC.cjs +12 -0
  3. package/dist/charts.cjs +1 -1
  4. package/dist/charts.js +264 -251
  5. package/dist/headless.cjs +1 -1
  6. package/dist/headless.js +21 -20
  7. package/dist/index-BgHMZe4Z.js +66 -0
  8. package/dist/index-Bt5y50Qa.cjs +1 -0
  9. package/dist/index-DnqXtpwV.cjs +2 -0
  10. package/dist/{index-CAXjeKoK.js → index-iUUHRxqJ.js} +77 -75
  11. package/dist/index.cjs +21 -21
  12. package/dist/index.js +24 -25
  13. package/dist/quantum.cjs +2 -2
  14. package/dist/quantum.js +4 -4
  15. package/dist/realtime.cjs +8 -4
  16. package/dist/realtime.js +391 -377
  17. package/dist/{security-loRa1HyV.js → security-C-ZPGoyG.js} +1 -1
  18. package/dist/{security-DMqZVW5d.cjs → security-Uf0mjv8o.cjs} +1 -1
  19. package/dist/src/charts/TkxDonutChart.d.ts.map +1 -1
  20. package/dist/src/charts/TkxPieChart.d.ts.map +1 -1
  21. package/dist/src/components/TkxForm.d.ts +17 -16
  22. package/dist/src/components/TkxForm.d.ts.map +1 -1
  23. package/dist/src/components/TkxLiveMetrics.d.ts.map +1 -1
  24. package/dist/src/components/TkxPlayground.d.ts.map +1 -1
  25. package/dist/src/components/TkxProgress.d.ts.map +1 -1
  26. package/dist/src/themes/index.d.ts.map +1 -1
  27. package/dist/{tkx-C_AlpMt4.js → tkx-BtHzWKTl.js} +1 -1
  28. package/dist/{tkx-69KQ0myA.cjs → tkx-C7GvVUE9.cjs} +1 -1
  29. package/package.json +5 -5
  30. package/src/charts/TkxDonutChart.tsx +6 -2
  31. package/src/charts/TkxPieChart.tsx +7 -3
  32. package/src/charts/shared.ts +4 -4
  33. package/src/components/TkxForm.tsx +35 -32
  34. package/src/components/TkxLiveMetrics.tsx +24 -2
  35. package/src/components/TkxPlayground.tsx +40 -5
  36. package/src/components/TkxProgress.tsx +0 -2
  37. package/src/themes/index.ts +8 -1
  38. package/dist/TkxForm-BK5Sq7oj.cjs +0 -12
  39. package/dist/TkxForm-br3i_vo8.js +0 -388
  40. 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?: (value: any) => string | null | Promise<string | null>;
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: Record<string, any>) => void | Promise<void>;
39
- onValuesChange?: (changed: Record<string, any>, all: Record<string, any>) => void;
40
- initialValues?: Record<string, any>;
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: string) => any;
71
- setFieldValue: (name: string, value: any) => void;
72
- getFieldsValue: () => Record<string, any>;
73
- setFieldsValue: (values: Record<string, any>) => void;
74
- validateFields: () => Promise<Record<string, any>>;
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, any>;
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, any>;
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: any) => void;
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, any>>({});
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: any) => { valuesRef.current[name] = value; },
233
+ setFieldValue: (name: string, value: unknown) => { valuesRef.current[name] = value; },
233
234
  getFieldsValue: () => ({ ...valuesRef.current }),
234
- setFieldsValue: (values: Record<string, any>) => {
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: any) => {
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 }, next.values);
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<Record<string, any>> => {
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, any>) => {
383
+ setFieldsValue: (values: Record<string, unknown>) => {
381
384
  setState(prev => {
382
385
  const merged = { ...prev.values, ...values };
383
- onValuesChange?.(values, merged);
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
- let source = `
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
- source = babel.transform(source, {
223
+ jsxSource = babel.transform(jsxSource, {
197
224
  presets: ['react'],
198
225
  filename: 'playground.jsx',
199
- }).code ?? source;
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
- ${source}
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 }}>
@@ -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;