tekivex-ui 2.5.12 → 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.
- package/dist/{TkxForm-9-W3Nl6m.js → TkxForm-BaO4wvXt.js} +109 -103
- package/dist/{TkxForm-1KDzoniV.cjs → TkxForm-M6ttomry.cjs} +1 -1
- package/dist/charts.cjs +1 -1
- package/dist/charts.js +2 -1
- package/dist/headless.cjs +1 -1
- package/dist/headless.js +3 -2
- package/dist/index.cjs +10 -10
- package/dist/index.js +953 -896
- package/dist/quantum.cjs +9 -9
- package/dist/quantum.js +160 -115
- package/dist/realtime.cjs +1 -1
- package/dist/realtime.js +1 -1
- package/dist/src/charts/TkxAreaChart.d.ts.map +1 -1
- package/dist/src/charts/TkxBarChart.d.ts.map +1 -1
- package/dist/src/charts/TkxDonutChart.d.ts.map +1 -1
- package/dist/src/charts/TkxLineChart.d.ts.map +1 -1
- package/dist/src/charts/TkxPieChart.d.ts.map +1 -1
- package/dist/src/charts/TkxRadarChart.d.ts.map +1 -1
- package/dist/src/charts/TkxScatterChart.d.ts.map +1 -1
- package/dist/src/charts/index.d.ts.map +1 -1
- package/dist/src/charts/shared.d.ts.map +1 -1
- package/dist/src/components/TkxAIChatBubble.d.ts.map +1 -1
- package/dist/src/components/TkxAIConfidenceBar.d.ts.map +1 -1
- package/dist/src/components/TkxAIThinking.d.ts.map +1 -1
- package/dist/src/components/TkxAccordion.d.ts.map +1 -1
- package/dist/src/components/TkxAffix.d.ts.map +1 -1
- package/dist/src/components/TkxAlert.d.ts.map +1 -1
- package/dist/src/components/TkxAnchor.d.ts.map +1 -1
- package/dist/src/components/TkxAppBar.d.ts.map +1 -1
- package/dist/src/components/TkxAutocomplete.d.ts.map +1 -1
- package/dist/src/components/TkxAvatar.d.ts.map +1 -1
- package/dist/src/components/TkxBadge.d.ts.map +1 -1
- package/dist/src/components/TkxBottomNav.d.ts.map +1 -1
- package/dist/src/components/TkxBreadcrumb.d.ts.map +1 -1
- package/dist/src/components/TkxButton.d.ts.map +1 -1
- package/dist/src/components/TkxCard.d.ts.map +1 -1
- package/dist/src/components/TkxCarousel.d.ts.map +1 -1
- package/dist/src/components/TkxCascader.d.ts.map +1 -1
- package/dist/src/components/TkxChat.d.ts.map +1 -1
- package/dist/src/components/TkxCheckbox.d.ts.map +1 -1
- package/dist/src/components/TkxClock.d.ts.map +1 -1
- package/dist/src/components/TkxColorPicker.d.ts.map +1 -1
- package/dist/src/components/TkxCommand.d.ts.map +1 -1
- package/dist/src/components/TkxConfigProvider.d.ts.map +1 -1
- package/dist/src/components/TkxDataGrid.d.ts.map +1 -1
- package/dist/src/components/TkxDatePicker.d.ts +1 -1
- package/dist/src/components/TkxDatePicker.d.ts.map +1 -1
- package/dist/src/components/TkxDivider.d.ts.map +1 -1
- package/dist/src/components/TkxDrawer.d.ts.map +1 -1
- package/dist/src/components/TkxDropdown.d.ts.map +1 -1
- package/dist/src/components/TkxEmpty.d.ts.map +1 -1
- package/dist/src/components/TkxFileUpload.d.ts.map +1 -1
- package/dist/src/components/TkxForm.d.ts.map +1 -1
- package/dist/src/components/TkxIcon.d.ts.map +1 -1
- package/dist/src/components/TkxImage.d.ts.map +1 -1
- package/dist/src/components/TkxInput.d.ts.map +1 -1
- package/dist/src/components/TkxLayout.d.ts.map +1 -1
- package/dist/src/components/TkxList.d.ts.map +1 -1
- package/dist/src/components/TkxLiveFeed.d.ts.map +1 -1
- package/dist/src/components/TkxLiveLog.d.ts.map +1 -1
- package/dist/src/components/TkxLiveMetrics.d.ts.map +1 -1
- package/dist/src/components/TkxLogo.d.ts.map +1 -1
- package/dist/src/components/TkxMasonry.d.ts.map +1 -1
- package/dist/src/components/TkxMentions.d.ts.map +1 -1
- package/dist/src/components/TkxMenu.d.ts.map +1 -1
- package/dist/src/components/TkxModal.d.ts.map +1 -1
- package/dist/src/components/TkxNumberInput.d.ts.map +1 -1
- package/dist/src/components/TkxOTP.d.ts.map +1 -1
- package/dist/src/components/TkxPagination.d.ts.map +1 -1
- package/dist/src/components/TkxPlayground.d.ts +15 -1
- package/dist/src/components/TkxPlayground.d.ts.map +1 -1
- package/dist/src/components/TkxPopover.d.ts.map +1 -1
- package/dist/src/components/TkxProgress.d.ts.map +1 -1
- package/dist/src/components/TkxQRCode.d.ts.map +1 -1
- package/dist/src/components/TkxQuantumForm.d.ts.map +1 -1
- package/dist/src/components/TkxRadio.d.ts.map +1 -1
- package/dist/src/components/TkxRating.d.ts.map +1 -1
- package/dist/src/components/TkxRealTimeChart.d.ts.map +1 -1
- package/dist/src/components/TkxResult.d.ts.map +1 -1
- package/dist/src/components/TkxRichTextDisplay.d.ts.map +1 -1
- package/dist/src/components/TkxSegmented.d.ts.map +1 -1
- package/dist/src/components/TkxSelect.d.ts.map +1 -1
- package/dist/src/components/TkxSkeleton.d.ts.map +1 -1
- package/dist/src/components/TkxSlider.d.ts.map +1 -1
- package/dist/src/components/TkxSnackbar.d.ts.map +1 -1
- package/dist/src/components/TkxSpeedDial.d.ts.map +1 -1
- package/dist/src/components/TkxSpin.d.ts.map +1 -1
- package/dist/src/components/TkxStatistic.d.ts.map +1 -1
- package/dist/src/components/TkxStepper.d.ts.map +1 -1
- package/dist/src/components/TkxTable.d.ts.map +1 -1
- package/dist/src/components/TkxTabs.d.ts.map +1 -1
- package/dist/src/components/TkxTag.d.ts.map +1 -1
- package/dist/src/components/TkxThemeBuilder.d.ts.map +1 -1
- package/dist/src/components/TkxTimeline.d.ts.map +1 -1
- package/dist/src/components/TkxToast.d.ts.map +1 -1
- package/dist/src/components/TkxToggle.d.ts.map +1 -1
- package/dist/src/components/TkxToolbar.d.ts.map +1 -1
- package/dist/src/components/TkxTooltip.d.ts.map +1 -1
- package/dist/src/components/TkxTour.d.ts.map +1 -1
- package/dist/src/components/TkxTransferList.d.ts.map +1 -1
- package/dist/src/components/TkxTreeView.d.ts.map +1 -1
- package/dist/src/components/TkxTypography.d.ts.map +1 -1
- package/dist/src/components/TkxVideoPlayer.d.ts.map +1 -1
- package/dist/src/components/TkxWatermark.d.ts.map +1 -1
- package/dist/src/engine/security.d.ts +8 -0
- package/dist/src/engine/security.d.ts.map +1 -1
- package/dist/src/headless/index.d.ts.map +1 -1
- package/dist/src/headless/useControllable.d.ts.map +1 -1
- package/dist/src/headless/useDebounce.d.ts.map +1 -1
- package/dist/src/headless/useDisclosure.d.ts.map +1 -1
- package/dist/src/headless/useFormState.d.ts.map +1 -1
- package/dist/src/headless/useInfiniteQuery.d.ts.map +1 -1
- package/dist/src/headless/useIntersectionObserver.d.ts.map +1 -1
- package/dist/src/headless/useListSelection.d.ts.map +1 -1
- package/dist/src/headless/useLocalStorage.d.ts.map +1 -1
- package/dist/src/headless/useMediaQuery.d.ts.map +1 -1
- package/dist/src/headless/useRovingTabIndex.d.ts.map +1 -1
- package/dist/src/headless/useSSE.d.ts.map +1 -1
- package/dist/src/headless/useThrottle.d.ts.map +1 -1
- package/dist/src/headless/useWebSocket.d.ts.map +1 -1
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/tkx-BoP5V5zy.cjs +4 -0
- package/dist/{tkx-DQkvHdE5.js → tkx-mOKMzvUK.js} +4 -2
- package/package.json +3 -4
- package/src/charts/TkxAreaChart.tsx +3 -1
- package/src/charts/TkxBarChart.tsx +3 -1
- package/src/charts/TkxDonutChart.tsx +3 -1
- package/src/charts/TkxLineChart.tsx +3 -1
- package/src/charts/TkxPieChart.tsx +4 -2
- package/src/charts/TkxRadarChart.tsx +3 -1
- package/src/charts/TkxScatterChart.tsx +3 -1
- package/src/charts/index.ts +3 -1
- package/src/charts/shared.ts +3 -1
- package/src/components/TkxAIChatBubble.tsx +55 -8
- package/src/components/TkxAIConfidenceBar.tsx +3 -1
- package/src/components/TkxAIThinking.tsx +3 -1
- package/src/components/TkxAccordion.tsx +3 -1
- package/src/components/TkxAffix.tsx +3 -1
- package/src/components/TkxAlert.tsx +3 -1
- package/src/components/TkxAnchor.tsx +3 -1
- package/src/components/TkxAppBar.tsx +3 -1
- package/src/components/TkxAutocomplete.tsx +3 -1
- package/src/components/TkxAvatar.tsx +3 -1
- package/src/components/TkxBadge.tsx +3 -1
- package/src/components/TkxBottomNav.tsx +3 -1
- package/src/components/TkxBreadcrumb.tsx +3 -1
- package/src/components/TkxButton.tsx +3 -1
- package/src/components/TkxCard.tsx +3 -1
- package/src/components/TkxCarousel.tsx +3 -1
- package/src/components/TkxCascader.tsx +3 -1
- package/src/components/TkxChat.tsx +3 -1
- package/src/components/TkxCheckbox.tsx +3 -1
- package/src/components/TkxClock.tsx +3 -1
- package/src/components/TkxColorPicker.tsx +3 -1
- package/src/components/TkxCommand.tsx +3 -1
- package/src/components/TkxConfigProvider.tsx +3 -1
- package/src/components/TkxDataGrid.tsx +3 -1
- package/src/components/TkxDatePicker.tsx +76 -18
- package/src/components/TkxDivider.tsx +3 -1
- package/src/components/TkxDrawer.tsx +3 -1
- package/src/components/TkxDropdown.tsx +3 -1
- package/src/components/TkxEmpty.tsx +3 -1
- package/src/components/TkxFileUpload.tsx +3 -1
- package/src/components/TkxForm.tsx +22 -3
- package/src/components/TkxIcon.tsx +3 -1
- package/src/components/TkxImage.tsx +3 -1
- package/src/components/TkxInput.tsx +3 -1
- package/src/components/TkxLayout.tsx +3 -1
- package/src/components/TkxList.tsx +3 -1
- package/src/components/TkxLiveFeed.tsx +3 -1
- package/src/components/TkxLiveLog.tsx +3 -1
- package/src/components/TkxLiveMetrics.tsx +3 -1
- package/src/components/TkxLogo.tsx +3 -1
- package/src/components/TkxMasonry.tsx +3 -1
- package/src/components/TkxMentions.tsx +3 -1
- package/src/components/TkxMenu.tsx +3 -1
- package/src/components/TkxModal.tsx +3 -1
- package/src/components/TkxNumberInput.tsx +3 -1
- package/src/components/TkxOTP.tsx +3 -1
- package/src/components/TkxPagination.tsx +3 -1
- package/src/components/TkxPlayground.tsx +77 -3
- package/src/components/TkxPopover.tsx +3 -1
- package/src/components/TkxProgress.tsx +3 -1
- package/src/components/TkxQRCode.tsx +3 -1
- package/src/components/TkxQuantumForm.tsx +3 -1
- package/src/components/TkxRadio.tsx +3 -1
- package/src/components/TkxRating.tsx +3 -1
- package/src/components/TkxRealTimeChart.tsx +3 -1
- package/src/components/TkxResult.tsx +3 -1
- package/src/components/TkxRichTextDisplay.tsx +3 -1
- package/src/components/TkxSegmented.tsx +3 -1
- package/src/components/TkxSelect.tsx +3 -1
- package/src/components/TkxSkeleton.tsx +3 -1
- package/src/components/TkxSlider.tsx +3 -1
- package/src/components/TkxSnackbar.tsx +3 -1
- package/src/components/TkxSpeedDial.tsx +3 -1
- package/src/components/TkxSpin.tsx +3 -1
- package/src/components/TkxStatistic.tsx +3 -1
- package/src/components/TkxStepper.tsx +3 -1
- package/src/components/TkxTable.tsx +3 -1
- package/src/components/TkxTabs.tsx +3 -1
- package/src/components/TkxTag.tsx +3 -1
- package/src/components/TkxThemeBuilder.tsx +3 -1
- package/src/components/TkxTimeline.tsx +3 -1
- package/src/components/TkxToast.tsx +3 -1
- package/src/components/TkxToggle.tsx +3 -1
- package/src/components/TkxToolbar.tsx +3 -1
- package/src/components/TkxTooltip.tsx +15 -2
- package/src/components/TkxTour.tsx +3 -1
- package/src/components/TkxTransferList.tsx +3 -1
- package/src/components/TkxTreeView.tsx +3 -1
- package/src/components/TkxTypography.tsx +3 -1
- package/src/components/TkxVideoPlayer.tsx +3 -1
- package/src/components/TkxWatermark.tsx +3 -1
- package/src/engine/security.ts +16 -1
- package/src/headless/index.ts +3 -1
- package/src/headless/useControllable.ts +3 -1
- package/src/headless/useDebounce.ts +3 -1
- package/src/headless/useDisclosure.ts +3 -1
- package/src/headless/useFormState.ts +3 -1
- package/src/headless/useInfiniteQuery.ts +3 -1
- package/src/headless/useIntersectionObserver.ts +3 -1
- package/src/headless/useListSelection.ts +3 -1
- package/src/headless/useLocalStorage.ts +3 -1
- package/src/headless/useMediaQuery.ts +3 -1
- package/src/headless/useRovingTabIndex.ts +3 -1
- package/src/headless/useSSE.ts +3 -1
- package/src/headless/useThrottle.ts +3 -1
- package/src/headless/useWebSocket.ts +3 -1
- package/src/hooks/index.ts +3 -1
- package/dist/tkx-0oAwZqgL.cjs +0 -4
package/src/charts/shared.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
1
3
|
// ── Shared chart utilities ───────────────────────────────────────────────────
|
|
2
4
|
|
|
3
5
|
import type { ThemeTokens } from '../themes';
|
|
@@ -42,4 +44,4 @@ export function tooltipStyle(theme: ThemeTokens) {
|
|
|
42
44
|
itemStyle: { color: theme.text },
|
|
43
45
|
cursor: { fill: `${theme.primary}15` },
|
|
44
46
|
};
|
|
45
|
-
}
|
|
47
|
+
}
|
|
@@ -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')
|
|
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
|
-
|
|
89
|
-
|
|
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 () =>
|
|
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
|
-
{
|
|
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 { 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 { 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 { 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 { 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
|
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
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 { 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
|
// ══════════════════════════════════════════════════════════════════════════════
|
|
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,
|
|
415
|
-
//
|
|
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();
|