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,334 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from typing import Any
5
+
6
+ PACK_KEYS = frozenset({
7
+ "side", "fill", "expand", "anchor",
8
+ "padx", "pady", "ipadx", "ipady",
9
+ "margin", "margin_x", "margin_y",
10
+ "index", "before", "after", "in_",
11
+ })
12
+
13
+ GRID_KEYS = frozenset({
14
+ "row", "column", "rowspan", "columnspan",
15
+ "sticky", "padx", "pady", "ipadx", "ipady",
16
+ "margin", "margin_x", "margin_y",
17
+ "in_",
18
+ })
19
+
20
+ PLACE_KEYS = frozenset({
21
+ "x", "y", "relx", "rely",
22
+ "width", "height", "relwidth", "relheight",
23
+ "anchor", "bordermode", "in_",
24
+ })
25
+
26
+ # Per-child placement keys for the flex/grid layout engine (Row/Column/Grid).
27
+ # Collision-free by design — never bare `align`/`justify`, which would shadow
28
+ # Label/TextField text `justify` and Snackbar `align` on every widget.
29
+ FLEX_CHILD_KEYS = frozenset({
30
+ "grow", "horizontal", "vertical",
31
+ "margin", "margin_x", "margin_y", "index",
32
+ })
33
+
34
+ # Legacy pack/grid placement kwargs the flex engine deliberately rejects, so a
35
+ # stale `fill=`/`expand=`/`anchor=`/`sticky=` raises a clear migration error
36
+ # instead of silently collapsing a child (data/canvas widgets especially).
37
+ _LEGACY_CHILD_KEYS = frozenset({"fill", "expand", "anchor", "sticky", "side"})
38
+
39
+ # Presence of any of these signals "use place() instead of pack()/grid()"
40
+ PLACE_TRIGGER_KEYS = frozenset({"x", "y", "relx", "rely", "relwidth", "relheight"})
41
+
42
+ # Human-readable aliases for Tk's single-character fill values.
43
+ # Accepted everywhere fill= or fill_items= appears in the public API.
44
+ _FILL_ALIASES: dict[str, str] = {
45
+ "horizontal": "x",
46
+ "vertical": "y",
47
+ "all": "both",
48
+ }
49
+
50
+
51
+ def normalize_fill(value: str | None) -> str | None:
52
+ """Resolve a fill alias to its Tk value, passing through unknowns unchanged."""
53
+ if value is None:
54
+ return None
55
+ return _FILL_ALIASES.get(value, value)
56
+
57
+
58
+ def _expand_margin(layout_kw: dict) -> None:
59
+ """Convert margin=/marginx=/marginy= to padx=/pady= in-place.
60
+
61
+ Processing order: margin sets both axes as a baseline; marginx and
62
+ marginy override their respective axis. Explicit padx/pady always win.
63
+ """
64
+ margin = layout_kw.pop("margin", None)
65
+ marginx = layout_kw.pop("margin_x", None)
66
+ marginy = layout_kw.pop("margin_y", None)
67
+
68
+ padx = pady = None
69
+
70
+ if margin is not None:
71
+ if isinstance(margin, (int, float)):
72
+ padx = pady = margin
73
+ elif len(margin) == 2:
74
+ padx, pady = margin # (horizontal, vertical)
75
+ else:
76
+ left, top, right, bottom = margin
77
+ padx = (left, right)
78
+ pady = (top, bottom)
79
+
80
+ if marginx is not None:
81
+ padx = marginx
82
+ if marginy is not None:
83
+ pady = marginy
84
+
85
+ if padx is not None:
86
+ layout_kw.setdefault("padx", padx)
87
+ if pady is not None:
88
+ layout_kw.setdefault("pady", pady)
89
+
90
+
91
+ # --- PublicContainer -------------------------------------------------------
92
+ # Imported here (after constants) to avoid circular imports in base.py.
93
+
94
+ from bootstack.widgets._core.base import PublicWidgetBase # noqa: E402
95
+ from bootstack.widgets._core.context import push_container, pop_container # noqa: E402
96
+
97
+
98
+ class Placement:
99
+ """Snapshot of how a widget was placed, enabling `detach`/`attach`.
100
+
101
+ Recorded on the child at layout time by `guide_layout`; consumed by
102
+ `PublicWidgetBase.detach`/`attach` to remove and re-insert the widget
103
+ without re-running its constructor. `options` holds the resolved Tk
104
+ geometry-manager options (no ordering refs); `index` is the pack position
105
+ among siblings, snapshotted afresh on each `detach`.
106
+ """
107
+
108
+ __slots__ = ("method", "master", "options", "index")
109
+
110
+ def __init__(
111
+ self,
112
+ method: str,
113
+ master: tkinter.Misc,
114
+ options: dict,
115
+ index: int | None = None,
116
+ ) -> None:
117
+ self.method = method # "pack" | "grid" | "place"
118
+ self.master = master # the Tk master the child is placed in
119
+ self.options = options # resolved Tk options (no before/after/index)
120
+ self.index = index # pack order among siblings (snapshot at detach)
121
+
122
+
123
+ def resolve_pack_order(order: dict, master: tkinter.Misc) -> dict:
124
+ """Translate public `index`/`before`/`after` into a Tk pack-order kwarg.
125
+
126
+ Pops the ordering keys from `order` and returns the corresponding Tk
127
+ `pack()` kwarg dict. `index=` is the position among the master's currently
128
+ packed children, translated to `before=<that child>` (appended when the
129
+ index is past the end). Explicit `before=`/`after=` accept either a public
130
+ widget or a raw Tk widget. `before` wins over `after` wins over `index`.
131
+ """
132
+ index = order.pop("index", None)
133
+ before = order.pop("before", None)
134
+ after = order.pop("after", None)
135
+ if before is not None:
136
+ return {"before": getattr(before, "_internal", before)}
137
+ if after is not None:
138
+ return {"after": getattr(after, "_internal", after)}
139
+ if index is not None:
140
+ slaves = master.pack_slaves()
141
+ if 0 <= index < len(slaves):
142
+ return {"before": slaves[index]}
143
+ return {}
144
+
145
+
146
+ class PublicContainer(PublicWidgetBase):
147
+ """Base for public containers (Row, Column, Grid, App).
148
+
149
+ Subclasses must implement `_child_master`, `_default_layout_method`,
150
+ and `_merge_layout_options`.
151
+ """
152
+
153
+ def _child_master(self) -> tkinter.Misc:
154
+ """Default: children parent directly to `self._internal`."""
155
+ return self._internal
156
+
157
+ def _default_layout_method(self) -> str:
158
+ raise NotImplementedError
159
+
160
+ def _merge_layout_options(
161
+ self, child: PublicWidgetBase, layout_kw: dict
162
+ ) -> tuple[str, dict]:
163
+ raise NotImplementedError
164
+
165
+ def guide_layout(self, child: PublicWidgetBase, **layout_kw: Any) -> None:
166
+ """Place `child._internal` under this container.
167
+
168
+ Records a `Placement` snapshot on the child so it can later be
169
+ `detach`-ed and `attach`-ed. When `attached=False` is passed, the
170
+ snapshot is recorded but the widget is NOT mapped — it starts hidden,
171
+ ready to be shown with `attach`.
172
+ """
173
+ attached = layout_kw.pop("attached", True)
174
+ if "fill" in layout_kw:
175
+ layout_kw["fill"] = normalize_fill(layout_kw["fill"])
176
+ _expand_margin(layout_kw)
177
+ place_mode = any(k in layout_kw for k in PLACE_TRIGGER_KEYS)
178
+ tk_widget = child._internal
179
+ master = self._child_master()
180
+
181
+ if place_mode:
182
+ options = {k: v for k, v in layout_kw.items() if k in PLACE_KEYS}
183
+ if attached:
184
+ tk_widget.place(in_=master, **options)
185
+ child._placement = Placement("place", master, options)
186
+ return
187
+
188
+ method, options = self._merge_layout_options(child, layout_kw)
189
+ if method == "pack":
190
+ # Capture an explicit index before resolve_pack_order strips it.
191
+ explicit_index = options.get("index")
192
+ order_kw = resolve_pack_order(options, master) # mutates options
193
+ if attached:
194
+ tk_widget.pack(in_=master, **options, **order_kw)
195
+ child._placement = Placement("pack", master, dict(options))
196
+ else:
197
+ # Remember the slot a later attach() should restore: an
198
+ # explicit index if given, else the natural append position
199
+ # (the count of siblings already packed at this point).
200
+ index = explicit_index if explicit_index is not None else len(master.pack_slaves())
201
+ child._placement = Placement("pack", master, dict(options), index=index)
202
+ elif method == "grid":
203
+ if attached:
204
+ tk_widget.grid(in_=master, **options)
205
+ child._placement = Placement("grid", master, dict(options))
206
+ else:
207
+ raise ValueError(f"Unknown layout method: {method!r}")
208
+
209
+ def __enter__(self) -> "PublicContainer":
210
+ push_container(self)
211
+ return self
212
+
213
+ def __exit__(self, exc_type, exc, tb) -> None:
214
+ pop_container(self)
215
+ return None
216
+
217
+
218
+ # --- Flex engine (Row / Column) --------------------------------------------
219
+
220
+
221
+ def _reject_legacy_child_kwargs(layout_kw: dict, where: str) -> None:
222
+ """Raise on legacy pack/grid placement kwargs passed to a flex child."""
223
+ bad = _LEGACY_CHILD_KEYS & layout_kw.keys()
224
+ if bad:
225
+ from bootstack.errors import BootstackError
226
+
227
+ raise BootstackError(
228
+ f"{where}: {', '.join(sorted(bad))} is not a valid layout option for "
229
+ f"a Row/Column/Grid child. Use grow= / align_self= (and justify_self= "
230
+ f"in a Grid) instead — see the layout guide."
231
+ )
232
+
233
+
234
+ def grid_sticky(horizontal: str | None, vertical: str | None) -> str:
235
+ """Derive a Tk `sticky` string from in-cell alignment edge values.
236
+
237
+ `horizontal` is `left`/`center`/`right`/`stretch`, `vertical` is
238
+ `top`/`center`/`bottom`/`stretch`. `stretch` fills the axis, the edge values
239
+ pin to that edge, and `center` (or None) leaves the axis unpinned so the
240
+ child sits centered in the cell.
241
+ """
242
+ sticky = ""
243
+ if horizontal == "stretch":
244
+ sticky += "ew"
245
+ elif horizontal == "left":
246
+ sticky += "w"
247
+ elif horizontal == "right":
248
+ sticky += "e"
249
+ if vertical == "stretch":
250
+ sticky += "ns"
251
+ elif vertical == "top":
252
+ sticky += "n"
253
+ elif vertical == "bottom":
254
+ sticky += "s"
255
+ return sticky
256
+
257
+
258
+ def _flex_child_opts(child: PublicWidgetBase, layout_kw: dict) -> dict:
259
+ """Build the FlexFrame per-child opts dict from resolved layout kwargs.
260
+
261
+ Expects `_expand_margin` to have already converted margins to padx/pady.
262
+ Carries spacer metadata when `child` is a `Spacer` (duck-typed to avoid a
263
+ circular import).
264
+ """
265
+ opts: dict[str, Any] = {}
266
+ for key in ("grow", "horizontal", "vertical", "padx", "pady"):
267
+ if key in layout_kw:
268
+ opts[key] = layout_kw[key]
269
+ if getattr(child, "_is_spacer", False):
270
+ opts["_spacer"] = True
271
+ opts["_spacer_size"] = getattr(child, "_spacer_size", None)
272
+ opts["_spacer_weight"] = getattr(child, "_spacer_weight", 1)
273
+ return opts
274
+
275
+
276
+ class FlexContainer(PublicContainer):
277
+ """Base for containers whose children flow through a `FlexFrame`.
278
+
279
+ Subclasses build a `FlexFrame` and expose it via `_flex_frame`; children
280
+ are added to it (re-planning the layout) and a `Placement(method="flex")`
281
+ is recorded for `detach`/`attach`. Defaults `_flex_frame` to `_internal`,
282
+ which suits the stack widgets; containers with a nested layout frame (App's
283
+ content frame, Card's body) override the property.
284
+ """
285
+
286
+ @property
287
+ def _flex_frame(self) -> Any:
288
+ return self._internal
289
+
290
+ def _child_master(self) -> tkinter.Misc:
291
+ return self._flex_frame
292
+
293
+ def _default_layout_method(self) -> str:
294
+ return "flex"
295
+
296
+ def guide_layout(self, child: PublicWidgetBase, **layout_kw: Any) -> None:
297
+ """Place `child` into this container's flow and snapshot its placement."""
298
+ place_flex_child(self._flex_frame, child, layout_kw, type(self).__name__)
299
+
300
+
301
+ def place_flex_child(
302
+ frame: Any, child: PublicWidgetBase, layout_kw: dict, where: str
303
+ ) -> None:
304
+ """Add `child` to a `FlexFrame` and record a `Placement` for detach/attach.
305
+
306
+ Shared by `FlexContainer` and the dual-mode containers (Card/GroupBox in
307
+ their column/row mode) so the flex placement contract lives in one place.
308
+ Absolute placement (`x=`/`y=`) is honored as an overlay escape hatch — the
309
+ child is `place`-d over the flow rather than entering it.
310
+ """
311
+ attached = layout_kw.pop("attached", True)
312
+
313
+ # Place-mode escape hatch: a child given x/y/relx/rely is positioned
314
+ # absolutely over the flow, not gridded into it.
315
+ if any(k in layout_kw for k in PLACE_TRIGGER_KEYS):
316
+ options = {k: v for k, v in layout_kw.items() if k in PLACE_KEYS}
317
+ if attached:
318
+ child._internal.place(in_=frame, **options)
319
+ child._placement = Placement("place", frame, options)
320
+ return
321
+
322
+ _reject_legacy_child_kwargs(layout_kw, where)
323
+ _expand_margin(layout_kw)
324
+ index = layout_kw.get("index")
325
+ opts = _flex_child_opts(child, layout_kw)
326
+ if attached:
327
+ frame.add_child(child._internal, opts, index=index)
328
+ child._placement = Placement("flex", frame, opts)
329
+ else:
330
+ # Record the slot a later attach() restores: an explicit index, else the
331
+ # natural append position (current child count).
332
+ if index is None:
333
+ index = len(frame._managed)
334
+ child._placement = Placement("flex", frame, opts, index=index)
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from bootstack.widgets._core.container import PublicContainer
8
+
9
+ _local = threading.local()
10
+
11
+
12
+ def _stack() -> list:
13
+ s = getattr(_local, "stack", None)
14
+ if s is None:
15
+ s = []
16
+ _local.stack = s
17
+ return s
18
+
19
+
20
+ def push_container(container: "PublicContainer") -> None:
21
+ _stack().append(container)
22
+
23
+
24
+ def pop_container(expected: "PublicContainer") -> None:
25
+ s = _stack()
26
+ if not s or s[-1] is not expected:
27
+ if expected in s:
28
+ s.remove(expected)
29
+ return
30
+ s.pop()
31
+
32
+
33
+ def current_container() -> "PublicContainer | None":
34
+ s = _stack()
35
+ return s[-1] if s else None
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class _EventCategory(str, Enum):
7
+ """String-valued enum so members compare equal to plain strings."""
8
+ pass
9
+
10
+
11
+ class _Widget(_EventCategory):
12
+ CLICK = "click"
13
+ DOUBLE_CLICK = "double_click"
14
+ RIGHT_CLICK = "right_click"
15
+ HOVER = "hover"
16
+ LEAVE = "leave"
17
+ FOCUS = "focus"
18
+ BLUR = "blur"
19
+ RESIZE = "resize"
20
+ DESTROY = "destroy"
21
+ ATTACH = "attach"
22
+ DETACH = "detach"
23
+ EXPAND = "expand"
24
+ COLLAPSE = "collapse"
25
+ ACTIVATE = "activate"
26
+ DEACTIVATE = "deactivate"
27
+ DISMISS = "dismiss"
28
+
29
+
30
+ class _Input(_EventCategory):
31
+ CHANGE = "change"
32
+ INPUT = "input"
33
+ SUBMIT = "submit"
34
+ VALIDATE = "validate"
35
+ VALID = "valid"
36
+ INVALID = "invalid"
37
+ COMMIT = "commit"
38
+
39
+
40
+ class _Selection(_EventCategory):
41
+ SELECT = "select"
42
+ DESELECT = "deselect"
43
+
44
+
45
+ class _App(_EventCategory):
46
+ THEME_CHANGE = "theme_change"
47
+ PAGE_MOUNT = "page_mount"
48
+ PAGE_UNMOUNT = "page_unmount"
49
+ PAGE_WILL_MOUNT = "page_will_mount"
50
+ PAGE_CHANGE = "page_change"
51
+
52
+
53
+ class EventName:
54
+ """Namespace of canonical event-name constants.
55
+
56
+ Internal reference for the string names accepted by `widget.on(...)`.
57
+ Members compare equal to their string values, so `widget.on("click", h)`
58
+ and `widget.on(EventName.Widget.CLICK, h)` are interchangeable. Public code
59
+ uses the plain string names; the handler argument type is
60
+ `bootstack.widgets.types.Event`.
61
+ """
62
+ Widget = _Widget
63
+ Input = _Input
64
+ Selection = _Selection
65
+ App = _App
66
+
67
+
68
+ GLOBAL_EVENT_MAP: dict[str, str] = {
69
+ # Widget
70
+ "click": "<Button-1>",
71
+ "double_click": "<Double-Button-1>",
72
+ "right_click": "<Button-3>",
73
+ "hover": "<Enter>",
74
+ "leave": "<Leave>",
75
+ "focus": "<FocusIn>",
76
+ "blur": "<FocusOut>",
77
+ "resize": "<Configure>",
78
+ "destroy": "<Destroy>",
79
+ "attach": "<Map>",
80
+ "detach": "<Unmap>",
81
+ # Input
82
+ "input": "<KeyRelease>",
83
+ "submit": "<Return>",
84
+ # App-level
85
+ "theme_change": "<<ThemeChanged>>",
86
+ }
87
+
88
+ # Per-class override map. Populated via `register_widget_events`.
89
+ _CLASS_EVENT_MAPS: dict[type, dict[str, str]] = {}
90
+
91
+
92
+ def register_widget_events(cls: type, mapping: dict[str, str]) -> None:
93
+ """Attach a class-specific event map to a public widget class.
94
+
95
+ Entries map the public name (e.g. "select") to the Tk sequence
96
+ (e.g. "<<TreeviewSelect>>"). Class maps take precedence over the global map.
97
+ """
98
+ _CLASS_EVENT_MAPS[cls] = mapping
99
+
100
+
101
+ def resolve_event(public_widget: object, name: str) -> str:
102
+ """Resolve a public event name to a Tk sequence.
103
+
104
+ Lookup order:
105
+ 1. Walk the public widget's MRO; first class-specific match wins.
106
+ 2. Global map.
107
+ 3. Pass-through literal `<...>` or `<<...>>` strings unchanged.
108
+ 4. Raise `UnknownEventError`.
109
+ """
110
+ from bootstack.errors import UnknownEventError
111
+
112
+ if name.startswith("<") and name.endswith(">"):
113
+ return name
114
+
115
+ for klass in type(public_widget).__mro__:
116
+ cmap = _CLASS_EVENT_MAPS.get(klass)
117
+ if cmap and name in cmap:
118
+ return cmap[name]
119
+
120
+ if name in GLOBAL_EVENT_MAP:
121
+ return GLOBAL_EVENT_MAP[name]
122
+
123
+ all_known = sorted(
124
+ set(GLOBAL_EVENT_MAP)
125
+ | set().union(*(set(m) for m in _CLASS_EVENT_MAPS.values()))
126
+ )
127
+ raise UnknownEventError(
128
+ f"Unknown event {name!r} on {type(public_widget).__name__}. "
129
+ f"Known events: {all_known}"
130
+ )