@pythia-software/query-table-ui 0.1.0 → 0.2.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.
- package/README.md +27 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +415 -132
- package/dist/index.js.map +1 -1
- package/dist/theme.css +522 -4
- package/dist/theme.css.map +1 -1
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -137,7 +137,8 @@ function formatRelative(iso) {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
// src/DataTable.tsx
|
|
140
|
-
import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
140
|
+
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
|
|
141
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
141
142
|
import { isSortable, readFieldValue } from "@pythia-software/query-table-core";
|
|
142
143
|
import { useColumnDrag } from "@pythia-software/query-table-react";
|
|
143
144
|
|
|
@@ -292,6 +293,9 @@ var AUTOFIT_EXTRA_PX = 16;
|
|
|
292
293
|
var MENU_WIDTH2 = 240;
|
|
293
294
|
var MENU_ROW_HEIGHT = 32;
|
|
294
295
|
var COPY_FEEDBACK_MS = 900;
|
|
296
|
+
var DEFAULT_MAX_HEIGHT = 600;
|
|
297
|
+
var DEFAULT_ROW_HEIGHT = 37;
|
|
298
|
+
var DEFAULT_OVERSCAN = 8;
|
|
295
299
|
var SELECTION_COLUMN = "__qt_selection__";
|
|
296
300
|
function DataTable(props) {
|
|
297
301
|
const {
|
|
@@ -307,27 +311,75 @@ function DataTable(props) {
|
|
|
307
311
|
total = null,
|
|
308
312
|
loading,
|
|
309
313
|
emptyMessage,
|
|
314
|
+
maxHeight = DEFAULT_MAX_HEIGHT,
|
|
315
|
+
estimateRowHeight = DEFAULT_ROW_HEIGHT,
|
|
316
|
+
overscan = DEFAULT_OVERSCAN,
|
|
310
317
|
classNames
|
|
311
318
|
} = props;
|
|
312
319
|
const [menu, setMenu] = useState2(null);
|
|
313
|
-
const [resizingField, setResizingField] = useState2(null);
|
|
314
|
-
const [draftWidths, setDraftWidths] = useState2({});
|
|
315
320
|
const [selectionColumnWidth, setSelectionColumnWidth] = useState2(DEFAULT_SELECTION_WIDTH);
|
|
316
321
|
const [tableColumnOrder, setTableColumnOrder] = useState2([]);
|
|
317
322
|
const [copiedCellKey, setCopiedCellKey] = useState2(null);
|
|
323
|
+
const [headerHeight, setHeaderHeight] = useState2(0);
|
|
324
|
+
const [showHorizontalCue, setShowHorizontalCue] = useState2(false);
|
|
318
325
|
const tableWrapRef = useRef(null);
|
|
326
|
+
const tableHeadRef = useRef(null);
|
|
327
|
+
const tableRef = useRef(null);
|
|
328
|
+
const resizeGuideRef = useRef(null);
|
|
319
329
|
const resizeCommitRef = useRef(null);
|
|
330
|
+
const resizePreviewRef = useRef(null);
|
|
331
|
+
const resizeFrameRef = useRef(null);
|
|
332
|
+
const cancelResizeRef = useRef(null);
|
|
320
333
|
const resizingFieldRef = useRef(null);
|
|
321
334
|
const copiedCellTimerRef = useRef(null);
|
|
322
335
|
const localColumnDrag = useColumnDrag();
|
|
323
336
|
const columnDrag = props.columnDrag ?? localColumnDrag;
|
|
337
|
+
const rowIds = useMemo(() => rows.map(rowId), [rowId, rows]);
|
|
338
|
+
const virtualRowKeys = useMemo(
|
|
339
|
+
() => rowIds.map((id, index) => id == null ? `index:${index}` : `${typeof id}:${String(id)}`),
|
|
340
|
+
[rowIds]
|
|
341
|
+
);
|
|
342
|
+
const getVirtualRowKey = useCallback((index) => virtualRowKeys[index] ?? `index:${index}`, [virtualRowKeys]);
|
|
343
|
+
const normalizedEstimate = Number.isFinite(estimateRowHeight) ? Math.max(1, estimateRowHeight) : DEFAULT_ROW_HEIGHT;
|
|
344
|
+
const normalizedOverscan = Number.isFinite(overscan) ? Math.max(0, Math.floor(overscan)) : DEFAULT_OVERSCAN;
|
|
345
|
+
const estimateVirtualRow = useCallback(() => normalizedEstimate, [normalizedEstimate]);
|
|
346
|
+
const initialViewportHeight = typeof maxHeight === "number" && maxHeight > 0 ? maxHeight : DEFAULT_MAX_HEIGHT;
|
|
347
|
+
const initialRect = useMemo(() => ({ width: 0, height: initialViewportHeight }), [initialViewportHeight]);
|
|
348
|
+
const rowVirtualizer = useVirtualizer({
|
|
349
|
+
count: rows.length,
|
|
350
|
+
getScrollElement: () => tableWrapRef.current,
|
|
351
|
+
estimateSize: estimateVirtualRow,
|
|
352
|
+
getItemKey: getVirtualRowKey,
|
|
353
|
+
overscan: normalizedOverscan,
|
|
354
|
+
// The header participates in normal table layout even while it is sticky.
|
|
355
|
+
// Offset row measurements so the virtual range matches the body's origin.
|
|
356
|
+
scrollMargin: headerHeight,
|
|
357
|
+
initialRect
|
|
358
|
+
});
|
|
324
359
|
useEffect2(() => {
|
|
325
360
|
return () => {
|
|
326
361
|
if (copiedCellTimerRef.current) {
|
|
327
362
|
clearTimeout(copiedCellTimerRef.current);
|
|
328
363
|
copiedCellTimerRef.current = null;
|
|
329
364
|
}
|
|
365
|
+
cancelResizeRef.current?.();
|
|
366
|
+
};
|
|
367
|
+
}, []);
|
|
368
|
+
useEffect2(() => {
|
|
369
|
+
const header = tableHeadRef.current;
|
|
370
|
+
if (!header) return;
|
|
371
|
+
const measureHeader = () => {
|
|
372
|
+
const next = header.getBoundingClientRect().height;
|
|
373
|
+
setHeaderHeight((current) => current === next ? current : next);
|
|
330
374
|
};
|
|
375
|
+
measureHeader();
|
|
376
|
+
if (typeof ResizeObserver === "undefined") {
|
|
377
|
+
window.addEventListener("resize", measureHeader);
|
|
378
|
+
return () => window.removeEventListener("resize", measureHeader);
|
|
379
|
+
}
|
|
380
|
+
const observer = new ResizeObserver(measureHeader);
|
|
381
|
+
observer.observe(header);
|
|
382
|
+
return () => observer.disconnect();
|
|
331
383
|
}, []);
|
|
332
384
|
function cellKey(rowIdValue, rowIndex, fieldName) {
|
|
333
385
|
return rowIdValue == null ? `row:${rowIndex}:${fieldName}` : `id:${rowIdValue}:${fieldName}`;
|
|
@@ -361,7 +413,7 @@ function DataTable(props) {
|
|
|
361
413
|
}
|
|
362
414
|
showCopyFeedback(cellKey(rowIdValue, rowIndex, f.name));
|
|
363
415
|
}
|
|
364
|
-
const pageIds =
|
|
416
|
+
const pageIds = rowIds.filter((id) => id != null);
|
|
365
417
|
const headerState = selection ? selection.pageState(pageIds) : "none";
|
|
366
418
|
const showSel = selection != null;
|
|
367
419
|
function sortKeyFor(f) {
|
|
@@ -396,11 +448,11 @@ function DataTable(props) {
|
|
|
396
448
|
return col?.width ?? f.select?.width;
|
|
397
449
|
}
|
|
398
450
|
function resolvedWidthFor(name, f) {
|
|
399
|
-
return
|
|
451
|
+
return widthFor(name, f);
|
|
400
452
|
}
|
|
401
453
|
function resolvedColumnWidthFor(name, f) {
|
|
402
|
-
if (name === SELECTION_COLUMN) return
|
|
403
|
-
return f ? resolvedWidthFor(name, f) :
|
|
454
|
+
if (name === SELECTION_COLUMN) return selectionColumnWidth;
|
|
455
|
+
return f ? resolvedWidthFor(name, f) : void 0;
|
|
404
456
|
}
|
|
405
457
|
function displayColumnWidthFor(name, f) {
|
|
406
458
|
if (name === SELECTION_COLUMN) return resolvedColumnWidthFor(name) ?? DEFAULT_SELECTION_WIDTH;
|
|
@@ -424,16 +476,20 @@ function DataTable(props) {
|
|
|
424
476
|
const px = clampWidth(width);
|
|
425
477
|
onQueryChange((prev) => ({ ...prev, select: writeWidth(prev.select, fields, name, px) }));
|
|
426
478
|
}
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
479
|
+
function applyResizeGuide() {
|
|
480
|
+
const preview = resizePreviewRef.current;
|
|
481
|
+
const guide = resizeGuideRef.current;
|
|
482
|
+
if (!preview || !guide) return;
|
|
483
|
+
guide.style.transform = `translateX(${preview.guideX}px)`;
|
|
484
|
+
}
|
|
485
|
+
function applyCommittedResize() {
|
|
486
|
+
const preview = resizePreviewRef.current;
|
|
487
|
+
const table = tableRef.current;
|
|
488
|
+
if (!preview || !table) return;
|
|
489
|
+
const selector = `col[data-qt-column="${cssAttributeValue(preview.name)}"]`;
|
|
490
|
+
const col = table.querySelector(selector);
|
|
491
|
+
if (col) col.style.width = `${preview.width}px`;
|
|
492
|
+
table.style.minWidth = `${preview.startTableWidth + preview.width - preview.previousWidth}px`;
|
|
437
493
|
}
|
|
438
494
|
function startResize(name, startWidth, e) {
|
|
439
495
|
e.preventDefault();
|
|
@@ -442,30 +498,70 @@ function DataTable(props) {
|
|
|
442
498
|
const startW = startWidth ?? e.currentTarget.parentElement?.getBoundingClientRect().width ?? DEFAULT_WIDTH;
|
|
443
499
|
const previousCursor = document.body.style.cursor;
|
|
444
500
|
const previousUserSelect = document.body.style.userSelect;
|
|
501
|
+
const initialWidth = clampColumnWidth(name, startW);
|
|
502
|
+
const wrap = tableWrapRef.current;
|
|
503
|
+
const wrapRect = wrap?.getBoundingClientRect();
|
|
504
|
+
const header = e.currentTarget.parentElement;
|
|
445
505
|
resizeCommitRef.current = null;
|
|
506
|
+
resizePreviewRef.current = {
|
|
507
|
+
name,
|
|
508
|
+
width: initialWidth,
|
|
509
|
+
previousWidth: displayColumnWidthFor(name, fieldByName.get(name)),
|
|
510
|
+
startTableWidth: tableWidth,
|
|
511
|
+
guideX: startX - (wrapRect?.left ?? 0) + (wrap?.scrollLeft ?? 0)
|
|
512
|
+
};
|
|
446
513
|
resizingFieldRef.current = name;
|
|
447
|
-
|
|
448
|
-
|
|
514
|
+
tableWrapRef.current?.classList.add("qt-table-wrap--resizing");
|
|
515
|
+
header?.classList.add("qt-th--resizing");
|
|
516
|
+
if (header) header.draggable = false;
|
|
517
|
+
if (resizeGuideRef.current) {
|
|
518
|
+
resizeGuideRef.current.style.display = "block";
|
|
519
|
+
resizeGuideRef.current.style.top = `${wrap?.scrollTop ?? 0}px`;
|
|
520
|
+
resizeGuideRef.current.style.height = `${wrap?.clientHeight ?? 0}px`;
|
|
521
|
+
}
|
|
522
|
+
applyResizeGuide();
|
|
449
523
|
document.body.style.cursor = "col-resize";
|
|
450
524
|
document.body.style.userSelect = "none";
|
|
451
|
-
const
|
|
525
|
+
const cleanup = (commitWidth) => {
|
|
452
526
|
window.removeEventListener("pointermove", onMove);
|
|
453
527
|
window.removeEventListener("pointerup", finish);
|
|
454
528
|
window.removeEventListener("pointercancel", finish);
|
|
529
|
+
if (resizeFrameRef.current != null) {
|
|
530
|
+
cancelAnimationFrame(resizeFrameRef.current);
|
|
531
|
+
resizeFrameRef.current = null;
|
|
532
|
+
applyResizeGuide();
|
|
533
|
+
}
|
|
455
534
|
document.body.style.cursor = previousCursor;
|
|
456
535
|
document.body.style.userSelect = previousUserSelect;
|
|
457
|
-
|
|
536
|
+
tableWrapRef.current?.classList.remove("qt-table-wrap--resizing");
|
|
537
|
+
tableRef.current?.querySelector(`th[data-qt-field="${cssAttributeValue(name)}"]`)?.classList.remove("qt-th--resizing");
|
|
538
|
+
if (header) header.draggable = true;
|
|
539
|
+
const commit = commitWidth ? resizeCommitRef.current : null;
|
|
540
|
+
if (commit) applyCommittedResize();
|
|
541
|
+
if (resizeGuideRef.current) resizeGuideRef.current.style.display = "none";
|
|
458
542
|
resizeCommitRef.current = null;
|
|
543
|
+
resizePreviewRef.current = null;
|
|
459
544
|
resizingFieldRef.current = null;
|
|
460
|
-
|
|
461
|
-
clearDraftWidth(name);
|
|
545
|
+
cancelResizeRef.current = null;
|
|
462
546
|
if (commit) commitColumnWidth(commit.name, commit.width);
|
|
463
547
|
};
|
|
548
|
+
const finish = () => cleanup(true);
|
|
464
549
|
const onMove = (ev) => {
|
|
465
550
|
const next = clampColumnWidth(name, startW + (ev.clientX - startX));
|
|
466
551
|
resizeCommitRef.current = { name, width: next };
|
|
467
|
-
|
|
552
|
+
const preview = resizePreviewRef.current;
|
|
553
|
+
if (preview) {
|
|
554
|
+
preview.width = next;
|
|
555
|
+
preview.guideX = startX + (next - startW) - (wrapRect?.left ?? 0) + (wrap?.scrollLeft ?? 0);
|
|
556
|
+
}
|
|
557
|
+
if (resizeFrameRef.current == null) {
|
|
558
|
+
resizeFrameRef.current = requestAnimationFrame(() => {
|
|
559
|
+
resizeFrameRef.current = null;
|
|
560
|
+
applyResizeGuide();
|
|
561
|
+
});
|
|
562
|
+
}
|
|
468
563
|
};
|
|
564
|
+
cancelResizeRef.current = () => cleanup(false);
|
|
469
565
|
window.addEventListener("pointermove", onMove);
|
|
470
566
|
window.addEventListener("pointerup", finish);
|
|
471
567
|
window.addEventListener("pointercancel", finish);
|
|
@@ -573,6 +669,22 @@ function DataTable(props) {
|
|
|
573
669
|
const f = fieldByName.get(name);
|
|
574
670
|
return sum + displayColumnWidthFor(name, f);
|
|
575
671
|
}, trailing ? 40 : 0);
|
|
672
|
+
useEffect2(() => {
|
|
673
|
+
const wrap = tableWrapRef.current;
|
|
674
|
+
if (!wrap) return;
|
|
675
|
+
const updateCue = () => {
|
|
676
|
+
const hasMoreToRight = wrap.scrollWidth - wrap.clientWidth - wrap.scrollLeft > 2;
|
|
677
|
+
setShowHorizontalCue(hasMoreToRight);
|
|
678
|
+
};
|
|
679
|
+
updateCue();
|
|
680
|
+
window.addEventListener("resize", updateCue);
|
|
681
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updateCue);
|
|
682
|
+
observer?.observe(wrap);
|
|
683
|
+
return () => {
|
|
684
|
+
window.removeEventListener("resize", updateCue);
|
|
685
|
+
observer?.disconnect();
|
|
686
|
+
};
|
|
687
|
+
}, [renderedColumnNames, tableWidth]);
|
|
576
688
|
const start = query.offset;
|
|
577
689
|
const end = total != null ? Math.min(start + query.limit, total) : start + rows.length;
|
|
578
690
|
const canPrev = start > 0;
|
|
@@ -580,7 +692,16 @@ function DataTable(props) {
|
|
|
580
692
|
const prevPage = () => onQueryChange((prev) => ({ ...prev, offset: Math.max(0, prev.offset - prev.limit) }));
|
|
581
693
|
const nextPage = () => onQueryChange((prev) => ({ ...prev, offset: prev.offset + prev.limit }));
|
|
582
694
|
const summaryText = total == null || total === 0 ? `${rows.length} rows` : `${start + 1}\u2013${end} of ${total}`;
|
|
695
|
+
const virtualRows = rowVirtualizer.getVirtualItems();
|
|
696
|
+
const firstVirtualRow = virtualRows[0];
|
|
697
|
+
const lastVirtualRow = virtualRows[virtualRows.length - 1];
|
|
698
|
+
const paddingTop = firstVirtualRow ? Math.max(0, firstVirtualRow.start - headerHeight) : 0;
|
|
699
|
+
const paddingBottom = lastVirtualRow ? Math.max(0, rowVirtualizer.getTotalSize() - (lastVirtualRow.end - headerHeight)) : 0;
|
|
583
700
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
701
|
+
showHorizontalCue ? /* @__PURE__ */ jsxs2("div", { className: "qt-table-scroll-cue", "aria-hidden": "true", children: [
|
|
702
|
+
"Swipe to see more ",
|
|
703
|
+
/* @__PURE__ */ jsx2("span", { children: "\u2192" })
|
|
704
|
+
] }) : null,
|
|
584
705
|
/* @__PURE__ */ jsxs2(
|
|
585
706
|
"div",
|
|
586
707
|
{
|
|
@@ -588,24 +709,40 @@ function DataTable(props) {
|
|
|
588
709
|
className: cx2(
|
|
589
710
|
"qt-table-wrap",
|
|
590
711
|
loading && "qt-table-wrap--loading",
|
|
591
|
-
!!resizingField && "qt-table-wrap--resizing",
|
|
592
712
|
classNames?.wrap
|
|
593
713
|
),
|
|
594
714
|
onWheel: onTableWheel,
|
|
715
|
+
style: { maxHeight, overflowY: "auto" },
|
|
716
|
+
onScroll: () => {
|
|
717
|
+
const wrap = tableWrapRef.current;
|
|
718
|
+
if (!wrap) return;
|
|
719
|
+
setShowHorizontalCue(wrap.scrollWidth - wrap.clientWidth - wrap.scrollLeft > 2);
|
|
720
|
+
},
|
|
721
|
+
role: "region",
|
|
722
|
+
"aria-label": "Scrollable data table",
|
|
723
|
+
tabIndex: 0,
|
|
595
724
|
children: [
|
|
596
725
|
loading && /* @__PURE__ */ jsx2("div", { className: cx2("qt-loading-bar", classNames?.loadingBar), role: "progressbar", "aria-label": "loading" }),
|
|
597
|
-
/* @__PURE__ */
|
|
726
|
+
/* @__PURE__ */ jsx2(
|
|
727
|
+
"div",
|
|
728
|
+
{
|
|
729
|
+
ref: resizeGuideRef,
|
|
730
|
+
className: cx2("qt-resize-guide", classNames?.resizeGuide),
|
|
731
|
+
"aria-hidden": "true"
|
|
732
|
+
}
|
|
733
|
+
),
|
|
734
|
+
/* @__PURE__ */ jsxs2("table", { ref: tableRef, className: cx2("qt-table", classNames?.table), style: { minWidth: tableWidth }, children: [
|
|
598
735
|
/* @__PURE__ */ jsxs2("colgroup", { children: [
|
|
599
736
|
renderedColumnNames.map((name) => {
|
|
600
737
|
const f = fieldByName.get(name);
|
|
601
738
|
const w = displayColumnWidthFor(name, f);
|
|
602
|
-
if (name === SELECTION_COLUMN) return /* @__PURE__ */ jsx2("col", { style: { width: w } }, name);
|
|
739
|
+
if (name === SELECTION_COLUMN) return /* @__PURE__ */ jsx2("col", { "data-qt-column": name, style: { width: w } }, name);
|
|
603
740
|
if (!f) return null;
|
|
604
|
-
return /* @__PURE__ */ jsx2("col", { style: { width: w } }, f.name);
|
|
741
|
+
return /* @__PURE__ */ jsx2("col", { "data-qt-column": f.name, style: { width: w } }, f.name);
|
|
605
742
|
}),
|
|
606
743
|
trailing && /* @__PURE__ */ jsx2("col", { style: { width: 40 } })
|
|
607
744
|
] }),
|
|
608
|
-
/* @__PURE__ */ jsx2("thead", { className: classNames?.thead, children: /* @__PURE__ */ jsxs2("tr", { className: classNames?.headerRow, children: [
|
|
745
|
+
/* @__PURE__ */ jsx2("thead", { ref: tableHeadRef, className: classNames?.thead, children: /* @__PURE__ */ jsxs2("tr", { className: classNames?.headerRow, children: [
|
|
609
746
|
renderedColumnNames.map((name) => {
|
|
610
747
|
if (name === SELECTION_COLUMN) {
|
|
611
748
|
return /* @__PURE__ */ jsxs2(
|
|
@@ -616,11 +753,10 @@ function DataTable(props) {
|
|
|
616
753
|
"qt-th",
|
|
617
754
|
"qt-checkbox-cell",
|
|
618
755
|
dragSource === name && "qt-th--dragging",
|
|
619
|
-
resizingField === name && "qt-th--resizing",
|
|
620
756
|
classNames?.th,
|
|
621
757
|
classNames?.checkboxCell
|
|
622
758
|
),
|
|
623
|
-
draggable:
|
|
759
|
+
draggable: true,
|
|
624
760
|
onDragStart: (e) => {
|
|
625
761
|
if (resizingFieldRef.current) {
|
|
626
762
|
e.preventDefault();
|
|
@@ -683,11 +819,10 @@ function DataTable(props) {
|
|
|
683
819
|
"qt-th",
|
|
684
820
|
sortable && "qt-th--sortable",
|
|
685
821
|
dragSource === f.name && "qt-th--dragging",
|
|
686
|
-
resizingField === f.name && "qt-th--resizing",
|
|
687
822
|
classNames?.th
|
|
688
823
|
),
|
|
689
824
|
style: f.select?.align ? { textAlign: f.select.align } : void 0,
|
|
690
|
-
draggable:
|
|
825
|
+
draggable: true,
|
|
691
826
|
onDragStart: (e) => {
|
|
692
827
|
if (resizingFieldRef.current) {
|
|
693
828
|
e.preventDefault();
|
|
@@ -746,12 +881,17 @@ function DataTable(props) {
|
|
|
746
881
|
] }) }),
|
|
747
882
|
/* @__PURE__ */ jsxs2("tbody", { className: classNames?.tbody, children: [
|
|
748
883
|
rows.length === 0 && /* @__PURE__ */ jsx2("tr", { children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, className: cx2("qt-cell", "qt-empty", classNames?.empty), children: emptyMessage ?? "No rows." }) }),
|
|
749
|
-
|
|
750
|
-
|
|
884
|
+
paddingTop > 0 && /* @__PURE__ */ jsx2("tr", { className: "qt-virtual-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, style: { height: paddingTop, padding: 0, border: 0 } }) }),
|
|
885
|
+
virtualRows.map((virtualRow) => {
|
|
886
|
+
const i = virtualRow.index;
|
|
887
|
+
const row = rows[i];
|
|
888
|
+
const id = rowIds[i] ?? null;
|
|
751
889
|
const selected = id != null && selection ? selection.isSelected(id) : false;
|
|
752
890
|
return /* @__PURE__ */ jsxs2(
|
|
753
891
|
"tr",
|
|
754
892
|
{
|
|
893
|
+
"data-index": i,
|
|
894
|
+
ref: rowVirtualizer.measureElement,
|
|
755
895
|
className: cx2("qt-row", selected && "qt-row--selected", classNames?.row, selected && classNames?.rowSelected),
|
|
756
896
|
children: [
|
|
757
897
|
renderedColumnNames.map((name) => {
|
|
@@ -804,9 +944,10 @@ function DataTable(props) {
|
|
|
804
944
|
trailing && /* @__PURE__ */ jsx2("td", { className: cx2("qt-cell", "qt-trailing-cell", classNames?.cell), children: trailing(row) })
|
|
805
945
|
]
|
|
806
946
|
},
|
|
807
|
-
|
|
947
|
+
virtualRow.key
|
|
808
948
|
);
|
|
809
|
-
})
|
|
949
|
+
}),
|
|
950
|
+
paddingBottom > 0 && /* @__PURE__ */ jsx2("tr", { className: "qt-virtual-spacer", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, style: { height: paddingBottom, padding: 0, border: 0 } }) })
|
|
810
951
|
] }),
|
|
811
952
|
/* @__PURE__ */ jsx2("tfoot", { children: /* @__PURE__ */ jsx2("tr", { className: cx2("qt-table-summary-row", classNames?.summaryRow), children: /* @__PURE__ */ jsx2("td", { colSpan: totalCols, className: cx2("qt-cell", "qt-table-summary-cell", classNames?.summaryCell), children: /* @__PURE__ */ jsxs2("div", { className: cx2("qt-table-summary", classNames?.summaryControls), children: [
|
|
812
953
|
/* @__PURE__ */ jsx2(
|
|
@@ -979,6 +1120,7 @@ import {
|
|
|
979
1120
|
NULLARY_OPS as NULLARY_OPS2,
|
|
980
1121
|
aggOpNeedsField,
|
|
981
1122
|
aggOpsForField,
|
|
1123
|
+
encodeQuery,
|
|
982
1124
|
filterValues as filterValuesFor,
|
|
983
1125
|
isGroupable,
|
|
984
1126
|
isMeasurable,
|
|
@@ -1047,7 +1189,7 @@ function FieldPicker({
|
|
|
1047
1189
|
disabled: !enabled,
|
|
1048
1190
|
className: enabled ? "qt-picker-item" : "qt-picker-item qt-picker-item--disabled",
|
|
1049
1191
|
title: enabled ? f.name : "not useful as a filter (\u22641 distinct value)",
|
|
1050
|
-
|
|
1192
|
+
onPointerDown: (e) => {
|
|
1051
1193
|
if (!enabled) return;
|
|
1052
1194
|
e.preventDefault();
|
|
1053
1195
|
onPick(f);
|
|
@@ -1131,68 +1273,78 @@ function SavedQueriesModal({ saved, onClose, onLoad }) {
|
|
|
1131
1273
|
window.addEventListener("keydown", onKey);
|
|
1132
1274
|
return () => window.removeEventListener("keydown", onKey);
|
|
1133
1275
|
}, [onClose]);
|
|
1134
|
-
return /* @__PURE__ */ jsx4("div", { className: "qt-modal-backdrop", onClick: onClose, children: /* @__PURE__ */ jsxs4(
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1276
|
+
return /* @__PURE__ */ jsx4("div", { className: "qt-modal-backdrop", onClick: onClose, children: /* @__PURE__ */ jsxs4(
|
|
1277
|
+
"div",
|
|
1278
|
+
{
|
|
1279
|
+
className: "qt-modal",
|
|
1280
|
+
onClick: (e) => e.stopPropagation(),
|
|
1281
|
+
role: "dialog",
|
|
1282
|
+
"aria-modal": "true",
|
|
1283
|
+
"aria-label": "Saved queries",
|
|
1284
|
+
children: [
|
|
1285
|
+
/* @__PURE__ */ jsx4("h3", { className: "qt-modal-title", children: "Saved queries" }),
|
|
1286
|
+
saved.loading ? /* @__PURE__ */ jsx4("p", { className: "qt-muted", children: "Loading\u2026" }) : saved.items.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "qt-muted", children: "None saved yet. Save the current query from the toolbar." }) : /* @__PURE__ */ jsx4("ul", { className: "qt-saved-list", children: saved.items.map((s) => {
|
|
1287
|
+
const filters = s.query.where.length;
|
|
1288
|
+
const isDefault = saved.defaultId === s.id;
|
|
1289
|
+
return /* @__PURE__ */ jsxs4("li", { className: "qt-saved-item", children: [
|
|
1290
|
+
/* @__PURE__ */ jsxs4(
|
|
1291
|
+
"button",
|
|
1292
|
+
{
|
|
1293
|
+
type: "button",
|
|
1294
|
+
className: "qt-saved-pick",
|
|
1295
|
+
onClick: () => {
|
|
1296
|
+
onLoad?.(s.id);
|
|
1297
|
+
saved.load(s.id);
|
|
1298
|
+
onClose();
|
|
1299
|
+
},
|
|
1300
|
+
children: [
|
|
1301
|
+
/* @__PURE__ */ jsx4("span", { className: "qt-saved-name", children: s.name }),
|
|
1302
|
+
/* @__PURE__ */ jsxs4("span", { className: "qt-saved-meta qt-muted", children: [
|
|
1303
|
+
isDefault ? "Default view \xB7 " : "",
|
|
1304
|
+
new Date(s.savedAt).toLocaleString(),
|
|
1305
|
+
" \xB7 ",
|
|
1306
|
+
filters,
|
|
1307
|
+
" filter",
|
|
1308
|
+
filters === 1 ? "" : "s"
|
|
1309
|
+
] })
|
|
1310
|
+
]
|
|
1311
|
+
}
|
|
1312
|
+
),
|
|
1313
|
+
isDefault ? /* @__PURE__ */ jsx4(
|
|
1314
|
+
"button",
|
|
1315
|
+
{
|
|
1316
|
+
type: "button",
|
|
1317
|
+
className: "qt-btn qt-saved-default",
|
|
1318
|
+
title: "Clear default view",
|
|
1319
|
+
onClick: () => void saved.clearDefault(),
|
|
1320
|
+
children: "default"
|
|
1321
|
+
}
|
|
1322
|
+
) : /* @__PURE__ */ jsx4(
|
|
1323
|
+
"button",
|
|
1324
|
+
{
|
|
1325
|
+
type: "button",
|
|
1326
|
+
className: "qt-btn qt-saved-default",
|
|
1327
|
+
title: "Use as default view",
|
|
1328
|
+
onClick: () => void saved.setDefault(s.id),
|
|
1329
|
+
children: "make default"
|
|
1330
|
+
}
|
|
1331
|
+
),
|
|
1332
|
+
/* @__PURE__ */ jsx4(
|
|
1333
|
+
"button",
|
|
1334
|
+
{
|
|
1335
|
+
type: "button",
|
|
1336
|
+
className: "qt-btn qt-saved-delete",
|
|
1337
|
+
title: "Delete saved query",
|
|
1338
|
+
onClick: () => void saved.remove(s.id),
|
|
1339
|
+
children: "delete"
|
|
1340
|
+
}
|
|
1341
|
+
)
|
|
1342
|
+
] }, s.id);
|
|
1343
|
+
}) }),
|
|
1344
|
+
/* @__PURE__ */ jsx4("div", { className: "qt-modal-footer", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "qt-btn", onClick: onClose, children: "close" }) })
|
|
1345
|
+
]
|
|
1346
|
+
}
|
|
1347
|
+
) });
|
|
1196
1348
|
}
|
|
1197
1349
|
|
|
1198
1350
|
// src/QueryBuilder.tsx
|
|
@@ -1201,6 +1353,7 @@ var cx3 = (...parts) => parts.filter((p) => Boolean(p)).join(" ");
|
|
|
1201
1353
|
var MAX_AUTO_REFRESH_POLLS = 1e3;
|
|
1202
1354
|
var DEFAULT_AUTO_REFRESH_FREQUENCY_MS = 2e4;
|
|
1203
1355
|
var DEFAULT_AUTO_REFRESH_TURN_OFF_AFTER_MS = 5 * 6e4;
|
|
1356
|
+
var SHARE_FEEDBACK_MS = 2e3;
|
|
1204
1357
|
var AUTO_REFRESH_FREQUENCIES = [
|
|
1205
1358
|
{ label: "Every 5s", value: 5e3 },
|
|
1206
1359
|
{ label: "Every 10s", value: 1e4 },
|
|
@@ -1301,6 +1454,7 @@ function QueryBuilder({
|
|
|
1301
1454
|
}) {
|
|
1302
1455
|
const UNSAVED_QUERY_EDIT_ID = "__qt-unsaved-query__";
|
|
1303
1456
|
const [showSaved, setShowSaved] = useState4(false);
|
|
1457
|
+
const [shareCopied, setShareCopied] = useState4(false);
|
|
1304
1458
|
const [showAutoRefresh, setShowAutoRefresh] = useState4(false);
|
|
1305
1459
|
const [autoRefreshFrequencyMs, setAutoRefreshFrequencyMs] = useState4(DEFAULT_AUTO_REFRESH_FREQUENCY_MS);
|
|
1306
1460
|
const [autoRefreshTurnOffAfterMs, setAutoRefreshTurnOffAfterMs] = useState4(DEFAULT_AUTO_REFRESH_TURN_OFF_AFTER_MS);
|
|
@@ -1359,6 +1513,20 @@ function QueryBuilder({
|
|
|
1359
1513
|
const autoRefreshRef = useRef2(null);
|
|
1360
1514
|
const autoRefreshPopoverRef = useRef2(null);
|
|
1361
1515
|
const [autoRefreshPopoverAlignRight, setAutoRefreshPopoverAlignRight] = useState4(false);
|
|
1516
|
+
const shareFeedbackTimerRef = useRef2(null);
|
|
1517
|
+
useEffect4(
|
|
1518
|
+
() => () => {
|
|
1519
|
+
if (shareFeedbackTimerRef.current) clearTimeout(shareFeedbackTimerRef.current);
|
|
1520
|
+
},
|
|
1521
|
+
[]
|
|
1522
|
+
);
|
|
1523
|
+
useEffect4(() => {
|
|
1524
|
+
setShareCopied(false);
|
|
1525
|
+
if (shareFeedbackTimerRef.current) {
|
|
1526
|
+
clearTimeout(shareFeedbackTimerRef.current);
|
|
1527
|
+
shareFeedbackTimerRef.current = null;
|
|
1528
|
+
}
|
|
1529
|
+
}, [api.query]);
|
|
1362
1530
|
useEffect4(() => {
|
|
1363
1531
|
if (activeSavedQuery) {
|
|
1364
1532
|
setLastSavedId(activeSavedQuery.id);
|
|
@@ -1523,6 +1691,22 @@ function QueryBuilder({
|
|
|
1523
1691
|
saveFailed(error);
|
|
1524
1692
|
}
|
|
1525
1693
|
}
|
|
1694
|
+
async function shareQuery() {
|
|
1695
|
+
if (typeof window === "undefined") return;
|
|
1696
|
+
const url = new URL(window.location.href);
|
|
1697
|
+
url.searchParams.set("q", encodeQuery(api.query));
|
|
1698
|
+
try {
|
|
1699
|
+
await navigator.clipboard.writeText(url.toString());
|
|
1700
|
+
setShareCopied(true);
|
|
1701
|
+
if (shareFeedbackTimerRef.current) clearTimeout(shareFeedbackTimerRef.current);
|
|
1702
|
+
shareFeedbackTimerRef.current = setTimeout(() => {
|
|
1703
|
+
setShareCopied(false);
|
|
1704
|
+
shareFeedbackTimerRef.current = null;
|
|
1705
|
+
}, SHARE_FEEDBACK_MS);
|
|
1706
|
+
} catch {
|
|
1707
|
+
window.prompt("Copy this query URL", url.toString());
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1526
1710
|
return /* @__PURE__ */ jsxs5("div", { className: cx3("qt-qb", classNames?.root), children: [
|
|
1527
1711
|
/* @__PURE__ */ jsxs5("div", { className: "qt-qb-bar qt-qb-state-bar qt-qb-row", children: [
|
|
1528
1712
|
/* @__PURE__ */ jsxs5("span", { className: "qt-qb-state-main", children: [
|
|
@@ -1606,6 +1790,17 @@ function QueryBuilder({
|
|
|
1606
1790
|
children: `Update Query '${lastSavedQuery?.name ?? "query"}'`
|
|
1607
1791
|
}
|
|
1608
1792
|
) : null,
|
|
1793
|
+
/* @__PURE__ */ jsx5(
|
|
1794
|
+
"button",
|
|
1795
|
+
{
|
|
1796
|
+
type: "button",
|
|
1797
|
+
className: cx3("qt-btn", classNames?.button),
|
|
1798
|
+
onClick: () => void shareQuery(),
|
|
1799
|
+
title: "Copy a URL for this query",
|
|
1800
|
+
"aria-live": "polite",
|
|
1801
|
+
children: shareCopied ? "Copied!" : "Share"
|
|
1802
|
+
}
|
|
1803
|
+
),
|
|
1609
1804
|
/* @__PURE__ */ jsxs5("button", { type: "button", className: cx3("qt-btn", classNames?.button), onClick: () => setShowSaved(true), children: [
|
|
1610
1805
|
"Saved",
|
|
1611
1806
|
api.saved.items.length > 0 ? ` (${api.saved.items.length})` : ""
|
|
@@ -1764,6 +1959,41 @@ function QueryBuilder({
|
|
|
1764
1959
|
!isCollapsed && showSaved ? /* @__PURE__ */ jsx5(SavedQueriesModal, { saved: api.saved, onClose: () => setShowSaved(false), onLoad: setLastSavedId }) : null
|
|
1765
1960
|
] });
|
|
1766
1961
|
}
|
|
1962
|
+
function MoveButtons({
|
|
1963
|
+
label,
|
|
1964
|
+
position,
|
|
1965
|
+
count,
|
|
1966
|
+
disabled,
|
|
1967
|
+
onMove
|
|
1968
|
+
}) {
|
|
1969
|
+
if (count < 2) return null;
|
|
1970
|
+
return /* @__PURE__ */ jsxs5("span", { className: "qt-chip-move-buttons", children: [
|
|
1971
|
+
/* @__PURE__ */ jsx5(
|
|
1972
|
+
"button",
|
|
1973
|
+
{
|
|
1974
|
+
type: "button",
|
|
1975
|
+
className: "qt-chip-move",
|
|
1976
|
+
disabled: disabled || position === 0,
|
|
1977
|
+
onClick: () => onMove(position - 1),
|
|
1978
|
+
"aria-label": `Move ${label} earlier`,
|
|
1979
|
+
title: "Move earlier",
|
|
1980
|
+
children: "\u2190"
|
|
1981
|
+
}
|
|
1982
|
+
),
|
|
1983
|
+
/* @__PURE__ */ jsx5(
|
|
1984
|
+
"button",
|
|
1985
|
+
{
|
|
1986
|
+
type: "button",
|
|
1987
|
+
className: "qt-chip-move",
|
|
1988
|
+
disabled: disabled || position === count - 1,
|
|
1989
|
+
onClick: () => onMove(position + 1),
|
|
1990
|
+
"aria-label": `Move ${label} later`,
|
|
1991
|
+
title: "Move later",
|
|
1992
|
+
children: "\u2192"
|
|
1993
|
+
}
|
|
1994
|
+
)
|
|
1995
|
+
] });
|
|
1996
|
+
}
|
|
1767
1997
|
function SelectRow({
|
|
1768
1998
|
api,
|
|
1769
1999
|
fields,
|
|
@@ -1781,7 +2011,7 @@ function SelectRow({
|
|
|
1781
2011
|
const rendered = columnDrag.preview(fieldNames);
|
|
1782
2012
|
return /* @__PURE__ */ jsxs5("div", { className: "qt-qb-row", children: [
|
|
1783
2013
|
/* @__PURE__ */ jsx5("span", { className: "qt-qb-kw", children: "select" }),
|
|
1784
|
-
rendered.map((name) => {
|
|
2014
|
+
rendered.map((name, position) => {
|
|
1785
2015
|
const label = fieldLabelByName.get(name) ?? name;
|
|
1786
2016
|
return /* @__PURE__ */ jsxs5(
|
|
1787
2017
|
"span",
|
|
@@ -1824,6 +2054,16 @@ function SelectRow({
|
|
|
1824
2054
|
children: [
|
|
1825
2055
|
/* @__PURE__ */ jsx5("span", { "aria-hidden": true, className: "qt-chip-grip", children: "\u22EE\u22EE" }),
|
|
1826
2056
|
/* @__PURE__ */ jsx5("span", { className: "qt-chip-field", children: label }),
|
|
2057
|
+
/* @__PURE__ */ jsx5(
|
|
2058
|
+
MoveButtons,
|
|
2059
|
+
{
|
|
2060
|
+
label,
|
|
2061
|
+
position,
|
|
2062
|
+
count: rendered.length,
|
|
2063
|
+
disabled,
|
|
2064
|
+
onMove: (nextPosition) => select.move(name, nextPosition)
|
|
2065
|
+
}
|
|
2066
|
+
),
|
|
1827
2067
|
/* @__PURE__ */ jsx5("button", { type: "button", className: "qt-chip-x", onClick: () => select.hide(name), disabled, children: "\u2715" })
|
|
1828
2068
|
]
|
|
1829
2069
|
},
|
|
@@ -2058,6 +2298,9 @@ function OrderTermChip({
|
|
|
2058
2298
|
disabled,
|
|
2059
2299
|
updateTerm,
|
|
2060
2300
|
removeTerm,
|
|
2301
|
+
position,
|
|
2302
|
+
count,
|
|
2303
|
+
moveTerm,
|
|
2061
2304
|
classNames,
|
|
2062
2305
|
label
|
|
2063
2306
|
}) {
|
|
@@ -2107,6 +2350,16 @@ function OrderTermChip({
|
|
|
2107
2350
|
]
|
|
2108
2351
|
}
|
|
2109
2352
|
),
|
|
2353
|
+
/* @__PURE__ */ jsx5(
|
|
2354
|
+
MoveButtons,
|
|
2355
|
+
{
|
|
2356
|
+
label,
|
|
2357
|
+
position,
|
|
2358
|
+
count,
|
|
2359
|
+
disabled,
|
|
2360
|
+
onMove: moveTerm
|
|
2361
|
+
}
|
|
2362
|
+
),
|
|
2110
2363
|
/* @__PURE__ */ jsx5("button", { type: "button", className: "qt-chip-x", onClick: () => removeTerm(dataIndex), disabled, children: "\u2715" })
|
|
2111
2364
|
]
|
|
2112
2365
|
}
|
|
@@ -2207,6 +2460,16 @@ function OrderRow({
|
|
|
2207
2460
|
disabled,
|
|
2208
2461
|
updateTerm,
|
|
2209
2462
|
removeTerm,
|
|
2463
|
+
position: pos,
|
|
2464
|
+
count: rendered.length,
|
|
2465
|
+
moveTerm: (toIndex) => {
|
|
2466
|
+
const next = [...orderBy];
|
|
2467
|
+
const fromIndex = next.findIndex((candidate) => candidate.field === f);
|
|
2468
|
+
if (fromIndex === -1) return;
|
|
2469
|
+
const [moved] = next.splice(fromIndex, 1);
|
|
2470
|
+
next.splice(toIndex, 0, moved);
|
|
2471
|
+
setOrderBy(next);
|
|
2472
|
+
},
|
|
2210
2473
|
classNames,
|
|
2211
2474
|
label: byName.get(f)?.label ?? f
|
|
2212
2475
|
},
|
|
@@ -2237,34 +2500,38 @@ function WindowRow({
|
|
|
2237
2500
|
api.setOffset(Math.max(0, Number(offsetDraft) || 0));
|
|
2238
2501
|
}
|
|
2239
2502
|
return /* @__PURE__ */ jsxs5("div", { className: "qt-qb-row qt-qb-pagination", children: [
|
|
2240
|
-
/* @__PURE__ */
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
{
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2503
|
+
/* @__PURE__ */ jsxs5("label", { className: "qt-qb-window-field", children: [
|
|
2504
|
+
/* @__PURE__ */ jsx5("span", { className: "qt-qb-kw", children: "limit" }),
|
|
2505
|
+
/* @__PURE__ */ jsx5(
|
|
2506
|
+
"input",
|
|
2507
|
+
{
|
|
2508
|
+
className: cx3("qt-qb-num", classNames?.input),
|
|
2509
|
+
type: "number",
|
|
2510
|
+
min: 1,
|
|
2511
|
+
value: limitDraft,
|
|
2512
|
+
disabled,
|
|
2513
|
+
onChange: (e) => setLimitDraft(e.target.value),
|
|
2514
|
+
onBlur: commitLimit,
|
|
2515
|
+
onKeyDown: (e) => e.key === "Enter" && commitLimit()
|
|
2516
|
+
}
|
|
2517
|
+
)
|
|
2518
|
+
] }),
|
|
2519
|
+
/* @__PURE__ */ jsxs5("label", { className: "qt-qb-window-field", children: [
|
|
2520
|
+
/* @__PURE__ */ jsx5("span", { className: "qt-qb-kw", children: "offset" }),
|
|
2521
|
+
/* @__PURE__ */ jsx5(
|
|
2522
|
+
"input",
|
|
2523
|
+
{
|
|
2524
|
+
className: cx3("qt-qb-num", classNames?.input),
|
|
2525
|
+
type: "number",
|
|
2526
|
+
min: 0,
|
|
2527
|
+
value: offsetDraft,
|
|
2528
|
+
disabled,
|
|
2529
|
+
onChange: (e) => setOffsetDraft(e.target.value),
|
|
2530
|
+
onBlur: commitOffset,
|
|
2531
|
+
onKeyDown: (e) => e.key === "Enter" && commitOffset()
|
|
2532
|
+
}
|
|
2533
|
+
)
|
|
2534
|
+
] }),
|
|
2268
2535
|
!hasWindowDefault && /* @__PURE__ */ jsx5(
|
|
2269
2536
|
"button",
|
|
2270
2537
|
{
|
|
@@ -2331,7 +2598,7 @@ function MetricsRow({
|
|
|
2331
2598
|
return /* @__PURE__ */ jsxs5("div", { className: "qt-qb-row qt-qb-row--metrics", children: [
|
|
2332
2599
|
/* @__PURE__ */ jsx5("span", { className: "qt-qb-kw", children: "metrics" }),
|
|
2333
2600
|
clauses.length === 0 && /* @__PURE__ */ jsx5("span", { className: "qt-qb-hint", children: "none" }),
|
|
2334
|
-
rendered.map((id) => {
|
|
2601
|
+
rendered.map((id, position) => {
|
|
2335
2602
|
const clause = byId.get(id);
|
|
2336
2603
|
return /* @__PURE__ */ jsx5(
|
|
2337
2604
|
MetricChip,
|
|
@@ -2350,7 +2617,10 @@ function MetricsRow({
|
|
|
2350
2617
|
onChangeField: (field) => aggregations.update(id, { field }),
|
|
2351
2618
|
onAddGroup: (field) => aggregations.update(id, { groupBy: [...clause.groupBy, field] }),
|
|
2352
2619
|
onRemoveGroup: (field) => aggregations.update(id, { groupBy: clause.groupBy.filter((g) => g !== field) }),
|
|
2353
|
-
onRemove: () => aggregations.remove(id)
|
|
2620
|
+
onRemove: () => aggregations.remove(id),
|
|
2621
|
+
position,
|
|
2622
|
+
count: rendered.length,
|
|
2623
|
+
onMove: (toIndex) => aggregations.move(id, toIndex)
|
|
2354
2624
|
},
|
|
2355
2625
|
id
|
|
2356
2626
|
);
|
|
@@ -2380,7 +2650,10 @@ function MetricChip({
|
|
|
2380
2650
|
onChangeField,
|
|
2381
2651
|
onAddGroup,
|
|
2382
2652
|
onRemoveGroup,
|
|
2383
|
-
onRemove
|
|
2653
|
+
onRemove,
|
|
2654
|
+
position,
|
|
2655
|
+
count,
|
|
2656
|
+
onMove
|
|
2384
2657
|
}) {
|
|
2385
2658
|
const [addingGroup, setAddingGroup] = useState4(false);
|
|
2386
2659
|
const byName = useMemo3(() => new Map(groupable.map((f) => [f.name, f])), [groupable]);
|
|
@@ -2457,6 +2730,16 @@ function MetricChip({
|
|
|
2457
2730
|
}
|
|
2458
2731
|
)
|
|
2459
2732
|
] }),
|
|
2733
|
+
/* @__PURE__ */ jsx5(
|
|
2734
|
+
MoveButtons,
|
|
2735
|
+
{
|
|
2736
|
+
label: clause.label ?? `${AGG_OP_LABELS[clause.op]} metric`,
|
|
2737
|
+
position,
|
|
2738
|
+
count,
|
|
2739
|
+
disabled,
|
|
2740
|
+
onMove
|
|
2741
|
+
}
|
|
2742
|
+
),
|
|
2460
2743
|
/* @__PURE__ */ jsx5("button", { type: "button", className: "qt-chip-x", onClick: onRemove, disabled, title: "Remove metric", children: "\u2715" })
|
|
2461
2744
|
]
|
|
2462
2745
|
}
|