@pythia-software/query-table-ui 0.9.8 → 1.0.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 +37 -20
- package/dist/index.d.ts +4 -1
- package/dist/index.js +148 -46
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -29,8 +29,8 @@ and selection toolbar.
|
|
|
29
29
|
for complete local datasets. The standard query builder supplies GROUP BY,
|
|
30
30
|
HAVING, aggregate column editing and representative-row ordering. Use
|
|
31
31
|
`api.schema`, `api.visibleFields` and `api.rowId` for the active result shape;
|
|
32
|
-
set `grouping: false` to opt out.
|
|
33
|
-
|
|
32
|
+
set `grouping: false` to opt out. Remote tables group on the server when its
|
|
33
|
+
`queryCapabilities.groupedRows` is true. See [grouping integration](../../docs/grouping.md) for the query
|
|
34
34
|
shape, source-stage controls, saved state and execution boundaries.
|
|
35
35
|
|
|
36
36
|
## Install
|
|
@@ -110,24 +110,41 @@ Click anywhere in a column header to open its actions menu, including padding
|
|
|
110
110
|
and blank space. Resize handles remain dedicated to resizing.
|
|
111
111
|
Open **Column Widths** to resize in one step:
|
|
112
112
|
|
|
113
|
-
- **Fit
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
- **
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
- **Fit All to Content** sets `query.fit = "content"` and clears explicit
|
|
114
|
+
widths. Every column without a width then keeps fitting its widest drawn
|
|
115
|
+
value (header included) after each change to what the table shows: sorting,
|
|
116
|
+
column changes, filters, grouping, paging, refreshes and new rows. The table
|
|
117
|
+
is then only as wide as its columns.
|
|
118
|
+
- **Fit All to Screen** sets `query.fit = "screen"` and clears explicit
|
|
119
|
+
widths. Columns without a width fill the viewport left by resized ones;
|
|
120
|
+
when content overflows, narrow columns keep their content width and only
|
|
121
|
+
the widest ones are trimmed. It also refits when the viewport changes.
|
|
122
|
+
- **Fit This Column** returns the clicked column to automatic fitting in
|
|
123
|
+
either mode. With no mode it sizes the column to its widest drawn value once.
|
|
124
|
+
- **Equal Widths** splits the viewport evenly and turns any mode off.
|
|
125
|
+
- **Reset Widths** drops explicit widths back to automatic fitting (or to
|
|
126
|
+
schema defaults with no mode).
|
|
127
|
+
|
|
128
|
+
Choosing the active mode again turns it off and keeps the widths it showed.
|
|
129
|
+
|
|
130
|
+
Fitted widths are local layout: they are never written to the query, the URL
|
|
131
|
+
or undo history. Dragging or nudging one column stores only that column's
|
|
132
|
+
`width`; the mode stays on and the other columns keep fitting around it. A
|
|
133
|
+
field's `select.width` is a fallback used only when no mode is set. While a
|
|
134
|
+
query is loading the previous widths stay; scrolling a virtualized body never
|
|
135
|
+
refits. A refresh that keeps the query (same columns, filters, sort, paging
|
|
136
|
+
and grouping) only grows content-fitted columns, so a live table stays calm;
|
|
137
|
+
any change to the query refits from scratch. Content fitting stops at 800px.
|
|
138
|
+
No column is fitted below what its buttons and links need (links shorten to
|
|
139
|
+
an ellipsis first); in screen mode, if those minimums exceed the screen, the
|
|
140
|
+
table scrolls horizontally. Set `select.minWidth` on a field whose host renderer needs room
|
|
141
|
+
the table cannot measure.
|
|
142
|
+
|
|
143
|
+
Set `fit` on a default or initial query to opt in; a stored query without
|
|
144
|
+
`fit` has no live sizing. The mode travels in the encoded `?q=` parameter
|
|
145
|
+
(`ft` in its compact payload). Links from 0.9.9 and earlier with
|
|
146
|
+
`fitToScreen: true` (`fs`) open in screen mode without their stored widths,
|
|
147
|
+
which screen fitting never showed.
|
|
131
148
|
|
|
132
149
|
## Cell quick filters
|
|
133
150
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, SetStateAction, CSSProperties, ButtonHTMLAttributes, PointerEvent, Ref, MutableRefObject } from 'react';
|
|
3
3
|
import { FieldDef, QueryState, LinkTargets, CellDisplay, JsonCellDisplay, JsonInspectionState, JsonInspectionScope, AttentionRegistry, RowId, AggregationClause, AggregationBucket, AggregationResultEntry, MetricValue, SavedQuery, FieldStats, WhereClause, MetricValueFormat, FieldSchema, ScopedJsonInspectionState, PropertyViewState, RelationshipAdapter, ObjectReference, PropertyFormat, PropertyViewStorage, ItemPropertyMode, FormulaFunction, GroupedRow } from '@pythia-software/query-table-core';
|
|
4
|
-
import { ItemDetailsApi, SelectionApi, ColumnDragApi, QueryTableApi, QueryFilterApi, AggregationsApi, SavedQueriesApi, RequestActivityApi, PropertyViewApi
|
|
4
|
+
import { ItemDetailsApi, SelectionApi, ColumnDragApi, GroupingApi, QueryTableApi, QueryFilterApi, AggregationsApi, SavedQueriesApi, RequestActivityApi, PropertyViewApi } from '@pythia-software/query-table-react';
|
|
5
5
|
import { Placement } from '@floating-ui/react';
|
|
6
6
|
|
|
7
7
|
interface CellContext<Row = any, V = unknown> {
|
|
@@ -200,6 +200,9 @@ interface DataTableProps<Row> {
|
|
|
200
200
|
* table header also live-previews/dims the matching QueryBuilder select chip
|
|
201
201
|
* (and vice versa). Omit for a self-contained, table-only drag. */
|
|
202
202
|
columnDrag?: ColumnDragApi;
|
|
203
|
+
/** Grouping controller (`api.grouping`). Pass it to offer Group By, Remove
|
|
204
|
+
* from Group By and Ungroup in the header menu. */
|
|
205
|
+
grouping?: GroupingApi<Row> | undefined;
|
|
203
206
|
/** Always-visible trailing column (row actions, links). */
|
|
204
207
|
trailing?: (row: Row) => ReactNode;
|
|
205
208
|
trailingLabel?: string;
|
package/dist/index.js
CHANGED
|
@@ -4202,7 +4202,7 @@ function ItemHoverCard({ api, query, id, renderers, anchor, onClose }) {
|
|
|
4202
4202
|
}
|
|
4203
4203
|
|
|
4204
4204
|
// src/DataTable.tsx
|
|
4205
|
-
import { GROUP_ID_FIELD, GROUP_LINK_RENDER, ITEM_DETAILS_COLUMN, groupKeyFields, isComputedCellError, isGroupLinkValue as isGroupLinkValue2 } from "@pythia-software/query-table-core";
|
|
4205
|
+
import { GROUP_ID_FIELD, GROUP_LINK_RENDER, ITEM_DETAILS_COLUMN, groupKeyField, groupKeyFields, isComputedCellError, isGroupLinkValue as isGroupLinkValue2 } from "@pythia-software/query-table-core";
|
|
4206
4206
|
import { useId as useId4, useCallback as useCallback3, useEffect as useEffect12, useLayoutEffect as useLayoutEffect6, useMemo as useMemo5, useRef as useRef14, useState as useState16 } from "react";
|
|
4207
4207
|
import { defaultRangeExtractor, useVirtualizer } from "@tanstack/react-virtual";
|
|
4208
4208
|
import { isSortable, readFieldValue as readFieldValue2 } from "@pythia-software/query-table-core";
|
|
@@ -4926,8 +4926,11 @@ function DataTable(props) {
|
|
|
4926
4926
|
const [copiedCellKey, setCopiedCellKey] = useState16(null);
|
|
4927
4927
|
const [headerHeight, setHeaderHeight] = useState16(0);
|
|
4928
4928
|
const [showHorizontalCue, setShowHorizontalCue] = useState16(false);
|
|
4929
|
-
const [
|
|
4929
|
+
const [fittedWidths, setFittedWidths] = useState16(/* @__PURE__ */ new Map());
|
|
4930
|
+
const lastFitRef = useRef14(null);
|
|
4930
4931
|
const [screenViewportWidth, setScreenViewportWidth] = useState16(0);
|
|
4932
|
+
const [shownRevision, setShownRevision] = useState16(0);
|
|
4933
|
+
const fitWaitingRef = useRef14(false);
|
|
4931
4934
|
const tableWrapRef = useRef14(null);
|
|
4932
4935
|
const tableHeadRef = useRef14(null);
|
|
4933
4936
|
const tableRef = useRef14(null);
|
|
@@ -5072,12 +5075,36 @@ function DataTable(props) {
|
|
|
5072
5075
|
const term = query.orderBy[idx];
|
|
5073
5076
|
return { dir: term.dir, priority: query.orderBy.length > 1 ? idx + 1 : null };
|
|
5074
5077
|
}
|
|
5078
|
+
const grouping = props.grouping;
|
|
5079
|
+
function headerGrouping(f) {
|
|
5080
|
+
if (!grouping) return null;
|
|
5081
|
+
if (!query.grouping) {
|
|
5082
|
+
const groupable = grouping.supported && grouping.model.groupableFields.some((g) => g.name === f.name);
|
|
5083
|
+
return { kind: "rows", groupable };
|
|
5084
|
+
}
|
|
5085
|
+
return { kind: "groups", key: groupKeys ? groupKeys.includes(f.name) : false, keyCount: query.grouping.keys.length };
|
|
5086
|
+
}
|
|
5087
|
+
function changeGroupKeys(keys) {
|
|
5088
|
+
setMenu(null);
|
|
5089
|
+
if (!grouping) return;
|
|
5090
|
+
try {
|
|
5091
|
+
grouping.changeKeys(keys);
|
|
5092
|
+
} catch (error) {
|
|
5093
|
+
grouping.setNotice(error instanceof Error ? error.message : String(error));
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
function removeGroupKey(f) {
|
|
5097
|
+
changeGroupKeys((query.grouping?.keys ?? []).filter((key) => groupKeyField(key) !== f.name));
|
|
5098
|
+
}
|
|
5075
5099
|
function widthFor(name, f) {
|
|
5076
5100
|
const col = query.select.find((c) => c.field === name);
|
|
5077
5101
|
return col?.width ?? f.select?.width;
|
|
5078
5102
|
}
|
|
5103
|
+
function explicitWidthFor(name) {
|
|
5104
|
+
return query.select.find((c) => c.field === name)?.width;
|
|
5105
|
+
}
|
|
5079
5106
|
function resolvedWidthFor(name, f) {
|
|
5080
|
-
if (query.
|
|
5107
|
+
if (query.fit && explicitWidthFor(name) == null && fittedWidths.has(name)) return fittedWidths.get(name);
|
|
5081
5108
|
return widthFor(name, f);
|
|
5082
5109
|
}
|
|
5083
5110
|
function resolvedColumnWidthFor(name, f) {
|
|
@@ -5098,7 +5125,6 @@ function DataTable(props) {
|
|
|
5098
5125
|
function commitColumnWidth(name, width) {
|
|
5099
5126
|
if (name === SELECTION_COLUMN) {
|
|
5100
5127
|
setSelectionColumnWidth(clampColumnWidth(name, width));
|
|
5101
|
-
if (query.fitToScreen) setWidths(/* @__PURE__ */ new Map());
|
|
5102
5128
|
return;
|
|
5103
5129
|
}
|
|
5104
5130
|
setWidth(name, width);
|
|
@@ -5106,17 +5132,25 @@ function DataTable(props) {
|
|
|
5106
5132
|
function setWidth(name, width) {
|
|
5107
5133
|
setWidths(/* @__PURE__ */ new Map([[name, width]]));
|
|
5108
5134
|
}
|
|
5109
|
-
function setWidths(widths,
|
|
5135
|
+
function setWidths(widths, leaveFit = false) {
|
|
5110
5136
|
const px = new Map(Array.from(widths, ([name, width]) => [name, clampWidth(width)]));
|
|
5111
5137
|
onQueryChange((prev) => {
|
|
5112
|
-
|
|
5113
|
-
|
|
5138
|
+
let current = prev.select;
|
|
5139
|
+
if (leaveFit && prev.fit) {
|
|
5140
|
+
const frozen = new Map([...fittedWidths].filter(([name]) => fieldByName.has(name) && prev.select.find((c) => c.field === name)?.width == null));
|
|
5141
|
+
current = writeWidths(current, fields, frozen);
|
|
5142
|
+
}
|
|
5114
5143
|
const next = { ...prev, select: writeWidths(current, fields, px) };
|
|
5115
|
-
if (
|
|
5116
|
-
else delete next.fitToScreen;
|
|
5144
|
+
if (leaveFit) delete next.fit;
|
|
5117
5145
|
return next;
|
|
5118
5146
|
});
|
|
5119
5147
|
}
|
|
5148
|
+
function clearWidths(names) {
|
|
5149
|
+
onQueryChange((prev) => ({
|
|
5150
|
+
...prev,
|
|
5151
|
+
select: prev.select.map(({ width, ...c }) => names && !names.has(c.field) && width != null ? { ...c, width } : c)
|
|
5152
|
+
}));
|
|
5153
|
+
}
|
|
5120
5154
|
function applyResizeGuide() {
|
|
5121
5155
|
const preview = resizePreviewRef.current;
|
|
5122
5156
|
const guide = resizeGuideRef.current;
|
|
@@ -5225,28 +5259,32 @@ function DataTable(props) {
|
|
|
5225
5259
|
});
|
|
5226
5260
|
}
|
|
5227
5261
|
function autofitColumn(name) {
|
|
5262
|
+
if (query.fit) {
|
|
5263
|
+
if (explicitWidthFor(name) != null) clearWidths(/* @__PURE__ */ new Set([name]));
|
|
5264
|
+
return;
|
|
5265
|
+
}
|
|
5228
5266
|
const [width = 0] = measureContentWidths([name]);
|
|
5229
5267
|
if (width > 0) setWidth(name, width);
|
|
5230
5268
|
}
|
|
5231
5269
|
function applyWidthPreset(preset) {
|
|
5232
5270
|
setMenu(null);
|
|
5271
|
+
if (preset !== "equal") {
|
|
5272
|
+
if (query.fit === preset) setWidths(/* @__PURE__ */ new Map(), true);
|
|
5273
|
+
else onQueryChange((prev) => ({ ...prev, fit: preset, select: prev.select.map(({ width: _width, ...c }) => c) }));
|
|
5274
|
+
return;
|
|
5275
|
+
}
|
|
5233
5276
|
const names = renderedColumnNames.filter((name) => fieldByName.has(name));
|
|
5234
5277
|
if (names.length === 0) return;
|
|
5235
|
-
const content = measureContentWidths(names).map((w, i) => w || displayColumnWidthFor(names[i], fieldByName.get(names[i])));
|
|
5236
5278
|
const wrap = tableWrapRef.current;
|
|
5237
5279
|
const available = (wrap?.clientWidth ?? 0) - (showSel ? selectionColumnWidth : 0) - (trailing ? TRAILING_WIDTH : 0) - TABLE_BORDER_PX;
|
|
5238
|
-
const widths =
|
|
5239
|
-
setWidths(new Map(names.map((name, i) => [name, widths[i]])),
|
|
5280
|
+
const widths = equalWidths(names.length, available);
|
|
5281
|
+
setWidths(new Map(names.map((name, i) => [name, widths[i]])), true);
|
|
5240
5282
|
}
|
|
5241
|
-
const hasCustomWidths =
|
|
5283
|
+
const hasCustomWidths = selectionColumnWidth !== DEFAULT_SELECTION_WIDTH || query.select.some((c) => c.width != null);
|
|
5242
5284
|
function resetWidths() {
|
|
5243
5285
|
setMenu(null);
|
|
5244
5286
|
setSelectionColumnWidth(DEFAULT_SELECTION_WIDTH);
|
|
5245
|
-
|
|
5246
|
-
const next = { ...prev, select: prev.select.map(({ width: _width, ...c }) => c) };
|
|
5247
|
-
delete next.fitToScreen;
|
|
5248
|
-
return next;
|
|
5249
|
-
});
|
|
5287
|
+
clearWidths();
|
|
5250
5288
|
}
|
|
5251
5289
|
function resizeWithKeyboard(name, startWidth, e) {
|
|
5252
5290
|
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight" && e.key !== "Enter") return;
|
|
@@ -5299,11 +5337,15 @@ function DataTable(props) {
|
|
|
5299
5337
|
const index = preview.indexOf(SELECTION_COLUMN);
|
|
5300
5338
|
if (index >= 0) setSelectionColumnIndex(index);
|
|
5301
5339
|
}, [columnDrag.active, columnDrag.dropRevision, renderedColumnNames, fieldNames]);
|
|
5340
|
+
const fit = query.fit;
|
|
5302
5341
|
useEffect12(() => {
|
|
5303
|
-
if (!
|
|
5342
|
+
if (!fit) return;
|
|
5304
5343
|
const wrap = tableWrapRef.current;
|
|
5305
5344
|
if (!wrap) return;
|
|
5306
|
-
const measureViewport = () =>
|
|
5345
|
+
const measureViewport = () => {
|
|
5346
|
+
if (fit === "screen") setScreenViewportWidth(wrap.clientWidth);
|
|
5347
|
+
else if (fitWaitingRef.current && wrap.clientWidth > 0) setShownRevision((revision) => revision + 1);
|
|
5348
|
+
};
|
|
5307
5349
|
measureViewport();
|
|
5308
5350
|
window.addEventListener("resize", measureViewport);
|
|
5309
5351
|
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measureViewport);
|
|
@@ -5312,20 +5354,50 @@ function DataTable(props) {
|
|
|
5312
5354
|
window.removeEventListener("resize", measureViewport);
|
|
5313
5355
|
observer?.disconnect();
|
|
5314
5356
|
};
|
|
5315
|
-
}, [
|
|
5357
|
+
}, [fit]);
|
|
5358
|
+
const fitQueryKey = useMemo5(() => JSON.stringify([
|
|
5359
|
+
query.select.map((c) => [c.field, c.display ?? null]),
|
|
5360
|
+
query.where,
|
|
5361
|
+
query.orderBy,
|
|
5362
|
+
query.limit,
|
|
5363
|
+
query.offset,
|
|
5364
|
+
query.grouping ?? null,
|
|
5365
|
+
query.hostState ?? null,
|
|
5366
|
+
fieldNames
|
|
5367
|
+
]), [query.select, query.where, query.orderBy, query.limit, query.offset, query.grouping, query.hostState, fieldNames]);
|
|
5316
5368
|
useCommitLayoutEffect(() => {
|
|
5317
|
-
if (!
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5369
|
+
if (!fit) {
|
|
5370
|
+
lastFitRef.current = null;
|
|
5371
|
+
return;
|
|
5372
|
+
}
|
|
5373
|
+
if (loading || fieldNames.length === 0) return;
|
|
5374
|
+
const viewport = tableWrapRef.current?.clientWidth ?? 0;
|
|
5375
|
+
fitWaitingRef.current = viewport <= 0;
|
|
5376
|
+
if (viewport <= 0) return;
|
|
5377
|
+
const names = fieldNames.filter((name) => explicitWidthFor(name) == null);
|
|
5378
|
+
const content = measureContentWidths(names).map(
|
|
5379
|
+
(width, i) => width || widthFor(names[i], fieldByName.get(names[i])) || DEFAULT_WIDTH
|
|
5322
5380
|
);
|
|
5323
|
-
const
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5381
|
+
const minimums = measureControlMinimums(names);
|
|
5382
|
+
let widths;
|
|
5383
|
+
if (fit === "screen") {
|
|
5384
|
+
const fixed = fieldNames.reduce((sum, name) => sum + (explicitWidthFor(name) ?? 0), 0);
|
|
5385
|
+
const available = viewport - (showSel ? selectionColumnWidth : 0) - (trailing ? TRAILING_WIDTH : 0) - TABLE_BORDER_PX - fixed;
|
|
5386
|
+
widths = fitScreenWidths(content, available, void 0, minimums);
|
|
5387
|
+
} else {
|
|
5388
|
+
const last = lastFitRef.current;
|
|
5389
|
+
const grow = last?.fit === "content" && last.key === fitQueryKey;
|
|
5390
|
+
widths = fitContentWidths(content).map((width, i) => {
|
|
5391
|
+
const fitted = Math.min(MAX_WIDTH, Math.max(width, Math.ceil(minimums[i])));
|
|
5392
|
+
return grow ? Math.max(fitted, fittedWidths.get(names[i]) ?? 0) : fitted;
|
|
5393
|
+
});
|
|
5394
|
+
}
|
|
5395
|
+
lastFitRef.current = { fit, key: fitQueryKey };
|
|
5396
|
+
setFittedWidths((current) => {
|
|
5397
|
+
if (current.size === names.length && names.every((name, i) => current.get(name) === widths[i])) return current;
|
|
5398
|
+
return new Map(names.map((name, i) => [name, widths[i]]));
|
|
5327
5399
|
});
|
|
5328
|
-
}, [
|
|
5400
|
+
}, [fit, query.select, fitQueryKey, fields, fieldNames, rows, loading, renderers, screenViewportWidth, shownRevision, showSel, selectionColumnWidth, trailing]);
|
|
5329
5401
|
function toggleHeader() {
|
|
5330
5402
|
if (!selection) return;
|
|
5331
5403
|
selection.setPage(pageIds, headerState !== "all");
|
|
@@ -5451,7 +5523,7 @@ function DataTable(props) {
|
|
|
5451
5523
|
"aria-hidden": "true"
|
|
5452
5524
|
}
|
|
5453
5525
|
),
|
|
5454
|
-
/* @__PURE__ */ jsxs17("table", { ref: tableRef, className: cx3("qt-table", classNames?.table), style: { minWidth: tableWidth }, children: [
|
|
5526
|
+
/* @__PURE__ */ jsxs17("table", { ref: tableRef, className: cx3("qt-table", classNames?.table), style: fit === "content" ? { width: tableWidth, minWidth: tableWidth } : { minWidth: tableWidth }, children: [
|
|
5455
5527
|
/* @__PURE__ */ jsxs17("colgroup", { children: [
|
|
5456
5528
|
renderedColumnNames.map((name) => {
|
|
5457
5529
|
const f = fieldByName.get(name);
|
|
@@ -5746,8 +5818,12 @@ function DataTable(props) {
|
|
|
5746
5818
|
canRemoveColumn: fields.length > 1,
|
|
5747
5819
|
onSetSort: (placement, dir) => applyHeaderSort(menu.field, dir, placement),
|
|
5748
5820
|
onRemove: () => removeColumn(menu.field),
|
|
5821
|
+
grouping: headerGrouping(menu.field),
|
|
5822
|
+
onGroupBy: () => changeGroupKeys([menu.field.name]),
|
|
5823
|
+
onRemoveGroupKey: () => removeGroupKey(menu.field),
|
|
5824
|
+
onUngroup: () => changeGroupKeys([]),
|
|
5749
5825
|
canResetWidths: hasCustomWidths,
|
|
5750
|
-
|
|
5826
|
+
fit: query.fit,
|
|
5751
5827
|
onFitColumn: () => {
|
|
5752
5828
|
setMenu(null);
|
|
5753
5829
|
autofitColumn(menu.field.name);
|
|
@@ -5766,9 +5842,13 @@ function HeaderMenu({
|
|
|
5766
5842
|
sortable,
|
|
5767
5843
|
canRemoveColumn,
|
|
5768
5844
|
canResetWidths,
|
|
5769
|
-
|
|
5845
|
+
fit,
|
|
5770
5846
|
onSetSort,
|
|
5771
5847
|
onRemove,
|
|
5848
|
+
grouping,
|
|
5849
|
+
onGroupBy,
|
|
5850
|
+
onRemoveGroupKey,
|
|
5851
|
+
onUngroup,
|
|
5772
5852
|
onFitColumn,
|
|
5773
5853
|
onWidthPreset,
|
|
5774
5854
|
onResetWidths,
|
|
@@ -5784,21 +5864,37 @@ function HeaderMenu({
|
|
|
5784
5864
|
")"
|
|
5785
5865
|
] }, `${placement}-${dir}`))
|
|
5786
5866
|
),
|
|
5867
|
+
grouping && /* @__PURE__ */ jsx21(MenuSeparator, {}),
|
|
5868
|
+
grouping?.kind === "rows" && /* @__PURE__ */ jsxs17(MenuItem, { disabled: !grouping.groupable, onSelect: onGroupBy, title: grouping.groupable ? `One row per ${field.label} value` : `${field.label} cannot be grouped`, children: [
|
|
5869
|
+
"Group By ",
|
|
5870
|
+
field.label
|
|
5871
|
+
] }),
|
|
5872
|
+
grouping?.kind === "groups" && grouping.key && grouping.keyCount > 1 && /* @__PURE__ */ jsx21(MenuItem, { onSelect: onRemoveGroupKey, title: "Group by the remaining keys only", children: "Remove from Group By" }),
|
|
5873
|
+
grouping?.kind === "groups" && /* @__PURE__ */ jsx21(MenuItem, { onSelect: onUngroup, title: "Back to the source rows", children: "Ungroup" }),
|
|
5787
5874
|
/* @__PURE__ */ jsx21(MenuSeparator, {}),
|
|
5788
5875
|
/* @__PURE__ */ jsxs17(MenuSubmenu, { label: "Column Widths", children: [
|
|
5789
|
-
/* @__PURE__ */ jsx21(MenuItem, { onSelect: onFitColumn, title: `Size ${field.label} to its widest visible value`, children: "Fit This Column" }),
|
|
5876
|
+
/* @__PURE__ */ jsx21(MenuItem, { onSelect: onFitColumn, title: fit ? `Return ${field.label} to automatic fitting` : `Size ${field.label} to its widest visible value`, children: "Fit This Column" }),
|
|
5790
5877
|
/* @__PURE__ */ jsx21(MenuSeparator, {}),
|
|
5791
|
-
WIDTH_PRESETS.map(({ preset, label, hint }) => /* @__PURE__ */ jsx21(
|
|
5878
|
+
WIDTH_PRESETS.map(({ preset, label, hint }) => /* @__PURE__ */ jsx21(
|
|
5879
|
+
MenuItem,
|
|
5880
|
+
{
|
|
5881
|
+
onSelect: () => onWidthPreset(preset),
|
|
5882
|
+
title: preset === fit ? "Stop fitting automatically and keep the current widths" : hint,
|
|
5883
|
+
checked: preset === "equal" ? void 0 : preset === fit,
|
|
5884
|
+
children: label
|
|
5885
|
+
},
|
|
5886
|
+
preset
|
|
5887
|
+
)),
|
|
5792
5888
|
/* @__PURE__ */ jsx21(MenuSeparator, {}),
|
|
5793
|
-
/* @__PURE__ */ jsx21(MenuItem, { disabled: !canResetWidths, onSelect: onResetWidths, title: "Back to the default column widths", children: "Reset Widths" })
|
|
5889
|
+
/* @__PURE__ */ jsx21(MenuItem, { disabled: !canResetWidths, onSelect: onResetWidths, title: fit ? "Return every resized column to automatic fitting" : "Back to the default column widths", children: "Reset Widths" })
|
|
5794
5890
|
] }),
|
|
5795
5891
|
/* @__PURE__ */ jsx21(MenuSeparator, {}),
|
|
5796
5892
|
/* @__PURE__ */ jsx21(MenuItem, { tone: "danger", disabled: !canRemoveColumn, onSelect: onRemove, children: "Remove Column" })
|
|
5797
5893
|
] });
|
|
5798
5894
|
}
|
|
5799
5895
|
var WIDTH_PRESETS = [
|
|
5800
|
-
{ preset: "content", label: "Fit All to Content", hint: "
|
|
5801
|
-
{ preset: "screen", label: "Fit All to Screen", hint: "
|
|
5896
|
+
{ preset: "content", label: "Fit All to Content", hint: "Keep every column sized to its widest visible value" },
|
|
5897
|
+
{ preset: "screen", label: "Fit All to Screen", hint: "Keep the columns filling the table width, trimming the widest first" },
|
|
5802
5898
|
{ preset: "equal", label: "Equal Widths", hint: "Split the table width evenly across columns" }
|
|
5803
5899
|
];
|
|
5804
5900
|
function nextHeaderOrderBy(existing, field, dir, placement) {
|
|
@@ -5891,7 +5987,7 @@ function valueToString2(value) {
|
|
|
5891
5987
|
import { useState as useState21 } from "react";
|
|
5892
5988
|
import {
|
|
5893
5989
|
GROUP_KEY_INTERVALS,
|
|
5894
|
-
groupKeyField,
|
|
5990
|
+
groupKeyField as groupKeyField2,
|
|
5895
5991
|
isCalendarGroupKey,
|
|
5896
5992
|
MAX_GROUP_BY_FIELDS,
|
|
5897
5993
|
OP_LABEL as OP_LABEL4
|
|
@@ -8417,7 +8513,7 @@ function KeyIntervalControl({
|
|
|
8417
8513
|
label,
|
|
8418
8514
|
onChange
|
|
8419
8515
|
}) {
|
|
8420
|
-
const field =
|
|
8516
|
+
const field = groupKeyField2(value);
|
|
8421
8517
|
const interval = isCalendarGroupKey(value) ? value.interval : "";
|
|
8422
8518
|
return /* @__PURE__ */ jsxs23(
|
|
8423
8519
|
"span",
|
|
@@ -8508,7 +8604,7 @@ function GroupingClauses({
|
|
|
8508
8604
|
/* @__PURE__ */ jsxs23("div", { className: "qt-qb-row", children: [
|
|
8509
8605
|
/* @__PURE__ */ jsx27("div", { className: "qt-qb-section-header", children: /* @__PURE__ */ jsx27("span", { className: "qt-qb-kw", "data-mobile-label": "Group by", children: "group by" }) }),
|
|
8510
8606
|
groupKeys.map((groupKey, index) => {
|
|
8511
|
-
const key =
|
|
8607
|
+
const key = groupKeyField2(groupKey);
|
|
8512
8608
|
const label = model.sourceField(key)?.label ?? key;
|
|
8513
8609
|
return /* @__PURE__ */ jsxs23("span", { className: "qt-chip", children: [
|
|
8514
8610
|
/* @__PURE__ */ jsx27(
|
|
@@ -8669,7 +8765,7 @@ function useGroupingPresentation(api, enabled = true) {
|
|
|
8669
8765
|
"\xB7 GROUP BY",
|
|
8670
8766
|
" ",
|
|
8671
8767
|
/* @__PURE__ */ jsx27("strong", { children: groupKeys.map((key) => {
|
|
8672
|
-
const name =
|
|
8768
|
+
const name = groupKeyField2(key);
|
|
8673
8769
|
const label = model.sourceField(name)?.label ?? name;
|
|
8674
8770
|
return isCalendarGroupKey(key) ? `${label} (${key.interval})` : label;
|
|
8675
8771
|
}).join(" + ") }),
|
|
@@ -13907,10 +14003,11 @@ function SetFilterEditor({ api, field, returnFocus, classNames, mode: initialMod
|
|
|
13907
14003
|
useEffect20(() => {
|
|
13908
14004
|
if (strategy.source !== "autocomplete") return;
|
|
13909
14005
|
let cancelled = false;
|
|
14006
|
+
const ac = new AbortController();
|
|
13910
14007
|
setLoading(true);
|
|
13911
14008
|
setError("");
|
|
13912
14009
|
const timer = setTimeout(() => {
|
|
13913
|
-
api.filterValues(field.name, search).then((result) => {
|
|
14010
|
+
api.filterValues(field.name, search, ac.signal).then((result) => {
|
|
13914
14011
|
if (!cancelled) setSuggestions(result.values);
|
|
13915
14012
|
}).catch(() => {
|
|
13916
14013
|
if (!cancelled) setError("Could not load values. Try searching again.");
|
|
@@ -13921,6 +14018,7 @@ function SetFilterEditor({ api, field, returnFocus, classNames, mode: initialMod
|
|
|
13921
14018
|
return () => {
|
|
13922
14019
|
cancelled = true;
|
|
13923
14020
|
clearTimeout(timer);
|
|
14021
|
+
ac.abort();
|
|
13924
14022
|
};
|
|
13925
14023
|
}, [api.filterValues, field.name, strategy.source, search]);
|
|
13926
14024
|
function toggle(value) {
|
|
@@ -14134,8 +14232,9 @@ function FilterValueAutocomplete({ api, field, value, contextKey, className, wid
|
|
|
14134
14232
|
useEffect21(() => {
|
|
14135
14233
|
if (!open || loaded) return;
|
|
14136
14234
|
let cancelled = false;
|
|
14235
|
+
const ac = new AbortController();
|
|
14137
14236
|
const timer = setTimeout(() => {
|
|
14138
|
-
void Promise.resolve().then(() => fetchValues(field.name, value)).then(
|
|
14237
|
+
void Promise.resolve().then(() => fetchValues(field.name, value, ac.signal)).then(
|
|
14139
14238
|
(result) => {
|
|
14140
14239
|
if (cancelled) return;
|
|
14141
14240
|
setResponse({ key: requestKey, fetchValues, result });
|
|
@@ -14149,6 +14248,7 @@ function FilterValueAutocomplete({ api, field, value, contextKey, className, wid
|
|
|
14149
14248
|
return () => {
|
|
14150
14249
|
cancelled = true;
|
|
14151
14250
|
clearTimeout(timer);
|
|
14251
|
+
ac.abort();
|
|
14152
14252
|
};
|
|
14153
14253
|
}, [open, loaded, fetchValues, requestKey, scopeKey, field.name, value]);
|
|
14154
14254
|
useEffect21(() => {
|
|
@@ -14305,7 +14405,8 @@ function useFieldHasNull(api, fieldName) {
|
|
|
14305
14405
|
useEffect22(() => {
|
|
14306
14406
|
if (!fieldName) return;
|
|
14307
14407
|
let cancelled = false;
|
|
14308
|
-
|
|
14408
|
+
const ac = new AbortController();
|
|
14409
|
+
void api.filterValues(fieldName, "", ac.signal).then((r) => {
|
|
14309
14410
|
if (!cancelled && typeof r.hasNull === "boolean") {
|
|
14310
14411
|
setHasNull(r.hasNull);
|
|
14311
14412
|
}
|
|
@@ -14314,6 +14415,7 @@ function useFieldHasNull(api, fieldName) {
|
|
|
14314
14415
|
});
|
|
14315
14416
|
return () => {
|
|
14316
14417
|
cancelled = true;
|
|
14418
|
+
ac.abort();
|
|
14317
14419
|
};
|
|
14318
14420
|
}, [api.filterValues, fieldName]);
|
|
14319
14421
|
return hasNull;
|