matterviz 0.3.0 → 0.3.2
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/dist/FilePicker.svelte +37 -20
- package/dist/Icon.svelte +2 -2
- package/dist/MillerIndexInput.svelte +60 -0
- package/dist/MillerIndexInput.svelte.d.ts +7 -0
- package/dist/app.css +38 -2
- package/dist/brillouin/BrillouinZone.svelte +20 -62
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneExportPane.svelte +12 -20
- package/dist/brillouin/BrillouinZoneScene.svelte +2 -2
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/chempot-diagram/ChemPotDiagram.svelte +192 -0
- package/dist/chempot-diagram/ChemPotDiagram.svelte.d.ts +13 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte +677 -0
- package/dist/chempot-diagram/ChemPotDiagram2D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte +2688 -0
- package/dist/chempot-diagram/ChemPotDiagram3D.svelte.d.ts +16 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte +8 -0
- package/dist/chempot-diagram/ChemPotScene3D.svelte.d.ts +7 -0
- package/dist/chempot-diagram/color.d.ts +10 -0
- package/dist/chempot-diagram/color.js +33 -0
- package/dist/chempot-diagram/compute.d.ts +38 -0
- package/dist/chempot-diagram/compute.js +650 -0
- package/dist/chempot-diagram/index.d.ts +5 -0
- package/dist/chempot-diagram/index.js +5 -0
- package/dist/chempot-diagram/pointer.d.ts +16 -0
- package/dist/chempot-diagram/pointer.js +40 -0
- package/dist/chempot-diagram/temperature.d.ts +15 -0
- package/dist/chempot-diagram/temperature.js +37 -0
- package/dist/chempot-diagram/types.d.ts +83 -0
- package/dist/chempot-diagram/types.js +27 -0
- package/dist/colors/index.d.ts +3 -1
- package/dist/colors/index.js +4 -0
- package/dist/composition/BarChart.svelte +13 -22
- package/dist/composition/BubbleChart.svelte +5 -3
- package/dist/composition/FormulaFilter.svelte +770 -90
- package/dist/composition/FormulaFilter.svelte.d.ts +37 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/convex-hull/ConvexHull.svelte +14 -1
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +14 -45
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +396 -134
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +93 -42
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte +94 -31
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +4 -2
- package/dist/convex-hull/ConvexHullStats.svelte +697 -128
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/GasPressureControls.svelte +72 -38
- package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
- package/dist/convex-hull/TemperatureSlider.svelte +46 -19
- package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/gas-thermodynamics.js +16 -5
- package/dist/convex-hull/helpers.d.ts +7 -1
- package/dist/convex-hull/helpers.js +45 -15
- package/dist/convex-hull/index.d.ts +15 -1
- package/dist/convex-hull/index.js +1 -0
- package/dist/convex-hull/thermodynamics.d.ts +8 -21
- package/dist/convex-hull/thermodynamics.js +106 -17
- package/dist/convex-hull/types.d.ts +7 -0
- package/dist/convex-hull/types.js +11 -0
- package/dist/coordination/CoordinationBarPlot.svelte +29 -46
- package/dist/element/BohrAtom.svelte +1 -1
- package/dist/element/data.js +2 -14
- package/dist/element/data.json.gz +0 -0
- package/dist/element/index.d.ts +1 -1
- package/dist/element/index.js +1 -0
- package/dist/element/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +21 -65
- package/dist/fermi-surface/FermiSurface.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceControls.svelte.d.ts +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte +1 -1
- package/dist/fermi-surface/FermiSurfaceScene.svelte.d.ts +1 -1
- package/dist/fermi-surface/compute.js +1 -21
- package/dist/fermi-surface/marching-cubes.d.ts +2 -13
- package/dist/fermi-surface/marching-cubes.js +2 -519
- package/dist/fermi-surface/parse.js +17 -23
- package/dist/heatmap-matrix/HeatmapMatrix.svelte +1273 -0
- package/dist/heatmap-matrix/HeatmapMatrix.svelte.d.ts +110 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte +171 -0
- package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +31 -0
- package/dist/heatmap-matrix/index.d.ts +53 -0
- package/dist/heatmap-matrix/index.js +100 -0
- package/dist/heatmap-matrix/shared.d.ts +2 -0
- package/dist/heatmap-matrix/shared.js +4 -0
- package/dist/icons.d.ts +119 -0
- package/dist/icons.js +119 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +6 -1
- package/dist/io/export.js +15 -3
- package/dist/io/file-drop.d.ts +7 -0
- package/dist/io/file-drop.js +43 -0
- package/dist/io/index.d.ts +2 -2
- package/dist/io/index.js +2 -112
- package/dist/io/types.d.ts +1 -0
- package/dist/io/url-drop.d.ts +2 -0
- package/dist/io/url-drop.js +118 -0
- package/dist/isosurface/Isosurface.svelte +231 -0
- package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
- package/dist/isosurface/IsosurfaceControls.svelte +273 -0
- package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
- package/dist/isosurface/index.d.ts +5 -0
- package/dist/isosurface/index.js +6 -0
- package/dist/isosurface/parse.d.ts +6 -0
- package/dist/isosurface/parse.js +548 -0
- package/dist/isosurface/slice.d.ts +11 -0
- package/dist/isosurface/slice.js +145 -0
- package/dist/isosurface/types.d.ts +55 -0
- package/dist/isosurface/types.js +178 -0
- package/dist/labels.d.ts +2 -1
- package/dist/labels.js +1 -0
- package/dist/layout/InfoTag.svelte +62 -62
- package/dist/layout/SubpageGrid.svelte +74 -0
- package/dist/layout/SubpageGrid.svelte.d.ts +14 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.js +1 -0
- package/dist/layout/json-tree/JsonNode.svelte +226 -53
- package/dist/layout/json-tree/JsonTree.svelte +425 -51
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +218 -97
- package/dist/layout/json-tree/types.d.ts +27 -2
- package/dist/layout/json-tree/utils.d.ts +14 -1
- package/dist/layout/json-tree/utils.js +254 -0
- package/dist/marching-cubes.d.ts +14 -0
- package/dist/marching-cubes.js +519 -0
- package/dist/math.d.ts +8 -0
- package/dist/math.js +374 -7
- package/dist/overlays/ContextMenu.svelte +3 -2
- package/dist/overlays/DraggablePane.svelte +163 -58
- package/dist/overlays/DraggablePane.svelte.d.ts +2 -0
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +232 -77
- package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte.d.ts +6 -2
- package/dist/phase-diagram/PhaseDiagramControls.svelte +32 -11
- package/dist/phase-diagram/PhaseDiagramControls.svelte.d.ts +3 -2
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte +103 -0
- package/dist/phase-diagram/PhaseDiagramEditorPane.svelte.d.ts +15 -0
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte +102 -95
- package/dist/phase-diagram/PhaseDiagramExportPane.svelte.d.ts +7 -0
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte +100 -26
- package/dist/phase-diagram/PhaseDiagramTooltip.svelte.d.ts +6 -3
- package/dist/phase-diagram/index.d.ts +2 -0
- package/dist/phase-diagram/index.js +2 -0
- package/dist/phase-diagram/svg-to-diagram.d.ts +2 -0
- package/dist/phase-diagram/svg-to-diagram.js +865 -0
- package/dist/phase-diagram/types.d.ts +10 -0
- package/dist/phase-diagram/utils.d.ts +7 -4
- package/dist/phase-diagram/utils.js +149 -59
- package/dist/plot/AxisLabel.svelte +26 -0
- package/dist/plot/AxisLabel.svelte.d.ts +16 -0
- package/dist/plot/BarPlot.svelte +473 -228
- package/dist/plot/BarPlot.svelte.d.ts +3 -3
- package/dist/plot/BarPlotControls.svelte +3 -2
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ColorBar.svelte +54 -54
- package/dist/plot/ColorBar.svelte.d.ts +1 -1
- package/dist/plot/ElementScatter.svelte +4 -3
- package/dist/plot/FillArea.svelte +4 -1
- package/dist/plot/Histogram.svelte +320 -230
- package/dist/plot/Histogram.svelte.d.ts +2 -2
- package/dist/plot/HistogramControls.svelte +29 -10
- package/dist/plot/HistogramControls.svelte.d.ts +6 -2
- package/dist/plot/InteractiveAxisLabel.svelte.d.ts +2 -2
- package/dist/plot/PlotControls.svelte +109 -27
- package/dist/plot/PlotControls.svelte.d.ts +1 -1
- package/dist/plot/PlotLegend.svelte +1 -1
- package/dist/plot/PortalSelect.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte +2 -1
- package/dist/plot/ReferenceLine.svelte.d.ts +1 -0
- package/dist/plot/ReferencePlane.svelte +1 -3
- package/dist/plot/ScatterPlot.svelte +343 -209
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -3
- package/dist/plot/ScatterPlot3D.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlot3DControls.svelte +203 -250
- package/dist/plot/ScatterPlot3DScene.svelte +4 -7
- package/dist/plot/ScatterPlot3DScene.svelte.d.ts +2 -2
- package/dist/plot/ScatterPlotControls.svelte +95 -55
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -1
- package/dist/plot/ZeroLines.svelte +44 -0
- package/dist/plot/ZeroLines.svelte.d.ts +32 -0
- package/dist/plot/ZoomRect.svelte +21 -0
- package/dist/plot/ZoomRect.svelte.d.ts +8 -0
- package/dist/plot/axis-utils.d.ts +1 -1
- package/dist/plot/data-cleaning.js +1 -5
- package/dist/plot/index.d.ts +6 -2
- package/dist/plot/index.js +6 -2
- package/dist/plot/interactions.d.ts +8 -10
- package/dist/plot/interactions.js +10 -19
- package/dist/plot/layout.d.ts +7 -1
- package/dist/plot/layout.js +12 -4
- package/dist/plot/reference-line.d.ts +4 -21
- package/dist/plot/reference-line.js +7 -81
- package/dist/plot/types.d.ts +42 -17
- package/dist/plot/types.js +10 -0
- package/dist/plot/utils/label-placement.js +14 -11
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/rdf/index.d.ts +1 -1
- package/dist/rdf/index.js +1 -1
- package/dist/settings.d.ts +5 -0
- package/dist/settings.js +37 -3
- package/dist/spectral/Bands.svelte +515 -143
- package/dist/spectral/Bands.svelte.d.ts +22 -2
- package/dist/spectral/helpers.d.ts +23 -1
- package/dist/spectral/helpers.js +65 -9
- package/dist/spectral/types.d.ts +2 -0
- package/dist/structure/AtomLegend.svelte +31 -10
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Lattice.svelte +2 -0
- package/dist/structure/Structure.svelte +716 -173
- package/dist/structure/Structure.svelte.d.ts +7 -2
- package/dist/structure/StructureControls.svelte +26 -14
- package/dist/structure/StructureControls.svelte.d.ts +5 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +386 -95
- package/dist/structure/StructureScene.svelte.d.ts +15 -4
- package/dist/structure/atom-properties.d.ts +6 -2
- package/dist/structure/atom-properties.js +38 -25
- package/dist/structure/export.js +10 -7
- package/dist/structure/ferrox-wasm-types.d.ts +3 -2
- package/dist/structure/ferrox-wasm-types.js +0 -3
- package/dist/structure/ferrox-wasm.d.ts +3 -2
- package/dist/structure/ferrox-wasm.js +1 -2
- package/dist/structure/index.d.ts +7 -0
- package/dist/structure/index.js +22 -0
- package/dist/structure/parse.js +19 -16
- package/dist/structure/partial-occupancy.d.ts +25 -0
- package/dist/structure/partial-occupancy.js +102 -0
- package/dist/structure/validation.js +6 -3
- package/dist/symmetry/SymmetryStats.svelte +18 -4
- package/dist/symmetry/WyckoffTable.svelte +18 -10
- package/dist/symmetry/index.d.ts +7 -4
- package/dist/symmetry/index.js +83 -18
- package/dist/table/HeatmapTable.svelte +468 -69
- package/dist/table/HeatmapTable.svelte.d.ts +13 -1
- package/dist/table/ToggleMenu.svelte +291 -44
- package/dist/table/ToggleMenu.svelte.d.ts +4 -1
- package/dist/table/index.d.ts +3 -0
- package/dist/tooltip/index.d.ts +1 -1
- package/dist/tooltip/index.js +1 -0
- package/dist/trajectory/Trajectory.svelte +147 -145
- package/dist/trajectory/TrajectoryExportPane.svelte +13 -9
- package/dist/trajectory/TrajectoryExportPane.svelte.d.ts +1 -1
- package/dist/trajectory/constants.d.ts +6 -0
- package/dist/trajectory/constants.js +7 -0
- package/dist/trajectory/extract.js +3 -5
- package/dist/trajectory/format-detect.d.ts +9 -0
- package/dist/trajectory/format-detect.js +76 -0
- package/dist/trajectory/frame-reader.d.ts +17 -0
- package/dist/trajectory/frame-reader.js +339 -0
- package/dist/trajectory/helpers.d.ts +15 -0
- package/dist/trajectory/helpers.js +187 -0
- package/dist/trajectory/index.d.ts +1 -0
- package/dist/trajectory/index.js +11 -4
- package/dist/trajectory/parse/ase.d.ts +2 -0
- package/dist/trajectory/parse/ase.js +76 -0
- package/dist/trajectory/parse/hdf5.d.ts +2 -0
- package/dist/trajectory/parse/hdf5.js +121 -0
- package/dist/trajectory/parse/index.d.ts +12 -0
- package/dist/trajectory/parse/index.js +304 -0
- package/dist/trajectory/parse/lammps.d.ts +5 -0
- package/dist/trajectory/parse/lammps.js +169 -0
- package/dist/trajectory/parse/vasp.d.ts +2 -0
- package/dist/trajectory/parse/vasp.js +65 -0
- package/dist/trajectory/parse/xyz.d.ts +2 -0
- package/dist/trajectory/parse/xyz.js +109 -0
- package/dist/trajectory/types.d.ts +11 -0
- package/dist/trajectory/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/dist/xrd/XrdPlot.svelte +6 -4
- package/dist/xrd/calc-xrd.js +0 -1
- package/package.json +33 -23
- package/readme.md +4 -4
- package/dist/trajectory/parse.d.ts +0 -42
- package/dist/trajectory/parse.js +0 -1267
- /package/dist/element/{data.json.d.ts → data.json.gz.d.ts} +0 -0
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
<script lang="ts">import
|
|
1
|
+
<script lang="ts">import { luminance, watch_dark_mode } from '../colors';
|
|
2
|
+
import Icon from '../Icon.svelte';
|
|
2
3
|
import { format_num } from '../labels';
|
|
4
|
+
import { SettingsSection } from '../layout';
|
|
5
|
+
import ContextMenu from '../overlays/ContextMenu.svelte';
|
|
6
|
+
import DraggablePane from '../overlays/DraggablePane.svelte';
|
|
3
7
|
import { calc_cell_color, strip_html } from './';
|
|
8
|
+
import { normalize_unicode_minus } from '../utils';
|
|
4
9
|
import { tooltip } from 'svelte-multiselect/attachments';
|
|
5
10
|
import { flip } from 'svelte/animate';
|
|
6
11
|
import { SvelteMap } from 'svelte/reactivity';
|
|
7
12
|
let { data = $bindable([]), columns = [], sort_hint = undefined, cell, special_cells, controls, initial_sort = undefined, sort = $bindable({ column: ``, dir: `asc` }), // allows external control/sync of sorting
|
|
8
|
-
fixed_header = false, default_num_format = `.3`, show_heatmap = $bindable(true), heatmap_class = `heatmap`, onrowdblclick, column_order = $bindable([]), export_data = false, show_column_toggle = false, search = false, show_row_select = false, pagination = false, selected_rows = $bindable([]), hidden_columns = $bindable([]), scroll_style, onsort = undefined, onsorterror = undefined, loading = $bindable(false), sort_data = true, ...rest } = $props();
|
|
13
|
+
fixed_header = false, default_num_format = `.3`, show_heatmap = $bindable(true), heatmap_class = `heatmap`, onrowclick, onrowdblclick, column_order = $bindable([]), export_data = false, show_column_toggle = false, search = false, show_row_select = false, pagination = false, selected_rows = $bindable([]), hidden_columns = $bindable([]), scroll_style, root_style, onsort = undefined, onsorterror = undefined, loading = $bindable(false), sort_data = true, heatmap_opacity = $bindable(1), empty_message = `No data`, show_row_numbers = false, allow_better_toggle = false, show_controls = $bindable(false), controls_open = $bindable(false), header_cell, footer, ...rest } = $props();
|
|
14
|
+
let container_el = $state();
|
|
15
|
+
// Read --page-bg from computed style for text contrast calculation.
|
|
16
|
+
// Recalculates on mount and when the theme changes (dark/light mode toggle).
|
|
17
|
+
let page_bg_lum = $state(luminance(`white`));
|
|
18
|
+
$effect(() => {
|
|
19
|
+
if (!container_el)
|
|
20
|
+
return;
|
|
21
|
+
const read_page_bg = () => {
|
|
22
|
+
const page_bg = getComputedStyle(container_el).getPropertyValue(`--page-bg`)
|
|
23
|
+
.trim();
|
|
24
|
+
page_bg_lum = luminance(page_bg || `white`);
|
|
25
|
+
};
|
|
26
|
+
read_page_bg();
|
|
27
|
+
return watch_dark_mode(read_page_bg);
|
|
28
|
+
});
|
|
9
29
|
// Detect HTML to prevent setting raw HTML as data-sort-value. Simple string matching
|
|
10
30
|
// suffices since false positives just skip setting the attr (sorting still works by inner data-sort-value).
|
|
11
31
|
function is_html_str(val) {
|
|
@@ -27,6 +47,8 @@ let initial_sort_config = $derived(initial_sort
|
|
|
27
47
|
let pagination_config = $derived(pagination
|
|
28
48
|
? { page_size: 25, ...(typeof pagination === `object` ? pagination : {}) }
|
|
29
49
|
: null);
|
|
50
|
+
// Mutable page size — writable $derived allows user to change via dropdown
|
|
51
|
+
let effective_page_size = $derived(pagination_config?.page_size ?? 25);
|
|
30
52
|
// Normalize search config
|
|
31
53
|
let search_config = $derived(search
|
|
32
54
|
? {
|
|
@@ -61,11 +83,41 @@ let current_page = $state(1);
|
|
|
61
83
|
// Dropdown states
|
|
62
84
|
let show_column_dropdown = $state(false);
|
|
63
85
|
let show_export_dropdown = $state(false);
|
|
86
|
+
// Per-column gradient direction overrides (user-toggled via header)
|
|
87
|
+
let better_overrides = new SvelteMap();
|
|
88
|
+
// Per-column color scale overrides
|
|
89
|
+
let color_scale_overrides = new SvelteMap();
|
|
90
|
+
const color_scale_options = [
|
|
91
|
+
`interpolateViridis`,
|
|
92
|
+
`interpolatePlasma`,
|
|
93
|
+
`interpolateInferno`,
|
|
94
|
+
`interpolateCividis`,
|
|
95
|
+
`interpolateTurbo`,
|
|
96
|
+
`interpolateBlues`,
|
|
97
|
+
`interpolateGreens`,
|
|
98
|
+
`interpolateReds`,
|
|
99
|
+
`interpolateYlOrRd`,
|
|
100
|
+
];
|
|
101
|
+
// Columns that have a color gradient
|
|
102
|
+
let colored_columns = $derived(columns.filter((col) => col.color_scale !== null && col.color_scale !== undefined));
|
|
64
103
|
// Column resize state
|
|
65
104
|
let resize_col_id = $state(null);
|
|
66
105
|
let resize_start_x = $state(0);
|
|
67
106
|
let resize_start_width = $state(0);
|
|
68
107
|
let column_widths = $state({});
|
|
108
|
+
// Auto-discover columns from data keys when none are provided
|
|
109
|
+
$effect.pre(() => {
|
|
110
|
+
if (columns.length > 0 || data.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
const seen = {};
|
|
113
|
+
for (const row of data.slice(0, 50)) {
|
|
114
|
+
for (const key of Object.keys(row)) {
|
|
115
|
+
if (key !== `style` && key !== `class`)
|
|
116
|
+
seen[key] = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
columns = Object.keys(seen).map((key) => ({ label: key }));
|
|
120
|
+
});
|
|
69
121
|
// Helper to make column IDs (needed since column labels in different groups can be repeated)
|
|
70
122
|
const get_col_id = (col) => col.group ? `${col.key ?? col.label} (${col.group})` : (col.key ?? col.label);
|
|
71
123
|
// Sync column_order with columns: initialize if empty, remove stale IDs, append new IDs
|
|
@@ -97,13 +149,19 @@ let ordered_columns = $derived.by(() => {
|
|
|
97
149
|
// Add columns in specified order, then any remaining columns that weren't in the order list
|
|
98
150
|
const ordered = column_order
|
|
99
151
|
.map((id) => col_map.get(id))
|
|
100
|
-
.filter(
|
|
152
|
+
.filter((col) => col != null);
|
|
101
153
|
const ordered_ids = new Set(ordered.map(get_col_id));
|
|
102
154
|
const remaining = columns.filter((col) => !ordered_ids.has(get_col_id(col)));
|
|
103
155
|
return [...ordered, ...remaining];
|
|
104
156
|
});
|
|
105
157
|
let drag_col_id = $state(null);
|
|
106
158
|
let drag_over_col_id = $state(null);
|
|
159
|
+
// Merge root_style with rest.style for root div; omit style from rest to avoid duplicate
|
|
160
|
+
let rest_props = $derived.by(() => {
|
|
161
|
+
const { style: rest_style, ...other_props } = rest;
|
|
162
|
+
const merged = [rest_style, root_style].filter(Boolean).join(`; `);
|
|
163
|
+
return { ...other_props, ...(merged ? { style: merged } : {}) };
|
|
164
|
+
});
|
|
107
165
|
// WeakMap to assign stable unique IDs to row objects for efficient comparison and keying
|
|
108
166
|
// This avoids O(n) JSON.stringify calls and prevents unnecessary re-renders
|
|
109
167
|
const row_id_map = new WeakMap();
|
|
@@ -272,10 +330,10 @@ let sorted_data = $derived.by(() => {
|
|
|
272
330
|
let paginated_data = $derived.by(() => {
|
|
273
331
|
if (!pagination_config)
|
|
274
332
|
return sorted_data;
|
|
275
|
-
const start = (current_page - 1) *
|
|
276
|
-
return sorted_data.slice(start, start +
|
|
333
|
+
const start = (current_page - 1) * effective_page_size;
|
|
334
|
+
return sorted_data.slice(start, start + effective_page_size);
|
|
277
335
|
});
|
|
278
|
-
let total_pages = $derived(Math.ceil(sorted_data.length /
|
|
336
|
+
let total_pages = $derived(Math.ceil(sorted_data.length / effective_page_size));
|
|
279
337
|
// Track previous values to detect actual changes
|
|
280
338
|
let prev_search_query = $state(``);
|
|
281
339
|
let prev_data_length = $state(0);
|
|
@@ -290,6 +348,10 @@ $effect(() => {
|
|
|
290
348
|
prev_search_query = search_query;
|
|
291
349
|
prev_data_length = sorted_data.length;
|
|
292
350
|
}
|
|
351
|
+
else if (total_pages > 0 && current_page > total_pages) {
|
|
352
|
+
// Clamp when total pages decreases (e.g., page size increase)
|
|
353
|
+
current_page = total_pages;
|
|
354
|
+
}
|
|
293
355
|
});
|
|
294
356
|
async function sort_rows(column, group, event) {
|
|
295
357
|
// Find the column using both label and group if provided
|
|
@@ -375,14 +437,14 @@ function parse_numeric_val(val) {
|
|
|
375
437
|
const error_match = val.match(/^([-+−]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][-+−]?\d+)?)\s*(?:±|\+[-−]|\()/);
|
|
376
438
|
if (error_match) {
|
|
377
439
|
// Normalize unicode minus (U+2212) to ASCII hyphen for Number()
|
|
378
|
-
const normalized = error_match[1]
|
|
440
|
+
const normalized = normalize_unicode_minus(error_match[1]);
|
|
379
441
|
const num = Number(normalized);
|
|
380
442
|
if (!isNaN(num))
|
|
381
443
|
return num;
|
|
382
444
|
}
|
|
383
445
|
// Try parsing as a plain number (handles "1.23" strings)
|
|
384
446
|
// Also normalize unicode minus for plain numbers
|
|
385
|
-
const normalized_val = val
|
|
447
|
+
const normalized_val = normalize_unicode_minus(val);
|
|
386
448
|
const plain_num = Number(normalized_val);
|
|
387
449
|
if (!isNaN(plain_num) && val.trim() !== ``)
|
|
388
450
|
return plain_num;
|
|
@@ -410,11 +472,25 @@ function calc_color(val, col) {
|
|
|
410
472
|
const col_id = get_col_id(col);
|
|
411
473
|
// Use memoized parsed values for the column
|
|
412
474
|
const numeric_vals = parsed_column_values.get(col_id) ?? [];
|
|
413
|
-
|
|
414
|
-
|
|
475
|
+
const better = better_overrides.get(col_id) ?? col.better;
|
|
476
|
+
const scale = (color_scale_overrides.get(col_id) ?? col.color_scale ??
|
|
477
|
+
`interpolateViridis`);
|
|
478
|
+
const color = calc_cell_color(numeric_val, numeric_vals, better, scale, col.scale_type || `linear`);
|
|
479
|
+
// Recompute text contrast against effective bg (cell bg blended with page bg by opacity).
|
|
480
|
+
// Approximation: blend luminances directly; accurate enough for black/white text choice.
|
|
481
|
+
if (color.bg && heatmap_opacity < 1) {
|
|
482
|
+
const blended_lum = luminance(color.bg) * heatmap_opacity +
|
|
483
|
+
page_bg_lum * (1 - heatmap_opacity);
|
|
484
|
+
color.text = blended_lum > 0.7 ? `black` : `white`;
|
|
485
|
+
}
|
|
486
|
+
return color;
|
|
415
487
|
}
|
|
416
488
|
let visible_columns = $derived(ordered_columns.filter((col) => col.visible !== false && !hidden_columns.includes(get_col_id(col))));
|
|
417
489
|
const sort_indicator = (col, sort_state) => {
|
|
490
|
+
const hide_sort_indicator = col.show_sort_indicator === false ||
|
|
491
|
+
col.style?.includes(`--hide-sort-indicator`);
|
|
492
|
+
if (hide_sort_indicator)
|
|
493
|
+
return ``;
|
|
418
494
|
const col_id = get_col_id(col);
|
|
419
495
|
// Check multi-sort first
|
|
420
496
|
const multi_idx = multi_sort.findIndex((s) => s.column === col_id);
|
|
@@ -424,13 +500,24 @@ const sort_indicator = (col, sort_state) => {
|
|
|
424
500
|
return `<span style="font-size: 0.8em;">${arrow}${badge}</span>`;
|
|
425
501
|
}
|
|
426
502
|
const is_sorted = sort_state.column === col_id;
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
: (col.better === `higher` ? `↑` : col.better === `lower` ? `↓` : ``);
|
|
503
|
+
if (!is_sorted)
|
|
504
|
+
return ``;
|
|
505
|
+
// Show indicator only for actively sorted columns.
|
|
506
|
+
const arrow = sort_state.ascending ? `↓` : `↑`;
|
|
432
507
|
return arrow ? `<span style="font-size: 0.8em;">${arrow}</span>` : ``;
|
|
433
508
|
};
|
|
509
|
+
// Context menu state for column header right-click
|
|
510
|
+
let context_menu_col = $state(null);
|
|
511
|
+
let context_menu_pos = $state({ x: 0, y: 0 });
|
|
512
|
+
const better_sections = [
|
|
513
|
+
{
|
|
514
|
+
title: `Gradient direction`,
|
|
515
|
+
options: [
|
|
516
|
+
{ value: `higher`, label: `▲ Higher is better` },
|
|
517
|
+
{ value: `lower`, label: `▼ Lower is better` },
|
|
518
|
+
],
|
|
519
|
+
},
|
|
520
|
+
];
|
|
434
521
|
// Row selection using WeakMap-based ID lookup instead of O(n) JSON.stringify comparison
|
|
435
522
|
function toggle_row_select(row) {
|
|
436
523
|
const row_id = get_row_id(row);
|
|
@@ -446,35 +533,57 @@ function is_row_selected(row) {
|
|
|
446
533
|
const row_id = get_row_id(row);
|
|
447
534
|
return selected_rows.some((r) => get_row_id(r) === row_id);
|
|
448
535
|
}
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
536
|
+
// Select-all: checks if every row on the current page is selected
|
|
537
|
+
let all_page_selected = $derived(paginated_data.length > 0 && paginated_data.every((row) => is_row_selected(row)));
|
|
538
|
+
function toggle_select_all() {
|
|
539
|
+
if (all_page_selected) {
|
|
540
|
+
const page_ids = new Set(paginated_data.map(get_row_id));
|
|
541
|
+
selected_rows = selected_rows.filter((row) => !page_ids.has(get_row_id(row)));
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
const already = new Set(selected_rows.map(get_row_id));
|
|
545
|
+
const new_rows = paginated_data.filter((row) => !already.has(get_row_id(row)));
|
|
546
|
+
selected_rows = [...selected_rows, ...new_rows];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Data source for exports: selected rows when any are selected, otherwise all sorted data
|
|
550
|
+
let export_rows = $derived(show_row_select && selected_rows.length > 0 ? selected_rows : sorted_data);
|
|
551
|
+
// Serialize table as delimited text (shared by CSV export and clipboard copy)
|
|
552
|
+
// Per RFC 4180, fields containing commas, double quotes, or newlines must be quoted
|
|
553
|
+
function serialize_table(delimiter, csv_quote = false) {
|
|
554
|
+
const quote = (str) => {
|
|
555
|
+
if (!csv_quote)
|
|
556
|
+
return str;
|
|
557
|
+
if (str.includes(`,`) || str.includes(`"`) || str.includes(`\n`)) {
|
|
558
|
+
return `"${str.replace(/"/g, `""`)}"`;
|
|
559
|
+
}
|
|
560
|
+
return str;
|
|
561
|
+
};
|
|
562
|
+
const headers = visible_columns.map((col) => quote(strip_html(col.label)));
|
|
563
|
+
const rows = export_rows.map((row) => visible_columns.map((col) => {
|
|
453
564
|
const val = row[get_col_id(col)];
|
|
454
565
|
if (val == null)
|
|
455
566
|
return ``;
|
|
456
|
-
|
|
457
|
-
// Escape quotes and wrap in quotes if contains comma
|
|
458
|
-
if (str_val.includes(`,`) || str_val.includes(`"`)) {
|
|
459
|
-
return `"${str_val.replace(/"/g, `""`)}"`;
|
|
460
|
-
}
|
|
461
|
-
return str_val;
|
|
567
|
+
return quote(strip_html(String(val)));
|
|
462
568
|
}));
|
|
463
|
-
|
|
464
|
-
|
|
569
|
+
return [headers.join(delimiter), ...rows.map((r) => r.join(delimiter))].join(`\n`);
|
|
570
|
+
}
|
|
571
|
+
function export_csv(filename = `table-export`) {
|
|
572
|
+
download_file(serialize_table(`,`, true), `${filename}.csv`, `text/csv`);
|
|
465
573
|
}
|
|
466
574
|
function export_json(filename = `table-export`) {
|
|
467
|
-
const rows =
|
|
575
|
+
const rows = export_rows.map((row) => {
|
|
468
576
|
const clean_row = {};
|
|
469
577
|
for (const col of visible_columns) {
|
|
470
578
|
const col_id = get_col_id(col);
|
|
471
579
|
const val = row[col_id];
|
|
472
|
-
clean_row[col.label] = typeof val === `string`
|
|
580
|
+
clean_row[strip_html(col.label)] = typeof val === `string`
|
|
581
|
+
? strip_html(val)
|
|
582
|
+
: val;
|
|
473
583
|
}
|
|
474
584
|
return clean_row;
|
|
475
585
|
});
|
|
476
|
-
|
|
477
|
-
download_file(json_content, `${filename}.json`, `application/json`);
|
|
586
|
+
download_file(JSON.stringify(rows, null, 2), `${filename}.json`, `application/json`);
|
|
478
587
|
}
|
|
479
588
|
function download_file(content, filename, mime_type) {
|
|
480
589
|
const blob = new Blob([content], { type: mime_type });
|
|
@@ -487,6 +596,9 @@ function download_file(content, filename, mime_type) {
|
|
|
487
596
|
document.body.removeChild(link);
|
|
488
597
|
URL.revokeObjectURL(url);
|
|
489
598
|
}
|
|
599
|
+
function copy_to_clipboard() {
|
|
600
|
+
navigator.clipboard.writeText(serialize_table(`\t`));
|
|
601
|
+
}
|
|
490
602
|
// Column visibility toggle
|
|
491
603
|
function toggle_column(col_id) {
|
|
492
604
|
if (hidden_columns.includes(col_id)) {
|
|
@@ -502,7 +614,7 @@ function start_resize(event, col) {
|
|
|
502
614
|
event.stopPropagation();
|
|
503
615
|
resize_col_id = get_col_id(col);
|
|
504
616
|
resize_start_x = event.clientX;
|
|
505
|
-
const th = event.target.parentElement;
|
|
617
|
+
const th = event.target instanceof Element ? event.target.parentElement : null;
|
|
506
618
|
resize_start_width = th?.offsetWidth ?? 100;
|
|
507
619
|
document.addEventListener(`mousemove`, handle_resize);
|
|
508
620
|
document.addEventListener(`mouseup`, stop_resize);
|
|
@@ -543,9 +655,15 @@ let hint_config = $derived(sort_hint
|
|
|
543
655
|
|
|
544
656
|
<div
|
|
545
657
|
{@attach tooltip()}
|
|
546
|
-
{...
|
|
547
|
-
|
|
548
|
-
|
|
658
|
+
{...rest_props}
|
|
659
|
+
bind:this={container_el}
|
|
660
|
+
class="table-container {rest_props.class ?? ``}"
|
|
661
|
+
style:--heatmap-opacity="{heatmap_opacity * 100}%"
|
|
662
|
+
onmouseleave={() => {
|
|
663
|
+
show_column_dropdown = false
|
|
664
|
+
show_export_dropdown = false
|
|
665
|
+
context_menu_col = null
|
|
666
|
+
}}
|
|
549
667
|
>
|
|
550
668
|
<!-- Floating control buttons -->
|
|
551
669
|
<section class="control-buttons">
|
|
@@ -566,12 +684,16 @@ let hint_config = $derived(sort_hint
|
|
|
566
684
|
search_query = ``
|
|
567
685
|
search_expanded = false
|
|
568
686
|
}}
|
|
569
|
-
|
|
687
|
+
{@attach tooltip({ content: `Clear`, placement: `top` })}
|
|
570
688
|
>
|
|
571
689
|
<Icon icon="Cross" style="width: 10px" />
|
|
572
690
|
</button>
|
|
573
691
|
{:else}
|
|
574
|
-
<button
|
|
692
|
+
<button
|
|
693
|
+
class="icon-btn"
|
|
694
|
+
onclick={() => search_expanded = true}
|
|
695
|
+
{@attach tooltip({ content: `Search`, placement: `top` })}
|
|
696
|
+
>
|
|
575
697
|
<Icon icon="Search" style="width: 14px" />
|
|
576
698
|
</button>
|
|
577
699
|
{/if}
|
|
@@ -583,7 +705,7 @@ let hint_config = $derived(sort_hint
|
|
|
583
705
|
class="icon-btn"
|
|
584
706
|
class:active={show_column_dropdown}
|
|
585
707
|
onclick={() => show_column_dropdown = !show_column_dropdown}
|
|
586
|
-
|
|
708
|
+
{@attach tooltip({ content: `Columns`, placement: `top` })}
|
|
587
709
|
>
|
|
588
710
|
<Icon icon="Columns" style="width: 14px" />
|
|
589
711
|
</button>
|
|
@@ -611,7 +733,7 @@ let hint_config = $derived(sort_hint
|
|
|
611
733
|
class="icon-btn"
|
|
612
734
|
class:active={show_export_dropdown}
|
|
613
735
|
onclick={() => show_export_dropdown = !show_export_dropdown}
|
|
614
|
-
|
|
736
|
+
{@attach tooltip({ content: `Export`, placement: `top` })}
|
|
615
737
|
>
|
|
616
738
|
<Icon icon="Export" style="width: 14px" />
|
|
617
739
|
</button>
|
|
@@ -639,6 +761,15 @@ let hint_config = $derived(sort_hint
|
|
|
639
761
|
<Icon icon="Download" style="width: 12px" /> JSON
|
|
640
762
|
</button>
|
|
641
763
|
{/if}
|
|
764
|
+
<button
|
|
765
|
+
class="dropdown-option"
|
|
766
|
+
onclick={() => {
|
|
767
|
+
copy_to_clipboard()
|
|
768
|
+
show_export_dropdown = false
|
|
769
|
+
}}
|
|
770
|
+
>
|
|
771
|
+
<Icon icon="Copy" style="width: 12px" /> Copy
|
|
772
|
+
</button>
|
|
642
773
|
</div>
|
|
643
774
|
{/if}
|
|
644
775
|
</div>
|
|
@@ -660,6 +791,106 @@ let hint_config = $derived(sort_hint
|
|
|
660
791
|
{/if}
|
|
661
792
|
</section>
|
|
662
793
|
|
|
794
|
+
{#if show_controls}
|
|
795
|
+
<DraggablePane
|
|
796
|
+
bind:show={controls_open}
|
|
797
|
+
closed_icon="Settings"
|
|
798
|
+
open_icon="Cross"
|
|
799
|
+
toggle_props={{
|
|
800
|
+
title: `${controls_open ? `Close` : `Open`} table controls`,
|
|
801
|
+
style: `position: absolute; top: 5pt; right: 1ex; z-index: 10`,
|
|
802
|
+
}}
|
|
803
|
+
pane_props={{ style: `max-height: 60vh; overflow-y: auto; font-size: 0.85em` }}
|
|
804
|
+
>
|
|
805
|
+
<SettingsSection
|
|
806
|
+
title="Heatmap"
|
|
807
|
+
current_values={{ show_heatmap, heatmap_opacity }}
|
|
808
|
+
on_reset={() => {
|
|
809
|
+
show_heatmap = true
|
|
810
|
+
heatmap_opacity = 1
|
|
811
|
+
}}
|
|
812
|
+
>
|
|
813
|
+
<label><input type="checkbox" bind:checked={show_heatmap} /> Show heatmap</label>
|
|
814
|
+
{#if show_heatmap}
|
|
815
|
+
<label>
|
|
816
|
+
Opacity
|
|
817
|
+
<input
|
|
818
|
+
type="range"
|
|
819
|
+
min="0"
|
|
820
|
+
max="1"
|
|
821
|
+
step="0.05"
|
|
822
|
+
bind:value={heatmap_opacity}
|
|
823
|
+
/>
|
|
824
|
+
<input
|
|
825
|
+
type="number"
|
|
826
|
+
min="0"
|
|
827
|
+
max="1"
|
|
828
|
+
step="0.05"
|
|
829
|
+
bind:value={heatmap_opacity}
|
|
830
|
+
style="width: 3.5em"
|
|
831
|
+
/>
|
|
832
|
+
</label>
|
|
833
|
+
{/if}
|
|
834
|
+
</SettingsSection>
|
|
835
|
+
|
|
836
|
+
<SettingsSection
|
|
837
|
+
title="Display"
|
|
838
|
+
current_values={{ show_row_numbers }}
|
|
839
|
+
on_reset={() => {
|
|
840
|
+
show_row_numbers = false
|
|
841
|
+
}}
|
|
842
|
+
>
|
|
843
|
+
<label><input type="checkbox" bind:checked={show_row_numbers} /> Row
|
|
844
|
+
numbers</label>
|
|
845
|
+
</SettingsSection>
|
|
846
|
+
|
|
847
|
+
{#if colored_columns.length > 0}
|
|
848
|
+
<SettingsSection
|
|
849
|
+
title="Column Colors"
|
|
850
|
+
current_values={Object.fromEntries([...better_overrides, ...color_scale_overrides])}
|
|
851
|
+
on_reset={() => {
|
|
852
|
+
better_overrides.clear()
|
|
853
|
+
color_scale_overrides.clear()
|
|
854
|
+
}}
|
|
855
|
+
>
|
|
856
|
+
{#each colored_columns as col (get_col_id(col))}
|
|
857
|
+
{@const col_id = get_col_id(col)}
|
|
858
|
+
<div class="col-color-row">
|
|
859
|
+
<span class="col-color-label">{@html col.label}</span>
|
|
860
|
+
<select
|
|
861
|
+
value={color_scale_overrides.get(col_id) ?? col.color_scale ??
|
|
862
|
+
`interpolateViridis`}
|
|
863
|
+
onchange={(event) => {
|
|
864
|
+
const val = event.currentTarget.value
|
|
865
|
+
if (
|
|
866
|
+
val === (col.color_scale ?? `interpolateViridis`)
|
|
867
|
+
) color_scale_overrides.delete(col_id)
|
|
868
|
+
else color_scale_overrides.set(col_id, val)
|
|
869
|
+
}}
|
|
870
|
+
>
|
|
871
|
+
{#each color_scale_options as scale (scale)}
|
|
872
|
+
<option value={scale}>{scale.replace(`interpolate`, ``)}</option>
|
|
873
|
+
{/each}
|
|
874
|
+
</select>
|
|
875
|
+
<select
|
|
876
|
+
value={better_overrides.get(col_id) ?? col.better ?? ``}
|
|
877
|
+
onchange={(event) => {
|
|
878
|
+
const val = event.currentTarget.value
|
|
879
|
+
if (!val) better_overrides.delete(col_id)
|
|
880
|
+
else better_overrides.set(col_id, val as `higher` | `lower`)
|
|
881
|
+
}}
|
|
882
|
+
>
|
|
883
|
+
<option value="">Default</option>
|
|
884
|
+
<option value="higher">▲ High</option>
|
|
885
|
+
<option value="lower">▼ Low</option>
|
|
886
|
+
</select>
|
|
887
|
+
</div>
|
|
888
|
+
{/each}
|
|
889
|
+
</SettingsSection>
|
|
890
|
+
{/if}
|
|
891
|
+
</DraggablePane>
|
|
892
|
+
{/if}
|
|
893
|
+
|
|
663
894
|
{@render sort_hint_element(`top`)}
|
|
664
895
|
|
|
665
896
|
<div
|
|
@@ -681,20 +912,24 @@ let hint_config = $derived(sort_hint
|
|
|
681
912
|
{#if show_row_select}
|
|
682
913
|
<th class="select-col"></th>
|
|
683
914
|
{/if}
|
|
684
|
-
{#
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
{#if !group}
|
|
689
|
-
<th class:sticky-col={sticky}></th>
|
|
915
|
+
{#if show_row_numbers}
|
|
916
|
+
<th class="row-num-col"></th>
|
|
917
|
+
{/if}
|
|
918
|
+
{#each visible_columns as col (get_col_id(col))}
|
|
919
|
+
{#if !col.group}
|
|
920
|
+
<th class:sticky-col={col.sticky}></th>
|
|
690
921
|
{:else}
|
|
691
|
-
{@const group_cols = visible_columns.filter((c) =>
|
|
922
|
+
{@const group_cols = visible_columns.filter((c) =>
|
|
923
|
+
c.group === col.group
|
|
924
|
+
)}
|
|
692
925
|
<!-- Only render the group header once for each group by checking if this is the first column of this group -->
|
|
693
|
-
{#if visible_columns.findIndex((c) => c.group === group) ===
|
|
926
|
+
{#if visible_columns.findIndex((c) => c.group === col.group) ===
|
|
694
927
|
visible_columns.findIndex((c) =>
|
|
695
|
-
c.group === group && c.label === label
|
|
928
|
+
c.group === col.group && c.label === col.label
|
|
696
929
|
)}
|
|
697
|
-
<th title={description} colspan={group_cols.length}>
|
|
930
|
+
<th title={col.description} colspan={group_cols.length}>
|
|
931
|
+
{@html col.group}
|
|
932
|
+
</th>
|
|
698
933
|
{/if}
|
|
699
934
|
{/if}
|
|
700
935
|
{/each}
|
|
@@ -703,11 +938,21 @@ let hint_config = $derived(sort_hint
|
|
|
703
938
|
<!-- Second level headers -->
|
|
704
939
|
<tr>
|
|
705
940
|
{#if show_row_select}
|
|
706
|
-
<th
|
|
707
|
-
|
|
941
|
+
<th
|
|
942
|
+
class="select-col"
|
|
943
|
+
title={all_page_selected ? `Deselect all` : `Select all on this page`}
|
|
944
|
+
>
|
|
945
|
+
<input
|
|
946
|
+
type="checkbox"
|
|
947
|
+
checked={all_page_selected}
|
|
948
|
+
onchange={toggle_select_all}
|
|
949
|
+
/>
|
|
708
950
|
</th>
|
|
709
951
|
{/if}
|
|
710
|
-
{#
|
|
952
|
+
{#if show_row_numbers}
|
|
953
|
+
<th class="row-num-col">#</th>
|
|
954
|
+
{/if}
|
|
955
|
+
{#each visible_columns as col (get_col_id(col))}
|
|
711
956
|
{@const col_id = get_col_id(col)}
|
|
712
957
|
{@const drag_side = drag_over_col_id === col_id
|
|
713
958
|
? get_drag_side(col_id)
|
|
@@ -717,6 +962,20 @@ let hint_config = $derived(sort_hint
|
|
|
717
962
|
title={col.description}
|
|
718
963
|
tabindex={col.sortable === false ? undefined : 0}
|
|
719
964
|
role={col.sortable === false ? undefined : `button`}
|
|
965
|
+
oncontextmenu={(event) => {
|
|
966
|
+
if (
|
|
967
|
+
!allow_better_toggle || col.color_scale === null ||
|
|
968
|
+
col.color_scale === undefined
|
|
969
|
+
) return
|
|
970
|
+
event.preventDefault()
|
|
971
|
+
event.stopPropagation()
|
|
972
|
+
context_menu_col = col_id
|
|
973
|
+
const rect = container_el?.getBoundingClientRect()
|
|
974
|
+
context_menu_pos = {
|
|
975
|
+
x: event.clientX - (rect?.left ?? 0),
|
|
976
|
+
y: event.clientY - (rect?.top ?? 0),
|
|
977
|
+
}
|
|
978
|
+
}}
|
|
720
979
|
onclick={(event) => {
|
|
721
980
|
if (!drag_col_id && !resize_col_id) {
|
|
722
981
|
sort_rows(
|
|
@@ -762,7 +1021,11 @@ let hint_config = $derived(sort_hint
|
|
|
762
1021
|
event.currentTarget.removeAttribute(`aria-grabbed`)
|
|
763
1022
|
}}
|
|
764
1023
|
>
|
|
765
|
-
{
|
|
1024
|
+
{#if header_cell}
|
|
1025
|
+
{@render header_cell({ col })}
|
|
1026
|
+
{:else}
|
|
1027
|
+
{@html col.label}
|
|
1028
|
+
{/if}
|
|
766
1029
|
{@html sort_indicator(col, sort_state)}
|
|
767
1030
|
<!-- Column resize handle -->
|
|
768
1031
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
@@ -780,14 +1043,32 @@ let hint_config = $derived(sort_hint
|
|
|
780
1043
|
</tr>
|
|
781
1044
|
</thead>
|
|
782
1045
|
<tbody>
|
|
783
|
-
{#each paginated_data as row (get_row_id(row))}
|
|
1046
|
+
{#each paginated_data as row, row_idx (get_row_id(row))}
|
|
784
1047
|
{@const row_selected = show_row_select && is_row_selected(row)}
|
|
785
1048
|
<tr
|
|
786
1049
|
animate:flip={{ duration: 500 }}
|
|
787
1050
|
style={row.style}
|
|
788
1051
|
class={row.class ?? ``}
|
|
789
1052
|
class:selected={row_selected}
|
|
1053
|
+
tabindex={onrowclick ? 0 : undefined}
|
|
1054
|
+
onclick={onrowclick ? (event) => onrowclick(event, row) : undefined}
|
|
790
1055
|
ondblclick={onrowdblclick ? (event) => onrowdblclick(event, row) : undefined}
|
|
1056
|
+
onkeydown={onrowclick
|
|
1057
|
+
? (event) => {
|
|
1058
|
+
if (event.key === `Enter` || event.key === ` `) {
|
|
1059
|
+
event.preventDefault()
|
|
1060
|
+
onrowclick(event, row)
|
|
1061
|
+
} else if (event.key === `ArrowDown`) {
|
|
1062
|
+
event.preventDefault()
|
|
1063
|
+
const next = event.currentTarget.nextElementSibling
|
|
1064
|
+
if (next instanceof HTMLElement) next.focus()
|
|
1065
|
+
} else if (event.key === `ArrowUp`) {
|
|
1066
|
+
event.preventDefault()
|
|
1067
|
+
const prev = event.currentTarget.previousElementSibling
|
|
1068
|
+
if (prev instanceof HTMLElement) prev.focus()
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
: undefined}
|
|
791
1072
|
>
|
|
792
1073
|
{#if show_row_select}
|
|
793
1074
|
<td class="select-col">
|
|
@@ -798,7 +1079,12 @@ let hint_config = $derived(sort_hint
|
|
|
798
1079
|
/>
|
|
799
1080
|
</td>
|
|
800
1081
|
{/if}
|
|
801
|
-
{#
|
|
1082
|
+
{#if show_row_numbers}
|
|
1083
|
+
<td class="row-num-col">
|
|
1084
|
+
{(current_page - 1) * effective_page_size + row_idx + 1}
|
|
1085
|
+
</td>
|
|
1086
|
+
{/if}
|
|
1087
|
+
{#each visible_columns as col (get_col_id(col))}
|
|
802
1088
|
{@const val = row[get_col_id(col)]}
|
|
803
1089
|
{@const color = calc_color(val, col)}
|
|
804
1090
|
{@const col_width = column_widths[get_col_id(col)]}
|
|
@@ -806,7 +1092,7 @@ let hint_config = $derived(sort_hint
|
|
|
806
1092
|
data-col={col.label}
|
|
807
1093
|
data-sort-value={is_html_str(val) ? null : val}
|
|
808
1094
|
class:sticky-col={col.sticky}
|
|
809
|
-
style
|
|
1095
|
+
style:--cell-bg={color.bg}
|
|
810
1096
|
style:color={color.text}
|
|
811
1097
|
style={`${col.cell_style ?? col.style ?? ``}${
|
|
812
1098
|
col_width
|
|
@@ -830,8 +1116,24 @@ let hint_config = $derived(sort_hint
|
|
|
830
1116
|
</td>
|
|
831
1117
|
{/each}
|
|
832
1118
|
</tr>
|
|
1119
|
+
{:else}
|
|
1120
|
+
{#if empty_message}
|
|
1121
|
+
<tr class="empty-row">
|
|
1122
|
+
<td
|
|
1123
|
+
colspan={visible_columns.length + (show_row_select ? 1 : 0) +
|
|
1124
|
+
(show_row_numbers ? 1 : 0)}
|
|
1125
|
+
>
|
|
1126
|
+
{empty_message}
|
|
1127
|
+
</td>
|
|
1128
|
+
</tr>
|
|
1129
|
+
{/if}
|
|
833
1130
|
{/each}
|
|
834
1131
|
</tbody>
|
|
1132
|
+
{#if footer}
|
|
1133
|
+
<tfoot>
|
|
1134
|
+
{@render footer()}
|
|
1135
|
+
</tfoot>
|
|
1136
|
+
{/if}
|
|
835
1137
|
</table>
|
|
836
1138
|
</div>
|
|
837
1139
|
|
|
@@ -888,8 +1190,47 @@ let hint_config = $derived(sort_hint
|
|
|
888
1190
|
>
|
|
889
1191
|
»
|
|
890
1192
|
</button>
|
|
1193
|
+
{#if pagination_config.page_sizes}
|
|
1194
|
+
<select
|
|
1195
|
+
class="page-size-select"
|
|
1196
|
+
onchange={(event) => {
|
|
1197
|
+
effective_page_size = parseInt(event.currentTarget.value, 10)
|
|
1198
|
+
current_page = 1
|
|
1199
|
+
}}
|
|
1200
|
+
>
|
|
1201
|
+
{#each pagination_config.page_sizes as size (size)}
|
|
1202
|
+
<option value={size} selected={size === effective_page_size}>
|
|
1203
|
+
{size} / page
|
|
1204
|
+
</option>
|
|
1205
|
+
{/each}
|
|
1206
|
+
</select>
|
|
1207
|
+
{/if}
|
|
891
1208
|
</div>
|
|
892
1209
|
{/if}
|
|
1210
|
+
|
|
1211
|
+
<ContextMenu
|
|
1212
|
+
sections={better_sections}
|
|
1213
|
+
selected_values={{ 'Gradient direction': better_overrides.get(context_menu_col ?? ``) ?? `` }}
|
|
1214
|
+
position={context_menu_pos}
|
|
1215
|
+
visible={context_menu_col !== null}
|
|
1216
|
+
on_close={() => context_menu_col = null}
|
|
1217
|
+
style={[
|
|
1218
|
+
`--surface-bg: light-dark(#fff, #1e1e1e)`,
|
|
1219
|
+
`--border-color: light-dark(rgba(0,0,0,0.15), rgba(255,255,255,0.15))`,
|
|
1220
|
+
`--text-color: light-dark(#333, #eee)`,
|
|
1221
|
+
`--text-color-muted: light-dark(#888, #999)`,
|
|
1222
|
+
`--surface-bg-hover: light-dark(rgba(0,0,0,0.06), rgba(255,255,255,0.1))`,
|
|
1223
|
+
`--accent-color: light-dark(rgba(0,0,0,0.1), rgba(255,255,255,0.15))`,
|
|
1224
|
+
`z-index: 200`,
|
|
1225
|
+
].join(`; `)}
|
|
1226
|
+
on_select={(_, option) => {
|
|
1227
|
+
if (!context_menu_col) return
|
|
1228
|
+
const current = better_overrides.get(context_menu_col)
|
|
1229
|
+
if (current === option.value) better_overrides.delete(context_menu_col)
|
|
1230
|
+
else better_overrides.set(context_menu_col, option.value as `higher` | `lower`)
|
|
1231
|
+
context_menu_col = null
|
|
1232
|
+
}}
|
|
1233
|
+
/>
|
|
893
1234
|
</div>
|
|
894
1235
|
|
|
895
1236
|
<style>
|
|
@@ -897,15 +1238,21 @@ let hint_config = $derived(sort_hint
|
|
|
897
1238
|
font-size: var(--heatmap-font-size, 0.9em);
|
|
898
1239
|
width: fit-content;
|
|
899
1240
|
max-width: 100%;
|
|
1241
|
+
max-height: inherit;
|
|
900
1242
|
margin: 0 auto;
|
|
901
1243
|
position: relative;
|
|
1244
|
+
display: flex;
|
|
1245
|
+
flex-direction: column;
|
|
902
1246
|
}
|
|
903
1247
|
.table-scroll {
|
|
904
1248
|
position: relative;
|
|
1249
|
+
overflow: auto;
|
|
905
1250
|
}
|
|
906
1251
|
.table-scroll.has-scroll {
|
|
907
|
-
|
|
908
|
-
border:
|
|
1252
|
+
border: 1px solid light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.12));
|
|
1253
|
+
border-radius: var(--border-radius, 3pt);
|
|
1254
|
+
overflow-x: hidden;
|
|
1255
|
+
overflow-y: auto;
|
|
909
1256
|
}
|
|
910
1257
|
table {
|
|
911
1258
|
border-collapse: separate;
|
|
@@ -919,6 +1266,13 @@ let hint_config = $derived(sort_hint
|
|
|
919
1266
|
white-space: nowrap;
|
|
920
1267
|
overflow: hidden;
|
|
921
1268
|
text-overflow: ellipsis;
|
|
1269
|
+
/* --cell-bg is set inline per-cell by calc_color(); --heatmap-opacity is set
|
|
1270
|
+
on the container from the heatmap_opacity prop to fade cell backgrounds */
|
|
1271
|
+
background-color: color-mix(
|
|
1272
|
+
in srgb,
|
|
1273
|
+
var(--cell-bg, transparent) var(--heatmap-opacity, 100%),
|
|
1274
|
+
transparent
|
|
1275
|
+
);
|
|
922
1276
|
}
|
|
923
1277
|
th {
|
|
924
1278
|
background: var(--heatmap-header-bg, var(--page-bg, Canvas));
|
|
@@ -962,6 +1316,13 @@ let hint_config = $derived(sort_hint
|
|
|
962
1316
|
tbody tr:hover {
|
|
963
1317
|
filter: var(--heatmap-row-hover-filter, brightness(1.1));
|
|
964
1318
|
}
|
|
1319
|
+
tbody tr[tabindex] {
|
|
1320
|
+
cursor: pointer;
|
|
1321
|
+
}
|
|
1322
|
+
tbody tr:focus-visible {
|
|
1323
|
+
outline: 2px solid var(--highlight, #4a9eff);
|
|
1324
|
+
outline-offset: -2px;
|
|
1325
|
+
}
|
|
965
1326
|
td[data-sort-value] {
|
|
966
1327
|
cursor: default;
|
|
967
1328
|
}
|
|
@@ -979,7 +1340,7 @@ let hint_config = $derived(sort_hint
|
|
|
979
1340
|
justify-content: flex-end;
|
|
980
1341
|
align-items: center;
|
|
981
1342
|
gap: 2px;
|
|
982
|
-
margin-bottom:
|
|
1343
|
+
margin-bottom: 1px;
|
|
983
1344
|
opacity: 0;
|
|
984
1345
|
pointer-events: none;
|
|
985
1346
|
transition: opacity 0.15s;
|
|
@@ -990,21 +1351,21 @@ let hint_config = $derived(sort_hint
|
|
|
990
1351
|
pointer-events: auto;
|
|
991
1352
|
}
|
|
992
1353
|
.icon-btn {
|
|
993
|
-
padding:
|
|
1354
|
+
padding: 2px 4px;
|
|
994
1355
|
border: none;
|
|
995
|
-
border-radius:
|
|
1356
|
+
border-radius: 3px;
|
|
996
1357
|
background: light-dark(rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.1));
|
|
997
1358
|
color: light-dark(#333, #ddd);
|
|
998
1359
|
cursor: pointer;
|
|
999
1360
|
display: flex;
|
|
1000
1361
|
align-items: center;
|
|
1001
1362
|
justify-content: center;
|
|
1002
|
-
gap:
|
|
1003
|
-
font-size: 0.
|
|
1363
|
+
gap: 2px;
|
|
1364
|
+
font-size: 0.8em;
|
|
1004
1365
|
}
|
|
1005
1366
|
.icon-btn :global(svg) {
|
|
1006
|
-
width:
|
|
1007
|
-
height:
|
|
1367
|
+
width: 12px;
|
|
1368
|
+
height: 12px;
|
|
1008
1369
|
}
|
|
1009
1370
|
.icon-btn:hover {
|
|
1010
1371
|
background: light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.2));
|
|
@@ -1066,13 +1427,13 @@ let hint_config = $derived(sort_hint
|
|
|
1066
1427
|
gap: 6px;
|
|
1067
1428
|
}
|
|
1068
1429
|
.search-input {
|
|
1069
|
-
padding:
|
|
1430
|
+
padding: 2px 4px;
|
|
1070
1431
|
border: 1px solid light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.2));
|
|
1071
|
-
border-radius:
|
|
1432
|
+
border-radius: 3px;
|
|
1072
1433
|
background: light-dark(rgba(255, 255, 255, 0.9), rgba(0, 0, 0, 0.3));
|
|
1073
1434
|
color: light-dark(#333, #eee);
|
|
1074
|
-
font-size: 0.
|
|
1075
|
-
width:
|
|
1435
|
+
font-size: 0.8em;
|
|
1436
|
+
width: 110px;
|
|
1076
1437
|
box-sizing: border-box;
|
|
1077
1438
|
}
|
|
1078
1439
|
.search-input:focus {
|
|
@@ -1179,6 +1540,23 @@ let hint_config = $derived(sort_hint
|
|
|
1179
1540
|
font-size: 0.85em;
|
|
1180
1541
|
}
|
|
1181
1542
|
|
|
1543
|
+
.col-color-row {
|
|
1544
|
+
display: flex;
|
|
1545
|
+
align-items: center;
|
|
1546
|
+
gap: 4px;
|
|
1547
|
+
padding: 2px 0;
|
|
1548
|
+
select {
|
|
1549
|
+
font-size: 0.85em;
|
|
1550
|
+
padding: 1px 2px;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
.col-color-label {
|
|
1554
|
+
flex: 1;
|
|
1555
|
+
overflow: hidden;
|
|
1556
|
+
text-overflow: ellipsis;
|
|
1557
|
+
white-space: nowrap;
|
|
1558
|
+
min-width: 0;
|
|
1559
|
+
}
|
|
1182
1560
|
/* Column resize */
|
|
1183
1561
|
.resize-handle {
|
|
1184
1562
|
position: absolute;
|
|
@@ -1216,4 +1594,25 @@ let hint_config = $derived(sort_hint
|
|
|
1216
1594
|
transform: rotate(360deg);
|
|
1217
1595
|
}
|
|
1218
1596
|
}
|
|
1597
|
+
.empty-row td {
|
|
1598
|
+
text-align: center;
|
|
1599
|
+
padding: 2em !important;
|
|
1600
|
+
color: var(--text-muted, #888);
|
|
1601
|
+
font-style: italic;
|
|
1602
|
+
}
|
|
1603
|
+
.row-num-col {
|
|
1604
|
+
text-align: right;
|
|
1605
|
+
color: var(--text-muted, #888);
|
|
1606
|
+
font-size: 0.85em;
|
|
1607
|
+
width: 2em;
|
|
1608
|
+
padding-right: 8px !important;
|
|
1609
|
+
}
|
|
1610
|
+
.page-size-select {
|
|
1611
|
+
padding: 2px 4px;
|
|
1612
|
+
border: 1px solid light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.2));
|
|
1613
|
+
border-radius: 3px;
|
|
1614
|
+
background: light-dark(#fff, #333);
|
|
1615
|
+
color: inherit;
|
|
1616
|
+
font-size: 0.9em;
|
|
1617
|
+
}
|
|
1219
1618
|
</style>
|