@prismiq/react 0.1.0

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 (82) hide show
  1. package/README.md +88 -0
  2. package/dist/CustomSQLEditor-BXB4rf1q.d.cts +1297 -0
  3. package/dist/CustomSQLEditor-DYeId0Gp.d.ts +1297 -0
  4. package/dist/DashboardDialog-B3vYC5Gs.d.ts +1106 -0
  5. package/dist/DashboardDialog-LHmrtNQU.d.cts +1106 -0
  6. package/dist/accessibility-2yy5yqRR.d.cts +145 -0
  7. package/dist/accessibility-2yy5yqRR.d.ts +145 -0
  8. package/dist/charts/index.cjs +110 -0
  9. package/dist/charts/index.cjs.map +1 -0
  10. package/dist/charts/index.d.cts +2 -0
  11. package/dist/charts/index.d.ts +2 -0
  12. package/dist/charts/index.js +5 -0
  13. package/dist/charts/index.js.map +1 -0
  14. package/dist/chunk-2H5WTH4K.js +2409 -0
  15. package/dist/chunk-2H5WTH4K.js.map +1 -0
  16. package/dist/chunk-4AVL6GQK.cjs +470 -0
  17. package/dist/chunk-4AVL6GQK.cjs.map +1 -0
  18. package/dist/chunk-EX74SI67.js +455 -0
  19. package/dist/chunk-EX74SI67.js.map +1 -0
  20. package/dist/chunk-FEABEF3J.cjs +7543 -0
  21. package/dist/chunk-FEABEF3J.cjs.map +1 -0
  22. package/dist/chunk-JTCBZDHY.js +126 -0
  23. package/dist/chunk-JTCBZDHY.js.map +1 -0
  24. package/dist/chunk-LMTG3LRC.cjs +326 -0
  25. package/dist/chunk-LMTG3LRC.cjs.map +1 -0
  26. package/dist/chunk-MDXGGZSW.cjs +273 -0
  27. package/dist/chunk-MDXGGZSW.cjs.map +1 -0
  28. package/dist/chunk-MOAEEF5P.js +7510 -0
  29. package/dist/chunk-MOAEEF5P.js.map +1 -0
  30. package/dist/chunk-NK7HKX2J.cjs +2459 -0
  31. package/dist/chunk-NK7HKX2J.cjs.map +1 -0
  32. package/dist/chunk-NY6TZLST.cjs +8781 -0
  33. package/dist/chunk-NY6TZLST.cjs.map +1 -0
  34. package/dist/chunk-T6STUE7E.js +321 -0
  35. package/dist/chunk-T6STUE7E.js.map +1 -0
  36. package/dist/chunk-TRW7DKLP.cjs +141 -0
  37. package/dist/chunk-TRW7DKLP.cjs.map +1 -0
  38. package/dist/chunk-UPYINBZU.js +8706 -0
  39. package/dist/chunk-UPYINBZU.js.map +1 -0
  40. package/dist/chunk-WWTT2OJ5.js +246 -0
  41. package/dist/chunk-WWTT2OJ5.js.map +1 -0
  42. package/dist/components/index.cjs +222 -0
  43. package/dist/components/index.cjs.map +1 -0
  44. package/dist/components/index.d.cts +207 -0
  45. package/dist/components/index.d.ts +207 -0
  46. package/dist/components/index.js +5 -0
  47. package/dist/components/index.js.map +1 -0
  48. package/dist/dashboard/index.cjs +140 -0
  49. package/dist/dashboard/index.cjs.map +1 -0
  50. package/dist/dashboard/index.d.cts +302 -0
  51. package/dist/dashboard/index.d.ts +302 -0
  52. package/dist/dashboard/index.js +7 -0
  53. package/dist/dashboard/index.js.map +1 -0
  54. package/dist/export/index.cjs +32 -0
  55. package/dist/export/index.cjs.map +1 -0
  56. package/dist/export/index.d.cts +197 -0
  57. package/dist/export/index.d.ts +197 -0
  58. package/dist/export/index.js +3 -0
  59. package/dist/export/index.js.map +1 -0
  60. package/dist/index-C-Qcuu4Y.d.cts +821 -0
  61. package/dist/index-rPc7ijt8.d.ts +821 -0
  62. package/dist/index.cjs +1486 -0
  63. package/dist/index.cjs.map +1 -0
  64. package/dist/index.d.cts +1435 -0
  65. package/dist/index.d.ts +1435 -0
  66. package/dist/index.js +926 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/ssr/index.cjs +64 -0
  69. package/dist/ssr/index.cjs.map +1 -0
  70. package/dist/ssr/index.d.cts +213 -0
  71. package/dist/ssr/index.d.ts +213 -0
  72. package/dist/ssr/index.js +3 -0
  73. package/dist/ssr/index.js.map +1 -0
  74. package/dist/types-WrCbOeAV.d.cts +569 -0
  75. package/dist/types-WrCbOeAV.d.ts +569 -0
  76. package/dist/utils/index.cjs +64 -0
  77. package/dist/utils/index.cjs.map +1 -0
  78. package/dist/utils/index.d.cts +112 -0
  79. package/dist/utils/index.d.ts +112 -0
  80. package/dist/utils/index.js +3 -0
  81. package/dist/utils/index.js.map +1 -0
  82. package/package.json +110 -0
@@ -0,0 +1,455 @@
1
+ import { useRef, useState, useCallback, useEffect } from 'react';
2
+
3
+ // src/utils/accessibility.ts
4
+ var FOCUSABLE_SELECTOR = [
5
+ "button:not([disabled])",
6
+ "input:not([disabled])",
7
+ "select:not([disabled])",
8
+ "textarea:not([disabled])",
9
+ "a[href]",
10
+ '[tabindex]:not([tabindex="-1"])',
11
+ '[contenteditable="true"]'
12
+ ].join(", ");
13
+ function getFocusableElements(container) {
14
+ const elements = container.querySelectorAll(FOCUSABLE_SELECTOR);
15
+ return Array.from(elements).filter(
16
+ (el) => !el.hasAttribute("aria-hidden") && el.offsetParent !== null
17
+ );
18
+ }
19
+ function useFocusTrap(options = {}) {
20
+ const {
21
+ active = true,
22
+ autoFocus = true,
23
+ restoreFocus = true,
24
+ onEscape
25
+ } = options;
26
+ const containerRef = useRef(null);
27
+ const previousFocusRef = useRef(null);
28
+ const [isActive, setIsActive] = useState(active);
29
+ const activate = useCallback(() => {
30
+ if (!containerRef.current) return;
31
+ previousFocusRef.current = document.activeElement;
32
+ if (autoFocus) {
33
+ const focusable = getFocusableElements(containerRef.current);
34
+ const firstElement = focusable[0];
35
+ if (firstElement) {
36
+ firstElement.focus();
37
+ }
38
+ }
39
+ setIsActive(true);
40
+ }, [autoFocus]);
41
+ const deactivate = useCallback(() => {
42
+ setIsActive(false);
43
+ if (restoreFocus && previousFocusRef.current) {
44
+ previousFocusRef.current.focus();
45
+ }
46
+ }, [restoreFocus]);
47
+ useEffect(() => {
48
+ if (!isActive || !containerRef.current) return;
49
+ const container = containerRef.current;
50
+ const handleKeyDown = (event) => {
51
+ if (event.key === "Escape") {
52
+ onEscape?.();
53
+ return;
54
+ }
55
+ if (event.key !== "Tab") return;
56
+ const focusable = getFocusableElements(container);
57
+ if (focusable.length === 0) return;
58
+ const firstElement = focusable[0];
59
+ const lastElement = focusable[focusable.length - 1];
60
+ if (!firstElement || !lastElement) return;
61
+ if (event.shiftKey && document.activeElement === firstElement) {
62
+ event.preventDefault();
63
+ lastElement.focus();
64
+ return;
65
+ }
66
+ if (!event.shiftKey && document.activeElement === lastElement) {
67
+ event.preventDefault();
68
+ firstElement.focus();
69
+ }
70
+ };
71
+ const handleFocusIn = (event) => {
72
+ if (!container.contains(event.target)) {
73
+ const focusable = getFocusableElements(container);
74
+ const firstElement = focusable[0];
75
+ if (firstElement) {
76
+ firstElement.focus();
77
+ }
78
+ }
79
+ };
80
+ container.addEventListener("keydown", handleKeyDown);
81
+ document.addEventListener("focusin", handleFocusIn);
82
+ return () => {
83
+ container.removeEventListener("keydown", handleKeyDown);
84
+ document.removeEventListener("focusin", handleFocusIn);
85
+ };
86
+ }, [isActive, onEscape]);
87
+ useEffect(() => {
88
+ if (active) {
89
+ activate();
90
+ } else {
91
+ deactivate();
92
+ }
93
+ }, [active, activate, deactivate]);
94
+ return {
95
+ containerRef,
96
+ activate,
97
+ deactivate,
98
+ isActive
99
+ };
100
+ }
101
+ function useArrowNavigation(itemCount, options = {}) {
102
+ const { wrap = true, orientation = "vertical", onActiveChange } = options;
103
+ const [activeIndex, setActiveIndex] = useState(0);
104
+ const handleKeyDown = useCallback(
105
+ (event) => {
106
+ let newIndex = activeIndex;
107
+ let handled = false;
108
+ const isNext = orientation === "vertical" && event.key === "ArrowDown" || orientation === "horizontal" && event.key === "ArrowRight" || orientation === "both" && (event.key === "ArrowDown" || event.key === "ArrowRight");
109
+ const isPrev = orientation === "vertical" && event.key === "ArrowUp" || orientation === "horizontal" && event.key === "ArrowLeft" || orientation === "both" && (event.key === "ArrowUp" || event.key === "ArrowLeft");
110
+ if (isNext) {
111
+ handled = true;
112
+ if (activeIndex < itemCount - 1) {
113
+ newIndex = activeIndex + 1;
114
+ } else if (wrap) {
115
+ newIndex = 0;
116
+ }
117
+ } else if (isPrev) {
118
+ handled = true;
119
+ if (activeIndex > 0) {
120
+ newIndex = activeIndex - 1;
121
+ } else if (wrap) {
122
+ newIndex = itemCount - 1;
123
+ }
124
+ } else if (event.key === "Home") {
125
+ handled = true;
126
+ newIndex = 0;
127
+ } else if (event.key === "End") {
128
+ handled = true;
129
+ newIndex = itemCount - 1;
130
+ }
131
+ if (handled) {
132
+ event.preventDefault();
133
+ setActiveIndex(newIndex);
134
+ onActiveChange?.(newIndex);
135
+ }
136
+ },
137
+ [activeIndex, itemCount, wrap, orientation, onActiveChange]
138
+ );
139
+ const getItemProps = useCallback(
140
+ (index) => ({
141
+ tabIndex: index === activeIndex ? 0 : -1,
142
+ "aria-selected": index === activeIndex,
143
+ onFocus: () => {
144
+ setActiveIndex(index);
145
+ onActiveChange?.(index);
146
+ }
147
+ }),
148
+ [activeIndex, onActiveChange]
149
+ );
150
+ const handleSetActiveIndex = useCallback(
151
+ (index) => {
152
+ setActiveIndex(index);
153
+ onActiveChange?.(index);
154
+ },
155
+ [onActiveChange]
156
+ );
157
+ return {
158
+ activeIndex,
159
+ setActiveIndex: handleSetActiveIndex,
160
+ onKeyDown: handleKeyDown,
161
+ getItemProps
162
+ };
163
+ }
164
+ var announceElement = null;
165
+ function getAnnounceElement() {
166
+ if (announceElement) return announceElement;
167
+ if (typeof document === "undefined") {
168
+ throw new Error("announceToScreenReader can only be used in browser environment");
169
+ }
170
+ announceElement = document.createElement("div");
171
+ announceElement.setAttribute("role", "status");
172
+ announceElement.setAttribute("aria-live", "polite");
173
+ announceElement.setAttribute("aria-atomic", "true");
174
+ announceElement.style.cssText = [
175
+ "position: absolute",
176
+ "width: 1px",
177
+ "height: 1px",
178
+ "padding: 0",
179
+ "margin: -1px",
180
+ "overflow: hidden",
181
+ "clip: rect(0, 0, 0, 0)",
182
+ "white-space: nowrap",
183
+ "border: 0"
184
+ ].join(";");
185
+ announceElement.id = "prismiq-announcer";
186
+ document.body.appendChild(announceElement);
187
+ return announceElement;
188
+ }
189
+ function announceToScreenReader(message, priority = "polite") {
190
+ if (typeof document === "undefined") return;
191
+ const element = getAnnounceElement();
192
+ element.setAttribute("aria-live", priority);
193
+ element.textContent = "";
194
+ requestAnimationFrame(() => {
195
+ element.textContent = message;
196
+ });
197
+ }
198
+ var focusVisibleStyles = `
199
+ /* Hide focus outline for mouse users */
200
+ :focus:not(:focus-visible) {
201
+ outline: none;
202
+ }
203
+
204
+ /* Show focus outline for keyboard users */
205
+ :focus-visible {
206
+ outline: 2px solid var(--prismiq-color-primary);
207
+ outline-offset: 2px;
208
+ }
209
+
210
+ /* Prismiq focus ring class */
211
+ .prismiq-focus-ring:focus-visible {
212
+ outline: 2px solid var(--prismiq-color-primary);
213
+ outline-offset: 2px;
214
+ box-shadow: 0 0 0 4px rgba(var(--prismiq-color-primary-rgb), 0.2);
215
+ }
216
+ `;
217
+ function useFocusVisible() {
218
+ const [focusVisible, setFocusVisible] = useState(false);
219
+ useEffect(() => {
220
+ if (typeof window === "undefined") return;
221
+ let hadKeyboardEvent = false;
222
+ const handleKeyDown = (event) => {
223
+ if (event.key === "Tab") {
224
+ hadKeyboardEvent = true;
225
+ }
226
+ };
227
+ const handlePointerDown = () => {
228
+ hadKeyboardEvent = false;
229
+ };
230
+ const handleFocus = () => {
231
+ setFocusVisible(hadKeyboardEvent);
232
+ };
233
+ const handleBlur = () => {
234
+ setFocusVisible(false);
235
+ };
236
+ document.addEventListener("keydown", handleKeyDown, true);
237
+ document.addEventListener("pointerdown", handlePointerDown, true);
238
+ document.addEventListener("focus", handleFocus, true);
239
+ document.addEventListener("blur", handleBlur, true);
240
+ return () => {
241
+ document.removeEventListener("keydown", handleKeyDown, true);
242
+ document.removeEventListener("pointerdown", handlePointerDown, true);
243
+ document.removeEventListener("focus", handleFocus, true);
244
+ document.removeEventListener("blur", handleBlur, true);
245
+ };
246
+ }, []);
247
+ return focusVisible;
248
+ }
249
+ var skipLinkStyles = {
250
+ position: "absolute",
251
+ left: "-9999px",
252
+ zIndex: 9999,
253
+ padding: "1em",
254
+ backgroundColor: "var(--prismiq-color-background)",
255
+ color: "var(--prismiq-color-primary)",
256
+ textDecoration: "underline"
257
+ // When focused, show the link
258
+ };
259
+ var skipLinkFocusStyles = {
260
+ ...skipLinkStyles,
261
+ left: 0,
262
+ top: 0
263
+ };
264
+ function useRovingTabIndex(itemRefs, options = {}) {
265
+ const result = useArrowNavigation(itemRefs.length, options);
266
+ useEffect(() => {
267
+ const activeRef = itemRefs[result.activeIndex];
268
+ if (activeRef?.current) {
269
+ activeRef.current.focus();
270
+ }
271
+ }, [result.activeIndex, itemRefs]);
272
+ return result;
273
+ }
274
+
275
+ // src/utils/dateFormat.ts
276
+ function createDateFormatter(formatString) {
277
+ return (value) => {
278
+ if (value === null || value === void 0) {
279
+ return "";
280
+ }
281
+ let date;
282
+ if (value instanceof Date) {
283
+ date = value;
284
+ } else if (typeof value === "string") {
285
+ date = new Date(value);
286
+ } else if (typeof value === "number") {
287
+ date = new Date(value);
288
+ } else {
289
+ return String(value);
290
+ }
291
+ if (isNaN(date.getTime())) {
292
+ return String(value);
293
+ }
294
+ return formatDateWithPattern(date, formatString);
295
+ };
296
+ }
297
+ function formatDateWithPattern(date, pattern) {
298
+ const year = date.getFullYear();
299
+ const month = date.getMonth() + 1;
300
+ const day = date.getDate();
301
+ const hours = date.getHours();
302
+ const minutes = date.getMinutes();
303
+ const seconds = date.getSeconds();
304
+ const monthNamesShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
305
+ const monthNamesFull = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
306
+ const dayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
307
+ const dayNamesFull = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
308
+ const quarter = Math.ceil(month / 3);
309
+ let result = pattern;
310
+ result = result.replace(/QQ/g, "__QUARTER_PLACEHOLDER__");
311
+ result = result.replace(/Q(?!['"])/g, String(quarter));
312
+ result = result.replace(/__QUARTER_PLACEHOLDER__/g, `Q${quarter}`);
313
+ result = result.replace(/yyyy/g, String(year).padStart(4, "0"));
314
+ result = result.replace(/yy/g, String(year % 100).padStart(2, "0"));
315
+ result = result.replace(/MMMM/g, monthNamesFull[month - 1] || "");
316
+ result = result.replace(/MMM/g, monthNamesShort[month - 1] || "");
317
+ result = result.replace(/MM/g, String(month).padStart(2, "0"));
318
+ result = result.replace(/M/g, String(month));
319
+ result = result.replace(/dddd/g, dayNamesFull[date.getDay()] || "");
320
+ result = result.replace(/ddd/g, dayNamesShort[date.getDay()] || "");
321
+ result = result.replace(/dd/g, String(day).padStart(2, "0"));
322
+ result = result.replace(/d/g, String(day));
323
+ result = result.replace(/HH/g, String(hours).padStart(2, "0"));
324
+ result = result.replace(/H/g, String(hours));
325
+ const hours12 = hours % 12 || 12;
326
+ result = result.replace(/hh/g, String(hours12).padStart(2, "0"));
327
+ result = result.replace(/h/g, String(hours12));
328
+ const ampm = hours >= 12 ? "PM" : "AM";
329
+ result = result.replace(/tt/g, ampm);
330
+ result = result.replace(/t/g, ampm.charAt(0));
331
+ result = result.replace(/mm/g, String(minutes).padStart(2, "0"));
332
+ result = result.replace(/m/g, String(minutes));
333
+ result = result.replace(/ss/g, String(seconds).padStart(2, "0"));
334
+ result = result.replace(/s/g, String(seconds));
335
+ return result;
336
+ }
337
+ function createDateFormatters(dateFormats) {
338
+ const formatters = {};
339
+ for (const [column, format] of Object.entries(dateFormats)) {
340
+ formatters[column] = createDateFormatter(format);
341
+ }
342
+ return formatters;
343
+ }
344
+ function formatRelativeTime(timestamp) {
345
+ if (timestamp === null || timestamp === void 0) {
346
+ return "Never";
347
+ }
348
+ const now = Date.now() / 1e3;
349
+ const seconds = Math.floor(now - timestamp);
350
+ if (seconds < 60) {
351
+ return "Just now";
352
+ }
353
+ const minutes = Math.floor(seconds / 60);
354
+ if (minutes < 60) {
355
+ return `${minutes} min ago`;
356
+ }
357
+ const hours = Math.floor(minutes / 60);
358
+ if (hours < 24) {
359
+ return `${hours} hr ago`;
360
+ }
361
+ const days = Math.floor(hours / 24);
362
+ if (days === 1) {
363
+ return "1 day ago";
364
+ }
365
+ return `${days} days ago`;
366
+ }
367
+
368
+ // src/utils/pivot.ts
369
+ function pivotQueryResult(result, config) {
370
+ const { pivotColumn, valueColumn, dimensionColumns } = config;
371
+ const pivotColIndex = result.columns.indexOf(pivotColumn);
372
+ const valueColIndex = result.columns.indexOf(valueColumn);
373
+ if (pivotColIndex === -1 || valueColIndex === -1) {
374
+ console.warn(
375
+ `[pivot] Cannot pivot: columns not found (pivot=${pivotColIndex}, value=${valueColIndex})`
376
+ );
377
+ return result;
378
+ }
379
+ const pivotValues = Array.from(
380
+ new Set(result.rows.map((row) => String(row[pivotColIndex])))
381
+ ).sort();
382
+ const dimIndices = dimensionColumns.map((col) => result.columns.indexOf(col)).filter((idx) => idx !== -1);
383
+ const grouped = /* @__PURE__ */ new Map();
384
+ for (const row of result.rows) {
385
+ const dimValues = dimIndices.map((i) => row[i]);
386
+ const key = dimValues.map((v) => String(v ?? "")).join("|");
387
+ if (!grouped.has(key)) {
388
+ const group = /* @__PURE__ */ new Map();
389
+ group.set("__dim_values__", dimValues);
390
+ grouped.set(key, group);
391
+ }
392
+ const pivotValue = String(row[pivotColIndex]);
393
+ const value = row[valueColIndex];
394
+ grouped.get(key).set(pivotValue, value);
395
+ }
396
+ const newColumns = [...dimensionColumns, ...pivotValues];
397
+ const newRows = [];
398
+ for (const [_key, valueMap] of grouped.entries()) {
399
+ const dimValues = valueMap.get("__dim_values__");
400
+ const newRow = [
401
+ ...dimValues,
402
+ ...pivotValues.map((pv) => valueMap.get(pv) ?? null)
403
+ ];
404
+ newRows.push(newRow);
405
+ }
406
+ const dimColTypes = dimensionColumns.map((col) => {
407
+ const idx = result.columns.indexOf(col);
408
+ return idx !== -1 && result.column_types && result.column_types[idx] ? result.column_types[idx] : "text";
409
+ }).filter((t) => t !== void 0);
410
+ const valueColType = (result.column_types && result.column_types[valueColIndex]) ?? "numeric";
411
+ const newColumnTypes = [...dimColTypes, ...pivotValues.map(() => valueColType)];
412
+ return {
413
+ columns: newColumns,
414
+ column_types: newColumnTypes,
415
+ rows: newRows,
416
+ row_count: newRows.length,
417
+ truncated: result.truncated,
418
+ execution_time_ms: result.execution_time_ms
419
+ };
420
+ }
421
+
422
+ // src/utils/columnRef.ts
423
+ function parseColumnRef(ref, defaultTableId) {
424
+ if (!ref || ref.trim() === "") {
425
+ return null;
426
+ }
427
+ if (!ref.includes(".")) {
428
+ return { tableId: defaultTableId, column: ref };
429
+ }
430
+ const parts = ref.split(".");
431
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
432
+ console.warn(`Invalid column reference format: "${ref}". Expected "tableId.column"`);
433
+ return null;
434
+ }
435
+ return { tableId: parts[0], column: parts[1] };
436
+ }
437
+
438
+ // src/utils/safeMarkdown.ts
439
+ function escapeHtml(text) {
440
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
441
+ }
442
+ function isValidCssStyle(style) {
443
+ const safePattern = /^[a-zA-Z0-9\s:;,.#%()\-]+$/;
444
+ return safePattern.test(style);
445
+ }
446
+ function parseMarkdownSafe(text, codeStyle) {
447
+ const escaped = escapeHtml(text);
448
+ const defaultCodeStyle = "background: rgba(0,0,0,0.05); padding: 0.1em 0.3em; border-radius: 3px;";
449
+ const safeCodeStyle = codeStyle && isValidCssStyle(codeStyle) ? codeStyle : defaultCodeStyle;
450
+ return escaped.replace(/^### (.+)$/gm, '<h3 style="margin: 0.5em 0; font-size: 1.1em;">$1</h3>').replace(/^## (.+)$/gm, '<h2 style="margin: 0.5em 0; font-size: 1.25em;">$1</h2>').replace(/^# (.+)$/gm, '<h1 style="margin: 0.5em 0; font-size: 1.5em;">$1</h1>').replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/`(.+?)`/g, `<code style="${safeCodeStyle}">$1</code>`).replace(/\n/g, "<br/>");
451
+ }
452
+
453
+ export { announceToScreenReader, createDateFormatter, createDateFormatters, focusVisibleStyles, formatRelativeTime, parseColumnRef, parseMarkdownSafe, pivotQueryResult, skipLinkFocusStyles, skipLinkStyles, useArrowNavigation, useFocusTrap, useFocusVisible, useRovingTabIndex };
454
+ //# sourceMappingURL=chunk-EX74SI67.js.map
455
+ //# sourceMappingURL=chunk-EX74SI67.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/accessibility.ts","../src/utils/dateFormat.ts","../src/utils/pivot.ts","../src/utils/columnRef.ts","../src/utils/safeMarkdown.ts"],"names":[],"mappings":"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","file":"chunk-EX74SI67.js","sourcesContent":["'use client';\n\n/**\n * Accessibility utilities for keyboard navigation and screen readers.\n */\n\nimport { useCallback, useEffect, useRef, useState, type RefObject } from 'react';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface FocusTrapOptions {\n /** Whether the focus trap is active. */\n active?: boolean;\n /** Whether to focus the first focusable element when activated. */\n autoFocus?: boolean;\n /** Whether to restore focus when deactivated. */\n restoreFocus?: boolean;\n /** Called when Escape is pressed. */\n onEscape?: () => void;\n}\n\nexport interface ArrowNavigationOptions {\n /** Whether navigation wraps around at edges. */\n wrap?: boolean;\n /** Orientation for arrow key mapping. */\n orientation?: 'horizontal' | 'vertical' | 'both';\n /** Callback when active item changes. */\n onActiveChange?: (index: number) => void;\n}\n\nexport interface UseFocusTrapResult {\n /** Ref to attach to the container element. */\n containerRef: RefObject<HTMLElement>;\n /** Activate the focus trap. */\n activate: () => void;\n /** Deactivate the focus trap. */\n deactivate: () => void;\n /** Whether the trap is active. */\n isActive: boolean;\n}\n\nexport interface UseArrowNavigationResult {\n /** Current active index. */\n activeIndex: number;\n /** Set active index. */\n setActiveIndex: (index: number) => void;\n /** Key down handler to attach to container. */\n onKeyDown: (event: React.KeyboardEvent) => void;\n /** Get props for an item. */\n getItemProps: (index: number) => {\n tabIndex: number;\n 'aria-selected': boolean;\n onFocus: () => void;\n };\n}\n\n// ============================================================================\n// Focus Trap Hook\n// ============================================================================\n\n/**\n * Selector for focusable elements.\n */\nconst FOCUSABLE_SELECTOR = [\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'a[href]',\n '[tabindex]:not([tabindex=\"-1\"])',\n '[contenteditable=\"true\"]',\n].join(', ');\n\n/**\n * Get all focusable elements within a container.\n */\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n const elements = container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);\n return Array.from(elements).filter(\n (el) => !el.hasAttribute('aria-hidden') && el.offsetParent !== null\n );\n}\n\n/**\n * Hook to trap focus within a container.\n *\n * @param options - Focus trap options\n * @returns Focus trap controls\n *\n * @example\n * ```tsx\n * function Modal({ isOpen, onClose }) {\n * const { containerRef, activate, deactivate } = useFocusTrap({\n * active: isOpen,\n * onEscape: onClose,\n * });\n *\n * return (\n * <div ref={containerRef} role=\"dialog\" aria-modal=\"true\">\n * <button onClick={onClose}>Close</button>\n * <input type=\"text\" />\n * </div>\n * );\n * }\n * ```\n */\nexport function useFocusTrap(options: FocusTrapOptions = {}): UseFocusTrapResult {\n const {\n active = true,\n autoFocus = true,\n restoreFocus = true,\n onEscape,\n } = options;\n\n const containerRef = useRef<HTMLElement>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const [isActive, setIsActive] = useState(active);\n\n const activate = useCallback(() => {\n if (!containerRef.current) return;\n\n // Store previously focused element\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n\n // Focus first focusable element\n if (autoFocus) {\n const focusable = getFocusableElements(containerRef.current);\n const firstElement = focusable[0];\n if (firstElement) {\n firstElement.focus();\n }\n }\n\n setIsActive(true);\n }, [autoFocus]);\n\n const deactivate = useCallback(() => {\n setIsActive(false);\n\n // Restore focus\n if (restoreFocus && previousFocusRef.current) {\n previousFocusRef.current.focus();\n }\n }, [restoreFocus]);\n\n // Handle focus trap\n useEffect(() => {\n if (!isActive || !containerRef.current) return;\n\n const container = containerRef.current;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n onEscape?.();\n return;\n }\n\n if (event.key !== 'Tab') return;\n\n const focusable = getFocusableElements(container);\n if (focusable.length === 0) return;\n\n const firstElement = focusable[0];\n const lastElement = focusable[focusable.length - 1];\n\n // Guard against undefined (though array check above should prevent this)\n if (!firstElement || !lastElement) return;\n\n // Shift+Tab on first element -> focus last\n if (event.shiftKey && document.activeElement === firstElement) {\n event.preventDefault();\n lastElement.focus();\n return;\n }\n\n // Tab on last element -> focus first\n if (!event.shiftKey && document.activeElement === lastElement) {\n event.preventDefault();\n firstElement.focus();\n }\n };\n\n // Handle focus escaping the container\n const handleFocusIn = (event: FocusEvent) => {\n if (!container.contains(event.target as Node)) {\n const focusable = getFocusableElements(container);\n const firstElement = focusable[0];\n if (firstElement) {\n firstElement.focus();\n }\n }\n };\n\n container.addEventListener('keydown', handleKeyDown);\n document.addEventListener('focusin', handleFocusIn);\n\n return () => {\n container.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('focusin', handleFocusIn);\n };\n }, [isActive, onEscape]);\n\n // Auto-activate when active prop changes\n useEffect(() => {\n if (active) {\n activate();\n } else {\n deactivate();\n }\n }, [active, activate, deactivate]);\n\n return {\n containerRef: containerRef as RefObject<HTMLElement>,\n activate,\n deactivate,\n isActive,\n };\n}\n\n// ============================================================================\n// Arrow Navigation Hook\n// ============================================================================\n\n/**\n * Hook for arrow key navigation through a list of items.\n *\n * @param itemCount - Number of items in the list\n * @param options - Navigation options\n * @returns Navigation state and handlers\n *\n * @example\n * ```tsx\n * function Menu({ items }) {\n * const { activeIndex, onKeyDown, getItemProps } = useArrowNavigation(items.length);\n *\n * return (\n * <ul role=\"menu\" onKeyDown={onKeyDown}>\n * {items.map((item, index) => (\n * <li key={index} role=\"menuitem\" {...getItemProps(index)}>\n * {item}\n * </li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useArrowNavigation(\n itemCount: number,\n options: ArrowNavigationOptions = {}\n): UseArrowNavigationResult {\n const { wrap = true, orientation = 'vertical', onActiveChange } = options;\n\n const [activeIndex, setActiveIndex] = useState(0);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n let newIndex = activeIndex;\n let handled = false;\n\n // Map arrow keys based on orientation\n const isNext =\n (orientation === 'vertical' && event.key === 'ArrowDown') ||\n (orientation === 'horizontal' && event.key === 'ArrowRight') ||\n (orientation === 'both' && (event.key === 'ArrowDown' || event.key === 'ArrowRight'));\n\n const isPrev =\n (orientation === 'vertical' && event.key === 'ArrowUp') ||\n (orientation === 'horizontal' && event.key === 'ArrowLeft') ||\n (orientation === 'both' && (event.key === 'ArrowUp' || event.key === 'ArrowLeft'));\n\n if (isNext) {\n handled = true;\n if (activeIndex < itemCount - 1) {\n newIndex = activeIndex + 1;\n } else if (wrap) {\n newIndex = 0;\n }\n } else if (isPrev) {\n handled = true;\n if (activeIndex > 0) {\n newIndex = activeIndex - 1;\n } else if (wrap) {\n newIndex = itemCount - 1;\n }\n } else if (event.key === 'Home') {\n handled = true;\n newIndex = 0;\n } else if (event.key === 'End') {\n handled = true;\n newIndex = itemCount - 1;\n }\n\n if (handled) {\n event.preventDefault();\n setActiveIndex(newIndex);\n onActiveChange?.(newIndex);\n }\n },\n [activeIndex, itemCount, wrap, orientation, onActiveChange]\n );\n\n const getItemProps = useCallback(\n (index: number) => ({\n tabIndex: index === activeIndex ? 0 : -1,\n 'aria-selected': index === activeIndex,\n onFocus: () => {\n setActiveIndex(index);\n onActiveChange?.(index);\n },\n }),\n [activeIndex, onActiveChange]\n );\n\n // Update active index setter to also call callback\n const handleSetActiveIndex = useCallback(\n (index: number) => {\n setActiveIndex(index);\n onActiveChange?.(index);\n },\n [onActiveChange]\n );\n\n return {\n activeIndex,\n setActiveIndex: handleSetActiveIndex,\n onKeyDown: handleKeyDown,\n getItemProps,\n };\n}\n\n// ============================================================================\n// Screen Reader Announcements\n// ============================================================================\n\nlet announceElement: HTMLElement | null = null;\n\n/**\n * Get or create the live region element for announcements.\n */\nfunction getAnnounceElement(): HTMLElement {\n if (announceElement) return announceElement;\n\n // Check if we're in a browser environment\n if (typeof document === 'undefined') {\n throw new Error('announceToScreenReader can only be used in browser environment');\n }\n\n announceElement = document.createElement('div');\n announceElement.setAttribute('role', 'status');\n announceElement.setAttribute('aria-live', 'polite');\n announceElement.setAttribute('aria-atomic', 'true');\n announceElement.style.cssText = [\n 'position: absolute',\n 'width: 1px',\n 'height: 1px',\n 'padding: 0',\n 'margin: -1px',\n 'overflow: hidden',\n 'clip: rect(0, 0, 0, 0)',\n 'white-space: nowrap',\n 'border: 0',\n ].join(';');\n announceElement.id = 'prismiq-announcer';\n\n document.body.appendChild(announceElement);\n return announceElement;\n}\n\n/**\n * Announce a message to screen readers.\n *\n * @param message - The message to announce\n * @param priority - Announcement priority ('polite' or 'assertive')\n *\n * @example\n * ```tsx\n * // Announce a status update\n * announceToScreenReader('Results loaded: 42 items');\n *\n * // Urgent announcement\n * announceToScreenReader('Error: Connection lost', 'assertive');\n * ```\n */\nexport function announceToScreenReader(\n message: string,\n priority: 'polite' | 'assertive' = 'polite'\n): void {\n // Skip in SSR\n if (typeof document === 'undefined') return;\n\n const element = getAnnounceElement();\n element.setAttribute('aria-live', priority);\n\n // Clear and set message (this triggers announcement)\n element.textContent = '';\n // Use requestAnimationFrame to ensure the clear happens first\n requestAnimationFrame(() => {\n element.textContent = message;\n });\n}\n\n// ============================================================================\n// Focus Visible\n// ============================================================================\n\n/**\n * CSS for focus-visible styling.\n * Add this to your global styles or use the useFocusVisible hook.\n */\nexport const focusVisibleStyles = `\n /* Hide focus outline for mouse users */\n :focus:not(:focus-visible) {\n outline: none;\n }\n\n /* Show focus outline for keyboard users */\n :focus-visible {\n outline: 2px solid var(--prismiq-color-primary);\n outline-offset: 2px;\n }\n\n /* Prismiq focus ring class */\n .prismiq-focus-ring:focus-visible {\n outline: 2px solid var(--prismiq-color-primary);\n outline-offset: 2px;\n box-shadow: 0 0 0 4px rgba(var(--prismiq-color-primary-rgb), 0.2);\n }\n`;\n\n/**\n * Hook to detect if focus is visible (keyboard navigation).\n */\nexport function useFocusVisible(): boolean {\n const [focusVisible, setFocusVisible] = useState(false);\n\n useEffect(() => {\n // Skip in SSR\n if (typeof window === 'undefined') return;\n\n let hadKeyboardEvent = false;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Tab') {\n hadKeyboardEvent = true;\n }\n };\n\n const handlePointerDown = () => {\n hadKeyboardEvent = false;\n };\n\n const handleFocus = () => {\n setFocusVisible(hadKeyboardEvent);\n };\n\n const handleBlur = () => {\n setFocusVisible(false);\n };\n\n document.addEventListener('keydown', handleKeyDown, true);\n document.addEventListener('pointerdown', handlePointerDown, true);\n document.addEventListener('focus', handleFocus, true);\n document.addEventListener('blur', handleBlur, true);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown, true);\n document.removeEventListener('pointerdown', handlePointerDown, true);\n document.removeEventListener('focus', handleFocus, true);\n document.removeEventListener('blur', handleBlur, true);\n };\n }, []);\n\n return focusVisible;\n}\n\n// ============================================================================\n// Skip Link\n// ============================================================================\n\n/**\n * Props for the skip link component.\n */\nexport interface SkipLinkProps {\n /** Target element ID to skip to. */\n targetId: string;\n /** Link text. */\n children?: React.ReactNode;\n}\n\n/**\n * Styles for skip link (visually hidden until focused).\n */\nexport const skipLinkStyles: React.CSSProperties = {\n position: 'absolute',\n left: '-9999px',\n zIndex: 9999,\n padding: '1em',\n backgroundColor: 'var(--prismiq-color-background)',\n color: 'var(--prismiq-color-primary)',\n textDecoration: 'underline',\n // When focused, show the link\n};\n\nexport const skipLinkFocusStyles: React.CSSProperties = {\n ...skipLinkStyles,\n left: 0,\n top: 0,\n};\n\n// ============================================================================\n// Roving Tab Index\n// ============================================================================\n\n/**\n * Hook for implementing roving tabindex pattern.\n *\n * @param itemRefs - Refs to the focusable items\n * @param options - Navigation options\n */\nexport function useRovingTabIndex<T extends HTMLElement>(\n itemRefs: RefObject<T>[],\n options: ArrowNavigationOptions = {}\n): UseArrowNavigationResult {\n const result = useArrowNavigation(itemRefs.length, options);\n\n // Focus the active element when activeIndex changes\n useEffect(() => {\n const activeRef = itemRefs[result.activeIndex];\n if (activeRef?.current) {\n activeRef.current.focus();\n }\n }, [result.activeIndex, itemRefs]);\n\n return result;\n}\n","/**\n * Date formatting utilities for converting .NET format strings to JavaScript date formatters.\n *\n * Supports common .NET date format patterns used by RevealBI.\n */\n\n/**\n * Convert .NET date format string to JavaScript date formatter.\n *\n * Common patterns:\n * - dd-MMM-yyyy HH:mm -> 20-Mar-2025 17:00\n * - yyyy-MM-dd -> 2025-03-20\n * - MM/dd/yyyy -> 03/20/2025\n * - yyyy-MM-dd HH:mm:ss -> 2025-03-20 17:00:00\n *\n * @param formatString .NET date format string\n * @returns Formatter function\n */\nexport function createDateFormatter(formatString: string): (value: unknown) => string {\n return (value: unknown): string => {\n if (value === null || value === undefined) {\n return '';\n }\n\n // Parse date from various formats\n let date: Date;\n if (value instanceof Date) {\n date = value;\n } else if (typeof value === 'string') {\n date = new Date(value);\n } else if (typeof value === 'number') {\n date = new Date(value);\n } else {\n return String(value);\n }\n\n // Check if date is valid\n if (isNaN(date.getTime())) {\n return String(value);\n }\n\n // Convert .NET format to formatted string\n return formatDateWithPattern(date, formatString);\n };\n}\n\n/**\n * Format date according to .NET format pattern.\n */\nfunction formatDateWithPattern(date: Date, pattern: string): string {\n // Get date components\n const year = date.getFullYear();\n const month = date.getMonth() + 1;\n const day = date.getDate();\n const hours = date.getHours();\n const minutes = date.getMinutes();\n const seconds = date.getSeconds();\n\n // Month names\n const monthNamesShort = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\n const monthNamesFull = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];\n\n // Day names\n const dayNamesShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n const dayNamesFull = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\n\n // Quarter (1-4)\n const quarter = Math.ceil(month / 3);\n\n // Replace tokens\n let result = pattern;\n\n // Quarter - use placeholder to avoid the single-Q pass reprocessing the Q inserted by QQ\n result = result.replace(/QQ/g, '__QUARTER_PLACEHOLDER__');\n result = result.replace(/Q(?!['\"])/g, String(quarter));\n result = result.replace(/__QUARTER_PLACEHOLDER__/g, `Q${quarter}`);\n\n // Year\n result = result.replace(/yyyy/g, String(year).padStart(4, '0'));\n result = result.replace(/yy/g, String(year % 100).padStart(2, '0'));\n\n // Month\n result = result.replace(/MMMM/g, monthNamesFull[month - 1] || '');\n result = result.replace(/MMM/g, monthNamesShort[month - 1] || '');\n result = result.replace(/MM/g, String(month).padStart(2, '0'));\n result = result.replace(/M/g, String(month));\n\n // Day\n result = result.replace(/dddd/g, dayNamesFull[date.getDay()] || '');\n result = result.replace(/ddd/g, dayNamesShort[date.getDay()] || '');\n result = result.replace(/dd/g, String(day).padStart(2, '0'));\n result = result.replace(/d/g, String(day));\n\n // Hours (24-hour)\n result = result.replace(/HH/g, String(hours).padStart(2, '0'));\n result = result.replace(/H/g, String(hours));\n\n // Hours (12-hour)\n const hours12 = hours % 12 || 12;\n result = result.replace(/hh/g, String(hours12).padStart(2, '0'));\n result = result.replace(/h/g, String(hours12));\n\n // AM/PM\n const ampm = hours >= 12 ? 'PM' : 'AM';\n result = result.replace(/tt/g, ampm);\n result = result.replace(/t/g, ampm.charAt(0));\n\n // Minutes\n result = result.replace(/mm/g, String(minutes).padStart(2, '0'));\n result = result.replace(/m/g, String(minutes));\n\n // Seconds\n result = result.replace(/ss/g, String(seconds).padStart(2, '0'));\n result = result.replace(/s/g, String(seconds));\n\n return result;\n}\n\n/**\n * Create date formatters from config map.\n *\n * @param dateFormats Map of column name to .NET format string\n * @returns Map of column name to formatter function\n */\nexport function createDateFormatters(\n dateFormats: Record<string, string>\n): Record<string, (value: unknown) => string> {\n const formatters: Record<string, (value: unknown) => string> = {};\n\n for (const [column, format] of Object.entries(dateFormats)) {\n formatters[column] = createDateFormatter(format);\n }\n\n return formatters;\n}\n\n/**\n * Format a Unix timestamp as relative time (e.g., \"5 min ago\").\n *\n * @param timestamp Unix timestamp in seconds\n * @returns Relative time string\n */\nexport function formatRelativeTime(timestamp: number | null | undefined): string {\n if (timestamp === null || timestamp === undefined) {\n return 'Never';\n }\n\n const now = Date.now() / 1000; // Convert to seconds\n const seconds = Math.floor(now - timestamp);\n\n if (seconds < 60) {\n return 'Just now';\n }\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) {\n return `${minutes} min ago`;\n }\n\n const hours = Math.floor(minutes / 60);\n if (hours < 24) {\n return `${hours} hr ago`;\n }\n\n const days = Math.floor(hours / 24);\n if (days === 1) {\n return '1 day ago';\n }\n\n return `${days} days ago`;\n}\n","/**\n * Pivot utility for transforming query results from long format to wide format.\n */\n\nimport type { QueryResult } from '../types';\n\nexport interface PivotConfig {\n /** Column whose unique values become new columns. */\n pivotColumn: string;\n /** Column containing values to distribute across pivot columns. */\n valueColumn: string;\n /** Columns to keep as-is (dimension/grouping columns). */\n dimensionColumns: string[];\n}\n\n/**\n * Pivot a query result by transforming unique values in pivotColumn\n * into separate columns.\n *\n * Example:\n * Input (long format):\n * account | date | name | value\n * Cerby | 2025-02-01 | MAU | 9\n * Cerby | 2025-02-01 | Searches | 1\n *\n * Output (wide format):\n * account | date | MAU | Searches\n * Cerby | 2025-02-01 | 9 | 1\n *\n * @param result - The query result to pivot\n * @param config - Pivot configuration\n * @returns Pivoted query result\n */\nexport function pivotQueryResult(\n result: QueryResult,\n config: PivotConfig\n): QueryResult {\n const { pivotColumn, valueColumn, dimensionColumns } = config;\n\n // Find column indices\n const pivotColIndex = result.columns.indexOf(pivotColumn);\n const valueColIndex = result.columns.indexOf(valueColumn);\n\n if (pivotColIndex === -1 || valueColIndex === -1) {\n console.warn(\n `[pivot] Cannot pivot: columns not found (pivot=${pivotColIndex}, value=${valueColIndex})`\n );\n return result; // Can't pivot, return as-is\n }\n\n // Get unique pivot values (these become new columns)\n const pivotValues = Array.from(\n new Set(result.rows.map((row) => String(row[pivotColIndex])))\n ).sort();\n\n // Build dimension column indices\n const dimIndices = dimensionColumns\n .map((col) => result.columns.indexOf(col))\n .filter((idx) => idx !== -1);\n\n // Group rows by dimension values\n const grouped = new Map<string, Map<string, unknown>>();\n\n for (const row of result.rows) {\n // Create key from dimension column values\n const dimValues = dimIndices.map((i) => row[i]);\n const key = dimValues.map((v) => String(v ?? '')).join('|');\n\n if (!grouped.has(key)) {\n // Store dimension values for this group\n const group = new Map<string, unknown>();\n group.set('__dim_values__', dimValues);\n grouped.set(key, group);\n }\n\n const pivotValue = String(row[pivotColIndex]);\n const value = row[valueColIndex];\n grouped.get(key)!.set(pivotValue, value);\n }\n\n // Build pivoted result\n const newColumns = [...dimensionColumns, ...pivotValues];\n const newRows: unknown[][] = [];\n\n for (const [_key, valueMap] of grouped.entries()) {\n const dimValues = valueMap.get('__dim_values__') as unknown[];\n const newRow = [\n ...dimValues,\n ...pivotValues.map((pv) => valueMap.get(pv) ?? null),\n ];\n newRows.push(newRow);\n }\n\n // Build column_types array (dimension types + value type for each pivot column)\n const dimColTypes = dimensionColumns\n .map((col) => {\n const idx = result.columns.indexOf(col);\n return idx !== -1 && result.column_types && result.column_types[idx]\n ? result.column_types[idx]\n : 'text';\n })\n .filter((t): t is string => t !== undefined);\n\n const valueColType = (result.column_types && result.column_types[valueColIndex]) ?? 'numeric';\n const newColumnTypes = [...dimColTypes, ...pivotValues.map(() => valueColType)];\n\n return {\n columns: newColumns,\n column_types: newColumnTypes,\n rows: newRows,\n row_count: newRows.length,\n truncated: result.truncated,\n execution_time_ms: result.execution_time_ms,\n };\n}\n","/**\n * Utilities for parsing column references in \"tableId.column\" format.\n */\n\n/**\n * Parsed column reference with table ID and column name.\n */\nexport interface ColumnReference {\n tableId: string;\n column: string;\n}\n\n/**\n * Parse a column reference in \"tableId.column\" format.\n *\n * @param ref - The column reference string (e.g., \"t1.created_at\")\n * @param defaultTableId - Table ID to use when ref has no table prefix\n * @returns Parsed reference, or null if invalid. Logs a warning only for\n * malformed references (e.g., \"table.\" or \".column\" or \"a.b.c\").\n * Empty/whitespace inputs return null without warning.\n */\nexport function parseColumnRef(\n ref: string,\n defaultTableId: string\n): ColumnReference | null {\n if (!ref || ref.trim() === '') {\n return null;\n }\n\n if (!ref.includes('.')) {\n // Simple column name without table prefix - use default table\n // for backward compatibility with single-table queries\n return { tableId: defaultTableId, column: ref };\n }\n\n const parts = ref.split('.');\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n console.warn(`Invalid column reference format: \"${ref}\". Expected \"tableId.column\"`);\n return null;\n }\n\n return { tableId: parts[0], column: parts[1] };\n}\n","/**\n * Safe markdown parser that escapes HTML to prevent XSS attacks.\n *\n * Supports a limited subset of markdown:\n * - Headings (#, ##, ###)\n * - Bold (**text**)\n * - Italic (*text*)\n * - Inline code (`code`)\n * - Line breaks\n */\n\n/**\n * Escape HTML special characters to prevent XSS.\n */\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\n/**\n * Validate that a CSS style string contains only safe characters.\n * Rejects strings that could break out of the style attribute.\n */\nfunction isValidCssStyle(style: string): boolean {\n // Only allow: letters, numbers, spaces, basic CSS punctuation\n // Reject: quotes, angle brackets, script-related chars\n const safePattern = /^[a-zA-Z0-9\\s:;,.#%()\\-]+$/;\n return safePattern.test(style);\n}\n\n/**\n * Parse markdown text into safe HTML.\n *\n * HTML is escaped first to prevent XSS, then markdown syntax is converted.\n *\n * @param text - Raw markdown text\n * @param codeStyle - Optional inline styles for code blocks\n * @returns Safe HTML string\n */\nexport function parseMarkdownSafe(\n text: string,\n codeStyle?: string\n): string {\n // First escape any HTML to prevent XSS\n const escaped = escapeHtml(text);\n\n // Then apply markdown transforms\n const defaultCodeStyle =\n 'background: rgba(0,0,0,0.05); padding: 0.1em 0.3em; border-radius: 3px;';\n\n // Validate codeStyle to prevent style attribute injection\n const safeCodeStyle =\n codeStyle && isValidCssStyle(codeStyle) ? codeStyle : defaultCodeStyle;\n\n return escaped\n .replace(/^### (.+)$/gm, '<h3 style=\"margin: 0.5em 0; font-size: 1.1em;\">$1</h3>')\n .replace(/^## (.+)$/gm, '<h2 style=\"margin: 0.5em 0; font-size: 1.25em;\">$1</h2>')\n .replace(/^# (.+)$/gm, '<h1 style=\"margin: 0.5em 0; font-size: 1.5em;\">$1</h1>')\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*(.+?)\\*/g, '<em>$1</em>')\n .replace(/`(.+?)`/g, `<code style=\"${safeCodeStyle}\">$1</code>`)\n .replace(/\\n/g, '<br/>');\n}\n"]}