__init__.py
1 # SPDX-License-Identifier: MIT 2 # 3 # Copyright (c) 2021 The Anvil Extras project team members listed at 4 # https://github.com/anvilistas/anvil-extras/graphs/contributors 5 # 6 # This software is published at https://github.com/anvilistas/anvil-extras 7 from anvil import HtmlPanel as _HtmlPanel 8 from anvil.js import get_dom_node 9 10 from ...utils._component_helpers import _add_roles, _remove_roles 11 from ._anvil_designer import OptionTemplate 12 13 __version__ = "3.1.0" 14 15 16 class Divider(_HtmlPanel): 17 _anvil_events_ = ["click"] 18 is_divider = True 19 20 def __init__(self): 21 get_dom_node(self).dataset["divider"] = "" 22 self.key = "" 23 self.value = None 24 25 @property 26 def selected(self): 27 return False 28 29 @selected.setter 30 def selected(self, val): 31 pass 32 33 34 class Option(OptionTemplate): 35 is_divider = False 36 37 def __init__(self, **properties): 38 self._props = properties 39 self._dom_node = get_dom_node(self) 40 self._icon_node = get_dom_node(self.icon_checked) 41 self._icon_node.style.visibility = "hidden" 42 self.label.role = "ae-ms-option-label" 43 self.label_sub.role = "ae-ms-option-subtext" 44 self.role = ["ae-ms-option"] 45 46 self.init_components(**properties) 47 self._dom_node.addEventListener("focus", self._on_focus) 48 self._dom_node.addEventListener("blur", self._on_blur) 49 50 @property 51 def selected(self): 52 return self._props.get("selected", False) 53 54 @selected.setter 55 def selected(self, val): 56 if self.disabled: 57 return 58 self._props["selected"] = val 59 self._icon_node.style.visibility = "visible" if val else "hidden" 60 61 @property 62 def active(self): 63 return "ae-ms-option-active" in self.role 64 65 @active.setter 66 def active(self, val): 67 if val: 68 _add_roles(self, "ae-ms-option-active") 69 self._dom_node.scrollIntoView({"block": "nearest"}) 70 else: 71 _remove_roles(self, "ae-ms-option-active") 72 73 @property 74 def disabled(self): 75 return self._props.get("disabled", False) 76 77 @disabled.setter 78 def disabled(self, val): 79 self._props["disabled"] = val 80 if val: 81 self._dom_node.dataset["disabled"] = "" 82 else: 83 del self._dom_node.dataset["disabled"] 84 85 def focus(self): 86 self._dom_node.focus() 87 88 def click(self): 89 self.raise_event("click") 90 91 def _on_focus(self, e): 92 if not self.disabled: 93 self.active = True 94 95 def _on_blur(self, e): 96 self.active = False 97 98 def _on_click(self, **event_args): 99 self.selected = not self.selected 100 101 def _on_hide(self, **event_args): 102 self.active = False 103 104 def _on_show(self, **event_args): 105 if self.disabled: 106 # and option isn't dynamic so we can do this on the show event and not worry about it 107 self._dom_node.parentElement.style.cursor = "not-allowed" 108 109 @classmethod 110 def from_str(cls, item: str, idx: str) -> tuple: 111 key = value = item 112 if item == "---": 113 return Divider() 114 else: 115 return cls(idx=idx, key=key, value=value) 116 117 @classmethod 118 def from_tuple(cls, item: tuple, idx: int) -> tuple: 119 key, value = item 120 if not isinstance(key, str): 121 raise TypeError( 122 f"expected a tuple of the form str, value in items at idx {idx}" 123 ) 124 return cls(idx=idx, key=key, value=value) 125 126 @classmethod 127 def from_dict(cls, item: dict, idx: int) -> tuple: 128 sentinel = object() 129 130 # if they only set a key and not a value then use the key as the value 131 value = item.get("value", sentinel) 132 if value is sentinel: 133 value = item.get("key") 134 135 title = item.get("title", "") 136 icon = item.get("icon", "") 137 subtext = item.get("subtext", "") 138 disabled = not item.get("enabled", True) 139 key = item.get("key") 140 141 return cls( 142 idx=idx, 143 key=key, 144 value=value, 145 icon=icon, 146 subtext=subtext, 147 title=title, 148 disabled=disabled, 149 ) 150 151 @classmethod 152 def from_items(cls, items): 153 options = [] 154 155 for idx, item in enumerate(items): 156 if isinstance(item, str): 157 option = cls.from_str(item, idx) 158 elif isinstance(item, (tuple, list)): 159 option = cls.from_tuple(item, idx) 160 elif isinstance(item, dict): 161 option = cls.from_dict(item, idx) 162 else: 163 raise TypeError(f"Invalid item at index {idx} (got type {type(item)})") 164 165 options.append(option) 166 167 return options