matterviz 0.3.0 → 0.3.1

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.
Files changed (89) hide show
  1. package/dist/MillerIndexInput.svelte +60 -0
  2. package/dist/MillerIndexInput.svelte.d.ts +7 -0
  3. package/dist/app.css +9 -2
  4. package/dist/brillouin/BrillouinZone.svelte +1 -1
  5. package/dist/composition/FormulaFilter.svelte +198 -10
  6. package/dist/composition/FormulaFilter.svelte.d.ts +2 -0
  7. package/dist/constants.d.ts +1 -0
  8. package/dist/constants.js +2 -0
  9. package/dist/convex-hull/ConvexHull.svelte +12 -1
  10. package/dist/convex-hull/ConvexHull2D.svelte +1 -1
  11. package/dist/convex-hull/ConvexHull3D.svelte +381 -128
  12. package/dist/convex-hull/ConvexHull3D.svelte.d.ts +1 -1
  13. package/dist/convex-hull/ConvexHull4D.svelte +76 -35
  14. package/dist/convex-hull/ConvexHull4D.svelte.d.ts +1 -1
  15. package/dist/convex-hull/ConvexHullControls.svelte +94 -31
  16. package/dist/convex-hull/ConvexHullControls.svelte.d.ts +3 -1
  17. package/dist/convex-hull/ConvexHullStats.svelte +187 -93
  18. package/dist/convex-hull/GasPressureControls.svelte +72 -38
  19. package/dist/convex-hull/GasPressureControls.svelte.d.ts +2 -1
  20. package/dist/convex-hull/TemperatureSlider.svelte +46 -19
  21. package/dist/convex-hull/TemperatureSlider.svelte.d.ts +2 -1
  22. package/dist/convex-hull/gas-thermodynamics.js +16 -5
  23. package/dist/convex-hull/helpers.d.ts +6 -0
  24. package/dist/convex-hull/helpers.js +43 -11
  25. package/dist/convex-hull/index.d.ts +14 -1
  26. package/dist/convex-hull/types.d.ts +2 -0
  27. package/dist/convex-hull/types.js +6 -0
  28. package/dist/element/index.d.ts +1 -1
  29. package/dist/element/index.js +1 -0
  30. package/dist/fermi-surface/FermiSurface.svelte +1 -1
  31. package/dist/fermi-surface/compute.js +1 -21
  32. package/dist/fermi-surface/marching-cubes.d.ts +2 -13
  33. package/dist/fermi-surface/marching-cubes.js +2 -519
  34. package/dist/fermi-surface/parse.js +1 -1
  35. package/dist/icons.d.ts +8 -0
  36. package/dist/icons.js +8 -0
  37. package/dist/index.d.ts +4 -1
  38. package/dist/index.js +4 -1
  39. package/dist/isosurface/Isosurface.svelte +175 -0
  40. package/dist/isosurface/Isosurface.svelte.d.ts +8 -0
  41. package/dist/isosurface/IsosurfaceControls.svelte +254 -0
  42. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +9 -0
  43. package/dist/isosurface/index.d.ts +5 -0
  44. package/dist/isosurface/index.js +6 -0
  45. package/dist/isosurface/parse.d.ts +6 -0
  46. package/dist/isosurface/parse.js +505 -0
  47. package/dist/isosurface/slice.d.ts +10 -0
  48. package/dist/isosurface/slice.js +145 -0
  49. package/dist/isosurface/types.d.ts +43 -0
  50. package/dist/isosurface/types.js +80 -0
  51. package/dist/layout/json-tree/JsonNode.svelte +196 -21
  52. package/dist/layout/json-tree/JsonTree.svelte +406 -33
  53. package/dist/layout/json-tree/JsonValue.svelte +44 -3
  54. package/dist/layout/json-tree/types.d.ts +19 -2
  55. package/dist/layout/json-tree/utils.d.ts +12 -1
  56. package/dist/layout/json-tree/utils.js +221 -0
  57. package/dist/marching-cubes.d.ts +14 -0
  58. package/dist/marching-cubes.js +519 -0
  59. package/dist/math.d.ts +1 -0
  60. package/dist/math.js +16 -0
  61. package/dist/overlays/DraggablePane.svelte +1 -1
  62. package/dist/plot/ColorBar.svelte +2 -2
  63. package/dist/plot/ColorScaleSelect.svelte +1 -1
  64. package/dist/plot/ElementScatter.svelte +1 -1
  65. package/dist/plot/data-cleaning.js +1 -5
  66. package/dist/plot/interactions.js +8 -16
  67. package/dist/plot/utils/label-placement.js +1 -1
  68. package/dist/rdf/RdfPlot.svelte.d.ts +1 -1
  69. package/dist/rdf/index.d.ts +1 -1
  70. package/dist/rdf/index.js +1 -1
  71. package/dist/settings.d.ts +2 -0
  72. package/dist/settings.js +20 -0
  73. package/dist/structure/AtomLegend.svelte +2 -2
  74. package/dist/structure/Lattice.svelte +2 -0
  75. package/dist/structure/Structure.svelte +613 -60
  76. package/dist/structure/Structure.svelte.d.ts +7 -2
  77. package/dist/structure/StructureControls.svelte +10 -1
  78. package/dist/structure/StructureControls.svelte.d.ts +5 -1
  79. package/dist/structure/StructureScene.svelte +295 -42
  80. package/dist/structure/StructureScene.svelte.d.ts +14 -4
  81. package/dist/structure/index.d.ts +1 -0
  82. package/dist/table/HeatmapTable.svelte +45 -6
  83. package/dist/table/HeatmapTable.svelte.d.ts +2 -1
  84. package/dist/table/ToggleMenu.svelte +289 -44
  85. package/dist/table/ToggleMenu.svelte.d.ts +4 -1
  86. package/dist/table/index.d.ts +1 -0
  87. package/dist/tooltip/index.d.ts +1 -1
  88. package/dist/tooltip/index.js +1 -0
  89. package/package.json +15 -11
@@ -0,0 +1,60 @@
1
+ <script lang="ts">let { value = $bindable([0, 0, 1]) } = $props();
2
+ // Format: compact "001" for single-digit, spaced "10 0 1" for multi-digit
3
+ let hkl_text = $derived(value.every((v) => Math.abs(v) < 10) ? value.join(``) : value.join(` `));
4
+ // Parse hkl string: supports compact "001"/"-101" and spaced/comma "10, 0, 1"
5
+ function parse_hkl(input) {
6
+ // Try spaced/comma format first (handles multi-digit)
7
+ const spaced = input.trim().split(/[,\s]+/);
8
+ if (spaced.length === 3) {
9
+ const nums = spaced.map(Number);
10
+ if (nums.every((n) => !isNaN(n)))
11
+ return nums;
12
+ }
13
+ // Fall back to compact single-digit format: "001", "-101"
14
+ const compact = input.replace(/\s+/g, ``);
15
+ const match = compact.match(/^(-?\d)(-?\d)(-?\d)$/);
16
+ if (match)
17
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
18
+ return null;
19
+ }
20
+ function handle_input(event) {
21
+ const parsed = parse_hkl(event.target.value);
22
+ if (parsed)
23
+ value = parsed;
24
+ }
25
+ export {};
26
+ </script>
27
+
28
+ <label class="miller-input">
29
+ <span>hkl</span>
30
+ <input
31
+ type="text"
32
+ value={hkl_text}
33
+ oninput={handle_input}
34
+ placeholder="001"
35
+ maxlength="12"
36
+ title="Miller indices (e.g. 001, -101, or 10 0 1)"
37
+ />
38
+ </label>
39
+
40
+ <style>
41
+ .miller-input {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 0.3em;
45
+ span {
46
+ font-weight: 600;
47
+ font-size: 0.85em;
48
+ }
49
+ input {
50
+ width: 4em;
51
+ padding: 0.15em 0.3em;
52
+ border: 1px solid var(--border-color, #ccc);
53
+ border-radius: 4px;
54
+ font-family: monospace;
55
+ font-size: 0.9em;
56
+ text-align: center;
57
+ box-sizing: border-box;
58
+ }
59
+ }
60
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { Vec3 } from './math';
2
+ type $$ComponentProps = {
3
+ value?: Vec3;
4
+ };
5
+ declare const MillerIndexInput: import("svelte").Component<$$ComponentProps, {}, "value">;
6
+ type MillerIndexInput = ReturnType<typeof MillerIndexInput>;
7
+ export default MillerIndexInput;
package/dist/app.css CHANGED
@@ -8,12 +8,14 @@
8
8
  --max-text-width: 50em;
9
9
 
10
10
  --sms-max-width: 20em;
11
+ --sms-min-height: 19pt;
11
12
  --sms-text-color: var(--text-color);
12
13
 
13
14
  /* Svelte MultiSelect */
14
15
  --sms-options-bg: var(--page-bg);
15
16
  --sms-active-color: light-dark(var(--accent-color), cornflowerblue);
16
17
  --sms-li-active-bg: light-dark(rgba(100, 149, 237, 0.25), cornflowerblue);
18
+ --sms-selected-bg: light-dark(rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.15));
17
19
  --border-radius: 3pt;
18
20
  }
19
21
 
@@ -33,18 +35,23 @@
33
35
  }
34
36
  body {
35
37
  background: var(--page-bg);
36
- padding: 6vh 3vw;
38
+ padding: 5vh 3vw 3vh;
37
39
  font-family: -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
38
40
  margin: auto;
39
41
  color: var(--text-color);
40
42
  line-height: 1.5;
43
+ /* Sticky footer: footer stays at bottom when content is short */
44
+ display: flex;
45
+ flex-direction: column;
46
+ min-height: 100vh;
47
+ box-sizing: border-box;
41
48
  }
42
49
  main {
43
50
  margin: auto;
44
- margin-bottom: 3em;
45
51
  width: 100%;
46
52
  max-width: var(--max-text-width);
47
53
  container-type: inline-size;
54
+ flex: 1; /* Grow to fill available space (sticky footer) */
48
55
  }
49
56
  a {
50
57
  color: var(--accent-color, cornflowerblue);
@@ -394,7 +394,7 @@ $effect(() => {
394
394
  display: flex;
395
395
  padding: 4px;
396
396
  border-radius: var(--border-radius, 3pt);
397
- font-size: clamp(0.85em, 2cqmin, 2.5em);
397
+ font-size: clamp(0.85em, 2cqmin, 1.3em);
398
398
  }
399
399
  section.control-buttons :global(button:hover) {
400
400
  background-color: color-mix(in srgb, currentColor 8%, transparent);
@@ -3,7 +3,7 @@ import { tooltip } from 'svelte-multiselect';
3
3
  import { extract_formula_elements, has_wildcards, normalize_element_symbols, parse_formula_with_wildcards, } from './parse';
4
4
  const SEARCH_EXAMPLES = [
5
5
  {
6
- label: `Contains elements`,
6
+ label: `Has elements`,
7
7
  description: `Materials containing at least these elements (may have others). Use * for any element.`,
8
8
  examples: [`Li,Fe`, `Si,O`, `Li,*,*`],
9
9
  },
@@ -18,23 +18,90 @@ const SEARCH_EXAMPLES = [
18
18
  examples: [`LiFePO4`, `LiFe*2*`, `*2O3`],
19
19
  },
20
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();
21
+ let { value = $bindable(``), search_mode = $bindable(`elements`), input_element = $bindable(null), show_clear_button = true, show_examples = true, disabled = false, max_history = 5, // Max recent inputs to remember; 0 disables history dropdown
22
+ history_key = `formula-filter-history`, // localStorage key for persisting history
23
+ onchange, onclear, ...rest } = $props();
22
24
  let input_value = $state(value);
23
25
  let examples_open = $state(false);
26
+ let history_open = $state(false);
24
27
  let wrapper = $state(null);
25
28
  let examples_wrapper = $state(null);
26
29
  let focused_item_idx = $state(-1);
30
+ let focused_history_idx = $state(-1);
27
31
  let anchor_left = $state(false);
28
32
  // Flatten examples for keyboard navigation
29
33
  const all_examples = SEARCH_EXAMPLES.flatMap((cat) => cat.examples);
34
+ // === History Management ===
35
+ const has_storage = typeof localStorage !== `undefined`;
36
+ function load_history() {
37
+ if (max_history <= 0 || !has_storage)
38
+ return [];
39
+ try {
40
+ const raw = localStorage.getItem(history_key);
41
+ if (!raw)
42
+ return [];
43
+ const parsed = JSON.parse(raw);
44
+ if (!Array.isArray(parsed))
45
+ return [];
46
+ return parsed.filter((item) => typeof item === `string`).slice(0, max_history);
47
+ }
48
+ catch {
49
+ return [];
50
+ }
51
+ }
52
+ function save_history(entries) {
53
+ if (max_history <= 0 || !has_storage)
54
+ return;
55
+ try {
56
+ localStorage.setItem(history_key, JSON.stringify(entries.slice(0, max_history)));
57
+ }
58
+ catch {
59
+ // localStorage may be unavailable (e.g. private browsing)
60
+ }
61
+ }
62
+ let history = $state(load_history());
63
+ function add_to_history(entry) {
64
+ if (max_history <= 0 || !entry.trim())
65
+ return;
66
+ // Remove duplicate if present, then prepend
67
+ const filtered = history.filter((item) => item !== entry);
68
+ history = [entry, ...filtered].slice(0, max_history);
69
+ save_history(history);
70
+ }
71
+ function remove_from_history(entry) {
72
+ history = history.filter((item) => item !== entry);
73
+ save_history(history);
74
+ // Clamp focused index to prevent out-of-bounds access on Enter
75
+ if (history.length === 0)
76
+ history_open = false;
77
+ else if (focused_history_idx >= visible_history.length) {
78
+ focused_history_idx = visible_history.length - 1;
79
+ }
80
+ }
81
+ // Filtered history: exclude current value to avoid redundant suggestion
82
+ let visible_history = $derived(history.filter((item) => item !== value));
83
+ function close_history() {
84
+ history_open = false;
85
+ focused_history_idx = -1;
86
+ }
87
+ function open_history() {
88
+ if (max_history <= 0 || visible_history.length === 0 || examples_open)
89
+ return;
90
+ history_open = true;
91
+ focused_history_idx = -1;
92
+ }
30
93
  function handle_document_click(event) {
31
- if (!wrapper || !examples_open)
94
+ if (!wrapper || (!examples_open && !history_open))
32
95
  return;
33
96
  const target = event.target;
34
97
  if (!(target instanceof Node))
35
98
  return;
36
- if (!wrapper.contains(target))
37
- close_examples();
99
+ if (!wrapper.contains(target)) {
100
+ if (examples_open)
101
+ close_examples();
102
+ if (history_open)
103
+ close_history();
104
+ }
38
105
  }
39
106
  function close_examples(restore_focus = true) {
40
107
  examples_open = false;
@@ -75,7 +142,7 @@ function infer_mode(input) {
75
142
  if (!trimmed)
76
143
  return `elements`;
77
144
  if (trimmed.includes(`,`))
78
- return `elements`; // Li,Fe,O → contains elements
145
+ return `elements`; // Li,Fe,O → has elements
79
146
  if (trimmed.includes(`-`))
80
147
  return `chemsys`; // Li-Fe-O → chemical system
81
148
  return `exact`; // LiFePO4 → exact formula
@@ -141,6 +208,9 @@ function set_value(new_value) {
141
208
  const mode = infer_mode(new_value);
142
209
  last_synced = value = input_value = new_value; // update last_synced to prevent effect re-inference
143
210
  search_mode = mode;
211
+ if (new_value.trim())
212
+ add_to_history(new_value);
213
+ close_history();
144
214
  onchange?.(value, mode);
145
215
  }
146
216
  function sync_value() {
@@ -167,14 +237,34 @@ function sync_value() {
167
237
  function onkeydown(event) {
168
238
  if (event.key === `Enter`) {
169
239
  event.preventDefault();
170
- sync_value();
240
+ if (history_open && focused_history_idx >= 0) {
241
+ set_value(visible_history[focused_history_idx]);
242
+ }
243
+ else {
244
+ sync_value();
245
+ }
171
246
  }
172
247
  else if (event.key === `Escape`) {
173
- if (examples_open)
248
+ if (history_open)
249
+ close_history();
250
+ else if (examples_open)
174
251
  examples_open = false;
175
252
  else if (input_value)
176
253
  clear_filter();
177
254
  }
255
+ else if (history_open && visible_history.length > 0) {
256
+ const len = visible_history.length;
257
+ if (event.key === `ArrowDown`) {
258
+ event.preventDefault();
259
+ focused_history_idx = (focused_history_idx + 1) % len;
260
+ }
261
+ else if (event.key === `ArrowUp`) {
262
+ event.preventDefault();
263
+ focused_history_idx = focused_history_idx <= 0
264
+ ? len - 1
265
+ : focused_history_idx - 1;
266
+ }
267
+ }
178
268
  }
179
269
  function clear_filter() {
180
270
  onclear?.();
@@ -186,6 +276,7 @@ function apply_example(example) {
186
276
  }
187
277
  function toggle_examples(event) {
188
278
  event.stopPropagation();
279
+ close_history();
189
280
  examples_open = !examples_open;
190
281
  focused_item_idx = examples_open ? 0 : -1;
191
282
  if (examples_open)
@@ -224,7 +315,7 @@ let placeholder = $derived(search_mode === `chemsys`
224
315
  ? `LiFePO4 or LiFe*2*`
225
316
  : `Li,Fe,O or Li,*,*`);
226
317
  const MODE_LABELS = {
227
- elements: `contains elements`,
318
+ elements: `has elements`,
228
319
  chemsys: `chemical system`,
229
320
  exact: `exact formula`,
230
321
  };
@@ -244,12 +335,51 @@ let next_mode = $derived.by(() => {
244
335
  <input
245
336
  bind:this={input_element}
246
337
  bind:value={input_value}
247
- onblur={sync_value}
338
+ onblur={() => {
339
+ // mousedown preventDefault on history items prevents blur, so this only
340
+ // fires when focus genuinely leaves (tab out, click outside, etc.)
341
+ // sync_value → set_value → close_history, so no separate close needed
342
+ sync_value()
343
+ }}
344
+ onfocus={open_history}
248
345
  {onkeydown}
249
346
  {placeholder}
250
347
  {disabled}
251
348
  aria-label="Formula filter"
252
349
  />
350
+ {#if history_open && visible_history.length > 0}
351
+ <div class="history-dropdown" role="listbox" aria-label="Recent searches">
352
+ <span class="history-header">Recent</span>
353
+ {#each visible_history as entry, idx (entry)}
354
+ <div class="history-item" class:focused={idx === focused_history_idx}>
355
+ <button
356
+ type="button"
357
+ class="history-value"
358
+ role="option"
359
+ aria-selected={idx === focused_history_idx}
360
+ onmousedown={(event) => {
361
+ event.preventDefault()
362
+ set_value(entry)
363
+ }}
364
+ >
365
+ {entry}
366
+ </button>
367
+ <button
368
+ type="button"
369
+ class="history-remove"
370
+ title="Remove from history"
371
+ aria-label="Remove {entry} from history"
372
+ onmousedown={(event) => {
373
+ event.preventDefault()
374
+ remove_from_history(entry)
375
+ }}
376
+ >
377
+ <Icon icon="Close" style="width: 0.7em; height: 0.7em" />
378
+ </button>
379
+ </div>
380
+ {/each}
381
+ </div>
382
+ {/if}
253
383
  {#if input_value}
254
384
  <button
255
385
  type="button"
@@ -394,6 +524,64 @@ let next_mode = $derived.by(() => {
394
524
  opacity: 1;
395
525
  color: var(--highlight, #4db6ff);
396
526
  }
527
+ .history-dropdown {
528
+ position: absolute;
529
+ top: calc(100% + 2pt);
530
+ left: 0;
531
+ right: 0;
532
+ z-index: 101;
533
+ background: var(--dropdown-bg, var(--surface-bg, #fff));
534
+ border: 1px solid var(--dropdown-border, rgba(128, 128, 128, 0.2));
535
+ border-radius: 8px;
536
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
537
+ padding: 4pt 0;
538
+ display: flex;
539
+ flex-direction: column;
540
+ }
541
+ .history-header {
542
+ font-size: 0.7em;
543
+ font-weight: 600;
544
+ opacity: 0.45;
545
+ padding: 2pt 10pt 4pt;
546
+ text-transform: uppercase;
547
+ letter-spacing: 0.5px;
548
+ }
549
+ .history-item {
550
+ display: flex;
551
+ align-items: center;
552
+ padding: 0 4pt 0 0;
553
+ }
554
+ .history-item.focused,
555
+ .history-item:hover {
556
+ background: rgba(77, 182, 255, 0.08);
557
+ }
558
+ .history-value {
559
+ flex: 1;
560
+ text-align: left;
561
+ background: none;
562
+ border: none;
563
+ cursor: pointer;
564
+ padding: 4pt 10pt;
565
+ font-family: var(--mono-font, monospace);
566
+ font-size: 0.88em;
567
+ color: inherit;
568
+ }
569
+ .history-remove {
570
+ display: flex;
571
+ align-items: center;
572
+ justify-content: center;
573
+ background: none;
574
+ border: none;
575
+ cursor: pointer;
576
+ padding: 3pt;
577
+ border-radius: 50%;
578
+ opacity: 0.3;
579
+ color: inherit;
580
+ }
581
+ .history-remove:hover {
582
+ opacity: 0.8;
583
+ background: rgba(128, 128, 128, 0.15);
584
+ }
397
585
  .examples-wrapper {
398
586
  position: relative;
399
587
  }
@@ -7,6 +7,8 @@ type $$ComponentProps = {
7
7
  show_clear_button?: boolean;
8
8
  show_examples?: boolean;
9
9
  disabled?: boolean;
10
+ max_history?: number;
11
+ history_key?: string;
10
12
  onchange?: (value: string, search_mode: FormulaSearchMode) => void;
11
13
  onclear?: () => void;
12
14
  } & HTMLAttributes<HTMLDivElement>;
@@ -21,6 +21,7 @@ export declare const STRUCTURE_EXTENSIONS_REGEX: RegExp;
21
21
  export declare const TRAJ_FALLBACK_EXTENSIONS: readonly string[];
22
22
  export declare const TRAJ_FALLBACK_EXTENSIONS_REGEX: RegExp;
23
23
  export declare const VASP_FILES_REGEX: RegExp;
24
+ export declare const VASP_VOLUMETRIC_REGEX: RegExp;
24
25
  export declare const XDATCAR_REGEX: RegExp;
25
26
  export declare const CONFIG_DIRS_REGEX: RegExp;
26
27
  export declare const MD_SIM_EXCLUDE_REGEX: RegExp;
package/dist/constants.js CHANGED
@@ -75,6 +75,7 @@ export const STRUCTURE_EXTENSIONS = Object.freeze([
75
75
  `.mcif`,
76
76
  `.poscar`,
77
77
  `.vasp`,
78
+ `.cube`,
78
79
  `.lmp`,
79
80
  `.data`,
80
81
  `.dump`,
@@ -95,6 +96,7 @@ export const TRAJ_FALLBACK_EXTENSIONS = Object.freeze([
95
96
  export const TRAJ_FALLBACK_EXTENSIONS_REGEX = new RegExp(`\\.(${TRAJ_FALLBACK_EXTENSIONS.map((ext) => ext.slice(1)).join(`|`)})$`, `i`);
96
97
  // Special regex patterns
97
98
  export const VASP_FILES_REGEX = /(?:^|[\\/_.-])(poscar|contcar|potcar|incar|kpoints|outcar)(?:[\\/_.-]|$)/i;
99
+ export const VASP_VOLUMETRIC_REGEX = /(?:^|[\\/_.-])(chgcar|aeccar[012]?|elfcar|locpot|parchg)(?:[\\/_.-]|$)/i;
98
100
  export const XDATCAR_REGEX = /xdatcar/i;
99
101
  export const CONFIG_DIRS_REGEX = /(?:^|[\\/])(\.vscode|\.idea|\.nyc_output|\.cache|\.tmp|\.temp|node_modules|dist|build|coverage)(?:[\\/]|$)/i;
100
102
  export const MD_SIM_EXCLUDE_REGEX = /md_simulation\.(out|txt|yml|py|csv|html|css|md|js|ts)$/i;
@@ -1,11 +1,12 @@
1
1
  <script lang="ts">import { extract_formula_elements } from '../composition/parse';
2
+ import { DEFAULTS } from '../settings';
2
3
  import { SvelteSet } from 'svelte/reactivity';
3
4
  import ConvexHull2D from './ConvexHull2D.svelte';
4
5
  import ConvexHull3D from './ConvexHull3D.svelte';
5
6
  import ConvexHull4D from './ConvexHull4D.svelte';
6
7
  let { entries = [],
7
8
  // bindable props not part of rest because Svelte 5 doesn't support spreading bindable props.
8
- fullscreen = $bindable(false), wrapper = $bindable(), show_stable = $bindable(true), show_unstable = $bindable(true), show_hull_faces = $bindable(true), hull_face_opacity = $bindable(0.3), color_mode = $bindable(`energy`), color_scale = $bindable(`interpolateViridis`), info_pane_open = $bindable(false), legend_pane_open = $bindable(false), max_hull_dist_show_phases = $bindable(0.1), max_hull_dist_show_labels = $bindable(0.1), show_stable_labels = $bindable(true), show_unstable_labels = $bindable(false), energy_source_mode = $bindable(`precomputed`), phase_stats = $bindable(null), display = $bindable({ x_grid: false, y_grid: false }), stable_entries = $bindable([]), unstable_entries = $bindable([]), highlighted_entries = $bindable([]), selected_entry = $bindable(null), temperature = $bindable(), gas_pressures = $bindable({}), ...rest } = $props();
9
+ fullscreen = $bindable(false), wrapper = $bindable(), show_stable = $bindable(true), show_unstable = $bindable(true), show_hull_faces = $bindable(true), hull_face_opacity: hull_face_opacity_prop = $bindable(undefined), color_mode = $bindable(`energy`), color_scale = $bindable(`interpolateViridis`), info_pane_open = $bindable(false), legend_pane_open = $bindable(false), max_hull_dist_show_phases = $bindable(0.1), max_hull_dist_show_labels = $bindable(0.1), show_stable_labels = $bindable(true), show_unstable_labels = $bindable(false), energy_source_mode = $bindable(`precomputed`), phase_stats = $bindable(null), display = $bindable({ x_grid: false, y_grid: false }), stable_entries = $bindable([]), unstable_entries = $bindable([]), highlighted_entries = $bindable([]), selected_entry = $bindable(null), temperature = $bindable(), gas_pressures = $bindable({}), ...rest } = $props();
9
10
  // Lightweight element extraction - count unique elements, stripping oxidation states
10
11
  // (e.g. "V4+" -> "V") to avoid counting the same element multiple times
11
12
  function extract_unique_elements(entries) {
@@ -23,6 +24,16 @@ function extract_unique_elements(entries) {
23
24
  // Detect dimensionality by counting unique elements (lightweight operation)
24
25
  const elements = $derived(extract_unique_elements(entries));
25
26
  const element_count = $derived(elements.length);
27
+ // Resolve hull face opacity: use caller's value if provided,
28
+ // otherwise pick the right default for the dimensionality (ternary=30%, quaternary=3%)
29
+ // Writable $derived handles forward sync (prop→local), back-sync $effect handles local→prop
30
+ const default_opacity = $derived(element_count === 4
31
+ ? DEFAULTS.convex_hull.quaternary.hull_face_opacity
32
+ : DEFAULTS.convex_hull.ternary.hull_face_opacity);
33
+ let hull_face_opacity = $derived(hull_face_opacity_prop ?? default_opacity);
34
+ $effect(() => {
35
+ hull_face_opacity_prop = hull_face_opacity;
36
+ });
26
37
  // Map element count to corresponding component
27
38
  // Note: Type assertion needed because TypeScript can't infer that all components
28
39
  // accept a compatible superset of props (BaseConvexHullProps + dimension-specific)
@@ -647,7 +647,7 @@ let style = $derived(`--hull-stable-color:${merged_config.colors?.stable || `#00
647
647
  color: var(--text-color, currentColor);
648
648
  transition: background-color 0.2s, opacity 0.2s;
649
649
  display: flex;
650
- font-size: clamp(0.85em, 2cqmin, 2.5em);
650
+ font-size: clamp(0.85em, 2cqmin, 1.3em);
651
651
  }
652
652
  :global(.convex-hull-2d .control-btn:hover) {
653
653
  background-color: color-mix(in srgb, currentColor 8%, transparent);