bootstack 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.
Files changed (471) hide show
  1. bootstack/__init__.py +157 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/_core/__init__.py +21 -0
  4. bootstack/_core/capabilities/__init__.py +45 -0
  5. bootstack/_core/capabilities/after.py +103 -0
  6. bootstack/_core/capabilities/bind.py +192 -0
  7. bootstack/_core/capabilities/bindtags.py +112 -0
  8. bootstack/_core/capabilities/busy.py +72 -0
  9. bootstack/_core/capabilities/clipboard.py +89 -0
  10. bootstack/_core/capabilities/focus.py +118 -0
  11. bootstack/_core/capabilities/grab.py +65 -0
  12. bootstack/_core/capabilities/grid.py +211 -0
  13. bootstack/_core/capabilities/localization.py +231 -0
  14. bootstack/_core/capabilities/pack.py +140 -0
  15. bootstack/_core/capabilities/place.py +113 -0
  16. bootstack/_core/capabilities/selection.py +136 -0
  17. bootstack/_core/capabilities/signals.py +244 -0
  18. bootstack/_core/capabilities/winfo.py +315 -0
  19. bootstack/_core/colorutils.py +234 -0
  20. bootstack/_core/exceptions.py +25 -0
  21. bootstack/_core/images.py +463 -0
  22. bootstack/_core/mixins/__init__.py +1 -0
  23. bootstack/_core/mixins/ttk_state.py +35 -0
  24. bootstack/_core/mixins/widget.py +132 -0
  25. bootstack/_core/paths.py +49 -0
  26. bootstack/_core/publisher.py +149 -0
  27. bootstack/_core/variables.py +62 -0
  28. bootstack/_runtime/__init__.py +3 -0
  29. bootstack/_runtime/app.py +930 -0
  30. bootstack/_runtime/base_window.py +945 -0
  31. bootstack/_runtime/events.py +399 -0
  32. bootstack/_runtime/shortcuts.py +496 -0
  33. bootstack/_runtime/tk_patch.py +43 -0
  34. bootstack/_runtime/toplevel.py +276 -0
  35. bootstack/_runtime/utility.py +457 -0
  36. bootstack/_runtime/visual_focus.py +240 -0
  37. bootstack/_runtime/window_utilities.py +1123 -0
  38. bootstack/assets/__init__.py +21 -0
  39. bootstack/assets/bootstack.ico +0 -0
  40. bootstack/assets/bootstack.png +0 -0
  41. bootstack/assets/elements/__init__.py +0 -0
  42. bootstack/assets/elements/badge-pill.png +0 -0
  43. bootstack/assets/elements/badge-square.png +0 -0
  44. bootstack/assets/elements/button-compact.png +0 -0
  45. bootstack/assets/elements/button-default.png +0 -0
  46. bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
  47. bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
  48. bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
  49. bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
  50. bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
  51. bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
  52. bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
  53. bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
  54. bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
  55. bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
  56. bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
  57. bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
  58. bootstack/assets/elements/card.png +0 -0
  59. bootstack/assets/elements/checkbox-checked.png +0 -0
  60. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  61. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  62. bootstack/assets/elements/field.png +0 -0
  63. bootstack/assets/elements/input-addon-compact.png +0 -0
  64. bootstack/assets/elements/input-addon-default.png +0 -0
  65. bootstack/assets/elements/input-compact.png +0 -0
  66. bootstack/assets/elements/input-default.png +0 -0
  67. bootstack/assets/elements/list-item-separated.png +0 -0
  68. bootstack/assets/elements/list-item.png +0 -0
  69. bootstack/assets/elements/listrow-compact.png +0 -0
  70. bootstack/assets/elements/listrow-default.png +0 -0
  71. bootstack/assets/elements/manifest.toml +361 -0
  72. bootstack/assets/elements/menu-item.png +0 -0
  73. bootstack/assets/elements/navitem-compact.png +0 -0
  74. bootstack/assets/elements/navitem-default.png +0 -0
  75. bootstack/assets/elements/progressbar-h-compact.png +0 -0
  76. bootstack/assets/elements/progressbar-h-default.png +0 -0
  77. bootstack/assets/elements/progressbar-v-compact.png +0 -0
  78. bootstack/assets/elements/progressbar-v-default.png +0 -0
  79. bootstack/assets/elements/radiobutton.png +0 -0
  80. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  81. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  82. bootstack/assets/elements/slider-handle.png +0 -0
  83. bootstack/assets/elements/slider-track-h.png +0 -0
  84. bootstack/assets/elements/slider-track-v.png +0 -0
  85. bootstack/assets/elements/switch-off.png +0 -0
  86. bootstack/assets/elements/switch-on.png +0 -0
  87. bootstack/assets/elements/tab-h.png +0 -0
  88. bootstack/assets/elements/tab-v.png +0 -0
  89. bootstack/assets/icons/bootstrap.ttf +0 -0
  90. bootstack/assets/icons/glyphmap.json +2080 -0
  91. bootstack/assets/icons/icon_metrics.json +12470 -0
  92. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  93. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
  94. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
  95. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
  96. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  97. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
  98. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  99. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
  100. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  101. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
  102. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  103. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
  104. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  105. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
  106. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  107. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
  108. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  109. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
  110. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  111. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
  112. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  113. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
  114. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  115. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
  116. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  117. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
  118. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  119. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
  120. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  121. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
  122. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  123. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
  124. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  125. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
  126. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  127. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
  128. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  129. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
  130. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  131. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
  132. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  133. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
  134. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  135. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
  136. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  137. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
  138. bootstack/cli/__init__.py +133 -0
  139. bootstack/cli/__main__.py +6 -0
  140. bootstack/cli/add.py +395 -0
  141. bootstack/cli/appicon.py +285 -0
  142. bootstack/cli/build.py +115 -0
  143. bootstack/cli/config.py +313 -0
  144. bootstack/cli/demo.py +564 -0
  145. bootstack/cli/dev.py +153 -0
  146. bootstack/cli/doctor.py +195 -0
  147. bootstack/cli/icons.py +98 -0
  148. bootstack/cli/promote.py +120 -0
  149. bootstack/cli/pyinstaller.py +268 -0
  150. bootstack/cli/run.py +95 -0
  151. bootstack/cli/start.py +117 -0
  152. bootstack/cli/templates/__init__.py +931 -0
  153. bootstack/clipboard.py +48 -0
  154. bootstack/constants.py +318 -0
  155. bootstack/data/README.md +615 -0
  156. bootstack/data/__init__.py +78 -0
  157. bootstack/data/_observable.py +276 -0
  158. bootstack/data/base.py +780 -0
  159. bootstack/data/file_source.py +367 -0
  160. bootstack/data/memory_source.py +450 -0
  161. bootstack/data/query.py +367 -0
  162. bootstack/data/readers.py +289 -0
  163. bootstack/data/sqlite_source.py +869 -0
  164. bootstack/data/types.py +354 -0
  165. bootstack/data/writers.py +232 -0
  166. bootstack/dev/__init__.py +36 -0
  167. bootstack/dev/_capture.py +141 -0
  168. bootstack/dev/_env.py +43 -0
  169. bootstack/dev/_registry.py +140 -0
  170. bootstack/dev/_reloader.py +351 -0
  171. bootstack/dev/_reset.py +50 -0
  172. bootstack/dev/_watcher.py +91 -0
  173. bootstack/dialogs/__init__.py +948 -0
  174. bootstack/dialogs/_impl/__init__.py +47 -0
  175. bootstack/dialogs/_impl/colorchooser.py +588 -0
  176. bootstack/dialogs/_impl/datedialog.py +450 -0
  177. bootstack/dialogs/_impl/dialog.py +594 -0
  178. bootstack/dialogs/_impl/filterdialog.py +358 -0
  179. bootstack/dialogs/_impl/fontdialog.py +364 -0
  180. bootstack/dialogs/_impl/formdialog.py +564 -0
  181. bootstack/dialogs/_impl/message.py +486 -0
  182. bootstack/dialogs/_impl/query.py +570 -0
  183. bootstack/errors.py +67 -0
  184. bootstack/events/__init__.py +111 -0
  185. bootstack/events/_event.py +135 -0
  186. bootstack/events/_payloads.py +539 -0
  187. bootstack/events/_subscription.py +38 -0
  188. bootstack/i18n/README.md +77 -0
  189. bootstack/i18n/__init__.py +23 -0
  190. bootstack/i18n/catalog.py +121 -0
  191. bootstack/i18n/intl_format.py +584 -0
  192. bootstack/i18n/msgcat.py +425 -0
  193. bootstack/i18n/specs.py +156 -0
  194. bootstack/images.py +563 -0
  195. bootstack/py.typed +1 -0
  196. bootstack/scheduling/__init__.py +11 -0
  197. bootstack/scheduling/_schedule.py +218 -0
  198. bootstack/shortcuts.py +21 -0
  199. bootstack/signals/README.md +98 -0
  200. bootstack/signals/__init__.py +10 -0
  201. bootstack/signals/integration.py +100 -0
  202. bootstack/signals/signal.py +353 -0
  203. bootstack/signals/types.py +6 -0
  204. bootstack/store.py +286 -0
  205. bootstack/streams/__init__.py +12 -0
  206. bootstack/streams/_stream.py +321 -0
  207. bootstack/style/__init__.py +38 -0
  208. bootstack/style/builders/__init__.py +51 -0
  209. bootstack/style/builders/badge.py +46 -0
  210. bootstack/style/builders/button.py +339 -0
  211. bootstack/style/builders/buttongroup.py +311 -0
  212. bootstack/style/builders/calendar.py +271 -0
  213. bootstack/style/builders/checkbutton.py +110 -0
  214. bootstack/style/builders/combobox.py +113 -0
  215. bootstack/style/builders/contextmenu.py +268 -0
  216. bootstack/style/builders/entry.py +82 -0
  217. bootstack/style/builders/expander.py +148 -0
  218. bootstack/style/builders/field.py +335 -0
  219. bootstack/style/builders/frame.py +50 -0
  220. bootstack/style/builders/label.py +28 -0
  221. bootstack/style/builders/labelframe.py +34 -0
  222. bootstack/style/builders/listview.py +369 -0
  223. bootstack/style/builders/menubar.py +91 -0
  224. bootstack/style/builders/menubutton.py +359 -0
  225. bootstack/style/builders/panedwindow.py +25 -0
  226. bootstack/style/builders/progressbar.py +67 -0
  227. bootstack/style/builders/radiobutton.py +99 -0
  228. bootstack/style/builders/scale.py +64 -0
  229. bootstack/style/builders/scrollbar.py +225 -0
  230. bootstack/style/builders/separator.py +49 -0
  231. bootstack/style/builders/sidenav.py +643 -0
  232. bootstack/style/builders/sizegrip.py +15 -0
  233. bootstack/style/builders/spinbox.py +119 -0
  234. bootstack/style/builders/switch.py +70 -0
  235. bootstack/style/builders/tabitem.py +204 -0
  236. bootstack/style/builders/togglegroup.py +294 -0
  237. bootstack/style/builders/toolbutton.py +275 -0
  238. bootstack/style/builders/tooltip.py +26 -0
  239. bootstack/style/builders/treeview.py +193 -0
  240. bootstack/style/builders/utils.py +455 -0
  241. bootstack/style/builders_tk/__init__.py +16 -0
  242. bootstack/style/builders_tk/defaults.py +229 -0
  243. bootstack/style/element.py +173 -0
  244. bootstack/style/fonts.py +123 -0
  245. bootstack/style/style.py +609 -0
  246. bootstack/style/style_builder_base.py +716 -0
  247. bootstack/style/style_builder_mixed.py +93 -0
  248. bootstack/style/style_builder_tk.py +109 -0
  249. bootstack/style/style_builder_ttk.py +353 -0
  250. bootstack/style/style_resolver.py +447 -0
  251. bootstack/style/theme.py +245 -0
  252. bootstack/style/theme_provider.py +471 -0
  253. bootstack/style/themes/__init__.py +128 -0
  254. bootstack/style/tk_patch.py +5 -0
  255. bootstack/style/token_maps.py +41 -0
  256. bootstack/style/types.py +32 -0
  257. bootstack/style/typography.py +523 -0
  258. bootstack/style/utility.py +746 -0
  259. bootstack/types.py +39 -0
  260. bootstack/validation/__init__.py +6 -0
  261. bootstack/validation/types.py +15 -0
  262. bootstack/validation/validation_result.py +17 -0
  263. bootstack/validation/validation_rules.py +205 -0
  264. bootstack/widgets/__init__.py +74 -0
  265. bootstack/widgets/_core/__init__.py +31 -0
  266. bootstack/widgets/_core/app_config.py +266 -0
  267. bootstack/widgets/_core/base.py +461 -0
  268. bootstack/widgets/_core/container.py +334 -0
  269. bootstack/widgets/_core/context.py +35 -0
  270. bootstack/widgets/_core/events.py +130 -0
  271. bootstack/widgets/_core/field_mixin.py +353 -0
  272. bootstack/widgets/_core/icon_image_props.py +72 -0
  273. bootstack/widgets/_core/image_binding.py +93 -0
  274. bootstack/widgets/_core/navmodel.py +345 -0
  275. bootstack/widgets/_core/options.py +210 -0
  276. bootstack/widgets/_core/selection_group.py +130 -0
  277. bootstack/widgets/_core/window_controls.py +96 -0
  278. bootstack/widgets/_core/window_menu.py +246 -0
  279. bootstack/widgets/_impl/__init__.py +1 -0
  280. bootstack/widgets/_impl/_internal/__init__.py +0 -0
  281. bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
  282. bootstack/widgets/_impl/_parts/__init__.py +11 -0
  283. bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
  284. bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
  285. bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
  286. bootstack/widgets/_impl/composites/__init__.py +33 -0
  287. bootstack/widgets/_impl/composites/_dateutils.py +33 -0
  288. bootstack/widgets/_impl/composites/_image_fit.py +105 -0
  289. bootstack/widgets/_impl/composites/accordion.py +381 -0
  290. bootstack/widgets/_impl/composites/avatar.py +191 -0
  291. bootstack/widgets/_impl/composites/buttongroup.py +371 -0
  292. bootstack/widgets/_impl/composites/calendar.py +972 -0
  293. bootstack/widgets/_impl/composites/carousel.py +567 -0
  294. bootstack/widgets/_impl/composites/chart.py +882 -0
  295. bootstack/widgets/_impl/composites/compositeframe.py +298 -0
  296. bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
  297. bootstack/widgets/_impl/composites/dateentry.py +404 -0
  298. bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
  299. bootstack/widgets/_impl/composites/expander.py +515 -0
  300. bootstack/widgets/_impl/composites/field.py +670 -0
  301. bootstack/widgets/_impl/composites/form.py +1066 -0
  302. bootstack/widgets/_impl/composites/gallery.py +551 -0
  303. bootstack/widgets/_impl/composites/list/__init__.py +15 -0
  304. bootstack/widgets/_impl/composites/list/listitem.py +802 -0
  305. bootstack/widgets/_impl/composites/list/listview.py +1433 -0
  306. bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
  307. bootstack/widgets/_impl/composites/menu/model.py +358 -0
  308. bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
  309. bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
  310. bootstack/widgets/_impl/composites/meter.py +860 -0
  311. bootstack/widgets/_impl/composites/numericentry.py +201 -0
  312. bootstack/widgets/_impl/composites/pagestack.py +395 -0
  313. bootstack/widgets/_impl/composites/passwordentry.py +142 -0
  314. bootstack/widgets/_impl/composites/pathentry.py +168 -0
  315. bootstack/widgets/_impl/composites/picture.py +289 -0
  316. bootstack/widgets/_impl/composites/radiogroup.py +511 -0
  317. bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
  318. bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
  319. bootstack/widgets/_impl/composites/scrollview.py +764 -0
  320. bootstack/widgets/_impl/composites/selectbox.py +1026 -0
  321. bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
  322. bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
  323. bootstack/widgets/_impl/composites/shell/layout.py +335 -0
  324. bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
  325. bootstack/widgets/_impl/composites/shell/providers.py +558 -0
  326. bootstack/widgets/_impl/composites/shell/rail.py +117 -0
  327. bootstack/widgets/_impl/composites/shell/shell.py +581 -0
  328. bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
  329. bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
  330. bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
  331. bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
  332. bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
  333. bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
  334. bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
  335. bootstack/widgets/_impl/composites/slider/slider.py +851 -0
  336. bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
  337. bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
  338. bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
  339. bootstack/widgets/_impl/composites/tableview/types.py +169 -0
  340. bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
  341. bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
  342. bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
  343. bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
  344. bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
  345. bootstack/widgets/_impl/composites/textarea/change.py +46 -0
  346. bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
  347. bootstack/widgets/_impl/composites/textarea/core.py +495 -0
  348. bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
  349. bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
  350. bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
  351. bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
  352. bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
  353. bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
  354. bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
  355. bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
  356. bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
  357. bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
  358. bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
  359. bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
  360. bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
  361. bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
  362. bootstack/widgets/_impl/composites/textentry.py +57 -0
  363. bootstack/widgets/_impl/composites/timeentry.py +176 -0
  364. bootstack/widgets/_impl/composites/toast.py +390 -0
  365. bootstack/widgets/_impl/composites/toast_stack.py +156 -0
  366. bootstack/widgets/_impl/composites/togglegroup.py +418 -0
  367. bootstack/widgets/_impl/composites/toolbar.py +608 -0
  368. bootstack/widgets/_impl/composites/tooltip.py +491 -0
  369. bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
  370. bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
  371. bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
  372. bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
  373. bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
  374. bootstack/widgets/_impl/mixins/__init__.py +24 -0
  375. bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
  376. bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
  377. bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
  378. bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
  379. bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
  380. bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
  381. bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
  382. bootstack/widgets/_impl/primitives/__init__.py +49 -0
  383. bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
  384. bootstack/widgets/_impl/primitives/badge.py +45 -0
  385. bootstack/widgets/_impl/primitives/button.py +76 -0
  386. bootstack/widgets/_impl/primitives/card.py +45 -0
  387. bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
  388. bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
  389. bootstack/widgets/_impl/primitives/combobox.py +156 -0
  390. bootstack/widgets/_impl/primitives/entry.py +87 -0
  391. bootstack/widgets/_impl/primitives/flexframe.py +448 -0
  392. bootstack/widgets/_impl/primitives/frame.py +185 -0
  393. bootstack/widgets/_impl/primitives/gridframe.py +546 -0
  394. bootstack/widgets/_impl/primitives/label.py +84 -0
  395. bootstack/widgets/_impl/primitives/labelframe.py +54 -0
  396. bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
  397. bootstack/widgets/_impl/primitives/packframe.py +227 -0
  398. bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
  399. bootstack/widgets/_impl/primitives/progressbar.py +83 -0
  400. bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
  401. bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
  402. bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
  403. bootstack/widgets/_impl/primitives/separator.py +43 -0
  404. bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
  405. bootstack/widgets/_impl/primitives/spinbox.py +95 -0
  406. bootstack/widgets/_impl/primitives/switch.py +44 -0
  407. bootstack/widgets/_impl/primitives/treeview.py +69 -0
  408. bootstack/widgets/app.py +371 -0
  409. bootstack/widgets/appshell.py +1179 -0
  410. bootstack/widgets/avatar.py +140 -0
  411. bootstack/widgets/boolean_controls.py +455 -0
  412. bootstack/widgets/button.py +224 -0
  413. bootstack/widgets/buttongroup.py +239 -0
  414. bootstack/widgets/calendar.py +195 -0
  415. bootstack/widgets/card.py +159 -0
  416. bootstack/widgets/carousel.py +241 -0
  417. bootstack/widgets/chart.py +302 -0
  418. bootstack/widgets/codeeditor.py +675 -0
  419. bootstack/widgets/contextmenu.py +371 -0
  420. bootstack/widgets/datatable.py +688 -0
  421. bootstack/widgets/datefield.py +395 -0
  422. bootstack/widgets/divider.py +60 -0
  423. bootstack/widgets/expander.py +579 -0
  424. bootstack/widgets/form.py +200 -0
  425. bootstack/widgets/gallery.py +250 -0
  426. bootstack/widgets/gauge.py +168 -0
  427. bootstack/widgets/grid.py +121 -0
  428. bootstack/widgets/groupbox.py +162 -0
  429. bootstack/widgets/label.py +229 -0
  430. bootstack/widgets/listview.py +354 -0
  431. bootstack/widgets/menubutton.py +419 -0
  432. bootstack/widgets/numberfield.py +431 -0
  433. bootstack/widgets/pagestack.py +345 -0
  434. bootstack/widgets/passwordfield.py +383 -0
  435. bootstack/widgets/pathfield.py +454 -0
  436. bootstack/widgets/picture.py +251 -0
  437. bootstack/widgets/progressbar.py +106 -0
  438. bootstack/widgets/radio_variants.py +271 -0
  439. bootstack/widgets/radiogroup.py +228 -0
  440. bootstack/widgets/scrollbar.py +64 -0
  441. bootstack/widgets/scrollview.py +186 -0
  442. bootstack/widgets/select.py +302 -0
  443. bootstack/widgets/selectbutton.py +182 -0
  444. bootstack/widgets/sidebar_toggle.py +130 -0
  445. bootstack/widgets/sizegrip.py +42 -0
  446. bootstack/widgets/slider.py +408 -0
  447. bootstack/widgets/spinbox.py +147 -0
  448. bootstack/widgets/spinnerfield.py +413 -0
  449. bootstack/widgets/splash.py +367 -0
  450. bootstack/widgets/splitview.py +482 -0
  451. bootstack/widgets/stacks.py +249 -0
  452. bootstack/widgets/statusbar.py +189 -0
  453. bootstack/widgets/tabs.py +425 -0
  454. bootstack/widgets/textarea.py +459 -0
  455. bootstack/widgets/textfield.py +410 -0
  456. bootstack/widgets/theme_toggle.py +92 -0
  457. bootstack/widgets/timefield.py +340 -0
  458. bootstack/widgets/toast.py +314 -0
  459. bootstack/widgets/togglegroup.py +231 -0
  460. bootstack/widgets/toolbar.py +347 -0
  461. bootstack/widgets/tooltip.py +94 -0
  462. bootstack/widgets/tree.py +648 -0
  463. bootstack/widgets/types.py +305 -0
  464. bootstack/widgets/window.py +307 -0
  465. bootstack-0.1.0.dist-info/METADATA +301 -0
  466. bootstack-0.1.0.dist-info/RECORD +471 -0
  467. bootstack-0.1.0.dist-info/WHEEL +5 -0
  468. bootstack-0.1.0.dist-info/entry_points.txt +2 -0
  469. bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
  470. bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
  471. bootstack-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,461 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from typing import TYPE_CHECKING, Any, Callable, overload
5
+
6
+ from bootstack.widgets._core.context import current_container
7
+ from bootstack.widgets._core.events import resolve_event
8
+ from bootstack.events import Event, Subscription
9
+ from bootstack.errors import ParentResolutionError
10
+
11
+ if TYPE_CHECKING:
12
+ from bootstack.scheduling import Schedule
13
+ from bootstack.streams import Stream
14
+
15
+
16
+ def adapt_handler(handler: Callable[[Any], Any]) -> Callable[[Any], Any]:
17
+ """Wrap a public handler so it receives a bootstack event, not a raw one.
18
+
19
+ Data-carrying events (emitted with `data=<payload>`) deliver the payload
20
+ object directly — the handler argument *is* the payload. Native/context
21
+ events carry no payload, so the raw toolkit event is curated into a clean
22
+ :class:`~bootstack.events.Event` first.
23
+ """
24
+
25
+ def _wrapped(raw: Any) -> Any:
26
+ payload = getattr(raw, "data", None)
27
+ if payload is not None:
28
+ return handler(payload)
29
+ return handler(Event._from_tk(raw))
30
+
31
+ return _wrapped
32
+
33
+
34
+ class _RawTkContainer:
35
+ """Adapts a bare Tk widget so a public widget can be parented into it.
36
+
37
+ Public widgets expect a parent that exposes `_child_master()` and
38
+ `guide_layout()`. A raw Tk frame — e.g. the content frame a dialog
39
+ `content_builder` receives — has neither, so `parent=<that frame>` wraps it
40
+ here. The child is packed to fill the frame; flex/grid placement kwargs do
41
+ not apply to a bare frame and are ignored.
42
+ """
43
+
44
+ __slots__ = ("_internal",)
45
+
46
+ def __init__(self, tk_widget: tkinter.Misc) -> None:
47
+ self._internal = tk_widget
48
+
49
+ def _child_master(self) -> tkinter.Misc:
50
+ return self._internal
51
+
52
+ def guide_layout(self, child: "PublicWidgetBase", **layout_kw: Any) -> None:
53
+ from bootstack.widgets._core.container import Placement
54
+
55
+ attached = layout_kw.pop("attached", True)
56
+ opts = {"fill": "both", "expand": True}
57
+ if attached:
58
+ child._internal.pack(in_=self._internal, **opts)
59
+ child._placement = Placement("pack", self._internal, dict(opts))
60
+
61
+
62
+ class PublicWidgetBase:
63
+ """Base class for every public widget.
64
+
65
+ Subclasses must set `self._internal` before calling `_attach_to_parent`,
66
+ and should use `_split_layout_kwargs` to strip layout kwargs before
67
+ constructing the internal widget.
68
+ """
69
+
70
+ _auto_place: bool = True
71
+
72
+ _internal: tkinter.Misc
73
+ _parent: "PublicWidgetBase | None"
74
+ _placement: Any # container.Placement — set when first placed in a layout
75
+
76
+ def _resolve_parent(
77
+ self,
78
+ explicit_parent: "PublicWidgetBase | None",
79
+ ) -> "PublicWidgetBase | None":
80
+ if explicit_parent is not None:
81
+ # A bare Tk widget (e.g. a dialog content frame handed to a
82
+ # content_builder) can't host public widgets directly — wrap it so
83
+ # `parent=<tk frame>` works with the public API.
84
+ if not isinstance(explicit_parent, PublicWidgetBase) and isinstance(
85
+ explicit_parent, tkinter.Misc
86
+ ):
87
+ return _RawTkContainer(explicit_parent)
88
+ return explicit_parent
89
+ container = current_container()
90
+ if container is None and self._auto_place:
91
+ # A placed widget needs somewhere to mount. Resolving to None here
92
+ # would fail later with a cryptic Tk error; surface the real cause —
93
+ # this is the one failure mode of the builder-function pattern (a
94
+ # builder called outside any layout context).
95
+ from bootstack.errors import BootstackError
96
+
97
+ raise BootstackError(
98
+ f"{type(self).__name__} was created outside any container and "
99
+ f"without parent=. Create it inside a layout context "
100
+ f"(e.g. `with bs.App():` or `with bs.Column():`), or pass "
101
+ f"parent=<container>. A reusable builder function must be called "
102
+ f"inside a container so its widgets have somewhere to mount."
103
+ )
104
+ return container
105
+
106
+ @staticmethod
107
+ def _split_layout_kwargs(kwargs: dict) -> dict:
108
+ """Pop and return layout kwargs from `kwargs`, mutating it in place."""
109
+ from bootstack.widgets._core.container import (
110
+ PACK_KEYS, GRID_KEYS, PLACE_KEYS, PLACE_TRIGGER_KEYS, FLEX_CHILD_KEYS,
111
+ )
112
+ place_mode = any(k in kwargs for k in PLACE_TRIGGER_KEYS)
113
+ if place_mode:
114
+ layout_keys = (PLACE_KEYS - {"width", "height", "anchor"}) | PLACE_TRIGGER_KEYS
115
+ else:
116
+ layout_keys = PACK_KEYS | GRID_KEYS | FLEX_CHILD_KEYS
117
+ layout = {k: kwargs.pop(k) for k in list(kwargs) if k in layout_keys}
118
+ # `attached` is geometry-manager-agnostic — capture it in every mode.
119
+ if "attached" in kwargs:
120
+ layout["attached"] = kwargs.pop("attached")
121
+ return layout
122
+
123
+ @staticmethod
124
+ def _reject_signal_text(text: Any) -> None:
125
+ """Guard against a `Signal` landing in the plain `text=` slot.
126
+
127
+ A `Signal` is truthy and stringifies to its name, so passing one as
128
+ `text=` silently renders something like `"SIG2"` instead of binding.
129
+ The supported path is `textsignal=` (two-way binding). Raise a clear
130
+ `TypeError` rather than render the name.
131
+ """
132
+ from bootstack._core.capabilities.signals import is_signal
133
+
134
+ if is_signal(text):
135
+ raise TypeError(
136
+ "text= expects a string; pass a Signal via textsignal= to bind "
137
+ "reactive text (text= would render the Signal's name)."
138
+ )
139
+
140
+ def _attach_to_parent(self, layout_kw: dict) -> None:
141
+ if not self._auto_place:
142
+ return
143
+ parent = self._parent
144
+ if parent is None:
145
+ return
146
+ guide = getattr(parent, "guide_layout", None)
147
+ if guide is None:
148
+ raise ParentResolutionError(
149
+ f"{type(parent).__name__} is not a container (no guide_layout); "
150
+ f"cannot place {type(self).__name__} inside it."
151
+ )
152
+ guide(self, **layout_kw)
153
+
154
+ @property
155
+ def tk(self) -> tkinter.Misc:
156
+ """Underlying tk/ttk widget. UNSUPPORTED — escape-hatch use only."""
157
+ return self._internal
158
+
159
+ @property
160
+ def schedule(self) -> "Schedule":
161
+ """Scheduler tied to this widget's lifetime.
162
+
163
+ All jobs are automatically cancelled when the widget is destroyed.
164
+ First access creates the `Schedule` instance; subsequent accesses
165
+ return the same instance.
166
+
167
+ Usage::
168
+
169
+ self.schedule.delay(500, callback)
170
+ self.schedule.every(1000, tick)
171
+ job = self.schedule.idle(refresh)
172
+ job.cancel()
173
+ """
174
+ if not hasattr(self, "_schedule"):
175
+ from bootstack.scheduling import Schedule
176
+ self._schedule = Schedule(self._internal)
177
+ return self._schedule
178
+
179
+ # ----- on() — overloaded -------------------------------------------------
180
+
181
+ @overload
182
+ def on(self, event: str) -> "Stream": ...
183
+ @overload
184
+ def on(self, event: str, handler: Callable[[Any], Any]) -> Subscription: ...
185
+
186
+ def on(
187
+ self,
188
+ event: str,
189
+ handler: Callable[[Any], Any] | None = None,
190
+ ) -> "Stream | Subscription":
191
+ """Bind `handler` to `event`, or return a composable `Stream`.
192
+
193
+ With a handler — binds immediately and returns a `Subscription`::
194
+
195
+ sub = widget.on("change", handler)
196
+ sub.cancel()
197
+
198
+ Without a handler — returns a `Stream` for operator chaining.
199
+ The Tk binding is created lazily when `.listen()` is called::
200
+
201
+ sub = widget.on("change").debounce(300).listen(handler)
202
+ sub.cancel()
203
+
204
+ Args:
205
+ event: Event name (e.g. `"change"`, `"click"`).
206
+ handler: Optional callback. If omitted, a `Stream` is returned.
207
+
208
+ Returns:
209
+ `Subscription` when a handler is provided; `Stream` otherwise.
210
+ """
211
+ sequence = resolve_event(self, str(event))
212
+
213
+ if handler is not None:
214
+ bind_id = self._internal.bind(sequence, adapt_handler(handler), add="+")
215
+ return Subscription(self._internal, sequence, bind_id)
216
+
217
+ # No handler — return a lazy Stream.
218
+ from bootstack.streams import Stream
219
+
220
+ widget = self._internal
221
+
222
+ def _source(downstream: Callable[[Any], Any]) -> Subscription:
223
+ bind_id = widget.bind(sequence, adapt_handler(downstream), add="+")
224
+ return Subscription(widget, sequence, bind_id)
225
+
226
+ return Stream(self._internal, _source=_source)
227
+
228
+ def emit(self, event: str, *, data: Any = None) -> None:
229
+ """Fire a named event on this widget, as if it produced the event itself.
230
+
231
+ This is how a composite widget surfaces high-level activity to its
232
+ listeners, and the generic counterpart to the `on_*()` shorthands for
233
+ firing events that have no dedicated method.
234
+
235
+ Args:
236
+ event: The event name, unprefixed — the same name you pass to `on()`
237
+ or an `on_<event>()` shorthand (e.g. `'change'`, `'select'`).
238
+ data: The payload delivered to handlers. For a data-carrying event,
239
+ pass the matching payload dataclass from `bootstack.events` — the
240
+ same object an `on_<event>()` handler receives. Leave as None for
241
+ native events (click, hover, focus, …), which carry no payload.
242
+
243
+ Example:
244
+ .. code-block:: python
245
+
246
+ widget.emit("change", data=bs.events.ChangeEvent(value=new_value))
247
+ """
248
+ sequence = resolve_event(self, str(event))
249
+ self._internal.event_generate(sequence, data=data)
250
+
251
+ # ----- Lifecycle --------------------------------------------------------
252
+
253
+ def destroy(self) -> None:
254
+ """Destroy the widget and release the resources it holds.
255
+
256
+ Removes the widget from its parent, destroys its children, and cancels
257
+ any pending or repeating jobs on its `schedule`. After this the widget
258
+ must not be used again. Destroying a container destroys everything
259
+ inside it.
260
+ """
261
+ self._internal.destroy()
262
+
263
+ @property
264
+ def is_attached(self) -> bool:
265
+ """Whether the widget is currently placed in its layout.
266
+
267
+ `True` while the widget occupies space in its parent; `False` after
268
+ `detach` (or before it has ever been placed). A detached widget keeps
269
+ its state and can be returned to the layout with `attach`.
270
+ """
271
+ try:
272
+ return bool(self._internal.winfo_manager())
273
+ except tkinter.TclError:
274
+ return False
275
+
276
+ def detach(self) -> None:
277
+ """Remove the widget from its layout without destroying it.
278
+
279
+ The widget stops occupying space but keeps its state, children, and
280
+ event bindings, ready to be returned with `attach`. The current
281
+ position is snapshotted so a plain `attach()` restores it exactly —
282
+ for stacked siblings this is the index among the *currently attached*
283
+ siblings, so detaching other siblings first shifts that index.
284
+
285
+ Calling `detach` on a widget that is already detached, or one that was
286
+ never placed in a layout, does nothing. Fires `on_detach`.
287
+ """
288
+ placement = getattr(self, "_placement", None)
289
+ if placement is None or not self.is_attached:
290
+ return
291
+ if placement.method == "flex":
292
+ # The FlexFrame's managed list IS the attached-sibling order, so the
293
+ # current managed index is the slot a plain attach() should restore.
294
+ idx = placement.master.index_of(self._internal)
295
+ placement.index = idx if idx >= 0 else None
296
+ placement.master.remove_child(self._internal)
297
+ elif placement.method == "pack":
298
+ slaves = list(placement.master.pack_slaves())
299
+ try:
300
+ placement.index = slaves.index(self._internal)
301
+ except ValueError:
302
+ placement.index = None
303
+ self._internal.pack_forget()
304
+ elif placement.method == "grid":
305
+ # grid_forget (not grid_remove): the "remembered" state left by
306
+ # grid_remove rejects an explicit re-grid; we reconstruct fully
307
+ # from the stored cell options instead.
308
+ self._internal.grid_forget()
309
+ else:
310
+ self._internal.place_forget()
311
+
312
+ def attach(self, **kwargs: Any) -> None:
313
+ """Return a detached widget to its layout, optionally moving it.
314
+
315
+ With no arguments, restores the widget to exactly where `detach` took
316
+ it from. Any layout kwargs accepted by the original placement (e.g.
317
+ `fill`, `expand`, `anchor`, `sticky`, `margin`) override the stored
318
+ options. For stacked widgets, `index=` sets the position among the
319
+ currently attached siblings (or pass an explicit `before=`/`after=`
320
+ sibling); without one, the snapshotted position is used.
321
+
322
+ Calling `attach` on a widget that is already attached moves it (the
323
+ kwargs are re-applied). Fires `on_attach`.
324
+
325
+ Args:
326
+ **kwargs: Layout placement options to override for this placement.
327
+
328
+ Raises:
329
+ ParentResolutionError: If the widget was never placed in a layout.
330
+ """
331
+ from bootstack.widgets._core.container import (
332
+ PACK_KEYS, GRID_KEYS, PLACE_KEYS, FLEX_CHILD_KEYS,
333
+ normalize_fill, _expand_margin, _flex_child_opts, _reject_legacy_child_kwargs,
334
+ )
335
+
336
+ placement = getattr(self, "_placement", None)
337
+ if placement is None:
338
+ raise ParentResolutionError(
339
+ f"{type(self).__name__} was never placed in a layout; "
340
+ f"nothing to attach()."
341
+ )
342
+ # Re-applying to an attached widget is a move: clear it first so the
343
+ # placement (and any index math) starts from a clean slate.
344
+ if self.is_attached:
345
+ self.detach()
346
+
347
+ master = placement.master
348
+ options = dict(placement.options)
349
+
350
+ if placement.method == "flex":
351
+ index = kwargs.pop("index", placement.index)
352
+ if kwargs:
353
+ _reject_legacy_child_kwargs(kwargs, type(self).__name__)
354
+ _expand_margin(kwargs)
355
+ options.update(_flex_child_opts(self, kwargs))
356
+ master.add_child(self._internal, options, index=index)
357
+ placement.options = options
358
+ return
359
+
360
+ if kwargs:
361
+ if "fill" in kwargs:
362
+ kwargs["fill"] = normalize_fill(kwargs["fill"])
363
+ _expand_margin(kwargs)
364
+
365
+ if placement.method == "pack":
366
+ order = {k: kwargs.pop(k) for k in ("index", "before", "after") if k in kwargs}
367
+ options.update({k: v for k, v in kwargs.items() if k in PACK_KEYS})
368
+ if not order and placement.index is not None:
369
+ order["index"] = placement.index
370
+ order_kw = resolve_pack_order(order, master)
371
+ self._internal.pack(in_=master, **options, **order_kw)
372
+ elif placement.method == "grid":
373
+ options.update({k: v for k, v in kwargs.items() if k in GRID_KEYS})
374
+ self._internal.grid(in_=master, **options)
375
+ else:
376
+ options.update({k: v for k, v in kwargs.items() if k in PLACE_KEYS})
377
+ self._internal.place(in_=master, **options)
378
+ placement.options = options
379
+
380
+ @overload
381
+ def on_destroy(self) -> "Stream": ...
382
+ @overload
383
+ def on_destroy(self, handler: Callable[[Event], Any]) -> Subscription: ...
384
+ def on_destroy(
385
+ self,
386
+ handler: Callable[[Event], Any] | None = None,
387
+ ) -> "Stream | Subscription":
388
+ """Register a callback fired when the widget is destroyed.
389
+
390
+ Fires once, as the widget is torn down — the place to release resources
391
+ the widget owns that aren't cleaned up automatically (file handles,
392
+ observers, external subscriptions). The handler receives a curated
393
+ :class:`~bootstack.events.Event`.
394
+
395
+ Args:
396
+ handler: Called as the widget is destroyed. Omit to get a composable
397
+ :class:`~bootstack.streams.Stream`.
398
+
399
+ Returns:
400
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
401
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
402
+ """
403
+ return self.on("destroy", handler)
404
+
405
+ @overload
406
+ def on_attach(self) -> "Stream": ...
407
+ @overload
408
+ def on_attach(self, handler: Callable[[Event], Any]) -> Subscription: ...
409
+ def on_attach(
410
+ self,
411
+ handler: Callable[[Event], Any] | None = None,
412
+ ) -> "Stream | Subscription":
413
+ """Register a callback fired when the widget enters the layout.
414
+
415
+ Fires each time the widget becomes visible in its parent — on initial
416
+ placement and on every `attach`. Pair it with `on_detach` to keep
417
+ per-visibility resources (timers, observers) tied to the widget's
418
+ presence on screen. The handler receives a curated
419
+ :class:`~bootstack.events.Event`.
420
+
421
+ Args:
422
+ handler: Called when the widget is attached. Omit to get a
423
+ composable :class:`~bootstack.streams.Stream`.
424
+
425
+ Returns:
426
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
427
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
428
+ """
429
+ return self.on("attach", handler)
430
+
431
+ @overload
432
+ def on_detach(self) -> "Stream": ...
433
+ @overload
434
+ def on_detach(self, handler: Callable[[Event], Any]) -> Subscription: ...
435
+ def on_detach(
436
+ self,
437
+ handler: Callable[[Event], Any] | None = None,
438
+ ) -> "Stream | Subscription":
439
+ """Register a callback fired when the widget leaves the layout.
440
+
441
+ Fires each time the widget stops occupying space in its parent — on
442
+ `detach` and when an ancestor hides it. Pair it with `on_attach` to
443
+ release per-visibility resources. The handler receives a curated
444
+ :class:`~bootstack.events.Event`.
445
+
446
+ Args:
447
+ handler: Called when the widget is detached. Omit to get a
448
+ composable :class:`~bootstack.streams.Stream`.
449
+
450
+ Returns:
451
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
452
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
453
+ """
454
+ return self.on("detach", handler)
455
+
456
+ def __repr__(self) -> str:
457
+ try:
458
+ name = self._internal._w # type: ignore[attr-defined]
459
+ except AttributeError:
460
+ name = "?"
461
+ return f"<{type(self).__name__} {name}>"