matterviz 0.3.1 → 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/app.css +29 -0
- package/dist/brillouin/BrillouinZone.svelte +19 -61
- 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 +586 -94
- package/dist/composition/FormulaFilter.svelte.d.ts +35 -1
- package/dist/composition/PieChart.svelte +43 -18
- package/dist/composition/PieChart.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull.svelte +4 -2
- package/dist/convex-hull/ConvexHull.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull2D.svelte +13 -44
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull3D.svelte +16 -7
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHull4D.svelte +17 -7
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullControls.svelte.d.ts +1 -1
- package/dist/convex-hull/ConvexHullStats.svelte +701 -226
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +6 -1
- package/dist/convex-hull/ConvexHullTooltip.svelte +1 -0
- package/dist/convex-hull/demo-temperature.d.ts +6 -0
- package/dist/convex-hull/demo-temperature.js +36 -0
- package/dist/convex-hull/helpers.d.ts +1 -1
- package/dist/convex-hull/helpers.js +2 -4
- package/dist/convex-hull/index.d.ts +1 -0
- 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 +5 -0
- package/dist/convex-hull/types.js +5 -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/types.d.ts +1 -0
- package/dist/fermi-surface/FermiSurface.svelte +20 -64
- 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/parse.js +16 -22
- 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 +111 -0
- package/dist/icons.js +111 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -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 +101 -45
- package/dist/isosurface/IsosurfaceControls.svelte +19 -0
- package/dist/isosurface/parse.js +73 -30
- package/dist/isosurface/slice.d.ts +2 -1
- package/dist/isosurface/slice.js +3 -3
- package/dist/isosurface/types.d.ts +13 -1
- package/dist/isosurface/types.js +98 -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 +83 -85
- package/dist/layout/json-tree/JsonTree.svelte +20 -19
- package/dist/layout/json-tree/JsonTree.svelte.d.ts +1 -1
- package/dist/layout/json-tree/JsonValue.svelte +196 -116
- package/dist/layout/json-tree/types.d.ts +10 -2
- package/dist/layout/json-tree/utils.d.ts +2 -0
- package/dist/layout/json-tree/utils.js +33 -0
- package/dist/math.d.ts +7 -0
- package/dist/math.js +358 -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/ColorScaleSelect.svelte +1 -1
- package/dist/plot/ElementScatter.svelte +3 -2
- 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/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 +2 -3
- 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 +13 -10
- package/dist/plot/utils.d.ts +1 -0
- package/dist/plot/utils.js +14 -0
- package/dist/rdf/RdfPlot.svelte +55 -66
- package/dist/settings.d.ts +3 -0
- package/dist/settings.js +17 -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 +29 -8
- package/dist/structure/AtomLegend.svelte.d.ts +1 -1
- package/dist/structure/CellSelect.svelte +92 -22
- package/dist/structure/Structure.svelte +108 -118
- package/dist/structure/Structure.svelte.d.ts +1 -1
- package/dist/structure/StructureControls.svelte +25 -22
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +7 -1
- package/dist/structure/StructureScene.svelte +104 -66
- package/dist/structure/StructureScene.svelte.d.ts +2 -1
- 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 +6 -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 +425 -65
- package/dist/table/HeatmapTable.svelte.d.ts +12 -1
- package/dist/table/ToggleMenu.svelte +2 -0
- package/dist/table/index.d.ts +2 -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 +30 -24
- 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,12 +1,16 @@
|
|
|
1
1
|
<script lang="ts">import { luminance, watch_dark_mode } from '../colors';
|
|
2
2
|
import Icon from '../Icon.svelte';
|
|
3
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';
|
|
4
7
|
import { calc_cell_color, strip_html } from './';
|
|
8
|
+
import { normalize_unicode_minus } from '../utils';
|
|
5
9
|
import { tooltip } from 'svelte-multiselect/attachments';
|
|
6
10
|
import { flip } from 'svelte/animate';
|
|
7
11
|
import { SvelteMap } from 'svelte/reactivity';
|
|
8
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
|
|
9
|
-
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, heatmap_opacity = $bindable(1), ...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();
|
|
10
14
|
let container_el = $state();
|
|
11
15
|
// Read --page-bg from computed style for text contrast calculation.
|
|
12
16
|
// Recalculates on mount and when the theme changes (dark/light mode toggle).
|
|
@@ -43,6 +47,8 @@ let initial_sort_config = $derived(initial_sort
|
|
|
43
47
|
let pagination_config = $derived(pagination
|
|
44
48
|
? { page_size: 25, ...(typeof pagination === `object` ? pagination : {}) }
|
|
45
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);
|
|
46
52
|
// Normalize search config
|
|
47
53
|
let search_config = $derived(search
|
|
48
54
|
? {
|
|
@@ -77,11 +83,41 @@ let current_page = $state(1);
|
|
|
77
83
|
// Dropdown states
|
|
78
84
|
let show_column_dropdown = $state(false);
|
|
79
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));
|
|
80
103
|
// Column resize state
|
|
81
104
|
let resize_col_id = $state(null);
|
|
82
105
|
let resize_start_x = $state(0);
|
|
83
106
|
let resize_start_width = $state(0);
|
|
84
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
|
+
});
|
|
85
121
|
// Helper to make column IDs (needed since column labels in different groups can be repeated)
|
|
86
122
|
const get_col_id = (col) => col.group ? `${col.key ?? col.label} (${col.group})` : (col.key ?? col.label);
|
|
87
123
|
// Sync column_order with columns: initialize if empty, remove stale IDs, append new IDs
|
|
@@ -113,13 +149,19 @@ let ordered_columns = $derived.by(() => {
|
|
|
113
149
|
// Add columns in specified order, then any remaining columns that weren't in the order list
|
|
114
150
|
const ordered = column_order
|
|
115
151
|
.map((id) => col_map.get(id))
|
|
116
|
-
.filter(
|
|
152
|
+
.filter((col) => col != null);
|
|
117
153
|
const ordered_ids = new Set(ordered.map(get_col_id));
|
|
118
154
|
const remaining = columns.filter((col) => !ordered_ids.has(get_col_id(col)));
|
|
119
155
|
return [...ordered, ...remaining];
|
|
120
156
|
});
|
|
121
157
|
let drag_col_id = $state(null);
|
|
122
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
|
+
});
|
|
123
165
|
// WeakMap to assign stable unique IDs to row objects for efficient comparison and keying
|
|
124
166
|
// This avoids O(n) JSON.stringify calls and prevents unnecessary re-renders
|
|
125
167
|
const row_id_map = new WeakMap();
|
|
@@ -288,10 +330,10 @@ let sorted_data = $derived.by(() => {
|
|
|
288
330
|
let paginated_data = $derived.by(() => {
|
|
289
331
|
if (!pagination_config)
|
|
290
332
|
return sorted_data;
|
|
291
|
-
const start = (current_page - 1) *
|
|
292
|
-
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);
|
|
293
335
|
});
|
|
294
|
-
let total_pages = $derived(Math.ceil(sorted_data.length /
|
|
336
|
+
let total_pages = $derived(Math.ceil(sorted_data.length / effective_page_size));
|
|
295
337
|
// Track previous values to detect actual changes
|
|
296
338
|
let prev_search_query = $state(``);
|
|
297
339
|
let prev_data_length = $state(0);
|
|
@@ -306,6 +348,10 @@ $effect(() => {
|
|
|
306
348
|
prev_search_query = search_query;
|
|
307
349
|
prev_data_length = sorted_data.length;
|
|
308
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
|
+
}
|
|
309
355
|
});
|
|
310
356
|
async function sort_rows(column, group, event) {
|
|
311
357
|
// Find the column using both label and group if provided
|
|
@@ -391,14 +437,14 @@ function parse_numeric_val(val) {
|
|
|
391
437
|
const error_match = val.match(/^([-+−]?(?:\d+\.?\d*|\d*\.\d+)(?:[eE][-+−]?\d+)?)\s*(?:±|\+[-−]|\()/);
|
|
392
438
|
if (error_match) {
|
|
393
439
|
// Normalize unicode minus (U+2212) to ASCII hyphen for Number()
|
|
394
|
-
const normalized = error_match[1]
|
|
440
|
+
const normalized = normalize_unicode_minus(error_match[1]);
|
|
395
441
|
const num = Number(normalized);
|
|
396
442
|
if (!isNaN(num))
|
|
397
443
|
return num;
|
|
398
444
|
}
|
|
399
445
|
// Try parsing as a plain number (handles "1.23" strings)
|
|
400
446
|
// Also normalize unicode minus for plain numbers
|
|
401
|
-
const normalized_val = val
|
|
447
|
+
const normalized_val = normalize_unicode_minus(val);
|
|
402
448
|
const plain_num = Number(normalized_val);
|
|
403
449
|
if (!isNaN(plain_num) && val.trim() !== ``)
|
|
404
450
|
return plain_num;
|
|
@@ -426,8 +472,10 @@ function calc_color(val, col) {
|
|
|
426
472
|
const col_id = get_col_id(col);
|
|
427
473
|
// Use memoized parsed values for the column
|
|
428
474
|
const numeric_vals = parsed_column_values.get(col_id) ?? [];
|
|
429
|
-
|
|
430
|
-
const
|
|
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`);
|
|
431
479
|
// Recompute text contrast against effective bg (cell bg blended with page bg by opacity).
|
|
432
480
|
// Approximation: blend luminances directly; accurate enough for black/white text choice.
|
|
433
481
|
if (color.bg && heatmap_opacity < 1) {
|
|
@@ -439,6 +487,10 @@ function calc_color(val, col) {
|
|
|
439
487
|
}
|
|
440
488
|
let visible_columns = $derived(ordered_columns.filter((col) => col.visible !== false && !hidden_columns.includes(get_col_id(col))));
|
|
441
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 ``;
|
|
442
494
|
const col_id = get_col_id(col);
|
|
443
495
|
// Check multi-sort first
|
|
444
496
|
const multi_idx = multi_sort.findIndex((s) => s.column === col_id);
|
|
@@ -448,13 +500,24 @@ const sort_indicator = (col, sort_state) => {
|
|
|
448
500
|
return `<span style="font-size: 0.8em;">${arrow}${badge}</span>`;
|
|
449
501
|
}
|
|
450
502
|
const is_sorted = sort_state.column === col_id;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
: (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 ? `↓` : `↑`;
|
|
456
507
|
return arrow ? `<span style="font-size: 0.8em;">${arrow}</span>` : ``;
|
|
457
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
|
+
];
|
|
458
521
|
// Row selection using WeakMap-based ID lookup instead of O(n) JSON.stringify comparison
|
|
459
522
|
function toggle_row_select(row) {
|
|
460
523
|
const row_id = get_row_id(row);
|
|
@@ -470,35 +533,57 @@ function is_row_selected(row) {
|
|
|
470
533
|
const row_id = get_row_id(row);
|
|
471
534
|
return selected_rows.some((r) => get_row_id(r) === row_id);
|
|
472
535
|
}
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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) => {
|
|
477
564
|
const val = row[get_col_id(col)];
|
|
478
565
|
if (val == null)
|
|
479
566
|
return ``;
|
|
480
|
-
|
|
481
|
-
// Escape quotes and wrap in quotes if contains comma
|
|
482
|
-
if (str_val.includes(`,`) || str_val.includes(`"`)) {
|
|
483
|
-
return `"${str_val.replace(/"/g, `""`)}"`;
|
|
484
|
-
}
|
|
485
|
-
return str_val;
|
|
567
|
+
return quote(strip_html(String(val)));
|
|
486
568
|
}));
|
|
487
|
-
|
|
488
|
-
|
|
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`);
|
|
489
573
|
}
|
|
490
574
|
function export_json(filename = `table-export`) {
|
|
491
|
-
const rows =
|
|
575
|
+
const rows = export_rows.map((row) => {
|
|
492
576
|
const clean_row = {};
|
|
493
577
|
for (const col of visible_columns) {
|
|
494
578
|
const col_id = get_col_id(col);
|
|
495
579
|
const val = row[col_id];
|
|
496
|
-
clean_row[col.label] = typeof val === `string`
|
|
580
|
+
clean_row[strip_html(col.label)] = typeof val === `string`
|
|
581
|
+
? strip_html(val)
|
|
582
|
+
: val;
|
|
497
583
|
}
|
|
498
584
|
return clean_row;
|
|
499
585
|
});
|
|
500
|
-
|
|
501
|
-
download_file(json_content, `${filename}.json`, `application/json`);
|
|
586
|
+
download_file(JSON.stringify(rows, null, 2), `${filename}.json`, `application/json`);
|
|
502
587
|
}
|
|
503
588
|
function download_file(content, filename, mime_type) {
|
|
504
589
|
const blob = new Blob([content], { type: mime_type });
|
|
@@ -511,6 +596,9 @@ function download_file(content, filename, mime_type) {
|
|
|
511
596
|
document.body.removeChild(link);
|
|
512
597
|
URL.revokeObjectURL(url);
|
|
513
598
|
}
|
|
599
|
+
function copy_to_clipboard() {
|
|
600
|
+
navigator.clipboard.writeText(serialize_table(`\t`));
|
|
601
|
+
}
|
|
514
602
|
// Column visibility toggle
|
|
515
603
|
function toggle_column(col_id) {
|
|
516
604
|
if (hidden_columns.includes(col_id)) {
|
|
@@ -526,7 +614,7 @@ function start_resize(event, col) {
|
|
|
526
614
|
event.stopPropagation();
|
|
527
615
|
resize_col_id = get_col_id(col);
|
|
528
616
|
resize_start_x = event.clientX;
|
|
529
|
-
const th = event.target.parentElement;
|
|
617
|
+
const th = event.target instanceof Element ? event.target.parentElement : null;
|
|
530
618
|
resize_start_width = th?.offsetWidth ?? 100;
|
|
531
619
|
document.addEventListener(`mousemove`, handle_resize);
|
|
532
620
|
document.addEventListener(`mouseup`, stop_resize);
|
|
@@ -567,11 +655,15 @@ let hint_config = $derived(sort_hint
|
|
|
567
655
|
|
|
568
656
|
<div
|
|
569
657
|
{@attach tooltip()}
|
|
570
|
-
{...
|
|
658
|
+
{...rest_props}
|
|
571
659
|
bind:this={container_el}
|
|
572
|
-
class="table-container {
|
|
660
|
+
class="table-container {rest_props.class ?? ``}"
|
|
573
661
|
style:--heatmap-opacity="{heatmap_opacity * 100}%"
|
|
574
|
-
onmouseleave={() =>
|
|
662
|
+
onmouseleave={() => {
|
|
663
|
+
show_column_dropdown = false
|
|
664
|
+
show_export_dropdown = false
|
|
665
|
+
context_menu_col = null
|
|
666
|
+
}}
|
|
575
667
|
>
|
|
576
668
|
<!-- Floating control buttons -->
|
|
577
669
|
<section class="control-buttons">
|
|
@@ -592,12 +684,16 @@ let hint_config = $derived(sort_hint
|
|
|
592
684
|
search_query = ``
|
|
593
685
|
search_expanded = false
|
|
594
686
|
}}
|
|
595
|
-
|
|
687
|
+
{@attach tooltip({ content: `Clear`, placement: `top` })}
|
|
596
688
|
>
|
|
597
689
|
<Icon icon="Cross" style="width: 10px" />
|
|
598
690
|
</button>
|
|
599
691
|
{:else}
|
|
600
|
-
<button
|
|
692
|
+
<button
|
|
693
|
+
class="icon-btn"
|
|
694
|
+
onclick={() => search_expanded = true}
|
|
695
|
+
{@attach tooltip({ content: `Search`, placement: `top` })}
|
|
696
|
+
>
|
|
601
697
|
<Icon icon="Search" style="width: 14px" />
|
|
602
698
|
</button>
|
|
603
699
|
{/if}
|
|
@@ -609,7 +705,7 @@ let hint_config = $derived(sort_hint
|
|
|
609
705
|
class="icon-btn"
|
|
610
706
|
class:active={show_column_dropdown}
|
|
611
707
|
onclick={() => show_column_dropdown = !show_column_dropdown}
|
|
612
|
-
|
|
708
|
+
{@attach tooltip({ content: `Columns`, placement: `top` })}
|
|
613
709
|
>
|
|
614
710
|
<Icon icon="Columns" style="width: 14px" />
|
|
615
711
|
</button>
|
|
@@ -637,7 +733,7 @@ let hint_config = $derived(sort_hint
|
|
|
637
733
|
class="icon-btn"
|
|
638
734
|
class:active={show_export_dropdown}
|
|
639
735
|
onclick={() => show_export_dropdown = !show_export_dropdown}
|
|
640
|
-
|
|
736
|
+
{@attach tooltip({ content: `Export`, placement: `top` })}
|
|
641
737
|
>
|
|
642
738
|
<Icon icon="Export" style="width: 14px" />
|
|
643
739
|
</button>
|
|
@@ -665,6 +761,15 @@ let hint_config = $derived(sort_hint
|
|
|
665
761
|
<Icon icon="Download" style="width: 12px" /> JSON
|
|
666
762
|
</button>
|
|
667
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>
|
|
668
773
|
</div>
|
|
669
774
|
{/if}
|
|
670
775
|
</div>
|
|
@@ -686,6 +791,106 @@ let hint_config = $derived(sort_hint
|
|
|
686
791
|
{/if}
|
|
687
792
|
</section>
|
|
688
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
|
+
|
|
689
894
|
{@render sort_hint_element(`top`)}
|
|
690
895
|
|
|
691
896
|
<div
|
|
@@ -707,20 +912,24 @@ let hint_config = $derived(sort_hint
|
|
|
707
912
|
{#if show_row_select}
|
|
708
913
|
<th class="select-col"></th>
|
|
709
914
|
{/if}
|
|
710
|
-
{#
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
}
|
|
714
|
-
{#if !group}
|
|
715
|
-
<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>
|
|
716
921
|
{:else}
|
|
717
|
-
{@const group_cols = visible_columns.filter((c) =>
|
|
922
|
+
{@const group_cols = visible_columns.filter((c) =>
|
|
923
|
+
c.group === col.group
|
|
924
|
+
)}
|
|
718
925
|
<!-- Only render the group header once for each group by checking if this is the first column of this group -->
|
|
719
|
-
{#if visible_columns.findIndex((c) => c.group === group) ===
|
|
926
|
+
{#if visible_columns.findIndex((c) => c.group === col.group) ===
|
|
720
927
|
visible_columns.findIndex((c) =>
|
|
721
|
-
c.group === group && c.label === label
|
|
928
|
+
c.group === col.group && c.label === col.label
|
|
722
929
|
)}
|
|
723
|
-
<th title={description} colspan={group_cols.length}>
|
|
930
|
+
<th title={col.description} colspan={group_cols.length}>
|
|
931
|
+
{@html col.group}
|
|
932
|
+
</th>
|
|
724
933
|
{/if}
|
|
725
934
|
{/if}
|
|
726
935
|
{/each}
|
|
@@ -729,11 +938,21 @@ let hint_config = $derived(sort_hint
|
|
|
729
938
|
<!-- Second level headers -->
|
|
730
939
|
<tr>
|
|
731
940
|
{#if show_row_select}
|
|
732
|
-
<th
|
|
733
|
-
|
|
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
|
+
/>
|
|
734
950
|
</th>
|
|
735
951
|
{/if}
|
|
736
|
-
{#
|
|
952
|
+
{#if show_row_numbers}
|
|
953
|
+
<th class="row-num-col">#</th>
|
|
954
|
+
{/if}
|
|
955
|
+
{#each visible_columns as col (get_col_id(col))}
|
|
737
956
|
{@const col_id = get_col_id(col)}
|
|
738
957
|
{@const drag_side = drag_over_col_id === col_id
|
|
739
958
|
? get_drag_side(col_id)
|
|
@@ -743,6 +962,20 @@ let hint_config = $derived(sort_hint
|
|
|
743
962
|
title={col.description}
|
|
744
963
|
tabindex={col.sortable === false ? undefined : 0}
|
|
745
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
|
+
}}
|
|
746
979
|
onclick={(event) => {
|
|
747
980
|
if (!drag_col_id && !resize_col_id) {
|
|
748
981
|
sort_rows(
|
|
@@ -788,7 +1021,11 @@ let hint_config = $derived(sort_hint
|
|
|
788
1021
|
event.currentTarget.removeAttribute(`aria-grabbed`)
|
|
789
1022
|
}}
|
|
790
1023
|
>
|
|
791
|
-
{
|
|
1024
|
+
{#if header_cell}
|
|
1025
|
+
{@render header_cell({ col })}
|
|
1026
|
+
{:else}
|
|
1027
|
+
{@html col.label}
|
|
1028
|
+
{/if}
|
|
792
1029
|
{@html sort_indicator(col, sort_state)}
|
|
793
1030
|
<!-- Column resize handle -->
|
|
794
1031
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
@@ -806,14 +1043,32 @@ let hint_config = $derived(sort_hint
|
|
|
806
1043
|
</tr>
|
|
807
1044
|
</thead>
|
|
808
1045
|
<tbody>
|
|
809
|
-
{#each paginated_data as row (get_row_id(row))}
|
|
1046
|
+
{#each paginated_data as row, row_idx (get_row_id(row))}
|
|
810
1047
|
{@const row_selected = show_row_select && is_row_selected(row)}
|
|
811
1048
|
<tr
|
|
812
1049
|
animate:flip={{ duration: 500 }}
|
|
813
1050
|
style={row.style}
|
|
814
1051
|
class={row.class ?? ``}
|
|
815
1052
|
class:selected={row_selected}
|
|
1053
|
+
tabindex={onrowclick ? 0 : undefined}
|
|
1054
|
+
onclick={onrowclick ? (event) => onrowclick(event, row) : undefined}
|
|
816
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}
|
|
817
1072
|
>
|
|
818
1073
|
{#if show_row_select}
|
|
819
1074
|
<td class="select-col">
|
|
@@ -824,7 +1079,12 @@ let hint_config = $derived(sort_hint
|
|
|
824
1079
|
/>
|
|
825
1080
|
</td>
|
|
826
1081
|
{/if}
|
|
827
|
-
{#
|
|
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))}
|
|
828
1088
|
{@const val = row[get_col_id(col)]}
|
|
829
1089
|
{@const color = calc_color(val, col)}
|
|
830
1090
|
{@const col_width = column_widths[get_col_id(col)]}
|
|
@@ -856,8 +1116,24 @@ let hint_config = $derived(sort_hint
|
|
|
856
1116
|
</td>
|
|
857
1117
|
{/each}
|
|
858
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}
|
|
859
1130
|
{/each}
|
|
860
1131
|
</tbody>
|
|
1132
|
+
{#if footer}
|
|
1133
|
+
<tfoot>
|
|
1134
|
+
{@render footer()}
|
|
1135
|
+
</tfoot>
|
|
1136
|
+
{/if}
|
|
861
1137
|
</table>
|
|
862
1138
|
</div>
|
|
863
1139
|
|
|
@@ -914,8 +1190,47 @@ let hint_config = $derived(sort_hint
|
|
|
914
1190
|
>
|
|
915
1191
|
»
|
|
916
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}
|
|
917
1208
|
</div>
|
|
918
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
|
+
/>
|
|
919
1234
|
</div>
|
|
920
1235
|
|
|
921
1236
|
<style>
|
|
@@ -1001,6 +1316,13 @@ let hint_config = $derived(sort_hint
|
|
|
1001
1316
|
tbody tr:hover {
|
|
1002
1317
|
filter: var(--heatmap-row-hover-filter, brightness(1.1));
|
|
1003
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
|
+
}
|
|
1004
1326
|
td[data-sort-value] {
|
|
1005
1327
|
cursor: default;
|
|
1006
1328
|
}
|
|
@@ -1018,7 +1340,7 @@ let hint_config = $derived(sort_hint
|
|
|
1018
1340
|
justify-content: flex-end;
|
|
1019
1341
|
align-items: center;
|
|
1020
1342
|
gap: 2px;
|
|
1021
|
-
margin-bottom:
|
|
1343
|
+
margin-bottom: 1px;
|
|
1022
1344
|
opacity: 0;
|
|
1023
1345
|
pointer-events: none;
|
|
1024
1346
|
transition: opacity 0.15s;
|
|
@@ -1029,21 +1351,21 @@ let hint_config = $derived(sort_hint
|
|
|
1029
1351
|
pointer-events: auto;
|
|
1030
1352
|
}
|
|
1031
1353
|
.icon-btn {
|
|
1032
|
-
padding:
|
|
1354
|
+
padding: 2px 4px;
|
|
1033
1355
|
border: none;
|
|
1034
|
-
border-radius:
|
|
1356
|
+
border-radius: 3px;
|
|
1035
1357
|
background: light-dark(rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.1));
|
|
1036
1358
|
color: light-dark(#333, #ddd);
|
|
1037
1359
|
cursor: pointer;
|
|
1038
1360
|
display: flex;
|
|
1039
1361
|
align-items: center;
|
|
1040
1362
|
justify-content: center;
|
|
1041
|
-
gap:
|
|
1042
|
-
font-size: 0.
|
|
1363
|
+
gap: 2px;
|
|
1364
|
+
font-size: 0.8em;
|
|
1043
1365
|
}
|
|
1044
1366
|
.icon-btn :global(svg) {
|
|
1045
|
-
width:
|
|
1046
|
-
height:
|
|
1367
|
+
width: 12px;
|
|
1368
|
+
height: 12px;
|
|
1047
1369
|
}
|
|
1048
1370
|
.icon-btn:hover {
|
|
1049
1371
|
background: light-dark(rgba(0, 0, 0, 0.12), rgba(255, 255, 255, 0.2));
|
|
@@ -1105,13 +1427,13 @@ let hint_config = $derived(sort_hint
|
|
|
1105
1427
|
gap: 6px;
|
|
1106
1428
|
}
|
|
1107
1429
|
.search-input {
|
|
1108
|
-
padding:
|
|
1430
|
+
padding: 2px 4px;
|
|
1109
1431
|
border: 1px solid light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.2));
|
|
1110
|
-
border-radius:
|
|
1432
|
+
border-radius: 3px;
|
|
1111
1433
|
background: light-dark(rgba(255, 255, 255, 0.9), rgba(0, 0, 0, 0.3));
|
|
1112
1434
|
color: light-dark(#333, #eee);
|
|
1113
|
-
font-size: 0.
|
|
1114
|
-
width:
|
|
1435
|
+
font-size: 0.8em;
|
|
1436
|
+
width: 110px;
|
|
1115
1437
|
box-sizing: border-box;
|
|
1116
1438
|
}
|
|
1117
1439
|
.search-input:focus {
|
|
@@ -1218,6 +1540,23 @@ let hint_config = $derived(sort_hint
|
|
|
1218
1540
|
font-size: 0.85em;
|
|
1219
1541
|
}
|
|
1220
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
|
+
}
|
|
1221
1560
|
/* Column resize */
|
|
1222
1561
|
.resize-handle {
|
|
1223
1562
|
position: absolute;
|
|
@@ -1255,4 +1594,25 @@ let hint_config = $derived(sort_hint
|
|
|
1255
1594
|
transform: rotate(360deg);
|
|
1256
1595
|
}
|
|
1257
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
|
+
}
|
|
1258
1618
|
</style>
|