@pythia-software/query-table-ui 0.9.5 → 0.9.6
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 +48 -0
- package/dist/index.js +236 -3
- package/dist/index.js.map +1 -1
- package/dist/theme.css +289 -117
- package/dist/theme.css.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -379,6 +379,54 @@ status indicators an accessible label on their wrapper.
|
|
|
379
379
|
|
|
380
380
|
## Mobile behavior
|
|
381
381
|
|
|
382
|
+
### Responsive target sizing
|
|
383
|
+
|
|
384
|
+
Set the public, unitless `--qt-touch-target-scale` CSS custom property to
|
|
385
|
+
configure the **added** touch-target size in the bundled theme:
|
|
386
|
+
|
|
387
|
+
| Value | Target sizing |
|
|
388
|
+
| --- | --- |
|
|
389
|
+
| `1` (default, including when unset) | Existing responsive enlargement |
|
|
390
|
+
| `0.5` | Half the added size |
|
|
391
|
+
| `0` | Desktop target sizing with the responsive layout |
|
|
392
|
+
|
|
393
|
+
```css
|
|
394
|
+
/* Scope to a host wrapper containing the builder, table and metrics. */
|
|
395
|
+
.pharos-query-table {
|
|
396
|
+
--qt-touch-target-scale: 0.5;
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
For each enlarged dimension, the theme uses
|
|
401
|
+
`desktop + (responsive - desktop) * scale`. A desktop 24px button that normally
|
|
402
|
+
grows to 44px therefore becomes 34px at `0.5`. Each control uses its own desktop
|
|
403
|
+
baseline, including its line box, padding and borders; collapse controls, chips,
|
|
404
|
+
picker rows and editor buttons have different baselines. Narrow and coarse
|
|
405
|
+
pointer rules use the final responsive size once, so matching both does not
|
|
406
|
+
compound the enlargement. Use a number from `0` to `1`, without a unit.
|
|
407
|
+
|
|
408
|
+
The setting covers query actions/chips, table cells/actions, picker and menu
|
|
409
|
+
rows, selection actions, saved-query controls, JSON controls, and select,
|
|
410
|
+
property and metric editor controls. Content remains a lower bound: wrapped
|
|
411
|
+
labels, icons, native controls and the retained mobile input fonts can require
|
|
412
|
+
more room than the scaled minimum. Existing responsive size reductions stay
|
|
413
|
+
compact. Layout widths, wrapping, sheets, scrolling, focus behavior, drag/touch
|
|
414
|
+
actions, breakpoints and input font sizes are independent of the factor.
|
|
415
|
+
|
|
416
|
+
Use `:root` for an application-wide setting, or a wrapper for independent
|
|
417
|
+
instances. Built-in portaled menus, pickers, dialogs and sheets inherit the
|
|
418
|
+
setting from their trigger/overlay anchor through the same theme propagation
|
|
419
|
+
as the palette, including live ancestor `class`, `style` and `data-theme`
|
|
420
|
+
changes. Keep standalone overlays beneath that wrapper in the React tree (and
|
|
421
|
+
their anchor within it). Host-owned portals and controls with custom styling
|
|
422
|
+
must propagate/apply their own sizing. `QueryBuilder`'s `compact` prop controls
|
|
423
|
+
clause presentation separately.
|
|
424
|
+
|
|
425
|
+
Run `npm run test:touch-targets` with `npm run demo` running to check relative
|
|
426
|
+
sizing, scoped portal updates and interactions in Chromium and WebKit.
|
|
427
|
+
|
|
428
|
+
### Responsive layout and interactions
|
|
429
|
+
|
|
382
430
|
The bundled theme keeps the table as a horizontally scrollable grid on narrow
|
|
383
431
|
screens so columns remain comparable. Narrow screens (up to 760px) and short,
|
|
384
432
|
touch-based landscape viewports use a mobile interaction model:
|
package/dist/index.js
CHANGED
|
@@ -8472,6 +8472,7 @@ import {
|
|
|
8472
8472
|
} from "react";
|
|
8473
8473
|
import {
|
|
8474
8474
|
MAX_AGGREGATIONS,
|
|
8475
|
+
MAX_METRIC_WINDOW_PERIODS,
|
|
8475
8476
|
METRIC_FUNCTIONS as METRIC_FUNCTIONS2,
|
|
8476
8477
|
aggOpsForField,
|
|
8477
8478
|
compileFormula,
|
|
@@ -9812,6 +9813,7 @@ function MetricCard({
|
|
|
9812
9813
|
buckets.length > clause.display.maxGroups ? " plus Other" : "",
|
|
9813
9814
|
"."
|
|
9814
9815
|
] }),
|
|
9816
|
+
(clause.window || clause.share) && /* @__PURE__ */ jsx31("p", { className: "qt-metric-note", children: seriesNote(clause) }),
|
|
9815
9817
|
!!result?.hiddenGroups && clause.minCount !== void 0 && /* @__PURE__ */ jsxs27("p", { className: "qt-metric-note", children: [
|
|
9816
9818
|
result.hiddenGroups.toLocaleString(),
|
|
9817
9819
|
" ",
|
|
@@ -9833,6 +9835,23 @@ function MetricCard({
|
|
|
9833
9835
|
}
|
|
9834
9836
|
);
|
|
9835
9837
|
}
|
|
9838
|
+
function seriesNote(clause) {
|
|
9839
|
+
const unit = clause.dateGrouping?.interval ?? "interval", window2 = clause.window;
|
|
9840
|
+
const parts = [];
|
|
9841
|
+
if (window2) {
|
|
9842
|
+
const span = window2.periods === "all" ? `from the first ${unit}` : `over the trailing ${window2.periods} ${unit}s`;
|
|
9843
|
+
parts.push(
|
|
9844
|
+
window2.reduce === "mean" ? `Mean per ${unit} ${span}` : `Total ${span}`
|
|
9845
|
+
);
|
|
9846
|
+
if (window2.periods !== "all" && window2.partial !== "include")
|
|
9847
|
+
parts.push("shorter windows at the start have no value");
|
|
9848
|
+
}
|
|
9849
|
+
if (clause.share)
|
|
9850
|
+
parts.push(
|
|
9851
|
+
clause.share === "group0" ? `${parts.length ? "as a share" : "Share"} of each ${clause.dateGrouping ? unit : "first group"}'s total` : `${parts.length ? "as a share" : "Share"} of the total`
|
|
9852
|
+
);
|
|
9853
|
+
return `${parts.join("; ")}.`;
|
|
9854
|
+
}
|
|
9836
9855
|
function BigNumber({
|
|
9837
9856
|
bucket,
|
|
9838
9857
|
fmt,
|
|
@@ -10508,12 +10527,26 @@ function MetricsEditor({
|
|
|
10508
10527
|
const index = name === void 0 ? -1 : groupBy.indexOf(name);
|
|
10509
10528
|
return index < 0 ? [] : [{ ...s, key: `group${index}` }];
|
|
10510
10529
|
});
|
|
10530
|
+
const dated = selected.dateGrouping?.field === groupBy[0];
|
|
10531
|
+
const share = groupBy.length >= (selected.share === "group0" ? 2 : 1) ? selected.share : void 0;
|
|
10511
10532
|
patch(selected.id, {
|
|
10512
10533
|
groupBy,
|
|
10513
10534
|
...sort ? { sort } : {},
|
|
10514
|
-
dateGrouping:
|
|
10535
|
+
dateGrouping: dated ? selected.dateGrouping : void 0,
|
|
10536
|
+
window: dated ? selected.window : void 0,
|
|
10537
|
+
share,
|
|
10538
|
+
...shareDisplay(selected.share, share)
|
|
10515
10539
|
});
|
|
10516
10540
|
}
|
|
10541
|
+
function shareDisplay(previous, next) {
|
|
10542
|
+
if (next && !previous && !chosen.format)
|
|
10543
|
+
return { display: { ...chosen, format: SHARE_FORMAT } };
|
|
10544
|
+
if (!next && previous && sameFormat(chosen.format, SHARE_FORMAT)) {
|
|
10545
|
+
const { format: _format, ...display2 } = chosen;
|
|
10546
|
+
return { display: display2 };
|
|
10547
|
+
}
|
|
10548
|
+
return {};
|
|
10549
|
+
}
|
|
10517
10550
|
function changeSize(key, raw) {
|
|
10518
10551
|
if (!selected || !Number.isFinite(raw)) return;
|
|
10519
10552
|
const layout = {
|
|
@@ -10526,7 +10559,12 @@ function MetricsEditor({
|
|
|
10526
10559
|
}
|
|
10527
10560
|
function changeDisplay(kind) {
|
|
10528
10561
|
if (!selected) return;
|
|
10529
|
-
const
|
|
10562
|
+
const single = !["box", "histogram", "scatter"].includes(kind);
|
|
10563
|
+
const display2 = !single && shareDisplay(selected.share, void 0).display || chosen;
|
|
10564
|
+
const next = {
|
|
10565
|
+
display: { ...display2, kind },
|
|
10566
|
+
...single ? {} : { window: void 0, share: void 0 }
|
|
10567
|
+
};
|
|
10530
10568
|
setFormatTarget(kind === "scatter" || kind === "histogram" ? "x" : "value");
|
|
10531
10569
|
if (selected.sort)
|
|
10532
10570
|
next.sort = selected.sort.filter(
|
|
@@ -11507,7 +11545,10 @@ function MetricsEditor({
|
|
|
11507
11545
|
value: selected.dateGrouping?.interval ?? "exact",
|
|
11508
11546
|
onChange: (e) => {
|
|
11509
11547
|
if (e.target.value === "exact") {
|
|
11510
|
-
patch(selected.id, {
|
|
11548
|
+
patch(selected.id, {
|
|
11549
|
+
dateGrouping: void 0,
|
|
11550
|
+
window: void 0
|
|
11551
|
+
});
|
|
11511
11552
|
return;
|
|
11512
11553
|
}
|
|
11513
11554
|
const interval = e.target.value;
|
|
@@ -11642,6 +11683,25 @@ function MetricsEditor({
|
|
|
11642
11683
|
] }),
|
|
11643
11684
|
/* @__PURE__ */ jsx33("p", { className: "qt-metric-help", children: "Direct counts and sums use 0 for empty dates. Averages, ratios, and other expressions have no value." })
|
|
11644
11685
|
] }),
|
|
11686
|
+
/* @__PURE__ */ jsxs28("label", { className: "qt-metric-date-toggle", children: [
|
|
11687
|
+
/* @__PURE__ */ jsx33(
|
|
11688
|
+
"input",
|
|
11689
|
+
{
|
|
11690
|
+
type: "checkbox",
|
|
11691
|
+
"aria-label": "Whole intervals only",
|
|
11692
|
+
disabled: !selected.dateGrouping,
|
|
11693
|
+
checked: selected.dateGrouping?.partialIntervals === "exclude",
|
|
11694
|
+
onChange: (e) => {
|
|
11695
|
+
const { partialIntervals: _partial, ...rest } = selected.dateGrouping;
|
|
11696
|
+
patch(selected.id, {
|
|
11697
|
+
dateGrouping: e.target.checked ? { ...rest, partialIntervals: "exclude" } : rest
|
|
11698
|
+
});
|
|
11699
|
+
}
|
|
11700
|
+
}
|
|
11701
|
+
),
|
|
11702
|
+
"Whole intervals only"
|
|
11703
|
+
] }),
|
|
11704
|
+
/* @__PURE__ */ jsx33("p", { className: "qt-metric-help", children: "Leave out intervals the date filters cut short and the interval still in progress." }),
|
|
11645
11705
|
/* @__PURE__ */ jsx33("p", { className: "qt-metric-help", children: "Calendar boundaries and custom dates use the grouping timezone. Weeks begin Monday." })
|
|
11646
11706
|
] }),
|
|
11647
11707
|
/* @__PURE__ */ jsxs28(
|
|
@@ -11664,6 +11724,18 @@ function MetricsEditor({
|
|
|
11664
11724
|
]
|
|
11665
11725
|
}
|
|
11666
11726
|
),
|
|
11727
|
+
selected.groupBy.length > 0 && !["box", "histogram", "scatter"].includes(chosen.kind) && /* @__PURE__ */ jsx33(
|
|
11728
|
+
MetricSeriesSettings,
|
|
11729
|
+
{
|
|
11730
|
+
clause: selected,
|
|
11731
|
+
fields,
|
|
11732
|
+
settings: settingsState("Window & share"),
|
|
11733
|
+
onChange: (values) => patch(selected.id, {
|
|
11734
|
+
...values,
|
|
11735
|
+
..."share" in values ? shareDisplay(selected.share, values.share) : {}
|
|
11736
|
+
})
|
|
11737
|
+
}
|
|
11738
|
+
),
|
|
11667
11739
|
/* @__PURE__ */ jsxs28(
|
|
11668
11740
|
Settings,
|
|
11669
11741
|
{
|
|
@@ -12567,6 +12639,167 @@ function Settings({
|
|
|
12567
12639
|
}
|
|
12568
12640
|
);
|
|
12569
12641
|
}
|
|
12642
|
+
var SHARE_FORMAT = { kind: "percent", decimals: 1 };
|
|
12643
|
+
var sameFormat = (a, b) => !!a && JSON.stringify(a) === JSON.stringify(b);
|
|
12644
|
+
var windowOf = (periods, mean, partial) => ({
|
|
12645
|
+
periods,
|
|
12646
|
+
...mean ? { reduce: "mean" } : {},
|
|
12647
|
+
...partial ? { partial: "include" } : {}
|
|
12648
|
+
});
|
|
12649
|
+
function MetricSeriesSettings({
|
|
12650
|
+
clause,
|
|
12651
|
+
fields,
|
|
12652
|
+
settings,
|
|
12653
|
+
onChange
|
|
12654
|
+
}) {
|
|
12655
|
+
const { window: window2, share, dateGrouping } = clause;
|
|
12656
|
+
const first = fields.find((f) => f.name === clause.groupBy[0])?.label ?? clause.groupBy[0];
|
|
12657
|
+
const unit = dateGrouping?.interval ?? "interval";
|
|
12658
|
+
const mode = !window2 ? "none" : window2.periods === "all" ? "all" : "trailing";
|
|
12659
|
+
const setWindow = (next) => onChange({
|
|
12660
|
+
window: next,
|
|
12661
|
+
...next && dateGrouping && !dateGrouping.includeEmpty ? { dateGrouping: { ...dateGrouping, includeEmpty: true } } : {},
|
|
12662
|
+
// A share of the total cannot be windowed, and its option is hidden.
|
|
12663
|
+
...next && share === "total" ? { share: void 0 } : {}
|
|
12664
|
+
});
|
|
12665
|
+
const summary = [
|
|
12666
|
+
!window2 ? "" : window2.periods === "all" ? "Running" : `Trailing ${window2.periods} ${unit}s`,
|
|
12667
|
+
window2?.reduce === "mean" ? `mean per ${unit}` : "",
|
|
12668
|
+
share === "group0" ? `share of each ${dateGrouping ? unit : first}` : share === "total" ? "share of total" : ""
|
|
12669
|
+
].filter(Boolean).join(" \xB7 ");
|
|
12670
|
+
return /* @__PURE__ */ jsxs28(
|
|
12671
|
+
Settings,
|
|
12672
|
+
{
|
|
12673
|
+
title: "Window & share",
|
|
12674
|
+
...settings,
|
|
12675
|
+
summary: summary || "Values as computed",
|
|
12676
|
+
children: [
|
|
12677
|
+
dateGrouping && /* @__PURE__ */ jsxs28(Fragment20, { children: [
|
|
12678
|
+
/* @__PURE__ */ jsxs28("label", { children: [
|
|
12679
|
+
"Window",
|
|
12680
|
+
/* @__PURE__ */ jsxs28(
|
|
12681
|
+
"select",
|
|
12682
|
+
{
|
|
12683
|
+
"aria-label": "Window",
|
|
12684
|
+
value: mode,
|
|
12685
|
+
onChange: (e) => setWindow(
|
|
12686
|
+
e.target.value === "none" ? void 0 : windowOf(
|
|
12687
|
+
e.target.value === "all" ? "all" : window2 && window2.periods !== "all" ? window2.periods : 7,
|
|
12688
|
+
window2?.reduce === "mean",
|
|
12689
|
+
// A running value has no partial windows.
|
|
12690
|
+
e.target.value !== "all" && window2?.partial === "include"
|
|
12691
|
+
)
|
|
12692
|
+
),
|
|
12693
|
+
children: [
|
|
12694
|
+
/* @__PURE__ */ jsx33("option", { value: "none", children: "None" }),
|
|
12695
|
+
/* @__PURE__ */ jsxs28("option", { value: "trailing", children: [
|
|
12696
|
+
"Trailing ",
|
|
12697
|
+
unit,
|
|
12698
|
+
"s"
|
|
12699
|
+
] }),
|
|
12700
|
+
/* @__PURE__ */ jsxs28("option", { value: "all", children: [
|
|
12701
|
+
"Running from the first ",
|
|
12702
|
+
unit
|
|
12703
|
+
] })
|
|
12704
|
+
]
|
|
12705
|
+
}
|
|
12706
|
+
)
|
|
12707
|
+
] }),
|
|
12708
|
+
window2 && window2.periods !== "all" && /* @__PURE__ */ jsxs28("label", { children: [
|
|
12709
|
+
`${unit[0].toUpperCase()}${unit.slice(1)}s per window`,
|
|
12710
|
+
/* @__PURE__ */ jsx33(
|
|
12711
|
+
"input",
|
|
12712
|
+
{
|
|
12713
|
+
"aria-label": "Window length",
|
|
12714
|
+
type: "number",
|
|
12715
|
+
min: 2,
|
|
12716
|
+
max: MAX_METRIC_WINDOW_PERIODS,
|
|
12717
|
+
value: window2.periods,
|
|
12718
|
+
onChange: (e) => {
|
|
12719
|
+
const n = e.currentTarget.valueAsNumber;
|
|
12720
|
+
if (Number.isInteger(n) && n >= 2 && n <= MAX_METRIC_WINDOW_PERIODS)
|
|
12721
|
+
setWindow({ ...window2, periods: n });
|
|
12722
|
+
}
|
|
12723
|
+
}
|
|
12724
|
+
)
|
|
12725
|
+
] }),
|
|
12726
|
+
window2 && /* @__PURE__ */ jsxs28(Fragment20, { children: [
|
|
12727
|
+
/* @__PURE__ */ jsxs28("label", { children: [
|
|
12728
|
+
"Combine",
|
|
12729
|
+
/* @__PURE__ */ jsxs28(
|
|
12730
|
+
"select",
|
|
12731
|
+
{
|
|
12732
|
+
"aria-label": "Combine window",
|
|
12733
|
+
value: window2.reduce ?? "total",
|
|
12734
|
+
onChange: (e) => setWindow(
|
|
12735
|
+
windowOf(
|
|
12736
|
+
window2.periods,
|
|
12737
|
+
e.target.value === "mean",
|
|
12738
|
+
window2.partial === "include"
|
|
12739
|
+
)
|
|
12740
|
+
),
|
|
12741
|
+
children: [
|
|
12742
|
+
/* @__PURE__ */ jsx33("option", { value: "total", children: "Total over the window" }),
|
|
12743
|
+
/* @__PURE__ */ jsxs28("option", { value: "mean", children: [
|
|
12744
|
+
"Mean per ",
|
|
12745
|
+
unit
|
|
12746
|
+
] })
|
|
12747
|
+
]
|
|
12748
|
+
}
|
|
12749
|
+
)
|
|
12750
|
+
] }),
|
|
12751
|
+
window2.periods !== "all" && /* @__PURE__ */ jsxs28("label", { className: "qt-metric-date-toggle", children: [
|
|
12752
|
+
/* @__PURE__ */ jsx33(
|
|
12753
|
+
"input",
|
|
12754
|
+
{
|
|
12755
|
+
type: "checkbox",
|
|
12756
|
+
"aria-label": "Show partial windows",
|
|
12757
|
+
checked: window2.partial === "include",
|
|
12758
|
+
onChange: (e) => setWindow(
|
|
12759
|
+
windowOf(
|
|
12760
|
+
window2.periods,
|
|
12761
|
+
window2.reduce === "mean",
|
|
12762
|
+
e.target.checked
|
|
12763
|
+
)
|
|
12764
|
+
)
|
|
12765
|
+
}
|
|
12766
|
+
),
|
|
12767
|
+
"Show windows that start before the range"
|
|
12768
|
+
] }),
|
|
12769
|
+
/* @__PURE__ */ jsxs28("p", { className: "qt-metric-help", children: [
|
|
12770
|
+
"A total evaluates the metric over every row in the window, so ratios and averages pool their parts. A mean per ",
|
|
12771
|
+
unit,
|
|
12772
|
+
" ",
|
|
12773
|
+
"averages a single sum, count or distinct count."
|
|
12774
|
+
] })
|
|
12775
|
+
] })
|
|
12776
|
+
] }),
|
|
12777
|
+
/* @__PURE__ */ jsxs28("label", { children: [
|
|
12778
|
+
"Show values as",
|
|
12779
|
+
/* @__PURE__ */ jsxs28(
|
|
12780
|
+
"select",
|
|
12781
|
+
{
|
|
12782
|
+
"aria-label": "Show values as",
|
|
12783
|
+
value: share ?? "values",
|
|
12784
|
+
onChange: (e) => onChange({
|
|
12785
|
+
share: e.target.value === "values" ? void 0 : e.target.value
|
|
12786
|
+
}),
|
|
12787
|
+
children: [
|
|
12788
|
+
/* @__PURE__ */ jsx33("option", { value: "values", children: "Values" }),
|
|
12789
|
+
clause.groupBy.length > 1 && /* @__PURE__ */ jsxs28("option", { value: "group0", children: [
|
|
12790
|
+
"Share of each ",
|
|
12791
|
+
dateGrouping ? unit : first
|
|
12792
|
+
] }),
|
|
12793
|
+
!window2 && /* @__PURE__ */ jsx33("option", { value: "total", children: "Share of all groups" })
|
|
12794
|
+
]
|
|
12795
|
+
}
|
|
12796
|
+
)
|
|
12797
|
+
] }),
|
|
12798
|
+
share && /* @__PURE__ */ jsx33("p", { className: "qt-metric-help", children: "Divides each group by a total of the same sum or count, including groups past the group limit." })
|
|
12799
|
+
]
|
|
12800
|
+
}
|
|
12801
|
+
);
|
|
12802
|
+
}
|
|
12570
12803
|
function MetricFormatControls({
|
|
12571
12804
|
format,
|
|
12572
12805
|
onChange,
|