react-bio-viz 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,44 @@
1
+ """Jupyter widgets for biological data visualization.
2
+
3
+ Thin `anywidget <https://anywidget.dev>`_ bindings around the ``react-bio-viz`` React components.
4
+ Every piece of interactive state is a synced traitlet, so the kernel can both drive and observe it::
5
+
6
+ from react_bio_viz import MSA
7
+
8
+ widget = MSA(msa=[{"header": "seq1", "sequence": "ACGT"}])
9
+ widget.observe(lambda change: print(change["new"]), names="viewport")
10
+ widget # renders in the notebook
11
+ widget.viewport = {...} # drives the view from Python
12
+
13
+ Each trait is bridged to the component as a ``StoreController``, the same external-store seam a
14
+ JavaScript app fills with a Zustand store — the components themselves contain no Jupyter-specific
15
+ code.
16
+ """
17
+
18
+ from ._base import BioVizWidget, fit_to_extent
19
+ from .blasthitdistribution import BlastHitDistribution
20
+ from .distancematrix import DistanceMatrix
21
+ from .genemodel import GeneModel
22
+ from .genomebrowser import GenomeBrowser
23
+ from .io import parse_fasta, parse_newick, read_fasta, read_newick, to_newick
24
+ from .msa import MSA
25
+ from .phylotree import PhyloTree
26
+
27
+ __version__ = "0.1.0"
28
+
29
+ __all__ = [
30
+ "BioVizWidget",
31
+ "BlastHitDistribution",
32
+ "DistanceMatrix",
33
+ "GeneModel",
34
+ "GenomeBrowser",
35
+ "MSA",
36
+ "PhyloTree",
37
+ "fit_to_extent",
38
+ "parse_fasta",
39
+ "parse_newick",
40
+ "read_fasta",
41
+ "read_newick",
42
+ "to_newick",
43
+ "__version__",
44
+ ]
react_bio_viz/_base.py ADDED
@@ -0,0 +1,74 @@
1
+ """Shared plumbing for every react-bio-viz widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pathlib
6
+ from typing import Any, Callable, Mapping
7
+
8
+ import anywidget
9
+ import traitlets
10
+
11
+ _STATIC = pathlib.Path(__file__).parent / "static"
12
+
13
+ #: One bundle serves every widget, dispatching on the ``_component`` trait, so React ships once.
14
+ _ESM = _STATIC / "widget.js"
15
+ _CSS = _STATIC / "widget.css"
16
+
17
+
18
+ def fit_to_extent(extent: Mapping[str, float]) -> dict[str, float]:
19
+ """A viewport showing the full extent, i.e. fully zoomed out — ``fitToExtent`` in the JS core."""
20
+ return {
21
+ "x0": extent["xMin"],
22
+ "x1": extent["xMax"],
23
+ "y0": extent["yMin"],
24
+ "y1": extent["yMax"],
25
+ "xMin": extent["xMin"],
26
+ "xMax": extent["xMax"],
27
+ "yMin": extent["yMin"],
28
+ "yMax": extent["yMax"],
29
+ }
30
+
31
+
32
+ class BioVizWidget(anywidget.AnyWidget):
33
+ """Base class for the react-bio-viz Jupyter widgets.
34
+
35
+ Every piece of interactive state is a synced trait, observable and writable from the kernel::
36
+
37
+ widget = MSA(msa=records)
38
+ widget.observe(lambda change: print(change["new"]), names="viewport")
39
+ widget.viewport = {...} # drives the view from the kernel
40
+ """
41
+
42
+ _esm = _ESM
43
+ _css = _CSS
44
+
45
+ #: Selects which React component the shared bundle renders.
46
+ _component = traitlets.Unicode("").tag(sync=True)
47
+
48
+ #: The visible window, as a plain ``Viewport`` dict. Seeded on construction so it is readable
49
+ #: and observable before the user has interacted with the widget.
50
+ viewport = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
51
+
52
+ def __init__(self, **kwargs: Any) -> None:
53
+ super().__init__(**kwargs)
54
+ if self.viewport is None:
55
+ extent = self._default_extent()
56
+ if extent is not None:
57
+ self.viewport = fit_to_extent(extent)
58
+
59
+ def _default_extent(self) -> dict[str, float] | None:
60
+ """The data extent this widget's viewport spans, or ``None`` if it has no viewport."""
61
+ return None
62
+
63
+ def _on_event(self, event: str, callback: Callable[..., None], *fields: str) -> None:
64
+ """Calls ``callback`` with ``fields`` of every ``event`` message the component sends.
65
+
66
+ One-off user actions (a rename, a click) arrive as custom messages, not traits: a trait would
67
+ drop the second of two identical events.
68
+ """
69
+
70
+ def handler(_widget: Any, content: Mapping[str, Any], _buffers: Any) -> None:
71
+ if isinstance(content, Mapping) and content.get("event") == event:
72
+ callback(*(content.get(field) for field in fields))
73
+
74
+ self.on_msg(handler)
@@ -0,0 +1,44 @@
1
+ """BLAST hit distribution widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import traitlets
8
+
9
+ from ._base import BioVizWidget
10
+
11
+
12
+ class BlastHitDistribution(BioVizWidget):
13
+ """BLAST hits along one query sequence, row-stacked and colored by a chosen metric.
14
+
15
+ :param hits: ``[{"id", "queryId", "subjectId", "queryStart", "queryEnd", "evalue",
16
+ "bitScore", "percentIdentity"}, ...]``.
17
+ :param selection: ``{"selectedHitIds": [...]}``; clicking a hit toggles it and writes this back.
18
+ :param metric: ``"evalue"``, ``"bitScore"`` or ``"percentIdentity"``; written back when the
19
+ user picks another in the widget's selector.
20
+
21
+ >>> w = BlastHitDistribution(hits=hits, query_length=2000)
22
+ >>> w.observe(lambda c: print(c["new"]["selectedHitIds"]), names="selection")
23
+ """
24
+
25
+ _component = traitlets.Unicode("blasthitdistribution").tag(sync=True)
26
+
27
+ hits = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
28
+ query_length = traitlets.Int(0).tag(sync=True)
29
+ query_name = traitlets.Unicode("").tag(sync=True)
30
+ width = traitlets.Int(800).tag(sync=True)
31
+ show_scale = traitlets.Bool(True).tag(sync=True)
32
+ metric = traitlets.Enum(["evalue", "bitScore", "percentIdentity"], default_value="evalue").tag(sync=True)
33
+
34
+ selection = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
35
+
36
+ def __init__(self, **kwargs: Any) -> None:
37
+ super().__init__(**kwargs)
38
+ if self.selection is None:
39
+ self.selection = {"selectedHitIds": []}
40
+
41
+ def _default_extent(self) -> dict[str, float] | None:
42
+ if not self.query_length:
43
+ return None
44
+ return {"xMin": 0, "xMax": self.query_length, "yMin": 0, "yMax": 1}
@@ -0,0 +1,40 @@
1
+ """Pairwise distance matrix widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import traitlets
6
+
7
+ from ._base import BioVizWidget
8
+
9
+
10
+ class DistanceMatrix(BioVizWidget):
11
+ """A pairwise distance matrix as a heatmap, windowed so it scales to thousands of rows.
12
+
13
+ :param labels: Row/column ids, in ``matrix`` order.
14
+ :param matrix: Symmetric ``len(labels)`` × ``len(labels)`` list of lists.
15
+ :param label_names: Optional ``{id: display name}``.
16
+ :param options: ``{"showNumbers": bool, "colorScheme": "warm" | "cool" | "green" | "grayscale"}``.
17
+ :param row_order: Display order of rows (and columns), as ids; written back when labels are
18
+ dragged. Same shape as :class:`MSA`'s, so the two can be linked.
19
+
20
+ >>> w = DistanceMatrix(labels=["a", "b"], matrix=[[0, 0.1], [0.1, 0]])
21
+ >>> w.observe(print, names="row_order")
22
+ """
23
+
24
+ _component = traitlets.Unicode("distancematrix").tag(sync=True)
25
+
26
+ labels = traitlets.List(traitlets.Unicode(), default_value=[]).tag(sync=True)
27
+ matrix = traitlets.List(traitlets.List(traitlets.Float()), default_value=[]).tag(sync=True)
28
+ label_names = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
29
+ width = traitlets.Int(650).tag(sync=True)
30
+ height = traitlets.Int(500).tag(sync=True)
31
+ options = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
32
+
33
+ row_order = traitlets.List(traitlets.Unicode(), default_value=[]).tag(sync=True)
34
+ panel_sizes = traitlets.Dict(default_value={"labelWidth": 160}).tag(sync=True)
35
+
36
+ def _default_extent(self) -> dict[str, float] | None:
37
+ if not self.labels:
38
+ return None
39
+ n = len(self.labels)
40
+ return {"xMin": 0, "xMax": n, "yMin": 0, "yMax": n}
@@ -0,0 +1,35 @@
1
+ """Gene model widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import traitlets
6
+
7
+ from ._base import BioVizWidget
8
+
9
+ #: Fraction of the gene's length padded onto each side of the default view, mirroring the
10
+ #: component's own ``VIEWPORT_PADDING_RATIO``.
11
+ _PADDING_RATIO = 0.1
12
+
13
+
14
+ class GeneModel(BioVizWidget):
15
+ """A (potentially spliced) gene model: transcripts with their exons and CDSs.
16
+
17
+ :param gene: A gff3-shaped ``SequenceInterval`` dict, with nested ``children``.
18
+
19
+ >>> w = GeneModel(gene=gff3_record)
20
+ >>> w.viewport = {**w.viewport, "x0": 35_040_000, "x1": 35_042_000} # zoom from the kernel
21
+ """
22
+
23
+ _component = traitlets.Unicode("genemodel").tag(sync=True)
24
+
25
+ gene = traitlets.Dict(default_value={}).tag(sync=True)
26
+ width = traitlets.Int(500).tag(sync=True)
27
+ color_seed = traitlets.Unicode("42").tag(sync=True)
28
+ show_scale = traitlets.Bool(True).tag(sync=True)
29
+
30
+ def _default_extent(self) -> dict[str, float] | None:
31
+ start, end = self.gene.get("start"), self.gene.get("end")
32
+ if start is None or end is None:
33
+ return None
34
+ padding = round(_PADDING_RATIO * (end - start))
35
+ return {"xMin": max(0, start - padding), "xMax": end + padding, "yMin": 0, "yMax": 1}
@@ -0,0 +1,31 @@
1
+ """Multi-track genome browser widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import traitlets
6
+
7
+ from ._base import BioVizWidget
8
+
9
+
10
+ class GenomeBrowser(BioVizWidget):
11
+ """Stacked genome tracks sharing one genomic-coordinate viewport.
12
+
13
+ :param tracks: ``[{"id", "label", "kind", "data"}, ...]`` where ``kind`` is one of
14
+ ``"feature"``, ``"coverage"`` or ``"genemodel"``.
15
+ :param reference_length: Length of the reference sequence; the x-axis extent.
16
+
17
+ >>> w = GenomeBrowser(tracks=tracks, reference_length=35_045_433)
18
+ """
19
+
20
+ _component = traitlets.Unicode("genomebrowser").tag(sync=True)
21
+
22
+ tracks = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
23
+ reference_length = traitlets.Int(0).tag(sync=True)
24
+ reference_name = traitlets.Unicode("").tag(sync=True)
25
+ width = traitlets.Int(1000).tag(sync=True)
26
+ show_scale = traitlets.Bool(True).tag(sync=True)
27
+
28
+ def _default_extent(self) -> dict[str, float] | None:
29
+ if not self.reference_length:
30
+ return None
31
+ return {"xMin": 0, "xMax": self.reference_length, "yMin": 0, "yMax": 1}
react_bio_viz/io.py ADDED
@@ -0,0 +1,133 @@
1
+ """Reading alignments and trees into the plain structures the widgets take.
2
+
3
+ The widgets take plain JSON-able data — a list of ``{"header", "sequence"}`` records for
4
+ :class:`~react_bio_viz.MSA`, nested ``{"name", "length", "children"}`` dicts for
5
+ :class:`~react_bio_viz.PhyloTree` — so any parser works. These cover the common formats without
6
+ extra dependencies, and parse Newick the same way the JavaScript ``parseNewick`` does.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ import re
13
+ from typing import Any, Union
14
+
15
+ PathLike = Union[str, "os.PathLike[str]"]
16
+
17
+ Record = dict[str, str]
18
+ TreeNode = dict[str, Any]
19
+
20
+
21
+ def parse_fasta(text: str) -> list[Record]:
22
+ """Parses FASTA text into ``[{"header": ..., "sequence": ...}, ...]``.
23
+
24
+ The header is the whole line after ``>``; sequence lines are joined with whitespace removed.
25
+ """
26
+ records: list[Record] = []
27
+ for line in text.splitlines():
28
+ line = line.strip()
29
+ if not line:
30
+ continue
31
+ if line.startswith(">"):
32
+ records.append({"header": line[1:].strip(), "sequence": ""})
33
+ elif records:
34
+ records[-1]["sequence"] += re.sub(r"\s+", "", line)
35
+ else:
36
+ raise ValueError("FASTA text must start with a '>' header line")
37
+ return records
38
+
39
+
40
+ def read_fasta(path: PathLike) -> list[Record]:
41
+ """Reads a FASTA file — see :func:`parse_fasta`."""
42
+ with open(path, encoding="utf-8") as handle:
43
+ return parse_fasta(handle.read())
44
+
45
+
46
+ def parse_newick(text: str) -> TreeNode:
47
+ """Parses a Newick string into nested ``{"name", "length", "children"}`` dicts.
48
+
49
+ Internal-node labels (typically bootstrap support) become the node's ``name``; ``:length``
50
+ suffixes become ``length`` (0 when absent). Quoted labels (``'a b'``, with ``''`` for a literal
51
+ quote) are unquoted, so FASTA-style names with spaces or ``|`` survive a round trip.
52
+ """
53
+ position = 0
54
+
55
+ def skip_space() -> None:
56
+ nonlocal position
57
+ while position < len(text) and text[position].isspace():
58
+ position += 1
59
+
60
+ def parse_label() -> str:
61
+ nonlocal position
62
+ skip_space()
63
+ if position < len(text) and text[position] in "'\"":
64
+ quote = text[position]
65
+ position += 1
66
+ label = []
67
+ while position < len(text):
68
+ if text[position] == quote:
69
+ if position + 1 < len(text) and text[position + 1] == quote:
70
+ label.append(quote)
71
+ position += 2
72
+ continue
73
+ position += 1
74
+ break
75
+ label.append(text[position])
76
+ position += 1
77
+ return "".join(label)
78
+ start = position
79
+ while position < len(text) and text[position] not in ":,();":
80
+ position += 1
81
+ return text[start:position].strip()
82
+
83
+ def parse_node() -> TreeNode:
84
+ nonlocal position
85
+ node: TreeNode = {"name": "", "length": 0.0, "children": []}
86
+ skip_space()
87
+ if position < len(text) and text[position] == "(":
88
+ position += 1
89
+ node["children"].append(parse_node())
90
+ skip_space()
91
+ while position < len(text) and text[position] == ",":
92
+ position += 1
93
+ node["children"].append(parse_node())
94
+ skip_space()
95
+ if position < len(text) and text[position] == ")":
96
+ position += 1
97
+ node["name"] = parse_label()
98
+ skip_space()
99
+ if position < len(text) and text[position] == ":":
100
+ position += 1
101
+ start = position
102
+ while position < len(text) and text[position] not in ",);":
103
+ position += 1
104
+ try:
105
+ node["length"] = float(text[start:position])
106
+ except ValueError:
107
+ node["length"] = 0.0
108
+ return node
109
+
110
+ return parse_node()
111
+
112
+
113
+ def read_newick(path: PathLike) -> TreeNode:
114
+ """Reads a Newick file — see :func:`parse_newick`."""
115
+ with open(path, encoding="utf-8") as handle:
116
+ return parse_newick(handle.read())
117
+
118
+
119
+ def _quote(name: str) -> str:
120
+ return f"'{name.replace(chr(39), chr(39) * 2)}'" if re.search(r"[\s:,;()\[\]']", name) else name
121
+
122
+
123
+ def to_newick(tree: TreeNode) -> str:
124
+ """Serializes nested ``{"name", "length", "children"}`` dicts to Newick, quoting names as needed."""
125
+
126
+ def write(node: TreeNode, is_root: bool) -> str:
127
+ children = node.get("children") or []
128
+ inner = "(" + ",".join(write(child, False) for child in children) + ")" if children else ""
129
+ length = node.get("length") or 0
130
+ suffix = "" if is_root and not length else f":{length:g}"
131
+ return f"{inner}{_quote(node.get('name', ''))}{suffix}"
132
+
133
+ return write(tree, True) + ";"
react_bio_viz/msa.py ADDED
@@ -0,0 +1,61 @@
1
+ """Multiple sequence alignment widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable
6
+
7
+ import traitlets
8
+
9
+ from ._base import BioVizWidget
10
+
11
+
12
+ class MSA(BioVizWidget):
13
+ """A multiple sequence alignment, with pan/zoom, a column ruler, a minimap and a consensus row.
14
+
15
+ :param msa: The alignment, as ``[{"header": ..., "sequence": ...}, ...]``.
16
+ :param options: Rendering toggles, mirroring ``MSADrawOptions`` (camelCase keys, e.g.
17
+ ``{"colorStyle": "AA Zappo", "tracks": ["conservation", "logo"]}``). Omitting
18
+ ``colorStyle`` lets the component pick a scheme from the alignment's own alphabet.
19
+ :param selection: Selected rows (by id — a record's ``id``, else its ``header``) and columns,
20
+ ``{"rows": [...], "columns": [...]}``; written back as the user selects.
21
+ :param row_order: Display order of the rows, as ids; written back when labels are dragged.
22
+ :param panel_sizes: ``{"labelWidth", "trackHeight", "minimapHeight"}`` in pixels.
23
+
24
+ The widget never edits ``msa`` itself: register :meth:`on_rename_row`,
25
+ :meth:`on_remove_rows` or :meth:`on_remove_columns` to apply edits (which also enables them).
26
+
27
+ >>> w = MSA(msa=[{"header": "a", "sequence": "ACGT"}])
28
+ >>> w.observe(print, names="viewport") # fires when the user pans or zooms
29
+ >>> w.observe(print, names="selection")
30
+ """
31
+
32
+ _component = traitlets.Unicode("msa").tag(sync=True)
33
+
34
+ msa = traitlets.List(traitlets.Dict(), default_value=[]).tag(sync=True)
35
+ width = traitlets.Int(650).tag(sync=True)
36
+ height = traitlets.Int(400).tag(sync=True)
37
+ options = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
38
+
39
+ selection = traitlets.Dict(default_value={"rows": [], "columns": []}).tag(sync=True)
40
+ row_order = traitlets.List(traitlets.Unicode(), default_value=[]).tag(sync=True)
41
+ panel_sizes = traitlets.Dict(
42
+ default_value={"labelWidth": 150, "trackHeight": 48, "minimapHeight": 50}
43
+ ).tag(sync=True)
44
+
45
+ def on_rename_row(self, callback: Callable[[str, str], Any]) -> None:
46
+ """Calls ``callback(row_id, new_name)`` when the user renames a row label."""
47
+ self._on_event("rename_row", callback, "row_id", "name")
48
+
49
+ def on_remove_rows(self, callback: Callable[[list[str]], Any]) -> None:
50
+ """Calls ``callback(row_ids)`` when the user removes rows (× on a label, or Delete)."""
51
+ self._on_event("remove_rows", callback, "row_ids")
52
+
53
+ def on_remove_columns(self, callback: Callable[[list[int]], Any]) -> None:
54
+ """Calls ``callback(columns)`` when the user deletes selected columns."""
55
+ self._on_event("remove_columns", callback, "columns")
56
+
57
+ def _default_extent(self) -> dict[str, float] | None:
58
+ if not self.msa:
59
+ return None
60
+ columns = len(self.msa[0].get("sequence", ""))
61
+ return {"xMin": 0, "xMax": columns, "yMin": 0, "yMax": len(self.msa)}
@@ -0,0 +1,80 @@
1
+ """Phylogenetic tree widget."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Callable
6
+
7
+ import traitlets
8
+
9
+ from ._base import BioVizWidget
10
+
11
+
12
+ class PhyloTree(BioVizWidget):
13
+ """A phylogenetic tree in rectangular, cladogram or radial layout.
14
+
15
+ :param tree: Recursively nested ``{"name": ..., "length": ..., "children": [...]}``.
16
+ :param selection: Where the tree is rooted and how it is arranged —
17
+ ``{"rerootedAt": str | None, "rerootPosition": float, "collapsed": [str], "order": {...}}``.
18
+ Writable from the kernel and written back when the user collapses, drags or reroots.
19
+ :param node_styles: ``{node_id: {"color": ..., "bold": ...}}``.
20
+ :param branch_styles: ``{node_id: {"color": ...}}`` for the branch above each node.
21
+ :param leaf_order: Read-only: leaf names in display order, updated as the user rearranges.
22
+
23
+ >>> w = PhyloTree(tree=newick_dict, tree_layout="radial", interactive=True)
24
+ >>> w.observe(lambda c: print(c["new"]["collapsed"]), names="selection")
25
+ >>> w.on_node_click(lambda node: print(node["id"], node["leafNames"]))
26
+ """
27
+
28
+ _component = traitlets.Unicode("phylotree").tag(sync=True)
29
+
30
+ tree = traitlets.Dict(default_value={}).tag(sync=True)
31
+ width = traitlets.Int(1000).tag(sync=True)
32
+ height = traitlets.Int(900).tag(sync=True)
33
+ #: ``"rectangular"``, ``"cladogram"`` or ``"radial"``. Not called ``layout``: every ipywidgets
34
+ #: widget already has a ``layout`` trait (its CSS layout), and shadowing it breaks the view.
35
+ tree_layout = traitlets.Enum(
36
+ ["rectangular", "cladogram", "radial"], default_value="rectangular"
37
+ ).tag(sync=True)
38
+ show_support_values = traitlets.Bool(True).tag(sync=True)
39
+ shade_branch_by_support = traitlets.Bool(True).tag(sync=True)
40
+ font_size = traitlets.Int(10).tag(sync=True)
41
+ align_tips = traitlets.Bool(True).tag(sync=True)
42
+ interactive = traitlets.Bool(False).tag(sync=True)
43
+ search_query = traitlets.Unicode(allow_none=True, default_value=None).tag(sync=True)
44
+ search_use_regex = traitlets.Bool(False).tag(sync=True)
45
+ show_scale_bar = traitlets.Bool(True).tag(sync=True)
46
+ show_branch_lengths = traitlets.Bool(False).tag(sync=True)
47
+ support_threshold = traitlets.Float(0).tag(sync=True)
48
+ drag_enabled = traitlets.Bool(allow_none=True, default_value=None).tag(sync=True)
49
+ branch_width = traitlets.Float(0.75).tag(sync=True)
50
+ node_radius = traitlets.Float(4).tag(sync=True)
51
+ label_font_size = traitlets.Float(11).tag(sync=True)
52
+ leaf_spacing = traitlets.Float(allow_none=True, default_value=None).tag(sync=True)
53
+ leaf_marker_color = traitlets.Unicode(allow_none=True, default_value=None).tag(sync=True)
54
+ node_styles = traitlets.Dict(default_value={}).tag(sync=True)
55
+ branch_styles = traitlets.Dict(default_value={}).tag(sync=True)
56
+ active_node_id = traitlets.Unicode(allow_none=True, default_value=None).tag(sync=True)
57
+ leaf_order = traitlets.List(traitlets.Unicode(), default_value=[]).tag(sync=True)
58
+
59
+ selection = traitlets.Dict(allow_none=True, default_value=None).tag(sync=True)
60
+
61
+ def __init__(self, **kwargs: Any) -> None:
62
+ # Accept the React prop's name as a convenience; a string can only mean the tree layout
63
+ # (ipywidgets' own `layout` takes a Layout widget or a dict).
64
+ if isinstance(kwargs.get("layout"), str):
65
+ kwargs["tree_layout"] = kwargs.pop("layout")
66
+ super().__init__(**kwargs)
67
+ if self.selection is None:
68
+ self.selection = {"collapsed": []}
69
+
70
+ def on_node_click(self, callback: Callable[[dict[str, Any]], Any]) -> None:
71
+ """Calls ``callback(node)`` when a node marker is clicked (see ``TreeNodeInfo``)."""
72
+ self._on_event("node_click", callback, "node")
73
+
74
+ def on_branch_click(self, callback: Callable[[dict[str, Any]], Any]) -> None:
75
+ """Calls ``callback(node)`` — the node below the branch — when a branch is clicked."""
76
+ self._on_event("branch_click", callback, "node")
77
+
78
+ def _default_extent(self) -> dict[str, float] | None:
79
+ # The tree's pan/zoom is over the rendered pixel surface, not over data coordinates.
80
+ return {"xMin": 0, "xMax": self.width, "yMin": 0, "yMax": self.height}
@@ -0,0 +1,2 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:where(:root,:host){--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-medium:500;--font-weight-bold:700;--radius-md:calc(var(--radius) * .8);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base{:where(:root){--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--destructive:oklch(57.7% .245 27.325);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--ring:oklch(70.8% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--radius:.625rem;--rbv-accent:#305cde;--rbv-accent-rgb:48, 92, 222}:where(.dark){--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--destructive:oklch(70.4% .191 22.216);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--ring:oklch(55.6% 0 0);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--rbv-accent:#7da3ff;--rbv-accent-rgb:125, 163, 255}:where(.rbv,.rbv *){box-sizing:border-box;border:0 solid;border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){:where(.rbv,.rbv *){outline-color:color-mix(in oklab,var(--ring) 50%,transparent)}}:where(.rbv) :where(button,input,select,textarea){font:inherit;color:inherit;letter-spacing:inherit;background-color:#0000;margin:0;padding:0}:where(.rbv) :where(button){cursor:pointer}:where(.rbv) :where(ul,ol){margin:0;padding:0;list-style:none}:where(.rbv) :where(svg,canvas){display:block}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.right-2{right:calc(var(--spacing) * 2)}.left-0{left:0}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.my-1{margin-block:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-0\.5{height:calc(var(--spacing) * .5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-px{height:1px}.max-h-\(--radix-select-content-available-height\){max-height:var(--radix-select-content-available-height)}.w-72{width:calc(var(--spacing) * 72)}.w-fit{width:fit-content}.w-full{width:100%}.w-px{width:1px}.max-w-xs{max-width:var(--container-xs)}.min-w-7{min-width:calc(var(--spacing) * 7)}.min-w-8{min-width:calc(var(--spacing) * 8)}.min-w-9{min-width:calc(var(--spacing) * 9)}.min-w-36{min-width:calc(var(--spacing) * 36)}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-\(--radix-popover-content-transform-origin\){transform-origin:var(--radix-popover-content-transform-origin)}.origin-\(--radix-select-content-transform-origin\){transform-origin:var(--radix-select-content-transform-origin)}.origin-\(--radix-tooltip-content-transform-origin\){transform-origin:var(--radix-tooltip-content-transform-origin)}.translate-y-\[calc\(-50\%_-_2px\)\]{--tw-translate-y: calc(-50% - 2px) ;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-45{rotate:45deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize{resize:both}.scroll-my-1{scroll-margin-block:var(--spacing)}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-\[--spacing\(var\(--gap\)\)\]{gap:calc(var(--spacing) * var(--gap))}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[2px\]{border-radius:2px}.rounded-\[min\(var\(--radius-md\)\,10px\)\]{border-radius:min(var(--radius-md),10px)}.rounded-\[min\(var\(--radius-md\)\,12px\)\]{border-radius:min(var(--radius-md),12px)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) * .8)}.rounded-sm{border-radius:calc(var(--radius) * .6)}.border{border-style:var(--tw-border-style);border-width:1px}.border-border{border-color:var(--border)}.border-input{border-color:var(--input)}.border-ring{border-color:var(--ring)}.border-transparent{border-color:#0000}.bg-background{background-color:var(--background)}.bg-border{background-color:var(--border)}.bg-current{background-color:currentColor}.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/10{background-color:color-mix(in oklab,var(--destructive) 10%,transparent)}}.bg-foreground{background-color:var(--foreground)}.bg-muted{background-color:var(--muted)}.bg-popover{background-color:var(--popover)}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-clip-padding{background-clip:padding-box}.fill-foreground{fill:var(--foreground)}.stroke-1{stroke-width:1px}.stroke-\[1\.5px\]{stroke-width:1.5px}.p-1{padding:var(--spacing)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pl-1\.5{padding-left:calc(var(--spacing) * 1.5)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.text-background{color:var(--background)}.text-destructive{color:var(--destructive)}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-popover-foreground{color:var(--popover-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.italic{font-style:italic}.underline-offset-4{text-underline-offset:4px}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-foreground\/10{--tw-ring-color:var(--foreground)}@supports (color:color-mix(in lab,red,red)){.ring-foreground\/10{--tw-ring-color:color-mix(in oklab, var(--foreground) 10%, transparent)}}.ring-ring\/50{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.ring-ring\/50{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.grayscale{--tw-grayscale:grayscale(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}@media (hover:hover){.group-hover\:bg-\(--rbv-accent\):is(:where(.group):hover *){background-color:var(--rbv-accent)}}.group-data-\[spacing\=0\]\/toggle-group\:rounded-none:is(:where(.group\/toggle-group)[data-spacing="0"] *){border-radius:0}.group-data-\[spacing\=0\]\/toggle-group\:px-2:is(:where(.group\/toggle-group)[data-spacing="0"] *){padding-inline:calc(var(--spacing) * 2)}.after\:absolute:after{content:var(--tw-content);position:absolute}.after\:-inset-2:after{content:var(--tw-content);inset:calc(var(--spacing) * -2)}@media (hover:hover){.hover\:bg-\[color-mix\(in_oklch\,var\(--secondary\)\,var\(--foreground\)_5\%\)\]:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-\[color-mix\(in_oklch\,var\(--secondary\)\,var\(--foreground\)_5\%\)\]:hover{background-color:color-mix(in oklch,var(--secondary),var(--foreground) 5%)}}.hover\:bg-destructive\/20:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/20:hover{background-color:color-mix(in oklab,var(--destructive) 20%,transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-primary\/80:hover{background-color:var(--primary)}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/80:hover{background-color:color-mix(in oklab,var(--primary) 80%,transparent)}}.hover\:stroke-\(--rbv-accent\):hover{stroke:var(--rbv-accent)}.hover\:stroke-2:hover{stroke-width:2px}.hover\:stroke-\[3px\]:hover{stroke-width:3px}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:ring-3:hover{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:z-10:focus{z-index:10}.focus\:bg-accent:focus{background-color:var(--accent)}.focus\:text-accent-foreground:focus,:is(.not-data-\[variant\=destructive\]\:focus\:\*\*\:text-accent-foreground:not([data-variant=destructive]):focus *){color:var(--accent-foreground)}.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:border-destructive\/40:focus-visible{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:border-destructive\/40:focus-visible{border-color:color-mix(in oklab,var(--destructive) 40%,transparent)}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-3:focus-visible,.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab,red,red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.focus-visible\:outline-hidden:focus-visible{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.focus-visible\:outline-hidden:focus-visible{outline-offset:2px;outline:2px solid #0000}}.active\:ring-3:active{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.active\:not-aria-\[haspopup\]\:translate-y-px:active:not([aria-haspopup]){--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}:where([data-slot=button-group]) .in-data-\[slot\=button-group\]\:rounded-lg{border-radius:var(--radius)}.has-data-\[icon\=inline-end\]\:pr-1\.5:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-end\]\:pr-2:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 2)}.group-data-\[spacing\=0\]\/toggle-group\:has-data-\[icon\=inline-end\]\:pr-1\.5:is(:where(.group\/toggle-group)[data-spacing="0"] *):has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-start\]\:pl-1\.5:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-start\]\:pl-2:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 2)}.group-data-\[spacing\=0\]\/toggle-group\:has-data-\[icon\=inline-start\]\:pl-1\.5:is(:where(.group\/toggle-group)[data-spacing="0"] *):has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 1.5)}.has-data-\[slot\=kbd\]\:pr-1\.5:has([data-slot=kbd]){padding-right:calc(var(--spacing) * 1.5)}.aria-expanded\:bg-muted[aria-expanded=true]{background-color:var(--muted)}.aria-expanded\:bg-secondary[aria-expanded=true]{background-color:var(--secondary)}.aria-expanded\:text-foreground[aria-expanded=true]{color:var(--foreground)}.aria-expanded\:text-secondary-foreground[aria-expanded=true]{color:var(--secondary-foreground)}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-3[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.aria-pressed\:bg-muted[aria-pressed=true]{background-color:var(--muted)}.data-placeholder\:text-muted-foreground[data-placeholder]{color:var(--muted-foreground)}.data-\[align-trigger\=true\]\:animate-none[data-align-trigger=true]{animation:none}.data-\[position\=popper\]\:h-\(--radix-select-trigger-height\)[data-position=popper]{height:var(--radix-select-trigger-height)}.data-\[position\=popper\]\:w-full[data-position=popper]{width:100%}.data-\[position\=popper\]\:min-w-\(--radix-select-trigger-width\)[data-position=popper]{min-width:var(--radix-select-trigger-width)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[size\=default\]\:h-8[data-size=default]{height:calc(var(--spacing) * 8)}.data-\[size\=sm\]\:h-7[data-size=sm]{height:calc(var(--spacing) * 7)}.data-\[size\=sm\]\:rounded-\[min\(var\(--radius-md\)\,10px\)\][data-size=sm]{border-radius:min(var(--radius-md),10px)}:is(.\*\*\:data-\[slot\=kbd\]\:relative *)[data-slot=kbd]{position:relative}:is(.\*\*\:data-\[slot\=kbd\]\:isolate *)[data-slot=kbd]{isolation:isolate}:is(.\*\*\:data-\[slot\=kbd\]\:z-50 *)[data-slot=kbd]{z-index:50}:is(.\*\*\:data-\[slot\=kbd\]\:rounded-sm *)[data-slot=kbd]{border-radius:calc(var(--radius) * .6)}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-1\.5>*)[data-slot=select-value]{gap:calc(var(--spacing) * 1.5)}.group-data-horizontal\/toggle-group\:data-\[spacing\=0\]\:first\:rounded-l-lg:is(:where(.group\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"]:first-child{border-top-left-radius:var(--radius);border-bottom-left-radius:var(--radius)}.group-data-vertical\/toggle-group\:data-\[spacing\=0\]\:first\:rounded-t-lg:is(:where(.group\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"]:first-child{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.group-data-horizontal\/toggle-group\:data-\[spacing\=0\]\:last\:rounded-r-lg:is(:where(.group\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"]:last-child{border-top-right-radius:var(--radius);border-bottom-right-radius:var(--radius)}.group-data-vertical\/toggle-group\:data-\[spacing\=0\]\:last\:rounded-b-lg:is(:where(.group\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"]:last-child{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.data-\[state\=delayed-open\]\:animate-in[data-state=delayed-open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=delayed-open\]\:fade-in-0[data-state=delayed-open]{--tw-enter-opacity:0}.data-\[state\=delayed-open\]\:zoom-in-95[data-state=delayed-open]{--tw-enter-scale:.95}.data-\[state\=on\]\:bg-muted[data-state=on]{background-color:var(--muted)}.group-data-horizontal\/toggle-group\:data-\[spacing\=0\]\:data-\[variant\=outline\]\:border-l-0:is(:where(.group\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"][data-variant=outline]{border-left-style:var(--tw-border-style);border-left-width:0}.group-data-vertical\/toggle-group\:data-\[spacing\=0\]\:data-\[variant\=outline\]\:border-t-0:is(:where(.group\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"][data-variant=outline]{border-top-style:var(--tw-border-style);border-top-width:0}.group-data-horizontal\/toggle-group\:data-\[spacing\=0\]\:data-\[variant\=outline\]\:first\:border-l:is(:where(.group\/toggle-group):where([data-orientation=horizontal]) *)[data-spacing="0"][data-variant=outline]:first-child{border-left-style:var(--tw-border-style);border-left-width:1px}.group-data-vertical\/toggle-group\:data-\[spacing\=0\]\:data-\[variant\=outline\]\:first\:border-t:is(:where(.group\/toggle-group):where([data-orientation=vertical]) *)[data-spacing="0"][data-variant=outline]:first-child{border-top-style:var(--tw-border-style);border-top-width:1px}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:bg-destructive\/20:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-destructive\/20:is(.dark *){background-color:color-mix(in oklab,var(--destructive) 20%,transparent)}}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab,var(--input) 30%,transparent)}}@media (hover:hover){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:color-mix(in oklab,var(--destructive) 30%,transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--input) 50%,transparent)}}.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:var(--muted)}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:color-mix(in oklab,var(--muted) 50%,transparent)}}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:color-mix(in oklab,var(--destructive) 50%,transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab,red,red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.data-open\:animate-in:where([data-state=open]),.data-open\:animate-in:where([data-open]:not([data-open=false])){animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-open\:fade-in-0:where([data-state=open]),.data-open\:fade-in-0:where([data-open]:not([data-open=false])){--tw-enter-opacity:0}.data-open\:zoom-in-95:where([data-state=open]),.data-open\:zoom-in-95:where([data-open]:not([data-open=false])){--tw-enter-scale:.95}.data-closed\:animate-out:where([data-state=closed]),.data-closed\:animate-out:where([data-closed]:not([data-closed=false])){animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-closed\:fade-out-0:where([data-state=closed]),.data-closed\:fade-out-0:where([data-closed]:not([data-closed=false])){--tw-exit-opacity:0}.data-closed\:zoom-out-95:where([data-state=closed]),.data-closed\:zoom-out-95:where([data-closed]:not([data-closed=false])){--tw-exit-scale:.95}.data-disabled\:pointer-events-none:where([data-disabled=true]),.data-disabled\:pointer-events-none:where([data-disabled]:not([data-disabled=false])){pointer-events:none}.data-disabled\:opacity-50:where([data-disabled=true]),.data-disabled\:opacity-50:where([data-disabled]:not([data-disabled=false])){opacity:.5}.data-horizontal\:h-1:where([data-orientation=horizontal]){height:var(--spacing)}.data-horizontal\:h-full:where([data-orientation=horizontal]){height:100%}.data-horizontal\:w-full:where([data-orientation=horizontal]){width:100%}.data-vertical\:h-full:where([data-orientation=vertical]){height:100%}.data-vertical\:min-h-40:where([data-orientation=vertical]){min-height:calc(var(--spacing) * 40)}.data-vertical\:w-1:where([data-orientation=vertical]){width:var(--spacing)}.data-vertical\:w-auto:where([data-orientation=vertical]){width:auto}.data-vertical\:w-full:where([data-orientation=vertical]){width:100%}.data-vertical\:flex-col:where([data-orientation=vertical]){flex-direction:column}.data-vertical\:items-stretch:where([data-orientation=vertical]){align-items:stretch}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3 svg:not([class*=size-]){width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3\.5 svg:not([class*=size-]){width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&\:hover\+path\]\:stroke-\(--rbv-accent\):hover+path{stroke:var(--rbv-accent)}:is(.\*\:\[span\]\:last\:flex>*):is(span):last-child{display:flex}:is(.\*\:\[span\]\:last\:items-center>*):is(span):last-child{align-items:center}:is(.\*\:\[span\]\:last\:gap-2>*):is(span):last-child{gap:calc(var(--spacing) * 2)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-content{syntax:"*";inherits:false;initial-value:""}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0)scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1))rotate(var(--tw-exit-rotate,0));filter:blur(var(--tw-exit-blur,0))}}
2
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */