matterviz 0.1.15 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FilePicker.svelte +9 -9
- package/dist/brillouin/BrillouinZone.svelte +4 -4
- package/dist/brillouin/BrillouinZone.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneControls.svelte.d.ts +1 -1
- package/dist/brillouin/BrillouinZoneScene.svelte.d.ts +1 -1
- package/dist/composition/Composition.svelte +1 -1
- package/dist/composition/Composition.svelte.d.ts +2 -0
- package/dist/composition/Formula.svelte +9 -2
- package/dist/composition/FormulaFilter.svelte +429 -0
- package/dist/composition/FormulaFilter.svelte.d.ts +15 -0
- package/dist/composition/index.d.ts +3 -1
- package/dist/composition/index.js +2 -1
- package/dist/constants.js +5 -1
- package/dist/{phase-diagram/PhaseDiagram.svelte → convex-hull/ConvexHull.svelte} +17 -16
- package/dist/convex-hull/ConvexHull.svelte.d.ts +10 -0
- package/dist/{phase-diagram/PhaseDiagram2D.svelte → convex-hull/ConvexHull2D.svelte} +124 -101
- package/dist/convex-hull/ConvexHull2D.svelte.d.ts +11 -0
- package/dist/{phase-diagram/PhaseDiagram3D.svelte → convex-hull/ConvexHull3D.svelte} +99 -83
- package/dist/convex-hull/ConvexHull3D.svelte.d.ts +8 -0
- package/dist/{phase-diagram/PhaseDiagram4D.svelte → convex-hull/ConvexHull4D.svelte} +128 -129
- package/dist/convex-hull/ConvexHull4D.svelte.d.ts +8 -0
- package/dist/{phase-diagram/PhaseDiagramControls.svelte → convex-hull/ConvexHullControls.svelte} +14 -6
- package/dist/{phase-diagram/PhaseDiagramControls.svelte.d.ts → convex-hull/ConvexHullControls.svelte.d.ts} +7 -7
- package/dist/{phase-diagram/PhaseDiagramInfoPane.svelte → convex-hull/ConvexHullInfoPane.svelte} +10 -10
- package/dist/{phase-diagram/PhaseDiagramInfoPane.svelte.d.ts → convex-hull/ConvexHullInfoPane.svelte.d.ts} +6 -6
- package/dist/{phase-diagram/PhaseDiagramStats.svelte → convex-hull/ConvexHullStats.svelte} +40 -64
- package/dist/convex-hull/ConvexHullStats.svelte.d.ts +10 -0
- package/dist/convex-hull/PhaseEntryTooltip.svelte +76 -0
- package/dist/{phase-diagram → convex-hull}/PhaseEntryTooltip.svelte.d.ts +2 -1
- package/dist/{phase-diagram → convex-hull}/StructurePopup.svelte +7 -5
- package/dist/{phase-diagram → convex-hull}/barycentric-coords.d.ts +3 -3
- package/dist/{phase-diagram → convex-hull}/barycentric-coords.js +3 -3
- package/dist/{phase-diagram → convex-hull}/helpers.d.ts +5 -6
- package/dist/{phase-diagram → convex-hull}/helpers.js +17 -27
- package/dist/{phase-diagram → convex-hull}/index.d.ts +20 -19
- package/dist/{phase-diagram → convex-hull}/index.js +11 -11
- package/dist/{phase-diagram → convex-hull}/thermodynamics.d.ts +4 -5
- package/dist/{phase-diagram → convex-hull}/thermodynamics.js +5 -5
- package/dist/{phase-diagram → convex-hull}/types.d.ts +5 -7
- package/dist/coordination/CoordinationBarPlot.svelte.d.ts +1 -1
- package/dist/element/ElementTile.svelte.d.ts +1 -1
- package/dist/feedback/index.d.ts +0 -1
- package/dist/feedback/index.js +0 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/layout/InfoCard.svelte +13 -3
- package/dist/layout/InfoTag.svelte +152 -0
- package/dist/layout/InfoTag.svelte.d.ts +19 -0
- package/dist/layout/PropertyFilter.svelte +199 -0
- package/dist/layout/PropertyFilter.svelte.d.ts +24 -0
- package/dist/layout/fullscreen.d.ts +2 -0
- package/dist/layout/fullscreen.js +33 -0
- package/dist/layout/index.d.ts +6 -0
- package/dist/layout/index.js +4 -0
- package/dist/overlays/DraggablePane.svelte +22 -6
- package/dist/periodic-table/PeriodicTable.svelte +12 -3
- package/dist/plot/BarPlot.svelte +54 -31
- package/dist/plot/BarPlot.svelte.d.ts +1 -6
- package/dist/plot/BarPlotControls.svelte.d.ts +1 -1
- package/dist/plot/Histogram.svelte +104 -74
- package/dist/plot/Histogram.svelte.d.ts +1 -6
- package/dist/plot/HistogramControls.svelte.d.ts +1 -1
- package/dist/plot/ScatterPlot.svelte +75 -59
- package/dist/plot/ScatterPlot.svelte.d.ts +3 -13
- package/dist/plot/ScatterPlotControls.svelte +30 -18
- package/dist/plot/ScatterPlotControls.svelte.d.ts +1 -0
- package/dist/plot/SpacegroupBarPlot.svelte +7 -2
- package/dist/plot/index.d.ts +1 -0
- package/dist/plot/index.js +1 -0
- package/dist/plot/svg.d.ts +1 -0
- package/dist/plot/svg.js +11 -0
- package/dist/plot/types.d.ts +23 -10
- package/dist/rdf/RdfPlot.svelte +1 -1
- package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
- package/dist/settings.d.ts +8 -10
- package/dist/settings.js +55 -57
- package/dist/{bands → spectral}/Bands.svelte +134 -18
- package/dist/{bands → spectral}/Bands.svelte.d.ts +2 -1
- package/dist/{bands → spectral}/helpers.d.ts +22 -1
- package/dist/{bands → spectral}/helpers.js +169 -16
- package/dist/{bands → spectral}/index.js +1 -1
- package/dist/{bands → spectral}/types.d.ts +10 -0
- package/dist/structure/AtomLegend.svelte +150 -59
- package/dist/structure/AtomLegend.svelte.d.ts +2 -1
- package/dist/structure/Structure.svelte +111 -32
- package/dist/structure/Structure.svelte.d.ts +2 -1
- package/dist/structure/StructureControls.svelte.d.ts +1 -1
- package/dist/structure/StructureInfoPane.svelte +3 -3
- package/dist/structure/StructureScene.svelte +75 -6
- package/dist/structure/StructureScene.svelte.d.ts +5 -3
- package/dist/structure/atom-properties.d.ts +1 -1
- package/dist/structure/atom-properties.js +1 -1
- package/dist/structure/bonding.d.ts +2 -1
- package/dist/structure/bonding.js +1 -1
- package/dist/structure/parse.js +2 -2
- package/dist/symmetry/SymmetryStats.svelte +3 -3
- package/dist/trajectory/Trajectory.svelte +19 -6
- package/dist/trajectory/Trajectory.svelte.d.ts +2 -1
- package/dist/trajectory/TrajectoryInfoPane.svelte +1 -1
- package/dist/trajectory/index.js +1 -1
- package/dist/trajectory/parse.d.ts +4 -1
- package/dist/trajectory/parse.js +161 -4
- package/dist/trajectory/plotting.js +2 -1
- package/package.json +12 -8
- package/dist/phase-diagram/PhaseDiagram.svelte.d.ts +0 -10
- package/dist/phase-diagram/PhaseDiagram2D.svelte.d.ts +0 -11
- package/dist/phase-diagram/PhaseDiagram3D.svelte.d.ts +0 -8
- package/dist/phase-diagram/PhaseDiagram4D.svelte.d.ts +0 -8
- package/dist/phase-diagram/PhaseDiagramStats.svelte.d.ts +0 -10
- package/dist/phase-diagram/PhaseEntryTooltip.svelte +0 -68
- /package/dist/{phase-diagram → convex-hull}/StructurePopup.svelte.d.ts +0 -0
- /package/dist/{phase-diagram → convex-hull}/types.js +0 -0
- /package/dist/{feedback → layout}/FullscreenToggle.svelte +0 -0
- /package/dist/{feedback → layout}/FullscreenToggle.svelte.d.ts +0 -0
- /package/dist/{bands → spectral}/BandsAndDos.svelte +0 -0
- /package/dist/{bands → spectral}/BandsAndDos.svelte.d.ts +0 -0
- /package/dist/{bands → spectral}/BrillouinBandsDos.svelte +0 -0
- /package/dist/{bands → spectral}/BrillouinBandsDos.svelte.d.ts +0 -0
- /package/dist/{bands → spectral}/Dos.svelte +0 -0
- /package/dist/{bands → spectral}/Dos.svelte.d.ts +0 -0
- /package/dist/{bands → spectral}/index.d.ts +0 -0
- /package/dist/{bands → spectral}/types.js +0 -0
package/dist/FilePicker.svelte
CHANGED
|
@@ -172,13 +172,13 @@ let uniq_categories = $derived([...new Set(files.map(get_category_id))].filter(B
|
|
|
172
172
|
}
|
|
173
173
|
.legend-item:hover {
|
|
174
174
|
opacity: 1;
|
|
175
|
-
background: rgba(255, 255, 255, 0.1);
|
|
176
|
-
border-color: rgba(255, 255, 255, 0.3);
|
|
175
|
+
background: light-dark(rgba(0, 0, 0, 0.06), rgba(255, 255, 255, 0.1));
|
|
176
|
+
border-color: light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.3));
|
|
177
177
|
}
|
|
178
178
|
.legend-item.active {
|
|
179
179
|
opacity: 1;
|
|
180
|
-
background: rgba(255, 255, 255, 0.2);
|
|
181
|
-
border-color: rgba(255, 255, 255, 0.5);
|
|
180
|
+
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.2));
|
|
181
|
+
border-color: light-dark(rgba(0, 0, 0, 0.25), rgba(255, 255, 255, 0.5));
|
|
182
182
|
font-weight: bold;
|
|
183
183
|
}
|
|
184
184
|
.clear-filter {
|
|
@@ -205,24 +205,24 @@ let uniq_categories = $derived([...new Set(files.map(get_category_id))].filter(B
|
|
|
205
205
|
display: flex;
|
|
206
206
|
align-items: center;
|
|
207
207
|
padding: 4pt 8pt;
|
|
208
|
-
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
208
|
+
border: 1px solid light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.2));
|
|
209
209
|
border-radius: 20px;
|
|
210
210
|
cursor: grab;
|
|
211
|
-
background: rgba(255, 255, 255, 0.1);
|
|
211
|
+
background: light-dark(rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.1));
|
|
212
212
|
transition: all 0.2s ease;
|
|
213
213
|
gap: 0.5em;
|
|
214
214
|
}
|
|
215
215
|
.file-item.active {
|
|
216
216
|
border-color: var(--success-color, #00ff00);
|
|
217
|
-
background: rgba(0, 255, 0, 0.
|
|
218
|
-
box-shadow: 0 0 8px rgba(0, 255, 0, 0.
|
|
217
|
+
background: light-dark(rgba(0, 255, 0, 0.12), rgba(0, 255, 0, 0.2));
|
|
218
|
+
box-shadow: 0 0 8px light-dark(rgba(0, 255, 0, 0.25), rgba(0, 255, 0, 0.35));
|
|
219
219
|
}
|
|
220
220
|
.file-item:active {
|
|
221
221
|
cursor: grabbing;
|
|
222
222
|
}
|
|
223
223
|
.file-item:hover {
|
|
224
224
|
border-color: var(--accent-color, #007acc);
|
|
225
|
-
background: rgba(0, 122, 204, 0.
|
|
225
|
+
background: light-dark(rgba(0, 122, 204, 0.15), rgba(0, 122, 204, 0.25));
|
|
226
226
|
filter: brightness(1.1);
|
|
227
227
|
}
|
|
228
228
|
.file-name {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">import { Icon, Spinner, toggle_fullscreen } from '..';
|
|
2
2
|
import { decompress_file, handle_url_drop, load_from_url } from '../io';
|
|
3
|
-
import { set_fullscreen_bg } from '../
|
|
3
|
+
import { set_fullscreen_bg } from '../layout';
|
|
4
4
|
import { DEFAULTS } from '../settings';
|
|
5
5
|
import { parse_any_structure } from '../structure/parse';
|
|
6
6
|
import { Canvas } from '@threlte/core';
|
|
@@ -197,7 +197,6 @@ $effect(() => {
|
|
|
197
197
|
title="{fullscreen ? `Exit` : `Enter`} fullscreen"
|
|
198
198
|
aria-pressed={fullscreen}
|
|
199
199
|
class="fullscreen-toggle"
|
|
200
|
-
style="padding: 0"
|
|
201
200
|
{@attach tooltip()}
|
|
202
201
|
>
|
|
203
202
|
{#if typeof fullscreen_toggle === `function`}
|
|
@@ -316,11 +315,12 @@ $effect(() => {
|
|
|
316
315
|
section.control-buttons > :global(button) {
|
|
317
316
|
background-color: transparent;
|
|
318
317
|
display: flex;
|
|
319
|
-
padding:
|
|
318
|
+
padding: 4px;
|
|
319
|
+
border-radius: var(--border-radius, 3pt);
|
|
320
320
|
font-size: clamp(0.85em, 2cqmin, 2.5em);
|
|
321
321
|
}
|
|
322
322
|
section.control-buttons :global(button:hover) {
|
|
323
|
-
background-color:
|
|
323
|
+
background-color: color-mix(in srgb, currentColor 8%, transparent);
|
|
324
324
|
}
|
|
325
325
|
.filename {
|
|
326
326
|
font-family: monospace;
|
|
@@ -62,6 +62,6 @@ type $$ComponentProps = {
|
|
|
62
62
|
on_error?: (data: BZHandlerData) => void;
|
|
63
63
|
on_fullscreen_change?: (data: BZHandlerData) => void;
|
|
64
64
|
} & HTMLAttributes<HTMLDivElement>;
|
|
65
|
-
declare const BrillouinZone: import("svelte").Component<$$ComponentProps, {}, "
|
|
65
|
+
declare const BrillouinZone: import("svelte").Component<$$ComponentProps, {}, "dragover" | "height" | "width" | "fullscreen" | "structure" | "camera_projection" | "info_pane_open" | "controls_open" | "wrapper" | "hovered" | "png_dpi" | "loading" | "error_msg" | "bz_order" | "surface_color" | "surface_opacity" | "edge_color" | "edge_width" | "show_vectors" | "bz_data" | "vector_scale">;
|
|
66
66
|
type BrillouinZone = ReturnType<typeof BrillouinZone>;
|
|
67
67
|
export default BrillouinZone;
|
|
@@ -9,6 +9,6 @@ type $$ComponentProps = {
|
|
|
9
9
|
show_vectors?: boolean;
|
|
10
10
|
camera_projection?: CameraProjection;
|
|
11
11
|
};
|
|
12
|
-
declare const BrillouinZoneControls: import("svelte").Component<$$ComponentProps, {}, "
|
|
12
|
+
declare const BrillouinZoneControls: import("svelte").Component<$$ComponentProps, {}, "camera_projection" | "controls_open" | "bz_order" | "surface_color" | "surface_opacity" | "edge_color" | "edge_width" | "show_vectors">;
|
|
13
13
|
type BrillouinZoneControls = ReturnType<typeof BrillouinZoneControls>;
|
|
14
14
|
export default BrillouinZoneControls;
|
|
@@ -38,6 +38,6 @@ type $$ComponentProps = {
|
|
|
38
38
|
hovered_k_point?: Vec3 | null;
|
|
39
39
|
hovered_qpoint_index?: number | null;
|
|
40
40
|
};
|
|
41
|
-
declare const BrillouinZoneScene: import("svelte").Component<$$ComponentProps, {}, "
|
|
41
|
+
declare const BrillouinZoneScene: import("svelte").Component<$$ComponentProps, {}, "camera_position" | "camera_projection" | "camera_is_moving" | "scene" | "camera" | "surface_color" | "surface_opacity" | "edge_color" | "edge_width" | "show_vectors" | "bz_data" | "vector_scale">;
|
|
42
42
|
type BrillouinZoneScene = ReturnType<typeof BrillouinZoneScene>;
|
|
43
43
|
export default BrillouinZoneScene;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">import { ContextMenu } from '..';
|
|
2
2
|
import { export_svg_as_png, export_svg_as_svg } from '../io/export';
|
|
3
|
-
import { BarChart, BubbleChart, PieChart } from './index';
|
|
4
3
|
import { get_electro_neg_formula } from './format';
|
|
4
|
+
import { BarChart, BubbleChart, PieChart } from './index';
|
|
5
5
|
import { parse_composition } from './parse';
|
|
6
6
|
let { composition, mode = `pie`, on_composition_change, color_scheme = `Vesta`, ...rest } = $props();
|
|
7
7
|
// Make these reactive so context menu changes propagate
|
|
@@ -6,6 +6,8 @@ type $$ComponentProps = SVGAttributes<SVGSVGElement> & {
|
|
|
6
6
|
mode?: CompositionChartMode;
|
|
7
7
|
on_composition_change?: (composition: CompositionType) => void;
|
|
8
8
|
color_scheme?: ColorSchemeName;
|
|
9
|
+
size?: number;
|
|
10
|
+
interactive?: boolean;
|
|
9
11
|
};
|
|
10
12
|
declare const Composition: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
11
13
|
type Composition = ReturnType<typeof Composition>;
|
|
@@ -145,9 +145,16 @@ function show_tooltip(element, event) {
|
|
|
145
145
|
align-items: center;
|
|
146
146
|
gap: var(--formula-tooltip-gap, 5pt);
|
|
147
147
|
padding: var(--formula-tooltip-padding, 3pt 4pt);
|
|
148
|
-
background: var(
|
|
148
|
+
background: var(
|
|
149
|
+
--formula-tooltip-bg,
|
|
150
|
+
light-dark(rgba(255, 255, 255, 0.95), rgba(30, 30, 30, 0.95))
|
|
151
|
+
);
|
|
152
|
+
color: var(--formula-tooltip-color, light-dark(#222, #eee));
|
|
149
153
|
border-radius: var(--formula-tooltip-border-radius, var(--border-radius, 3pt));
|
|
150
|
-
box-shadow: var(
|
|
154
|
+
box-shadow: var(
|
|
155
|
+
--formula-tooltip-box-shadow,
|
|
156
|
+
0 4px 12px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.4))
|
|
157
|
+
);
|
|
151
158
|
z-index: var(--tooltip-z-index, 2);
|
|
152
159
|
}
|
|
153
160
|
.script-wrapper {
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
<script lang="ts">import Icon from '../Icon.svelte';
|
|
2
|
+
import { tooltip } from 'svelte-multiselect';
|
|
3
|
+
import { extract_formula_elements, normalize_element_symbols } from './parse';
|
|
4
|
+
const SEARCH_EXAMPLES = [
|
|
5
|
+
{
|
|
6
|
+
label: `Contains elements`,
|
|
7
|
+
description: `Materials containing at least these elements (may have others)`,
|
|
8
|
+
examples: [`Li,Fe`, `Si,O`, `Mn,Co,Ni`],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
label: `Chemical system`,
|
|
12
|
+
description: `Materials with only these elements (no others)`,
|
|
13
|
+
examples: [`Li-Fe-O`, `Si-O`, `Na-Cl`],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
label: `Exact formula`,
|
|
17
|
+
description: `Materials with this exact stoichiometry`,
|
|
18
|
+
examples: [`LiFePO4`, `SiO2`, `NaCl`],
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
let { value = $bindable(``), search_mode = $bindable(`elements`), input_element = $bindable(null), show_clear_button = true, show_examples = true, disabled = false, onchange, onclear, ...rest } = $props();
|
|
22
|
+
let input_value = $state(value);
|
|
23
|
+
let examples_open = $state(false);
|
|
24
|
+
let wrapper = $state(null);
|
|
25
|
+
let examples_wrapper = $state(null);
|
|
26
|
+
let focused_item_idx = $state(-1);
|
|
27
|
+
let anchor_left = $state(false);
|
|
28
|
+
// Flatten examples for keyboard navigation
|
|
29
|
+
const all_examples = SEARCH_EXAMPLES.flatMap((cat) => cat.examples);
|
|
30
|
+
function handle_document_click(event) {
|
|
31
|
+
if (!wrapper || !examples_open)
|
|
32
|
+
return;
|
|
33
|
+
const target = event.target;
|
|
34
|
+
if (!(target instanceof Node))
|
|
35
|
+
return;
|
|
36
|
+
if (!wrapper.contains(target))
|
|
37
|
+
close_examples();
|
|
38
|
+
}
|
|
39
|
+
function close_examples(restore_focus = true) {
|
|
40
|
+
examples_open = false;
|
|
41
|
+
focused_item_idx = -1;
|
|
42
|
+
if (restore_focus)
|
|
43
|
+
input_element?.focus({ preventScroll: true });
|
|
44
|
+
}
|
|
45
|
+
// Track last synced value to detect external changes (e.g. from URL params)
|
|
46
|
+
// and re-infer mode accordingly. Without this, mode would only be set on first render.
|
|
47
|
+
let last_synced = $state(null);
|
|
48
|
+
$effect(() => {
|
|
49
|
+
input_value = value;
|
|
50
|
+
if (value !== last_synced) {
|
|
51
|
+
last_synced = value;
|
|
52
|
+
if (value) {
|
|
53
|
+
const inferred = infer_mode(value);
|
|
54
|
+
if (inferred !== search_mode)
|
|
55
|
+
search_mode = inferred;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Detect if dropdown would exit viewport on the right and adjust anchor
|
|
60
|
+
$effect(() => {
|
|
61
|
+
if (!examples_open || !examples_wrapper)
|
|
62
|
+
return;
|
|
63
|
+
requestAnimationFrame(() => {
|
|
64
|
+
const dropdown = examples_wrapper?.querySelector(`.examples-dropdown`);
|
|
65
|
+
if (!dropdown)
|
|
66
|
+
return;
|
|
67
|
+
const rect = dropdown.getBoundingClientRect();
|
|
68
|
+
if (rect.right > window.innerWidth && !anchor_left)
|
|
69
|
+
anchor_left = true;
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
// Infer search mode from input format
|
|
73
|
+
function infer_mode(input) {
|
|
74
|
+
const trimmed = input.trim();
|
|
75
|
+
if (!trimmed)
|
|
76
|
+
return `elements`;
|
|
77
|
+
if (trimmed.includes(`,`))
|
|
78
|
+
return `elements`; // Li,Fe,O → contains elements
|
|
79
|
+
if (trimmed.includes(`-`))
|
|
80
|
+
return `chemsys`; // Li-Fe-O → chemical system
|
|
81
|
+
return `exact`; // LiFePO4 → exact formula
|
|
82
|
+
}
|
|
83
|
+
// Cycle through modes: elements → chemsys → exact → elements
|
|
84
|
+
const MODE_CYCLE = [`elements`, `chemsys`, `exact`];
|
|
85
|
+
// Extract elements from any input format (formula, comma-separated, dash-separated)
|
|
86
|
+
// Always returns elements in alphabetical order for consistency
|
|
87
|
+
function extract_elements(input) {
|
|
88
|
+
const trimmed = input.trim();
|
|
89
|
+
if (!trimmed)
|
|
90
|
+
return [];
|
|
91
|
+
// If contains commas or dashes, split by those and sort alphabetically
|
|
92
|
+
if (trimmed.includes(`,`) || trimmed.includes(`-`)) {
|
|
93
|
+
const parts = trimmed.split(/[-,]/).map((str) => str.trim()).filter(Boolean);
|
|
94
|
+
// Filter valid elements and sort alphabetically
|
|
95
|
+
return normalize_element_symbols(parts.join(`,`)).sort();
|
|
96
|
+
}
|
|
97
|
+
// Otherwise parse as formula (already returns sorted by default)
|
|
98
|
+
try {
|
|
99
|
+
return extract_formula_elements(trimmed, { sorted: true });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Format elements for the given mode
|
|
106
|
+
function format_for_mode(elements, mode) {
|
|
107
|
+
if (elements.length === 0)
|
|
108
|
+
return ``;
|
|
109
|
+
if (mode === `elements`)
|
|
110
|
+
return elements.join(`,`);
|
|
111
|
+
if (mode === `chemsys`)
|
|
112
|
+
return elements.join(`-`);
|
|
113
|
+
// For exact mode, just join without separator (user will need to add counts)
|
|
114
|
+
return elements.join(``);
|
|
115
|
+
}
|
|
116
|
+
function cycle_mode() {
|
|
117
|
+
const current_idx = MODE_CYCLE.indexOf(search_mode);
|
|
118
|
+
const next_idx = (current_idx + 1) % MODE_CYCLE.length;
|
|
119
|
+
const next_mode = MODE_CYCLE[next_idx];
|
|
120
|
+
// Extract elements from current value and reformat for new mode
|
|
121
|
+
const elements = extract_elements(value);
|
|
122
|
+
const reformatted = format_for_mode(elements, next_mode);
|
|
123
|
+
search_mode = next_mode;
|
|
124
|
+
last_synced = value = input_value = reformatted; // update last_synced to prevent effect re-inference
|
|
125
|
+
onchange?.(reformatted, next_mode);
|
|
126
|
+
}
|
|
127
|
+
function set_value(new_value) {
|
|
128
|
+
const mode = infer_mode(new_value);
|
|
129
|
+
last_synced = value = input_value = new_value; // update last_synced to prevent effect re-inference
|
|
130
|
+
search_mode = mode;
|
|
131
|
+
onchange?.(value, mode);
|
|
132
|
+
}
|
|
133
|
+
function sync_value() {
|
|
134
|
+
const trimmed = input_value.trim();
|
|
135
|
+
if (!trimmed)
|
|
136
|
+
return set_value(``);
|
|
137
|
+
const mode = infer_mode(trimmed);
|
|
138
|
+
if (mode === `exact`)
|
|
139
|
+
return set_value(trimmed);
|
|
140
|
+
// Normalize element symbols for elements/chemsys modes
|
|
141
|
+
const separator = mode === `chemsys` ? `-` : `,`;
|
|
142
|
+
const normalized = normalize_element_symbols(trimmed.replace(/[-,]/g, `,`));
|
|
143
|
+
set_value(normalized.join(separator));
|
|
144
|
+
}
|
|
145
|
+
function onkeydown(event) {
|
|
146
|
+
if (event.key === `Enter`) {
|
|
147
|
+
event.preventDefault();
|
|
148
|
+
sync_value();
|
|
149
|
+
}
|
|
150
|
+
else if (event.key === `Escape`) {
|
|
151
|
+
if (examples_open)
|
|
152
|
+
examples_open = false;
|
|
153
|
+
else if (input_value)
|
|
154
|
+
clear_filter();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function clear_filter() {
|
|
158
|
+
onclear?.();
|
|
159
|
+
set_value(``);
|
|
160
|
+
}
|
|
161
|
+
function apply_example(example) {
|
|
162
|
+
set_value(example);
|
|
163
|
+
close_examples();
|
|
164
|
+
}
|
|
165
|
+
function toggle_examples(event) {
|
|
166
|
+
event.stopPropagation();
|
|
167
|
+
examples_open = !examples_open;
|
|
168
|
+
focused_item_idx = examples_open ? 0 : -1;
|
|
169
|
+
if (examples_open)
|
|
170
|
+
anchor_left = false;
|
|
171
|
+
}
|
|
172
|
+
function handle_menu_keydown(event) {
|
|
173
|
+
const len = all_examples.length;
|
|
174
|
+
if (!len)
|
|
175
|
+
return;
|
|
176
|
+
const is_button_activation = (event.key === `Enter` || event.key === ` `) &&
|
|
177
|
+
event.target instanceof HTMLButtonElement;
|
|
178
|
+
if (is_button_activation)
|
|
179
|
+
return;
|
|
180
|
+
const key_actions = {
|
|
181
|
+
ArrowDown: () => (focused_item_idx = (focused_item_idx + 1) % len),
|
|
182
|
+
ArrowUp: () => (focused_item_idx = (focused_item_idx - 1 + len) % len),
|
|
183
|
+
Home: () => (focused_item_idx = 0),
|
|
184
|
+
End: () => (focused_item_idx = len - 1),
|
|
185
|
+
Escape: close_examples,
|
|
186
|
+
};
|
|
187
|
+
if (event.key in key_actions) {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
key_actions[event.key]();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Focus the active menu item when index changes
|
|
193
|
+
$effect(() => {
|
|
194
|
+
if (!examples_open || focused_item_idx < 0)
|
|
195
|
+
return;
|
|
196
|
+
const items = wrapper?.querySelectorAll(`[data-example-item]`);
|
|
197
|
+
items?.[focused_item_idx]?.focus({ preventScroll: true });
|
|
198
|
+
});
|
|
199
|
+
let placeholder = $derived(search_mode === `chemsys`
|
|
200
|
+
? `Li-Fe-O`
|
|
201
|
+
: search_mode === `exact`
|
|
202
|
+
? `LiFePO4`
|
|
203
|
+
: `Li,Fe,O`);
|
|
204
|
+
const MODE_LABELS = {
|
|
205
|
+
elements: `contains elements`,
|
|
206
|
+
chemsys: `chemical system`,
|
|
207
|
+
exact: `exact formula`,
|
|
208
|
+
};
|
|
209
|
+
let mode_hint = $derived(MODE_LABELS[search_mode]);
|
|
210
|
+
// Preview of next mode cycle step for tooltip
|
|
211
|
+
let next_mode = $derived.by(() => {
|
|
212
|
+
const next = MODE_CYCLE[(MODE_CYCLE.indexOf(search_mode) + 1) % MODE_CYCLE.length];
|
|
213
|
+
const mode = MODE_LABELS[next];
|
|
214
|
+
const next_value = format_for_mode(extract_elements(value), next);
|
|
215
|
+
return { mode, value: next_value };
|
|
216
|
+
});
|
|
217
|
+
</script>
|
|
218
|
+
|
|
219
|
+
<svelte:document onclick={handle_document_click} />
|
|
220
|
+
|
|
221
|
+
<div class="formula-filter" bind:this={wrapper} class:disabled {...rest}>
|
|
222
|
+
<input
|
|
223
|
+
bind:this={input_element}
|
|
224
|
+
bind:value={input_value}
|
|
225
|
+
onblur={sync_value}
|
|
226
|
+
{onkeydown}
|
|
227
|
+
{placeholder}
|
|
228
|
+
{disabled}
|
|
229
|
+
aria-label="Formula filter"
|
|
230
|
+
/>
|
|
231
|
+
{#if input_value}
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
class="mode-hint clickable"
|
|
235
|
+
onclick={cycle_mode}
|
|
236
|
+
title="Click to switch to '{next_mode.mode}' → {next_mode.value}"
|
|
237
|
+
{@attach tooltip({ style: `font-size: 0.6em; padding: 1pt 5pt;` })}
|
|
238
|
+
aria-label="Change search mode"
|
|
239
|
+
>
|
|
240
|
+
{mode_hint}
|
|
241
|
+
</button>
|
|
242
|
+
{/if}
|
|
243
|
+
{#if show_clear_button && value && !disabled}
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
class="icon-btn clear-btn"
|
|
247
|
+
onclick={clear_filter}
|
|
248
|
+
title="Clear (Escape)"
|
|
249
|
+
aria-label="Clear filter"
|
|
250
|
+
>
|
|
251
|
+
<Icon icon="Close" style="width: 1em; height: 1em" />
|
|
252
|
+
</button>
|
|
253
|
+
{/if}
|
|
254
|
+
{#if show_examples && !disabled}
|
|
255
|
+
<div class="examples-wrapper" bind:this={examples_wrapper}>
|
|
256
|
+
<button
|
|
257
|
+
type="button"
|
|
258
|
+
class="icon-btn help-btn"
|
|
259
|
+
class:active={examples_open}
|
|
260
|
+
onclick={toggle_examples}
|
|
261
|
+
title="Show search examples"
|
|
262
|
+
aria-label="Show search examples"
|
|
263
|
+
aria-expanded={examples_open}
|
|
264
|
+
aria-haspopup="menu"
|
|
265
|
+
>
|
|
266
|
+
<Icon icon="Info" style="width: 1.1em; height: 1.1em" />
|
|
267
|
+
</button>
|
|
268
|
+
{#if examples_open}
|
|
269
|
+
<div
|
|
270
|
+
class="examples-dropdown"
|
|
271
|
+
class:anchor-left={anchor_left}
|
|
272
|
+
role="menu"
|
|
273
|
+
tabindex="-1"
|
|
274
|
+
onkeydown={handle_menu_keydown}
|
|
275
|
+
>
|
|
276
|
+
{#each SEARCH_EXAMPLES as category (category.label)}
|
|
277
|
+
<div class="example-category">
|
|
278
|
+
<div class="category-label">{category.label}:</div>
|
|
279
|
+
<div class="example-tags">
|
|
280
|
+
{#each category.examples as example (example)}
|
|
281
|
+
<button
|
|
282
|
+
type="button"
|
|
283
|
+
class="example-tag"
|
|
284
|
+
data-example-item
|
|
285
|
+
onclick={() => apply_example(example)}
|
|
286
|
+
title={category.description}
|
|
287
|
+
role="menuitem"
|
|
288
|
+
tabindex="-1"
|
|
289
|
+
>
|
|
290
|
+
{example}
|
|
291
|
+
</button>
|
|
292
|
+
{/each}
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
{/each}
|
|
296
|
+
</div>
|
|
297
|
+
{/if}
|
|
298
|
+
</div>
|
|
299
|
+
{/if}
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<style>
|
|
303
|
+
.formula-filter {
|
|
304
|
+
position: relative;
|
|
305
|
+
display: flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
gap: 6pt;
|
|
308
|
+
padding: 4pt 8pt;
|
|
309
|
+
border-radius: 6px;
|
|
310
|
+
background: var(--filter-bg, rgba(128, 128, 128, 0.05));
|
|
311
|
+
transition: background 0.15s;
|
|
312
|
+
}
|
|
313
|
+
.formula-filter:focus-within {
|
|
314
|
+
background: rgba(77, 182, 255, 0.08);
|
|
315
|
+
}
|
|
316
|
+
.formula-filter.disabled {
|
|
317
|
+
opacity: 0.5;
|
|
318
|
+
pointer-events: none;
|
|
319
|
+
}
|
|
320
|
+
input {
|
|
321
|
+
flex: 1;
|
|
322
|
+
min-width: 0;
|
|
323
|
+
border: none;
|
|
324
|
+
background: transparent;
|
|
325
|
+
color: inherit;
|
|
326
|
+
padding: 2pt 0;
|
|
327
|
+
outline: none;
|
|
328
|
+
font-family: var(--mono-font, monospace);
|
|
329
|
+
}
|
|
330
|
+
input::placeholder {
|
|
331
|
+
opacity: 0.4;
|
|
332
|
+
}
|
|
333
|
+
.mode-hint {
|
|
334
|
+
opacity: 0.5;
|
|
335
|
+
white-space: nowrap;
|
|
336
|
+
}
|
|
337
|
+
.mode-hint.clickable {
|
|
338
|
+
display: inline-flex;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 2pt;
|
|
341
|
+
background: rgba(77, 182, 255, 0.1);
|
|
342
|
+
border: 1px solid rgba(77, 182, 255, 0.25);
|
|
343
|
+
border-radius: 4px;
|
|
344
|
+
padding: 1pt 5pt;
|
|
345
|
+
cursor: pointer;
|
|
346
|
+
color: var(--highlight, #4db6ff);
|
|
347
|
+
opacity: 0.8;
|
|
348
|
+
transition: opacity 0.15s, background 0.15s;
|
|
349
|
+
}
|
|
350
|
+
.mode-hint.clickable:hover {
|
|
351
|
+
opacity: 1;
|
|
352
|
+
background: rgba(77, 182, 255, 0.2);
|
|
353
|
+
border-color: rgba(77, 182, 255, 0.4);
|
|
354
|
+
}
|
|
355
|
+
.icon-btn {
|
|
356
|
+
display: flex;
|
|
357
|
+
align-items: center;
|
|
358
|
+
justify-content: center;
|
|
359
|
+
background: none;
|
|
360
|
+
border: none;
|
|
361
|
+
cursor: pointer;
|
|
362
|
+
padding: 3pt;
|
|
363
|
+
border-radius: 50%;
|
|
364
|
+
color: inherit;
|
|
365
|
+
opacity: 0.4;
|
|
366
|
+
}
|
|
367
|
+
.icon-btn:hover {
|
|
368
|
+
opacity: 1;
|
|
369
|
+
background: rgba(128, 128, 128, 0.15);
|
|
370
|
+
}
|
|
371
|
+
.icon-btn.active {
|
|
372
|
+
opacity: 1;
|
|
373
|
+
color: var(--highlight, #4db6ff);
|
|
374
|
+
}
|
|
375
|
+
.examples-wrapper {
|
|
376
|
+
position: relative;
|
|
377
|
+
}
|
|
378
|
+
.examples-dropdown {
|
|
379
|
+
position: absolute;
|
|
380
|
+
top: calc(100% + 4pt);
|
|
381
|
+
right: 0;
|
|
382
|
+
z-index: 100;
|
|
383
|
+
width: max-content;
|
|
384
|
+
background: var(--dropdown-bg, var(--surface-bg, #fff));
|
|
385
|
+
border: 1px solid var(--dropdown-border, rgba(128, 128, 128, 0.2));
|
|
386
|
+
border-radius: 8px;
|
|
387
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
388
|
+
padding: 8pt;
|
|
389
|
+
box-sizing: border-box;
|
|
390
|
+
display: flex;
|
|
391
|
+
flex-direction: column;
|
|
392
|
+
gap: 6pt;
|
|
393
|
+
}
|
|
394
|
+
.examples-dropdown.anchor-left {
|
|
395
|
+
right: auto;
|
|
396
|
+
left: 0;
|
|
397
|
+
}
|
|
398
|
+
.example-category {
|
|
399
|
+
display: flex;
|
|
400
|
+
align-items: center;
|
|
401
|
+
gap: 6pt;
|
|
402
|
+
flex-wrap: wrap;
|
|
403
|
+
}
|
|
404
|
+
.category-label {
|
|
405
|
+
font-size: 0.75em;
|
|
406
|
+
font-weight: 600;
|
|
407
|
+
opacity: 0.6;
|
|
408
|
+
min-width: 115px;
|
|
409
|
+
}
|
|
410
|
+
.example-tags {
|
|
411
|
+
display: flex;
|
|
412
|
+
gap: 4pt;
|
|
413
|
+
flex-wrap: wrap;
|
|
414
|
+
}
|
|
415
|
+
.example-tag {
|
|
416
|
+
background: rgba(77, 182, 255, 0.1);
|
|
417
|
+
border: 1px solid rgba(77, 182, 255, 0.3);
|
|
418
|
+
border-radius: 4px;
|
|
419
|
+
padding: 3pt 7pt;
|
|
420
|
+
font-size: 0.82em;
|
|
421
|
+
font-family: var(--mono-font, monospace);
|
|
422
|
+
color: var(--highlight, #4db6ff);
|
|
423
|
+
cursor: pointer;
|
|
424
|
+
}
|
|
425
|
+
.example-tag:hover {
|
|
426
|
+
background: rgba(77, 182, 255, 0.2);
|
|
427
|
+
border-color: rgba(77, 182, 255, 0.5);
|
|
428
|
+
}
|
|
429
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import type { FormulaSearchMode } from './index';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
value: string;
|
|
5
|
+
search_mode?: FormulaSearchMode;
|
|
6
|
+
input_element?: HTMLInputElement | null;
|
|
7
|
+
show_clear_button?: boolean;
|
|
8
|
+
show_examples?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
onchange?: (value: string, search_mode: FormulaSearchMode) => void;
|
|
11
|
+
onclear?: () => void;
|
|
12
|
+
} & HTMLAttributes<HTMLDivElement>;
|
|
13
|
+
declare const FormulaFilter: import("svelte").Component<$$ComponentProps, {}, "value" | "search_mode" | "input_element">;
|
|
14
|
+
type FormulaFilter = ReturnType<typeof FormulaFilter>;
|
|
15
|
+
export default FormulaFilter;
|
|
@@ -2,11 +2,13 @@ import type { ElementSymbol } from '..';
|
|
|
2
2
|
export { default as BarChart } from './BarChart.svelte';
|
|
3
3
|
export { default as BubbleChart } from './BubbleChart.svelte';
|
|
4
4
|
export { default as Composition } from './Composition.svelte';
|
|
5
|
-
export { default as Formula } from './Formula.svelte';
|
|
6
5
|
export * from './format';
|
|
6
|
+
export { default as Formula } from './Formula.svelte';
|
|
7
|
+
export { default as FormulaFilter } from './FormulaFilter.svelte';
|
|
7
8
|
export * from './parse';
|
|
8
9
|
export { default as PieChart } from './PieChart.svelte';
|
|
9
10
|
export type CompositionType = Partial<Record<ElementSymbol, number>>;
|
|
11
|
+
export type FormulaSearchMode = `elements` | `chemsys` | `exact`;
|
|
10
12
|
export type ChartSegmentData = {
|
|
11
13
|
element: ElementSymbol;
|
|
12
14
|
amount: number;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { default as BarChart } from './BarChart.svelte';
|
|
2
2
|
export { default as BubbleChart } from './BubbleChart.svelte';
|
|
3
3
|
export { default as Composition } from './Composition.svelte';
|
|
4
|
-
export { default as Formula } from './Formula.svelte';
|
|
5
4
|
export * from './format';
|
|
5
|
+
export { default as Formula } from './Formula.svelte';
|
|
6
|
+
export { default as FormulaFilter } from './FormulaFilter.svelte';
|
|
6
7
|
export * from './parse';
|
|
7
8
|
export { default as PieChart } from './PieChart.svelte';
|
|
8
9
|
export function get_chart_font_scale(base_scale, label_text, available_space, min_scale_factor = 0.7, base_font_size = 16) {
|
package/dist/constants.js
CHANGED
|
@@ -64,7 +64,11 @@ export const STRUCT_KEYWORDS_REGEX = new RegExp(`(${STRUCT_KEYWORDS.join(`|`)})`
|
|
|
64
64
|
export const STRUCT_KEYWORDS_STRICT_REGEX = new RegExp(`(${STRUCT_KEYWORDS_STRICT.join(`|`)})`, `i`);
|
|
65
65
|
export const TRAJ_KEYWORDS_SIMPLE_REGEX = new RegExp(`(${TRAJ_KEYWORDS.join(`|`)})`, `i`);
|
|
66
66
|
// File extensions for different file types
|
|
67
|
-
export const TRAJ_EXTENSIONS = Object.freeze([
|
|
67
|
+
export const TRAJ_EXTENSIONS = Object.freeze([
|
|
68
|
+
`.traj`,
|
|
69
|
+
`.xtc`,
|
|
70
|
+
`.lammpstrj`,
|
|
71
|
+
]);
|
|
68
72
|
export const TRAJ_EXTENSIONS_REGEX = new RegExp(`\\.(${TRAJ_EXTENSIONS.map((ext) => ext.slice(1)).join(`|`)})$`, `i`);
|
|
69
73
|
export const STRUCTURE_EXTENSIONS = Object.freeze([
|
|
70
74
|
`.cif`,
|