tekivex-ui 2.5.13 → 2.5.14

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 (231) hide show
  1. package/dist/{TkxForm-9-W3Nl6m.js → TkxForm-BaO4wvXt.js} +109 -103
  2. package/dist/{TkxForm-1KDzoniV.cjs → TkxForm-M6ttomry.cjs} +1 -1
  3. package/dist/charts.cjs +1 -1
  4. package/dist/charts.js +1 -0
  5. package/dist/headless.cjs +1 -1
  6. package/dist/headless.js +3 -2
  7. package/dist/index.cjs +10 -10
  8. package/dist/index.js +953 -896
  9. package/dist/quantum.cjs +9 -9
  10. package/dist/quantum.js +160 -115
  11. package/dist/realtime.cjs +1 -1
  12. package/dist/realtime.js +1 -1
  13. package/dist/src/charts/TkxAreaChart.d.ts.map +1 -1
  14. package/dist/src/charts/TkxBarChart.d.ts.map +1 -1
  15. package/dist/src/charts/TkxDonutChart.d.ts.map +1 -1
  16. package/dist/src/charts/TkxLineChart.d.ts.map +1 -1
  17. package/dist/src/charts/TkxPieChart.d.ts.map +1 -1
  18. package/dist/src/charts/TkxRadarChart.d.ts.map +1 -1
  19. package/dist/src/charts/TkxScatterChart.d.ts.map +1 -1
  20. package/dist/src/charts/index.d.ts.map +1 -1
  21. package/dist/src/charts/shared.d.ts.map +1 -1
  22. package/dist/src/components/TkxAIChatBubble.d.ts.map +1 -1
  23. package/dist/src/components/TkxAIConfidenceBar.d.ts.map +1 -1
  24. package/dist/src/components/TkxAIThinking.d.ts.map +1 -1
  25. package/dist/src/components/TkxAccordion.d.ts.map +1 -1
  26. package/dist/src/components/TkxAffix.d.ts.map +1 -1
  27. package/dist/src/components/TkxAlert.d.ts.map +1 -1
  28. package/dist/src/components/TkxAnchor.d.ts.map +1 -1
  29. package/dist/src/components/TkxAppBar.d.ts.map +1 -1
  30. package/dist/src/components/TkxAutocomplete.d.ts.map +1 -1
  31. package/dist/src/components/TkxAvatar.d.ts.map +1 -1
  32. package/dist/src/components/TkxBadge.d.ts.map +1 -1
  33. package/dist/src/components/TkxBottomNav.d.ts.map +1 -1
  34. package/dist/src/components/TkxBreadcrumb.d.ts.map +1 -1
  35. package/dist/src/components/TkxButton.d.ts.map +1 -1
  36. package/dist/src/components/TkxCard.d.ts.map +1 -1
  37. package/dist/src/components/TkxCarousel.d.ts.map +1 -1
  38. package/dist/src/components/TkxCascader.d.ts.map +1 -1
  39. package/dist/src/components/TkxChat.d.ts.map +1 -1
  40. package/dist/src/components/TkxCheckbox.d.ts.map +1 -1
  41. package/dist/src/components/TkxClock.d.ts.map +1 -1
  42. package/dist/src/components/TkxColorPicker.d.ts.map +1 -1
  43. package/dist/src/components/TkxCommand.d.ts.map +1 -1
  44. package/dist/src/components/TkxConfigProvider.d.ts.map +1 -1
  45. package/dist/src/components/TkxDataGrid.d.ts.map +1 -1
  46. package/dist/src/components/TkxDatePicker.d.ts +1 -1
  47. package/dist/src/components/TkxDatePicker.d.ts.map +1 -1
  48. package/dist/src/components/TkxDivider.d.ts.map +1 -1
  49. package/dist/src/components/TkxDrawer.d.ts.map +1 -1
  50. package/dist/src/components/TkxDropdown.d.ts.map +1 -1
  51. package/dist/src/components/TkxEmpty.d.ts.map +1 -1
  52. package/dist/src/components/TkxFileUpload.d.ts.map +1 -1
  53. package/dist/src/components/TkxForm.d.ts.map +1 -1
  54. package/dist/src/components/TkxIcon.d.ts.map +1 -1
  55. package/dist/src/components/TkxImage.d.ts.map +1 -1
  56. package/dist/src/components/TkxInput.d.ts.map +1 -1
  57. package/dist/src/components/TkxLayout.d.ts.map +1 -1
  58. package/dist/src/components/TkxList.d.ts.map +1 -1
  59. package/dist/src/components/TkxLiveFeed.d.ts.map +1 -1
  60. package/dist/src/components/TkxLiveLog.d.ts.map +1 -1
  61. package/dist/src/components/TkxLiveMetrics.d.ts.map +1 -1
  62. package/dist/src/components/TkxLogo.d.ts.map +1 -1
  63. package/dist/src/components/TkxMasonry.d.ts.map +1 -1
  64. package/dist/src/components/TkxMentions.d.ts.map +1 -1
  65. package/dist/src/components/TkxMenu.d.ts.map +1 -1
  66. package/dist/src/components/TkxModal.d.ts.map +1 -1
  67. package/dist/src/components/TkxNumberInput.d.ts.map +1 -1
  68. package/dist/src/components/TkxOTP.d.ts.map +1 -1
  69. package/dist/src/components/TkxPagination.d.ts.map +1 -1
  70. package/dist/src/components/TkxPlayground.d.ts +15 -1
  71. package/dist/src/components/TkxPlayground.d.ts.map +1 -1
  72. package/dist/src/components/TkxPopover.d.ts.map +1 -1
  73. package/dist/src/components/TkxProgress.d.ts.map +1 -1
  74. package/dist/src/components/TkxQRCode.d.ts.map +1 -1
  75. package/dist/src/components/TkxQuantumForm.d.ts.map +1 -1
  76. package/dist/src/components/TkxRadio.d.ts.map +1 -1
  77. package/dist/src/components/TkxRating.d.ts.map +1 -1
  78. package/dist/src/components/TkxRealTimeChart.d.ts.map +1 -1
  79. package/dist/src/components/TkxResult.d.ts.map +1 -1
  80. package/dist/src/components/TkxRichTextDisplay.d.ts.map +1 -1
  81. package/dist/src/components/TkxSegmented.d.ts.map +1 -1
  82. package/dist/src/components/TkxSelect.d.ts.map +1 -1
  83. package/dist/src/components/TkxSkeleton.d.ts.map +1 -1
  84. package/dist/src/components/TkxSlider.d.ts.map +1 -1
  85. package/dist/src/components/TkxSnackbar.d.ts.map +1 -1
  86. package/dist/src/components/TkxSpeedDial.d.ts.map +1 -1
  87. package/dist/src/components/TkxSpin.d.ts.map +1 -1
  88. package/dist/src/components/TkxStatistic.d.ts.map +1 -1
  89. package/dist/src/components/TkxStepper.d.ts.map +1 -1
  90. package/dist/src/components/TkxTable.d.ts.map +1 -1
  91. package/dist/src/components/TkxTabs.d.ts.map +1 -1
  92. package/dist/src/components/TkxTag.d.ts.map +1 -1
  93. package/dist/src/components/TkxThemeBuilder.d.ts.map +1 -1
  94. package/dist/src/components/TkxTimeline.d.ts.map +1 -1
  95. package/dist/src/components/TkxToast.d.ts.map +1 -1
  96. package/dist/src/components/TkxToggle.d.ts.map +1 -1
  97. package/dist/src/components/TkxToolbar.d.ts.map +1 -1
  98. package/dist/src/components/TkxTooltip.d.ts.map +1 -1
  99. package/dist/src/components/TkxTour.d.ts.map +1 -1
  100. package/dist/src/components/TkxTransferList.d.ts.map +1 -1
  101. package/dist/src/components/TkxTreeView.d.ts.map +1 -1
  102. package/dist/src/components/TkxTypography.d.ts.map +1 -1
  103. package/dist/src/components/TkxVideoPlayer.d.ts.map +1 -1
  104. package/dist/src/components/TkxWatermark.d.ts.map +1 -1
  105. package/dist/src/engine/security.d.ts +8 -0
  106. package/dist/src/engine/security.d.ts.map +1 -1
  107. package/dist/src/headless/index.d.ts.map +1 -1
  108. package/dist/src/headless/useControllable.d.ts.map +1 -1
  109. package/dist/src/headless/useDebounce.d.ts.map +1 -1
  110. package/dist/src/headless/useDisclosure.d.ts.map +1 -1
  111. package/dist/src/headless/useFormState.d.ts.map +1 -1
  112. package/dist/src/headless/useInfiniteQuery.d.ts.map +1 -1
  113. package/dist/src/headless/useIntersectionObserver.d.ts.map +1 -1
  114. package/dist/src/headless/useListSelection.d.ts.map +1 -1
  115. package/dist/src/headless/useLocalStorage.d.ts.map +1 -1
  116. package/dist/src/headless/useMediaQuery.d.ts.map +1 -1
  117. package/dist/src/headless/useRovingTabIndex.d.ts.map +1 -1
  118. package/dist/src/headless/useSSE.d.ts.map +1 -1
  119. package/dist/src/headless/useThrottle.d.ts.map +1 -1
  120. package/dist/src/headless/useWebSocket.d.ts.map +1 -1
  121. package/dist/src/hooks/index.d.ts.map +1 -1
  122. package/dist/tkx-BoP5V5zy.cjs +4 -0
  123. package/dist/{tkx-DQkvHdE5.js → tkx-mOKMzvUK.js} +4 -2
  124. package/package.json +1 -1
  125. package/src/charts/TkxAreaChart.tsx +3 -1
  126. package/src/charts/TkxBarChart.tsx +3 -1
  127. package/src/charts/TkxDonutChart.tsx +3 -1
  128. package/src/charts/TkxLineChart.tsx +3 -1
  129. package/src/charts/TkxPieChart.tsx +3 -1
  130. package/src/charts/TkxRadarChart.tsx +3 -1
  131. package/src/charts/TkxScatterChart.tsx +3 -1
  132. package/src/charts/index.ts +3 -1
  133. package/src/charts/shared.ts +3 -1
  134. package/src/components/TkxAIChatBubble.tsx +55 -8
  135. package/src/components/TkxAIConfidenceBar.tsx +3 -1
  136. package/src/components/TkxAIThinking.tsx +3 -1
  137. package/src/components/TkxAccordion.tsx +3 -1
  138. package/src/components/TkxAffix.tsx +3 -1
  139. package/src/components/TkxAlert.tsx +3 -1
  140. package/src/components/TkxAnchor.tsx +3 -1
  141. package/src/components/TkxAppBar.tsx +3 -1
  142. package/src/components/TkxAutocomplete.tsx +3 -1
  143. package/src/components/TkxAvatar.tsx +3 -1
  144. package/src/components/TkxBadge.tsx +3 -1
  145. package/src/components/TkxBottomNav.tsx +3 -1
  146. package/src/components/TkxBreadcrumb.tsx +3 -1
  147. package/src/components/TkxButton.tsx +3 -1
  148. package/src/components/TkxCard.tsx +3 -1
  149. package/src/components/TkxCarousel.tsx +3 -1
  150. package/src/components/TkxCascader.tsx +3 -1
  151. package/src/components/TkxChat.tsx +3 -1
  152. package/src/components/TkxCheckbox.tsx +3 -1
  153. package/src/components/TkxClock.tsx +3 -1
  154. package/src/components/TkxColorPicker.tsx +3 -1
  155. package/src/components/TkxCommand.tsx +3 -1
  156. package/src/components/TkxConfigProvider.tsx +3 -1
  157. package/src/components/TkxDataGrid.tsx +3 -1
  158. package/src/components/TkxDatePicker.tsx +76 -18
  159. package/src/components/TkxDivider.tsx +3 -1
  160. package/src/components/TkxDrawer.tsx +3 -1
  161. package/src/components/TkxDropdown.tsx +3 -1
  162. package/src/components/TkxEmpty.tsx +3 -1
  163. package/src/components/TkxFileUpload.tsx +3 -1
  164. package/src/components/TkxForm.tsx +22 -3
  165. package/src/components/TkxIcon.tsx +3 -1
  166. package/src/components/TkxImage.tsx +3 -1
  167. package/src/components/TkxInput.tsx +3 -1
  168. package/src/components/TkxLayout.tsx +3 -1
  169. package/src/components/TkxList.tsx +3 -1
  170. package/src/components/TkxLiveFeed.tsx +3 -1
  171. package/src/components/TkxLiveLog.tsx +3 -1
  172. package/src/components/TkxLiveMetrics.tsx +3 -1
  173. package/src/components/TkxLogo.tsx +3 -1
  174. package/src/components/TkxMasonry.tsx +3 -1
  175. package/src/components/TkxMentions.tsx +3 -1
  176. package/src/components/TkxMenu.tsx +3 -1
  177. package/src/components/TkxModal.tsx +3 -1
  178. package/src/components/TkxNumberInput.tsx +3 -1
  179. package/src/components/TkxOTP.tsx +3 -1
  180. package/src/components/TkxPagination.tsx +3 -1
  181. package/src/components/TkxPlayground.tsx +77 -3
  182. package/src/components/TkxPopover.tsx +3 -1
  183. package/src/components/TkxProgress.tsx +3 -1
  184. package/src/components/TkxQRCode.tsx +3 -1
  185. package/src/components/TkxQuantumForm.tsx +3 -1
  186. package/src/components/TkxRadio.tsx +3 -1
  187. package/src/components/TkxRating.tsx +3 -1
  188. package/src/components/TkxRealTimeChart.tsx +3 -1
  189. package/src/components/TkxResult.tsx +3 -1
  190. package/src/components/TkxRichTextDisplay.tsx +3 -1
  191. package/src/components/TkxSegmented.tsx +3 -1
  192. package/src/components/TkxSelect.tsx +3 -1
  193. package/src/components/TkxSkeleton.tsx +3 -1
  194. package/src/components/TkxSlider.tsx +3 -1
  195. package/src/components/TkxSnackbar.tsx +3 -1
  196. package/src/components/TkxSpeedDial.tsx +3 -1
  197. package/src/components/TkxSpin.tsx +3 -1
  198. package/src/components/TkxStatistic.tsx +3 -1
  199. package/src/components/TkxStepper.tsx +3 -1
  200. package/src/components/TkxTable.tsx +3 -1
  201. package/src/components/TkxTabs.tsx +3 -1
  202. package/src/components/TkxTag.tsx +3 -1
  203. package/src/components/TkxThemeBuilder.tsx +3 -1
  204. package/src/components/TkxTimeline.tsx +3 -1
  205. package/src/components/TkxToast.tsx +3 -1
  206. package/src/components/TkxToggle.tsx +3 -1
  207. package/src/components/TkxToolbar.tsx +3 -1
  208. package/src/components/TkxTooltip.tsx +15 -2
  209. package/src/components/TkxTour.tsx +3 -1
  210. package/src/components/TkxTransferList.tsx +3 -1
  211. package/src/components/TkxTreeView.tsx +3 -1
  212. package/src/components/TkxTypography.tsx +3 -1
  213. package/src/components/TkxVideoPlayer.tsx +3 -1
  214. package/src/components/TkxWatermark.tsx +3 -1
  215. package/src/engine/security.ts +16 -1
  216. package/src/headless/index.ts +3 -1
  217. package/src/headless/useControllable.ts +3 -1
  218. package/src/headless/useDebounce.ts +3 -1
  219. package/src/headless/useDisclosure.ts +3 -1
  220. package/src/headless/useFormState.ts +3 -1
  221. package/src/headless/useInfiniteQuery.ts +3 -1
  222. package/src/headless/useIntersectionObserver.ts +3 -1
  223. package/src/headless/useListSelection.ts +3 -1
  224. package/src/headless/useLocalStorage.ts +3 -1
  225. package/src/headless/useMediaQuery.ts +3 -1
  226. package/src/headless/useRovingTabIndex.ts +3 -1
  227. package/src/headless/useSSE.ts +3 -1
  228. package/src/headless/useThrottle.ts +3 -1
  229. package/src/headless/useWebSocket.ts +3 -1
  230. package/src/hooks/index.ts +3 -1
  231. package/dist/tkx-0oAwZqgL.cjs +0 -4
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useState, useEffect, useRef, type CSSProperties, type ReactNode } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { cx, tkx } from '../engine/tkx';
@@ -77,18 +79,39 @@ export function TkxAIChatBubble({
77
79
  const fullText = typeof content === 'string' ? sanitizeString(content) : '';
78
80
  const [displayed, setDisplayed] = useState(streaming ? '' : fullText);
79
81
  const [copied, setCopied] = useState(false);
82
+ // Debounced text exposed to assistive tech. We deliberately avoid
83
+ // announcing each keystroke — screen readers would read every character.
84
+ // Instead we publish on word boundaries (~350ms idle) and on completion.
85
+ const [liveAnnouncement, setLiveAnnouncement] = useState(streaming ? '' : fullText);
80
86
  const idxRef = useRef(0);
87
+ const liveDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
81
88
 
82
89
  useEffect(() => {
83
- if (!streaming || typeof content !== 'string') return;
90
+ if (!streaming || typeof content !== 'string') {
91
+ setLiveAnnouncement(fullText);
92
+ return;
93
+ }
84
94
  idxRef.current = 0;
85
95
  setDisplayed('');
96
+ setLiveAnnouncement('');
86
97
  const interval = setInterval(() => {
87
98
  idxRef.current++;
88
- setDisplayed(fullText.slice(0, idxRef.current));
89
- if (idxRef.current >= fullText.length) clearInterval(interval);
99
+ const next = fullText.slice(0, idxRef.current);
100
+ setDisplayed(next);
101
+ // Queue a debounced live-region update. Clear prior timer so
102
+ // only the latest pause flushes — AT hears words, not letters.
103
+ if (liveDebounceRef.current) clearTimeout(liveDebounceRef.current);
104
+ liveDebounceRef.current = setTimeout(() => setLiveAnnouncement(next), 350);
105
+ if (idxRef.current >= fullText.length) {
106
+ clearInterval(interval);
107
+ if (liveDebounceRef.current) clearTimeout(liveDebounceRef.current);
108
+ setLiveAnnouncement(fullText);
109
+ }
90
110
  }, 18);
91
- return () => clearInterval(interval);
111
+ return () => {
112
+ clearInterval(interval);
113
+ if (liveDebounceRef.current) clearTimeout(liveDebounceRef.current);
114
+ };
92
115
  }, [fullText, streaming, content]);
93
116
 
94
117
  const handleCopy = () => {
@@ -154,12 +177,36 @@ export function TkxAIChatBubble({
154
177
  )}
155
178
 
156
179
  {/* Bubble */}
157
- <div style={bubbleStyle}>
180
+ <div style={bubbleStyle} role="article" aria-label={isUser ? 'Your message' : 'Assistant message'}>
158
181
  {typeof content === 'string' ? (
159
182
  <>
160
- {streaming ? displayed : sanitizeString(content)}
183
+ {/* Visible (noisy) streamed text: hidden from AT during streaming
184
+ so screen readers aren't spammed with every character. */}
185
+ <span aria-hidden={streaming ? 'true' : undefined}>
186
+ {streaming ? displayed : sanitizeString(content)}
187
+ </span>
161
188
  {streaming && displayed.length < fullText.length && (
162
- <span style={{ display: 'inline-block', width: 2, height: 14, background: isUser ? '#fff' : theme.primary, marginLeft: 2, animation: 'tkx-blink 1s step-end infinite', verticalAlign: 'text-bottom' }} />
189
+ <span aria-hidden="true" style={{ display: 'inline-block', width: 2, height: 14, background: isUser ? '#fff' : theme.primary, marginLeft: 2, animation: 'tkx-blink 1s step-end infinite', verticalAlign: 'text-bottom' }} />
190
+ )}
191
+ {/* Debounced polite live region — announces completed words, not keystrokes. */}
192
+ {streaming && (
193
+ <span
194
+ aria-live="polite"
195
+ aria-atomic="false"
196
+ style={{
197
+ position: 'absolute',
198
+ width: 1,
199
+ height: 1,
200
+ padding: 0,
201
+ margin: -1,
202
+ overflow: 'hidden',
203
+ clip: 'rect(0,0,0,0)',
204
+ whiteSpace: 'nowrap',
205
+ border: 0,
206
+ }}
207
+ >
208
+ {liveAnnouncement}
209
+ </span>
163
210
  )}
164
211
  </>
165
212
  ) : content}
@@ -206,4 +253,4 @@ export function TkxAIChatBubble({
206
253
  `}</style>
207
254
  </div>
208
255
  );
209
- }
256
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useEffect, useRef, type CSSProperties } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { cx, tkx } from '../engine/tkx';
@@ -128,4 +130,4 @@ export function TkxAIConfidenceBar({
128
130
  `}</style>
129
131
  </div>
130
132
  );
131
- }
133
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useState, useEffect, type CSSProperties } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { cx, tkx } from '../engine/tkx';
@@ -188,4 +190,4 @@ export function TkxAIThinking({
188
190
  `}</style>
189
191
  </div>
190
192
  );
191
- }
193
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useRef,
@@ -585,4 +587,4 @@ export function TkxAccordion({
585
587
  );
586
588
  }
587
589
 
588
- TkxAccordion.displayName = 'TkxAccordion';
590
+ TkxAccordion.displayName = 'TkxAccordion';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { type ReactNode, useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { sanitizeString } from '../engine/security';
@@ -135,4 +137,4 @@ export function TkxAffix({
135
137
  </div>
136
138
  </>
137
139
  );
138
- }
140
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { type ReactNode, useEffect } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { sanitizeString } from '../engine/security';
@@ -89,4 +91,4 @@ export function TkxAlert({ variant, title, children, dismissible, onDismiss, ico
89
91
  )}
90
92
  </div>
91
93
  );
92
- }
94
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useState, useEffect, useCallback, useRef } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { sanitizeString } from '../engine/security';
@@ -190,4 +192,4 @@ export function TkxAnchor({
190
192
  </ul>
191
193
  </nav>
192
194
  );
193
- }
195
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useEffect,
@@ -257,4 +259,4 @@ export function TkxAppBar({
257
259
  )}
258
260
  </header>
259
261
  );
260
- }
262
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useRef,
@@ -450,4 +452,4 @@ export function TkxAutocomplete({
450
452
  );
451
453
  }
452
454
 
453
- export default TkxAutocomplete;
455
+ export default TkxAutocomplete;
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { forwardRef, useState, type ImgHTMLAttributes } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { getAccessibleForeground } from '../engine/wcag';
@@ -98,4 +100,4 @@ export const TkxAvatar = forwardRef<HTMLDivElement, TkxAvatarProps>(
98
100
  },
99
101
  );
100
102
 
101
- TkxAvatar.displayName = 'TkxAvatar';
103
+ TkxAvatar.displayName = 'TkxAvatar';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { forwardRef, type HTMLAttributes } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { getAccessibleForeground } from '../engine/wcag';
@@ -70,4 +72,4 @@ export const TkxBadge = forwardRef<HTMLSpanElement, TkxBadgeProps>(
70
72
  },
71
73
  );
72
74
 
73
- TkxBadge.displayName = 'TkxBadge';
75
+ TkxBadge.displayName = 'TkxBadge';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useCallback,
3
5
  useRef,
@@ -236,4 +238,4 @@ export function TkxBottomNav({
236
238
  </div>
237
239
  </nav>
238
240
  );
239
- }
241
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useCallback,
3
5
  type ReactNode,
@@ -319,4 +321,4 @@ export function TkxBreadcrumb({
319
321
  );
320
322
  }
321
323
 
322
- export default TkxBreadcrumb;
324
+ export default TkxBreadcrumb;
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { getAccessibleForeground } from '../engine/wcag';
@@ -150,4 +152,4 @@ export const TkxButton = forwardRef<HTMLButtonElement, TkxButtonProps>(
150
152
  },
151
153
  );
152
154
 
153
- TkxButton.displayName = 'TkxButton';
155
+ TkxButton.displayName = 'TkxButton';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { forwardRef, type HTMLAttributes, type ReactNode } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { useReducedMotion } from '../hooks';
@@ -91,4 +93,4 @@ export function TkxCardFooter({ children, className, style, ...rest }: HTMLAttri
91
93
  {children}
92
94
  </div>
93
95
  );
94
- }
96
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useRef,
@@ -549,4 +551,4 @@ export function TkxCarousel({
549
551
  );
550
552
  }
551
553
 
552
- TkxCarousel.displayName = 'TkxCarousel';
554
+ TkxCarousel.displayName = 'TkxCarousel';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useState, useRef, useEffect, useCallback } from 'react';
2
4
  import { createPortal } from 'react-dom';
3
5
  import { useTheme } from '../themes';
@@ -273,4 +275,4 @@ export function TkxCascader({
273
275
  {dropdown}
274
276
  </div>
275
277
  );
276
- }
278
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useRef,
@@ -351,4 +353,4 @@ export function TkxChat({
351
353
  {inputArea}
352
354
  </div>
353
355
  );
354
- }
356
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  forwardRef,
3
5
  useId,
@@ -215,4 +217,4 @@ export const TkxCheckbox = forwardRef<HTMLInputElement, TkxCheckboxProps>(
215
217
  },
216
218
  );
217
219
 
218
- TkxCheckbox.displayName = 'TkxCheckbox';
220
+ TkxCheckbox.displayName = 'TkxCheckbox';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import React, {
2
4
  useState,
3
5
  useEffect,
@@ -598,4 +600,4 @@ export function TkxClock({
598
600
  );
599
601
  }
600
602
 
601
- TkxClock.displayName = 'TkxClock';
603
+ TkxClock.displayName = 'TkxClock';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useEffect,
@@ -630,4 +632,4 @@ export function TkxColorPicker({
630
632
  )}
631
633
  </>
632
634
  );
633
- }
635
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useEffect,
@@ -641,4 +643,4 @@ export function TkxCommand({
641
643
  );
642
644
  }
643
645
 
644
- TkxCommand.displayName = 'TkxCommand';
646
+ TkxCommand.displayName = 'TkxCommand';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  createContext,
3
5
  useContext,
@@ -455,4 +457,4 @@ export function getLocaleDisplayName(locale: string): string {
455
457
  // const { locale, direction, getSize, cls } = useConfig();
456
458
  // const size = getSize('button', props.size);
457
459
  // const className = cls('button');
458
- // ══════════════════════════════════════════════════════════════════════════════
460
+ // ══════════════════════════════════════════════════════════════════════════════
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useCallback,
@@ -936,4 +938,4 @@ export function TkxDataGrid<T = any>({
936
938
  )}
937
939
  </div>
938
940
  );
939
- }
941
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useRef,
@@ -102,19 +104,74 @@ function isRangeEndpoint(d: Date, start: Date | null, end: Date | null): boolean
102
104
  return false;
103
105
  }
104
106
 
105
- function formatDate(d: Date | null | undefined, locale = 'en-US'): string {
107
+ /**
108
+ * Format a date using either a custom token-based `dateFormat` string
109
+ * (e.g. "YYYY-MM-DD", "DD/MM/YYYY", "MMM D, YYYY") or fall back to the
110
+ * locale's 2-digit date style.
111
+ *
112
+ * Supported tokens: YYYY, YY, MMMM, MMM, MM, M, DD, D.
113
+ */
114
+ function formatDate(
115
+ d: Date | null | undefined,
116
+ locale = 'en-US',
117
+ dateFormat?: string,
118
+ ): string {
106
119
  if (!d) return '';
120
+ if (dateFormat) {
121
+ const yyyy = String(d.getFullYear());
122
+ const yy = yyyy.slice(-2);
123
+ const M = d.getMonth() + 1;
124
+ const MM = String(M).padStart(2, '0');
125
+ const MMM = MONTH_ABBR[d.getMonth()];
126
+ const MMMM = MONTH_NAMES[d.getMonth()];
127
+ const D = d.getDate();
128
+ const DD = String(D).padStart(2, '0');
129
+ // Order matters: longest tokens first so "MM" doesn't clobber "MMM".
130
+ return dateFormat
131
+ .replace(/YYYY/g, yyyy)
132
+ .replace(/YY/g, yy)
133
+ .replace(/MMMM/g, MMMM)
134
+ .replace(/MMM/g, MMM)
135
+ .replace(/MM/g, MM)
136
+ .replace(/\bM\b/g, String(M))
137
+ .replace(/DD/g, DD)
138
+ .replace(/\bD\b/g, String(D));
139
+ }
107
140
  return d.toLocaleDateString(locale, { month: '2-digit', day: '2-digit', year: 'numeric' });
108
141
  }
109
142
 
110
- function parseDate(str: string): Date | null {
111
- const match = str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
112
- if (!match) return null;
113
- const [, m, d, y] = match.map(Number);
114
- if (m < 1 || m > 12 || d < 1 || d > 31) return null;
115
- const date = new Date(y, m - 1, d);
116
- if (date.getMonth() !== m - 1) return null;
117
- return date;
143
+ /**
144
+ * Parse a date string. Accepts US MM/DD/YYYY and common ISO-ish shapes
145
+ * (YYYY-MM-DD, DD/MM/YYYY, D/M/YYYY). If `dateFormat` is supplied and the
146
+ * input doesn't match any format, we attempt a token-driven parse.
147
+ */
148
+ function parseDate(str: string, dateFormat?: string): Date | null {
149
+ // MM/DD/YYYY (legacy default)
150
+ const us = str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
151
+ if (us) {
152
+ const [, m, d, y] = us.map(Number);
153
+ if (m >= 1 && m <= 12 && d >= 1 && d <= 31) {
154
+ const date = new Date(y, m - 1, d);
155
+ if (date.getMonth() === m - 1) return date;
156
+ }
157
+ }
158
+ // YYYY-MM-DD
159
+ const iso = str.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
160
+ if (iso) {
161
+ const [, y, m, d] = iso.map(Number);
162
+ const date = new Date(y, m - 1, d);
163
+ if (date.getMonth() === m - 1) return date;
164
+ }
165
+ // DD/MM/YYYY when user explicitly asked for a DD-first format
166
+ if (dateFormat && /^DD/.test(dateFormat)) {
167
+ const eu = str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
168
+ if (eu) {
169
+ const [, d, m, y] = eu.map(Number);
170
+ const date = new Date(y, m - 1, d);
171
+ if (date.getMonth() === m - 1) return date;
172
+ }
173
+ }
174
+ return null;
118
175
  }
119
176
 
120
177
  function getDaysInMonth(year: number, month: number): number {
@@ -563,6 +620,7 @@ export function TkxDatePicker({
563
620
  maxDate,
564
621
  disabledDates,
565
622
  locale = 'en-US',
623
+ dateFormat,
566
624
  showTime = false,
567
625
  timeValue,
568
626
  onTimeChange,
@@ -624,10 +682,10 @@ export function TkxDatePicker({
624
682
  // UI
625
683
  const [open, setOpen] = useState(false);
626
684
  const [inputValue, setInputValue] = useState(() => {
627
- if (mode === 'single') return formatDate(selectedDate, locale);
685
+ if (mode === 'single') return formatDate(selectedDate, locale, dateFormat);
628
686
  if (mode === 'range') {
629
687
  const [s, e] = selectedRange;
630
- if (s && e) return `${formatDate(s, locale)} – ${formatDate(e, locale)}`;
688
+ if (s && e) return `${formatDate(s, locale, dateFormat)} – ${formatDate(e, locale, dateFormat)}`;
631
689
  }
632
690
  return '';
633
691
  });
@@ -646,13 +704,13 @@ export function TkxDatePicker({
646
704
 
647
705
  useEffect(() => {
648
706
  if (mode === 'single') {
649
- setInputValue(formatDate(selectedDate, locale));
707
+ setInputValue(formatDate(selectedDate, locale, dateFormat));
650
708
  } else if (mode === 'range') {
651
709
  const [s, e] = selectedRange;
652
710
  if (s && e) {
653
- setInputValue(`${formatDate(s, locale)} – ${formatDate(e, locale)}`);
711
+ setInputValue(`${formatDate(s, locale, dateFormat)} – ${formatDate(e, locale, dateFormat)}`);
654
712
  } else if (s) {
655
- setInputValue(formatDate(s, locale));
713
+ setInputValue(formatDate(s, locale, dateFormat));
656
714
  } else {
657
715
  setInputValue('');
658
716
  }
@@ -660,7 +718,7 @@ export function TkxDatePicker({
660
718
  setInputValue(multiDates.length > 0 ? `${multiDates.length} date${multiDates.length !== 1 ? 's' : ''} selected` : '');
661
719
  }
662
720
  // eslint-disable-next-line react-hooks/exhaustive-deps
663
- }, [selectedDate, selectedRange, multiDates, mode, locale]);
721
+ }, [selectedDate, selectedRange, multiDates, mode, locale, dateFormat]);
664
722
 
665
723
  // ── Position popup ───────────────────────────────────────────────────────────
666
724
 
@@ -734,7 +792,7 @@ export function TkxDatePicker({
734
792
  } else {
735
793
  if (!isSingleControlled) setInternalDate(d);
736
794
  onChange?.(d);
737
- setInputValue(formatDate(d, locale));
795
+ setInputValue(formatDate(d, locale, dateFormat));
738
796
  setOpen(false);
739
797
  }
740
798
  } else if (mode === 'range') {
@@ -820,7 +878,7 @@ export function TkxDatePicker({
820
878
  const handleInputChange = (v: string) => {
821
879
  setInputValue(v);
822
880
  if (mode === 'single') {
823
- const parsed = parseDate(v);
881
+ const parsed = parseDate(v, dateFormat);
824
882
  if (parsed && !isDateDisabled(parsed)) {
825
883
  if (!isSingleControlled) setInternalDate(parsed);
826
884
  onChange?.(parsed);
@@ -1564,4 +1622,4 @@ export function TkxDatePicker({
1564
1622
  );
1565
1623
  }
1566
1624
 
1567
- TkxDatePicker.displayName = 'TkxDatePicker';
1625
+ TkxDatePicker.displayName = 'TkxDatePicker';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { forwardRef, type HTMLAttributes } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { sanitizeString } from '../engine/security';
@@ -46,4 +48,4 @@ export const TkxDivider = forwardRef<HTMLHRElement, TkxDividerProps>(
46
48
  },
47
49
  );
48
50
 
49
- TkxDivider.displayName = 'TkxDivider';
51
+ TkxDivider.displayName = 'TkxDivider';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useEffect,
3
5
  useId,
@@ -231,4 +233,4 @@ export function TkxDrawer({
231
233
  </div>,
232
234
  document.body,
233
235
  );
234
- }
236
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useRef,
@@ -799,4 +801,4 @@ export function TkxDropdown({
799
801
  );
800
802
  }
801
803
 
802
- export default TkxDropdown;
804
+ export default TkxDropdown;
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { type ReactNode, type CSSProperties, createElement } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { sanitizeString } from '../engine/security';
@@ -216,4 +218,4 @@ export function TkxEmpty({
216
218
  children,
217
219
  ),
218
220
  );
219
- }
221
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import {
2
4
  useState,
3
5
  useRef,
@@ -368,4 +370,4 @@ export function TkxFileUpload({
368
370
  )}
369
371
  </div>
370
372
  );
371
- }
373
+ }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  // ══════════════════════════════════════════════════════════════════════════════
2
4
  // TKX FORM — Enterprise-grade form controller
3
5
  // Context-based form state management with field-level validation,
@@ -8,6 +10,7 @@ import {
8
10
  createContext,
9
11
  useContext,
10
12
  useCallback,
13
+ useEffect,
11
14
  useMemo,
12
15
  useRef,
13
16
  useState,
@@ -411,8 +414,24 @@ export function TkxForm<T extends Record<string, unknown> = Record<string, unkno
411
414
  : tkx('flex flex-col gap-5');
412
415
 
413
416
  // ─── Context Value ─────────────────────────────────────────────────────
414
- // When an external form instance is provided via the `form` prop, expose it
415
- // through the context so that useTkxForm() inside children returns it.
417
+ // When an external form instance is provided via the `form` prop, bind its
418
+ // methods to the live internal instance so programmatic calls (e.g.
419
+ // form.setFieldValue) drive re-renders. Without this binding the external
420
+ // instance is just a detached ref store and the UI never updates.
421
+ useEffect(() => {
422
+ if (!externalInstance) return;
423
+ const live = externalInstance as FormInstance;
424
+ live.getFieldValue = instance.getFieldValue;
425
+ live.setFieldValue = instance.setFieldValue;
426
+ live.getFieldsValue = instance.getFieldsValue;
427
+ live.setFieldsValue = instance.setFieldsValue;
428
+ live.validateFields = instance.validateFields;
429
+ live.validateField = instance.validateField;
430
+ live.resetFields = instance.resetFields;
431
+ live.getFieldError = instance.getFieldError;
432
+ live.isFieldTouched = instance.isFieldTouched;
433
+ }, [externalInstance, instance]);
434
+
416
435
  const activeInstance: FormInstance = (externalInstance as FormInstance | undefined) ?? instance;
417
436
 
418
437
  const contextValue = useMemo<FormContextValue>(() => ({
@@ -661,4 +680,4 @@ function injectFormStyles(): void {
661
680
  }
662
681
 
663
682
  // Auto-inject styles on module load (client only)
664
- injectFormStyles();
683
+ injectFormStyles();
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { forwardRef, type ReactNode, type SVGAttributes } from 'react';
2
4
  import { useTheme } from '../themes';
3
5
  import { tkx, cx } from '../engine/tkx';
@@ -680,4 +682,4 @@ export const ICON_CATEGORIES: Record<string, IconName[]> = {
680
682
  'file', 'file-text', 'folder', 'folder-open', 'code', 'terminal',
681
683
  'lock', 'unlock', 'key', 'shield',
682
684
  ],
683
- };
685
+ };