tekivex-ui 2.5.1 → 2.5.3

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 (80) hide show
  1. package/README.md +436 -83
  2. package/dist/TkxForm-DSMDo2Ol.js +388 -0
  3. package/dist/TkxForm-DwV_n-eE.cjs +12 -0
  4. package/dist/headless.cjs +1 -1
  5. package/dist/headless.js +396 -195
  6. package/dist/i18n.cjs +1 -0
  7. package/dist/i18n.js +757 -0
  8. package/dist/index.cjs +27 -22
  9. package/dist/index.d.ts +2 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4994 -5089
  12. package/dist/quantum.cjs +91 -0
  13. package/dist/quantum.js +2312 -0
  14. package/dist/realtime.cjs +25 -0
  15. package/dist/realtime.js +944 -0
  16. package/dist/security-C369oOWH.cjs +1 -0
  17. package/dist/security-CzAmaMqa.js +97 -0
  18. package/dist/src/components/TkxDataGrid.d.ts +9 -1
  19. package/dist/src/components/TkxDataGrid.d.ts.map +1 -1
  20. package/dist/src/components/TkxDropdown.d.ts +41 -0
  21. package/dist/src/components/TkxDropdown.d.ts.map +1 -0
  22. package/dist/src/components/TkxLiveFeed.d.ts +891 -0
  23. package/dist/src/components/TkxLiveFeed.d.ts.map +1 -0
  24. package/dist/src/components/TkxLiveLog.d.ts +892 -0
  25. package/dist/src/components/TkxLiveLog.d.ts.map +1 -0
  26. package/dist/src/components/TkxLiveMetrics.d.ts +24 -0
  27. package/dist/src/components/TkxLiveMetrics.d.ts.map +1 -0
  28. package/dist/src/components/TkxPlayground.d.ts +15 -0
  29. package/dist/src/components/TkxPlayground.d.ts.map +1 -0
  30. package/dist/src/components/TkxQuantumForm.d.ts +20 -0
  31. package/dist/src/components/TkxQuantumForm.d.ts.map +1 -0
  32. package/dist/src/components/TkxRealTimeChart.d.ts +27 -0
  33. package/dist/src/components/TkxRealTimeChart.d.ts.map +1 -0
  34. package/dist/src/components/TkxTable.d.ts +11 -1
  35. package/dist/src/components/TkxTable.d.ts.map +1 -1
  36. package/dist/src/components/TkxThemeBuilder.d.ts +9 -0
  37. package/dist/src/components/TkxThemeBuilder.d.ts.map +1 -0
  38. package/dist/src/components/index.d.ts +1 -0
  39. package/dist/src/components/index.d.ts.map +1 -1
  40. package/dist/src/engine/index.d.ts +2 -0
  41. package/dist/src/engine/index.d.ts.map +1 -1
  42. package/dist/src/engine/quantum-ai.d.ts +201 -0
  43. package/dist/src/engine/quantum-ai.d.ts.map +1 -0
  44. package/dist/src/engine/tkx.d.ts.map +1 -1
  45. package/dist/src/headless/index.d.ts +6 -0
  46. package/dist/src/headless/index.d.ts.map +1 -1
  47. package/dist/src/headless/useInfiniteQuery.d.ts +35 -0
  48. package/dist/src/headless/useInfiniteQuery.d.ts.map +1 -0
  49. package/dist/src/headless/useSSE.d.ts +36 -0
  50. package/dist/src/headless/useSSE.d.ts.map +1 -0
  51. package/dist/src/headless/useWebSocket.d.ts +33 -0
  52. package/dist/src/headless/useWebSocket.d.ts.map +1 -0
  53. package/dist/src/quantum/index.d.ts +9 -0
  54. package/dist/src/quantum/index.d.ts.map +1 -0
  55. package/dist/src/realtime/index.d.ts +9 -0
  56. package/dist/src/realtime/index.d.ts.map +1 -0
  57. package/dist/{TkxForm-BWK4LqY3.js → tkx-BJ10JtMv.js} +372 -721
  58. package/dist/tkx-BeNyC0w7.cjs +4 -0
  59. package/package.json +16 -1
  60. package/src/components/TkxDataGrid.tsx +72 -0
  61. package/src/components/TkxDropdown.tsx +802 -0
  62. package/src/components/TkxLiveFeed.tsx +329 -0
  63. package/src/components/TkxLiveLog.tsx +470 -0
  64. package/src/components/TkxLiveMetrics.tsx +291 -0
  65. package/src/components/TkxPlayground.tsx +736 -0
  66. package/src/components/TkxQuantumForm.tsx +592 -0
  67. package/src/components/TkxRealTimeChart.tsx +381 -0
  68. package/src/components/TkxTable.tsx +351 -77
  69. package/src/components/TkxThemeBuilder.tsx +685 -0
  70. package/src/components/index.ts +4 -0
  71. package/src/engine/index.ts +2 -0
  72. package/src/engine/quantum-ai.ts +1045 -0
  73. package/src/engine/tkx.ts +95 -4
  74. package/src/headless/index.ts +6 -0
  75. package/src/headless/useInfiniteQuery.ts +153 -0
  76. package/src/headless/useSSE.ts +165 -0
  77. package/src/headless/useWebSocket.ts +229 -0
  78. package/src/quantum/index.ts +39 -0
  79. package/src/realtime/index.ts +21 -0
  80. package/dist/TkxForm-ljQjX7KD.cjs +0 -15
@@ -0,0 +1,329 @@
1
+ import {
2
+ useState,
3
+ useEffect,
4
+ useRef,
5
+ useCallback,
6
+ useMemo,
7
+ createElement,
8
+ type CSSProperties,
9
+ } from 'react';
10
+ import { useTheme } from '../themes';
11
+ import { sanitizeString } from '../engine/security';
12
+ import { tkx } from '../engine/tkx';
13
+
14
+ // ── Types ────────────────────────────────────────────────────────────────────
15
+
16
+ export interface FeedItem {
17
+ id: string;
18
+ content: string;
19
+ timestamp: number;
20
+ type?: 'info' | 'success' | 'warning' | 'error' | 'default';
21
+ avatar?: string;
22
+ author?: string;
23
+ meta?: string;
24
+ }
25
+
26
+ export interface TkxLiveFeedProps {
27
+ items: FeedItem[];
28
+ maxItems?: number;
29
+ height?: number | string;
30
+ autoScroll?: boolean;
31
+ showTimestamps?: boolean;
32
+ onItemClick?: (item: FeedItem) => void;
33
+ emptyMessage?: string;
34
+ pauseOnHover?: boolean;
35
+ renderItem?: (item: FeedItem) => React.ReactNode;
36
+ }
37
+
38
+ // ── Constants ────────────────────────────────────────────────────────────────
39
+
40
+ const ITEM_HEIGHT = 64;
41
+ const OVERSCAN = 5;
42
+
43
+ // ── CSS injection ────────────────────────────────────────────────────────────
44
+
45
+ let feedStylesInjected = false;
46
+ function injectFeedStyles() {
47
+ if (feedStylesInjected || typeof document === 'undefined') return;
48
+ feedStylesInjected = true;
49
+ const el = document.createElement('style');
50
+ el.id = 'tkx-live-feed-styles';
51
+ el.textContent = `
52
+ @keyframes tkx-feed-slide-in {
53
+ from { transform: translateY(-20px); opacity: 0; }
54
+ to { transform: translateY(0); opacity: 1; }
55
+ }
56
+ .tkx-feed-item-new {
57
+ animation: tkx-feed-slide-in 0.25s ease-out both;
58
+ }
59
+ `.trim();
60
+ document.head.appendChild(el);
61
+ }
62
+
63
+ // ── Helpers ──────────────────────────────────────────────────────────────────
64
+
65
+ function relativeTime(ts: number): string {
66
+ const diff = Date.now() - ts;
67
+ if (diff < 10_000) return 'just now';
68
+ if (diff < 60_000) return `${Math.floor(diff / 1_000)}s ago`;
69
+ if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
70
+ if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
71
+ return `${Math.floor(diff / 86_400_000)}d ago`;
72
+ }
73
+
74
+ function getTypeColor(type: FeedItem['type'], theme: ReturnType<typeof useTheme>): string {
75
+ switch (type) {
76
+ case 'info': return theme.info;
77
+ case 'success': return theme.success;
78
+ case 'warning': return theme.warning;
79
+ case 'error': return theme.danger;
80
+ default: return theme.border;
81
+ }
82
+ }
83
+
84
+ function getInitials(author?: string): string {
85
+ if (!author) return '?';
86
+ return author
87
+ .split(' ')
88
+ .map((w) => w[0] ?? '')
89
+ .slice(0, 2)
90
+ .join('')
91
+ .toUpperCase();
92
+ }
93
+
94
+ // ── FeedRow ──────────────────────────────────────────────────────────────────
95
+
96
+ interface FeedRowProps {
97
+ item: FeedItem;
98
+ isNew: boolean;
99
+ showTimestamps: boolean;
100
+ onItemClick?: (item: FeedItem) => void;
101
+ renderItem?: (item: FeedItem) => React.ReactNode;
102
+ theme: ReturnType<typeof useTheme>;
103
+ style?: CSSProperties;
104
+ }
105
+
106
+ function FeedRow({ item, isNew, showTimestamps, onItemClick, renderItem, theme, style }: FeedRowProps) {
107
+ const accentColor = getTypeColor(item.type, theme);
108
+
109
+ if (renderItem) {
110
+ return createElement('div', { style, className: isNew ? 'tkx-feed-item-new' : undefined }, renderItem(item));
111
+ }
112
+
113
+ const rowStyle: CSSProperties = {
114
+ ...style,
115
+ display: 'flex',
116
+ alignItems: 'center',
117
+ gap: 10,
118
+ padding: '0 12px',
119
+ borderLeft: `3px solid ${accentColor}`,
120
+ cursor: onItemClick ? 'pointer' : 'default',
121
+ boxSizing: 'border-box',
122
+ height: ITEM_HEIGHT,
123
+ background: theme.surface,
124
+ transition: 'background 0.15s',
125
+ };
126
+
127
+ return createElement(
128
+ 'div',
129
+ {
130
+ style: rowStyle,
131
+ className: isNew ? 'tkx-feed-item-new' : undefined,
132
+ onClick: onItemClick ? () => onItemClick(item) : undefined,
133
+ role: onItemClick ? 'button' : undefined,
134
+ tabIndex: onItemClick ? 0 : undefined,
135
+ onKeyDown: onItemClick
136
+ ? (e: React.KeyboardEvent) => { if (e.key === 'Enter' || e.key === ' ') onItemClick(item); }
137
+ : undefined,
138
+ },
139
+ // Avatar
140
+ createElement(
141
+ 'div',
142
+ {
143
+ style: {
144
+ width: 36,
145
+ height: 36,
146
+ borderRadius: '50%',
147
+ background: accentColor,
148
+ display: 'flex',
149
+ alignItems: 'center',
150
+ justifyContent: 'center',
151
+ fontSize: 13,
152
+ fontWeight: 700,
153
+ color: theme.bg,
154
+ flexShrink: 0,
155
+ overflow: 'hidden',
156
+ },
157
+ },
158
+ item.avatar
159
+ ? createElement('img', { src: item.avatar, alt: item.author ?? '', style: { width: '100%', height: '100%', objectFit: 'cover' } })
160
+ : getInitials(item.author),
161
+ ),
162
+ // Body
163
+ createElement(
164
+ 'div',
165
+ { style: { flex: 1, minWidth: 0 } },
166
+ createElement(
167
+ 'div',
168
+ { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 2 } },
169
+ item.author && createElement('span', { style: { fontWeight: 600, fontSize: 13, color: theme.text } }, sanitizeString(item.author)),
170
+ item.meta && createElement('span', { style: { fontSize: 11, color: theme.textMuted, background: theme.surfaceAlt, borderRadius: 4, padding: '1px 5px' } }, sanitizeString(item.meta)),
171
+ ),
172
+ createElement(
173
+ 'div',
174
+ { style: { fontSize: 13, color: theme.text, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
175
+ sanitizeString(item.content),
176
+ ),
177
+ ),
178
+ // Timestamp
179
+ showTimestamps && createElement(
180
+ 'div',
181
+ { style: { fontSize: 11, color: theme.textMuted, flexShrink: 0 } },
182
+ relativeTime(item.timestamp),
183
+ ),
184
+ );
185
+ }
186
+
187
+ // ── TkxLiveFeed ──────────────────────────────────────────────────────────────
188
+
189
+ export function TkxLiveFeed({
190
+ items,
191
+ maxItems = 100,
192
+ height = 400,
193
+ autoScroll = true,
194
+ showTimestamps = true,
195
+ onItemClick,
196
+ emptyMessage = 'No activity yet.',
197
+ pauseOnHover = true,
198
+ renderItem,
199
+ }: TkxLiveFeedProps) {
200
+ const theme = useTheme();
201
+ injectFeedStyles();
202
+
203
+ const containerRef = useRef<HTMLDivElement>(null);
204
+ const [scrollTop, setScrollTop] = useState(0);
205
+ const [isHovered, setIsHovered] = useState(false);
206
+ const [newIds, setNewIds] = useState<Set<string>>(new Set());
207
+ const prevItemsRef = useRef<FeedItem[]>([]);
208
+
209
+ // Trim to maxItems
210
+ const visibleItems = useMemo(
211
+ () => items.slice(-maxItems),
212
+ [items, maxItems],
213
+ );
214
+
215
+ const containerHeight = typeof height === 'number' ? height : 400;
216
+ const totalHeight = visibleItems.length * ITEM_HEIGHT;
217
+
218
+ // Track new items for animation
219
+ useEffect(() => {
220
+ const prevIds = new Set(prevItemsRef.current.map((i) => i.id));
221
+ const added = visibleItems.filter((i) => !prevIds.has(i.id)).map((i) => i.id);
222
+ if (added.length > 0) {
223
+ setNewIds((prev) => {
224
+ const next = new Set(prev);
225
+ added.forEach((id) => next.add(id));
226
+ return next;
227
+ });
228
+ const timer = window.setTimeout(() => {
229
+ setNewIds((prev) => {
230
+ const next = new Set(prev);
231
+ added.forEach((id) => next.delete(id));
232
+ return next;
233
+ });
234
+ }, 400);
235
+ return () => clearTimeout(timer);
236
+ }
237
+ return undefined;
238
+ }, [visibleItems]);
239
+
240
+ useEffect(() => {
241
+ prevItemsRef.current = visibleItems;
242
+ }, [visibleItems]);
243
+
244
+ // Auto-scroll
245
+ const shouldAutoScroll = autoScroll && !(pauseOnHover && isHovered);
246
+ useEffect(() => {
247
+ if (!shouldAutoScroll || !containerRef.current) return;
248
+ const el = containerRef.current;
249
+ const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < ITEM_HEIGHT * 3;
250
+ if (nearBottom || visibleItems.length <= 5) {
251
+ el.scrollTop = el.scrollHeight;
252
+ }
253
+ }, [visibleItems.length, shouldAutoScroll]);
254
+
255
+ const handleScroll = useCallback(() => {
256
+ if (containerRef.current) setScrollTop(containerRef.current.scrollTop);
257
+ }, []);
258
+
259
+ // Virtualization
260
+ const startIndex = Math.max(0, Math.floor(scrollTop / ITEM_HEIGHT) - OVERSCAN);
261
+ const endIndex = Math.min(
262
+ visibleItems.length - 1,
263
+ Math.ceil((scrollTop + containerHeight) / ITEM_HEIGHT) + OVERSCAN,
264
+ );
265
+
266
+ const containerStyle: CSSProperties = {
267
+ position: 'relative',
268
+ overflow: 'auto',
269
+ height: typeof height === 'number' ? `${height}px` : height,
270
+ background: theme.bg,
271
+ border: `1px solid ${theme.border}`,
272
+ borderRadius: 8,
273
+ boxSizing: 'border-box',
274
+ };
275
+
276
+ const innerStyle: CSSProperties = {
277
+ position: 'relative',
278
+ height: totalHeight,
279
+ minHeight: '100%',
280
+ };
281
+
282
+ if (visibleItems.length === 0) {
283
+ return createElement(
284
+ 'div',
285
+ { style: { ...containerStyle, display: 'flex', alignItems: 'center', justifyContent: 'center' } },
286
+ createElement('span', { style: { color: theme.textMuted, fontSize: 14 } }, sanitizeString(emptyMessage)),
287
+ );
288
+ }
289
+
290
+ const rows: React.ReactNode[] = [];
291
+ for (let i = startIndex; i <= endIndex; i++) {
292
+ const item = visibleItems[i];
293
+ if (!item) continue;
294
+ rows.push(
295
+ createElement(FeedRow, {
296
+ key: item.id,
297
+ item,
298
+ isNew: newIds.has(item.id),
299
+ showTimestamps,
300
+ onItemClick,
301
+ renderItem,
302
+ theme,
303
+ style: {
304
+ position: 'absolute',
305
+ top: i * ITEM_HEIGHT,
306
+ left: 0,
307
+ right: 0,
308
+ },
309
+ }),
310
+ );
311
+ }
312
+
313
+ return createElement(
314
+ 'div',
315
+ {
316
+ ref: containerRef,
317
+ style: containerStyle,
318
+ onScroll: handleScroll,
319
+ onMouseEnter: pauseOnHover ? () => setIsHovered(true) : undefined,
320
+ onMouseLeave: pauseOnHover ? () => setIsHovered(false) : undefined,
321
+ 'aria-label': 'Live feed',
322
+ role: 'feed',
323
+ },
324
+ createElement('div', { style: innerStyle }, ...rows),
325
+ );
326
+ }
327
+
328
+ // Ensure tkx is referenced (satisfies import requirement)
329
+ void tkx;