bootstack 0.1.0a1__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 (419) hide show
  1. bootstack/__init__.py +249 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/api/__init__.py +127 -0
  4. bootstack/api/app.py +30 -0
  5. bootstack/api/constants.py +3 -0
  6. bootstack/api/data.py +23 -0
  7. bootstack/api/dialogs.py +44 -0
  8. bootstack/api/i18n.py +17 -0
  9. bootstack/api/localization.py +16 -0
  10. bootstack/api/menu.py +7 -0
  11. bootstack/api/style.py +23 -0
  12. bootstack/api/utils.py +24 -0
  13. bootstack/api/widgets.py +137 -0
  14. bootstack/assets/__init__.py +24 -0
  15. bootstack/assets/bootstack-transparent.png +0 -0
  16. bootstack/assets/bootstack.ico +0 -0
  17. bootstack/assets/bootstack.png +0 -0
  18. bootstack/assets/elements/__init__.py +0 -0
  19. bootstack/assets/elements/badge-pill.png +0 -0
  20. bootstack/assets/elements/badge-square.png +0 -0
  21. bootstack/assets/elements/border.png +0 -0
  22. bootstack/assets/elements/button-compact.png +0 -0
  23. bootstack/assets/elements/button-default.png +0 -0
  24. bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
  25. bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
  26. bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
  27. bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
  28. bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
  29. bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
  30. bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
  31. bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
  32. bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
  33. bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
  34. bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
  35. bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
  36. bootstack/assets/elements/checkbox-checked.png +0 -0
  37. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  38. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  39. bootstack/assets/elements/field.png +0 -0
  40. bootstack/assets/elements/input-after-compact.png +0 -0
  41. bootstack/assets/elements/input-after-default.png +0 -0
  42. bootstack/assets/elements/input-before-compact.png +0 -0
  43. bootstack/assets/elements/input-before-default.png +0 -0
  44. bootstack/assets/elements/input-compact.png +0 -0
  45. bootstack/assets/elements/input-default.png +0 -0
  46. bootstack/assets/elements/list-item-separated.png +0 -0
  47. bootstack/assets/elements/list-item.png +0 -0
  48. bootstack/assets/elements/manifest.toml +480 -0
  49. bootstack/assets/elements/menu-item.png +0 -0
  50. bootstack/assets/elements/nav-button-compact.png +0 -0
  51. bootstack/assets/elements/nav-button-default.png +0 -0
  52. bootstack/assets/elements/nav-icon-button-compact.png +0 -0
  53. bootstack/assets/elements/nav-icon-button-default.png +0 -0
  54. bootstack/assets/elements/notebook-client-border.png +0 -0
  55. bootstack/assets/elements/notebook-tab-active.png +0 -0
  56. bootstack/assets/elements/notebook-tab-bar.png +0 -0
  57. bootstack/assets/elements/notebook-tab-normal.png +0 -0
  58. bootstack/assets/elements/notebook-tab-pill.png +0 -0
  59. bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
  60. bootstack/assets/elements/progress-bar-solid.png +0 -0
  61. bootstack/assets/elements/progress-bar-thin.png +0 -0
  62. bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
  63. bootstack/assets/elements/radio-selected.png +0 -0
  64. bootstack/assets/elements/radio-unselected.png +0 -0
  65. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  66. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  67. bootstack/assets/elements/slider-handle-focus.png +0 -0
  68. bootstack/assets/elements/slider-handle.png +0 -0
  69. bootstack/assets/elements/slider-track-horizontal.png +0 -0
  70. bootstack/assets/elements/slider-track-vertical.png +0 -0
  71. bootstack/assets/elements/switch-off.png +0 -0
  72. bootstack/assets/elements/switch-on.png +0 -0
  73. bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
  74. bootstack/assets/elements/tabs-bar-vertical.png +0 -0
  75. bootstack/assets/elements/tabs-pill.png +0 -0
  76. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  77. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
  78. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
  79. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  80. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
  81. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  82. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
  83. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  84. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
  85. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  86. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
  87. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  88. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
  89. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  90. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
  91. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  92. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
  93. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  94. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
  95. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  96. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
  97. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  98. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
  99. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  100. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
  101. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  102. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
  103. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  104. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
  105. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  106. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
  107. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  108. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
  109. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  110. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
  111. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  112. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
  113. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  114. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
  115. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  116. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
  117. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  118. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
  119. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  120. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
  121. bootstack/assets/themes/__init__.py +0 -0
  122. bootstack/assets/themes/amber-dark.json +32 -0
  123. bootstack/assets/themes/amber-light.json +32 -0
  124. bootstack/assets/themes/aurora-dark.json +32 -0
  125. bootstack/assets/themes/aurora-light.json +32 -0
  126. bootstack/assets/themes/bootstrap-dark.json +32 -0
  127. bootstack/assets/themes/bootstrap-light.json +32 -0
  128. bootstack/assets/themes/classic-dark.json +32 -0
  129. bootstack/assets/themes/classic-light.json +32 -0
  130. bootstack/assets/themes/docs-dark.json +32 -0
  131. bootstack/assets/themes/docs-light.json +32 -0
  132. bootstack/assets/themes/forest-dark.json +32 -0
  133. bootstack/assets/themes/forest-light.json +32 -0
  134. bootstack/assets/themes/ocean-dark.json +32 -0
  135. bootstack/assets/themes/ocean-light.json +32 -0
  136. bootstack/assets/themes/rose-dark.json +32 -0
  137. bootstack/assets/themes/rose-light.json +32 -0
  138. bootstack/assets/widgets/__init__.py +0 -0
  139. bootstack/assets/widgets/badge-default.png +0 -0
  140. bootstack/assets/widgets/badge-pill.png +0 -0
  141. bootstack/assets/widgets/border.png +0 -0
  142. bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
  143. bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
  144. bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
  145. bootstack/assets/widgets/button-group-vertical-after.png +0 -0
  146. bootstack/assets/widgets/button-group-vertical-before.png +0 -0
  147. bootstack/assets/widgets/button-group-vertical-center.png +0 -0
  148. bootstack/assets/widgets/button.png +0 -0
  149. bootstack/assets/widgets/checkbox-checked.png +0 -0
  150. bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
  151. bootstack/assets/widgets/checkbox-unchecked.png +0 -0
  152. bootstack/assets/widgets/field.png +0 -0
  153. bootstack/assets/widgets/icon-button.png +0 -0
  154. bootstack/assets/widgets/input-inner.png +0 -0
  155. bootstack/assets/widgets/input-prefix.png +0 -0
  156. bootstack/assets/widgets/input-suffix.png +0 -0
  157. bootstack/assets/widgets/input.png +0 -0
  158. bootstack/assets/widgets/list-item-focus.png +0 -0
  159. bootstack/assets/widgets/list-item-separated.png +0 -0
  160. bootstack/assets/widgets/menu-item-separated.png +0 -0
  161. bootstack/assets/widgets/notebook-client-border.png +0 -0
  162. bootstack/assets/widgets/notebook-pill-active.png +0 -0
  163. bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
  164. bootstack/assets/widgets/notebook-tab-active.png +0 -0
  165. bootstack/assets/widgets/notebook-tab-border.png +0 -0
  166. bootstack/assets/widgets/notebook-tab-normal.png +0 -0
  167. bootstack/assets/widgets/notebook-underline.png +0 -0
  168. bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
  169. bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
  170. bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
  171. bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
  172. bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
  173. bootstack/assets/widgets/progress-trough-vertical.png +0 -0
  174. bootstack/assets/widgets/radio-selected.png +0 -0
  175. bootstack/assets/widgets/radio-unselected.png +0 -0
  176. bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
  177. bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
  178. bootstack/assets/widgets/separator-horizontal.png +0 -0
  179. bootstack/assets/widgets/separator-vertical.png +0 -0
  180. bootstack/assets/widgets/slider-handle-focus.png +0 -0
  181. bootstack/assets/widgets/slider-handle.png +0 -0
  182. bootstack/assets/widgets/slider-track-horizontal.png +0 -0
  183. bootstack/assets/widgets/slider-track-vertical.png +0 -0
  184. bootstack/assets/widgets/switch-off.png +0 -0
  185. bootstack/assets/widgets/switch-on.png +0 -0
  186. bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
  187. bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
  188. bootstack/assets/widgets/tabs-pill.png +0 -0
  189. bootstack/cli/__init__.py +124 -0
  190. bootstack/cli/__main__.py +6 -0
  191. bootstack/cli/add.py +439 -0
  192. bootstack/cli/build.py +115 -0
  193. bootstack/cli/config.py +287 -0
  194. bootstack/cli/demo.py +1267 -0
  195. bootstack/cli/doctor.py +195 -0
  196. bootstack/cli/list_cmd.py +71 -0
  197. bootstack/cli/promote.py +120 -0
  198. bootstack/cli/pyinstaller.py +246 -0
  199. bootstack/cli/run.py +99 -0
  200. bootstack/cli/start.py +105 -0
  201. bootstack/cli/templates/__init__.py +861 -0
  202. bootstack/constants.py +325 -0
  203. bootstack/core/__init__.py +34 -0
  204. bootstack/core/capabilities/__init__.py +45 -0
  205. bootstack/core/capabilities/after.py +103 -0
  206. bootstack/core/capabilities/bind.py +154 -0
  207. bootstack/core/capabilities/bindtags.py +112 -0
  208. bootstack/core/capabilities/busy.py +61 -0
  209. bootstack/core/capabilities/clipboard.py +88 -0
  210. bootstack/core/capabilities/focus.py +118 -0
  211. bootstack/core/capabilities/grab.py +65 -0
  212. bootstack/core/capabilities/grid.py +188 -0
  213. bootstack/core/capabilities/localization.py +231 -0
  214. bootstack/core/capabilities/pack.py +119 -0
  215. bootstack/core/capabilities/place.py +92 -0
  216. bootstack/core/capabilities/selection.py +136 -0
  217. bootstack/core/capabilities/signals.py +242 -0
  218. bootstack/core/capabilities/winfo.py +315 -0
  219. bootstack/core/colorutils.py +234 -0
  220. bootstack/core/exceptions.py +95 -0
  221. bootstack/core/images.py +283 -0
  222. bootstack/core/localization/README.md +90 -0
  223. bootstack/core/localization/__init__.py +13 -0
  224. bootstack/core/localization/intl_format.py +580 -0
  225. bootstack/core/localization/msgcat.py +425 -0
  226. bootstack/core/localization/specs.py +143 -0
  227. bootstack/core/mixins/__init__.py +1 -0
  228. bootstack/core/mixins/ttk_state.py +35 -0
  229. bootstack/core/mixins/widget.py +132 -0
  230. bootstack/core/publisher.py +147 -0
  231. bootstack/core/signals/README.md +112 -0
  232. bootstack/core/signals/__init__.py +8 -0
  233. bootstack/core/signals/integration.py +100 -0
  234. bootstack/core/signals/signal.py +317 -0
  235. bootstack/core/signals/types.py +4 -0
  236. bootstack/core/validation/__init__.py +5 -0
  237. bootstack/core/validation/types.py +13 -0
  238. bootstack/core/validation/validation_result.py +17 -0
  239. bootstack/core/validation/validation_rules.py +112 -0
  240. bootstack/core/variables.py +62 -0
  241. bootstack/datasource/README.md +607 -0
  242. bootstack/datasource/__init__.py +51 -0
  243. bootstack/datasource/base.py +474 -0
  244. bootstack/datasource/file_source.py +541 -0
  245. bootstack/datasource/memory_source.py +482 -0
  246. bootstack/datasource/sqlite_source.py +453 -0
  247. bootstack/datasource/types.py +259 -0
  248. bootstack/dialogs/__init__.py +56 -0
  249. bootstack/dialogs/colorchooser.py +674 -0
  250. bootstack/dialogs/colordropper.py +257 -0
  251. bootstack/dialogs/datedialog.py +404 -0
  252. bootstack/dialogs/dialog.py +514 -0
  253. bootstack/dialogs/filterdialog.py +358 -0
  254. bootstack/dialogs/fontdialog.py +339 -0
  255. bootstack/dialogs/formdialog.py +541 -0
  256. bootstack/dialogs/message.py +489 -0
  257. bootstack/dialogs/query.py +561 -0
  258. bootstack/py.typed +1 -0
  259. bootstack/runtime/__init__.py +3 -0
  260. bootstack/runtime/app.py +879 -0
  261. bootstack/runtime/base_window.py +786 -0
  262. bootstack/runtime/events.py +399 -0
  263. bootstack/runtime/menu.py +510 -0
  264. bootstack/runtime/shortcuts.py +423 -0
  265. bootstack/runtime/tk_patch.py +31 -0
  266. bootstack/runtime/toplevel.py +131 -0
  267. bootstack/runtime/utility.py +371 -0
  268. bootstack/runtime/visual_focus.py +228 -0
  269. bootstack/runtime/window_utilities.py +1043 -0
  270. bootstack/style/__init__.py +5498 -0
  271. bootstack/style/bootstyle.py +507 -0
  272. bootstack/style/bootstyle_builder_base.py +752 -0
  273. bootstack/style/bootstyle_builder_mixed.py +93 -0
  274. bootstack/style/bootstyle_builder_tk.py +109 -0
  275. bootstack/style/bootstyle_builder_ttk.py +354 -0
  276. bootstack/style/builders/__init__.py +51 -0
  277. bootstack/style/builders/badge.py +44 -0
  278. bootstack/style/builders/button.py +453 -0
  279. bootstack/style/builders/buttongroup.py +344 -0
  280. bootstack/style/builders/calendar.py +271 -0
  281. bootstack/style/builders/checkbutton.py +95 -0
  282. bootstack/style/builders/combobox.py +112 -0
  283. bootstack/style/builders/contextmenu.py +268 -0
  284. bootstack/style/builders/entry.py +83 -0
  285. bootstack/style/builders/expander.py +171 -0
  286. bootstack/style/builders/field.py +312 -0
  287. bootstack/style/builders/frame.py +27 -0
  288. bootstack/style/builders/label.py +28 -0
  289. bootstack/style/builders/labelframe.py +41 -0
  290. bootstack/style/builders/listview.py +267 -0
  291. bootstack/style/builders/menubar.py +74 -0
  292. bootstack/style/builders/menubutton.py +408 -0
  293. bootstack/style/builders/notebook.py +316 -0
  294. bootstack/style/builders/panedwindow.py +25 -0
  295. bootstack/style/builders/progressbar.py +71 -0
  296. bootstack/style/builders/radiobutton.py +68 -0
  297. bootstack/style/builders/scale.py +66 -0
  298. bootstack/style/builders/scrollbar.py +360 -0
  299. bootstack/style/builders/separator.py +45 -0
  300. bootstack/style/builders/sidenav.py +313 -0
  301. bootstack/style/builders/sizegrip.py +15 -0
  302. bootstack/style/builders/spinbox.py +119 -0
  303. bootstack/style/builders/switch.py +67 -0
  304. bootstack/style/builders/tabitem.py +205 -0
  305. bootstack/style/builders/toolbutton.py +260 -0
  306. bootstack/style/builders/tooltip.py +26 -0
  307. bootstack/style/builders/treeview.py +269 -0
  308. bootstack/style/builders/utils.py +404 -0
  309. bootstack/style/builders_tk/__init__.py +16 -0
  310. bootstack/style/builders_tk/defaults.py +229 -0
  311. bootstack/style/element.py +173 -0
  312. bootstack/style/style.py +499 -0
  313. bootstack/style/theme_provider.py +449 -0
  314. bootstack/style/tk_patch.py +5 -0
  315. bootstack/style/token_maps.py +42 -0
  316. bootstack/style/types.py +32 -0
  317. bootstack/style/typography.py +527 -0
  318. bootstack/style/utility.py +696 -0
  319. bootstack/themes/__init__.py +12 -0
  320. bootstack/themes/standard.py +415 -0
  321. bootstack/themes/user.py +45 -0
  322. bootstack/widgets/__init__.py +53 -0
  323. bootstack/widgets/composites/__init__.py +38 -0
  324. bootstack/widgets/composites/accordion.py +385 -0
  325. bootstack/widgets/composites/appshell.py +445 -0
  326. bootstack/widgets/composites/buttongroup.py +391 -0
  327. bootstack/widgets/composites/calendar.py +914 -0
  328. bootstack/widgets/composites/compositeframe.py +282 -0
  329. bootstack/widgets/composites/contextmenu.py +1754 -0
  330. bootstack/widgets/composites/dateentry.py +261 -0
  331. bootstack/widgets/composites/dropdownbutton.py +190 -0
  332. bootstack/widgets/composites/expander.py +508 -0
  333. bootstack/widgets/composites/field.py +448 -0
  334. bootstack/widgets/composites/floodgauge.py +434 -0
  335. bootstack/widgets/composites/form.py +983 -0
  336. bootstack/widgets/composites/labeledscale.py +209 -0
  337. bootstack/widgets/composites/list/__init__.py +15 -0
  338. bootstack/widgets/composites/list/listitem.py +733 -0
  339. bootstack/widgets/composites/list/listview.py +1507 -0
  340. bootstack/widgets/composites/menubar.py +303 -0
  341. bootstack/widgets/composites/meter.py +882 -0
  342. bootstack/widgets/composites/numericentry.py +183 -0
  343. bootstack/widgets/composites/pagestack.py +330 -0
  344. bootstack/widgets/composites/passwordentry.py +149 -0
  345. bootstack/widgets/composites/pathentry.py +223 -0
  346. bootstack/widgets/composites/radiogroup.py +466 -0
  347. bootstack/widgets/composites/scrolledtext.py +388 -0
  348. bootstack/widgets/composites/scrolledtext.pyi +186 -0
  349. bootstack/widgets/composites/scrollview.py +675 -0
  350. bootstack/widgets/composites/selectbox.py +544 -0
  351. bootstack/widgets/composites/sidenav/__init__.py +24 -0
  352. bootstack/widgets/composites/sidenav/group.py +485 -0
  353. bootstack/widgets/composites/sidenav/header.py +83 -0
  354. bootstack/widgets/composites/sidenav/item.py +413 -0
  355. bootstack/widgets/composites/sidenav/separator.py +51 -0
  356. bootstack/widgets/composites/sidenav/view.py +919 -0
  357. bootstack/widgets/composites/spinnerentry.py +232 -0
  358. bootstack/widgets/composites/tableview/__init__.py +5 -0
  359. bootstack/widgets/composites/tableview/tableview.py +2254 -0
  360. bootstack/widgets/composites/tableview/types.py +169 -0
  361. bootstack/widgets/composites/tabs/__init__.py +6 -0
  362. bootstack/widgets/composites/tabs/tabitem.py +372 -0
  363. bootstack/widgets/composites/tabs/tabs.py +478 -0
  364. bootstack/widgets/composites/tabs/tabview.py +352 -0
  365. bootstack/widgets/composites/textentry.py +90 -0
  366. bootstack/widgets/composites/timeentry.py +189 -0
  367. bootstack/widgets/composites/toast.py +364 -0
  368. bootstack/widgets/composites/togglegroup.py +382 -0
  369. bootstack/widgets/composites/toolbar.py +393 -0
  370. bootstack/widgets/composites/tooltip.py +404 -0
  371. bootstack/widgets/internal/__init__.py +0 -0
  372. bootstack/widgets/internal/wrapper_base.py +304 -0
  373. bootstack/widgets/mixins/__init__.py +25 -0
  374. bootstack/widgets/mixins/configure_mixin.py +186 -0
  375. bootstack/widgets/mixins/entry_mixin.py +70 -0
  376. bootstack/widgets/mixins/font_mixin.py +346 -0
  377. bootstack/widgets/mixins/icon_mixin.py +38 -0
  378. bootstack/widgets/mixins/localization_mixin.py +255 -0
  379. bootstack/widgets/mixins/signal_mixin.py +272 -0
  380. bootstack/widgets/mixins/validation_mixin.py +204 -0
  381. bootstack/widgets/parts/__init__.py +11 -0
  382. bootstack/widgets/parts/numberentry_part.py +345 -0
  383. bootstack/widgets/parts/spinnerentry_part.py +394 -0
  384. bootstack/widgets/parts/textentry_part.py +344 -0
  385. bootstack/widgets/primitives/__init__.py +55 -0
  386. bootstack/widgets/primitives/badge.py +44 -0
  387. bootstack/widgets/primitives/button.py +89 -0
  388. bootstack/widgets/primitives/card.py +66 -0
  389. bootstack/widgets/primitives/checkbutton.py +124 -0
  390. bootstack/widgets/primitives/checktoggle.py +53 -0
  391. bootstack/widgets/primitives/combobox.py +165 -0
  392. bootstack/widgets/primitives/entry.py +98 -0
  393. bootstack/widgets/primitives/frame.py +206 -0
  394. bootstack/widgets/primitives/gridframe.py +479 -0
  395. bootstack/widgets/primitives/label.py +95 -0
  396. bootstack/widgets/primitives/labelframe.py +63 -0
  397. bootstack/widgets/primitives/menubutton.py +118 -0
  398. bootstack/widgets/primitives/notebook.py +551 -0
  399. bootstack/widgets/primitives/optionmenu.py +248 -0
  400. bootstack/widgets/primitives/packframe.py +228 -0
  401. bootstack/widgets/primitives/panedwindow.py +58 -0
  402. bootstack/widgets/primitives/progressbar.py +95 -0
  403. bootstack/widgets/primitives/radiobutton.py +115 -0
  404. bootstack/widgets/primitives/radiotoggle.py +50 -0
  405. bootstack/widgets/primitives/scale.py +85 -0
  406. bootstack/widgets/primitives/scrollbar.py +56 -0
  407. bootstack/widgets/primitives/separator.py +56 -0
  408. bootstack/widgets/primitives/sizegrip.py +47 -0
  409. bootstack/widgets/primitives/spinbox.py +91 -0
  410. bootstack/widgets/primitives/switch.py +41 -0
  411. bootstack/widgets/primitives/treeview.py +77 -0
  412. bootstack/widgets/types.py +20 -0
  413. bootstack-0.1.0a1.dist-info/METADATA +196 -0
  414. bootstack-0.1.0a1.dist-info/RECORD +419 -0
  415. bootstack-0.1.0a1.dist-info/WHEEL +5 -0
  416. bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
  417. bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
  418. bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
  419. bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,514 @@
1
+ """Core dialog base class for bootstack dialogs.
2
+
3
+ This module provides the base `Dialog` class using a builder pattern
4
+ for creating flexible, customizable dialogs with composition-based content
5
+ and footer builders.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ import tkinter
12
+ from tkinter import Widget
13
+ from typing import Any, Callable, Iterable, Literal, Mapping, Optional, Tuple, TypedDict, Union
14
+
15
+ import bootstack as bs
16
+ from bootstack.widgets.types import Master
17
+ from bootstack.runtime.toplevel import Toplevel
18
+ from bootstack.runtime.window_utilities import AnchorPoint, WindowPositioning
19
+
20
+ # --- Types -----------------------------------------------------------------
21
+
22
+ ContentBuilder = Callable[[Widget], None]
23
+ FooterBuilder = Callable[[Widget], None]
24
+
25
+ ButtonRole = Literal["primary", "secondary", "danger", "cancel", "help"]
26
+ DialogMode = Literal["modal", "popover", "sheet"]
27
+
28
+
29
+ @dataclass
30
+ class DialogButton:
31
+ """Specification for a dialog button.
32
+
33
+ Attributes:
34
+ text (str): Button label text displayed to the user.
35
+ role (ButtonRole): Button role determining styling and behavior.
36
+ - `"primary"`: Main action (blue, triggered by Enter)
37
+ - `"secondary"`: Standard action (gray)
38
+ - `"danger"`: Destructive action (red)
39
+ - `"cancel"`: Cancel action (outline, triggered by Escape)
40
+ - `"help"`: Help/info action (link style)
41
+ result (Any | None): Value assigned to dialog.result when clicked.
42
+ closes (bool): Whether button closes the dialog when clicked.
43
+ default (bool): Whether this is the default button (focused, triggered by Enter).
44
+ command (Callable[[Dialog], None] | None): Callback called when clicked.
45
+ accent (str | None): Accent token for styling (e.g., 'primary', 'danger').
46
+ variant (str | None): Style variant (e.g., 'outline', 'link').
47
+ icon (str | dict | None): Optional icon specification for the button.
48
+ """
49
+ text: str
50
+ role: ButtonRole = "secondary"
51
+ result: Any | None = None # value assigned to dialog.result
52
+ closes: bool = True # close dialog after click
53
+ default: bool = False # default button (Enter)
54
+ command: Callable[[Dialog], None] | None = None
55
+ accent: str | None = None # accent token (e.g., 'primary', 'danger')
56
+ variant: str | None = None # style variant (e.g., 'outline', 'link')
57
+ icon: str | dict[str, Any] | None = None # passed straight to bs.Button(icon=...)
58
+
59
+
60
+ ButtonSpec = Union[DialogButton, Mapping[str, Any]]
61
+
62
+
63
+ class ShowOptions(TypedDict, total=False):
64
+ """Options for showing the dialog window.
65
+
66
+ Attributes:
67
+ position (tuple[int, int] | None): Optional (x, y) coordinates.
68
+ modal (bool | None): Override the mode's default modality.
69
+ anchor_to (Widget | str | None): Positioning target widget or string.
70
+ anchor_point (AnchorPoint): Point on the anchor target.
71
+ window_point (AnchorPoint): Point on the dialog window.
72
+ offset (tuple[int, int]): Additional (x, y) offset in pixels.
73
+ auto_flip (bool | str): Smart positioning to keep window on screen.
74
+ """
75
+ position: Optional[Tuple[int, int]]
76
+ modal: Optional[bool]
77
+ anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]]
78
+ anchor_point: AnchorPoint
79
+ window_point: AnchorPoint
80
+ offset: Tuple[int, int]
81
+ auto_flip: Union[bool, Literal['vertical', 'horizontal']]
82
+
83
+
84
+ # --- Dialog ----------------------------------------------------------------
85
+
86
+ class Dialog:
87
+ """A flexible dialog window using the builder pattern.
88
+
89
+ Dialog provides a composition-based approach to creating modal and non-modal
90
+ dialogs with customizable content, buttons, and behavior. Instead of requiring
91
+ inheritance, you provide callback functions to build the dialog content and
92
+ optionally the footer.
93
+
94
+ The dialog manages window creation, positioning, button handling, and keyboard
95
+ shortcuts automatically.
96
+
97
+ Attributes:
98
+ result: The value returned by the dialog after closing.
99
+ Set automatically when a button with a result value is clicked.
100
+ Defaults to None.
101
+
102
+ Args:
103
+ master: Parent widget for the dialog. If None, uses the default root window.
104
+ title: Dialog window title displayed in the title bar.
105
+ Defaults to "bootstack".
106
+ content_builder: Optional callback function to build dialog content.
107
+ Receives a Frame widget as parameter. Should pack/grid widgets into it.
108
+ If None, dialog will have no content area. Defaults to None.
109
+ footer_builder: Optional callback function to build custom footer.
110
+ Receives a Frame widget as parameter. If provided, replaces the
111
+ standard button footer. Defaults to None.
112
+ buttons: Optional list of DialogButton or dict specifications for footer buttons.
113
+ Ignored if footer_builder is provided. Button order in list determines
114
+ right-to-left display order (first button appears rightmost).
115
+ Defaults to None (no footer).
116
+ minsize: Optional (width, height) minimum window size in pixels.
117
+ Defaults to None (no minimum).
118
+ maxsize: Optional (width, height) maximum window size in pixels.
119
+ Defaults to None (no maximum).
120
+ resizable: Optional (width, height) tuple of booleans controlling window resize.
121
+ (True, True) allows full resizing, (False, False) prevents all resizing.
122
+ Defaults to (False, False).
123
+ alert: If True, plays system alert sound when dialog is shown.
124
+ Defaults to False.
125
+ mode: Dialog interaction mode.
126
+ - "modal": Blocks parent window interaction, requires user response
127
+ - "popover": Closes automatically when focus leaves dialog
128
+ - "sheet": Like "modal" but on macOS applies the Cocoa sheet
129
+ window class for a chromeless, sheet-styled dialog tied to
130
+ its parent (via `transient`). Falls back to plain modal
131
+ behavior on Windows/Linux where there's no equivalent.
132
+ Defaults to "modal".
133
+ frameless: If True, removes window decorations (title bar, borders) and adds
134
+ a solid border frame around the dialog content. Useful for dropdown-style
135
+ menus or popover UIs. Defaults to False.
136
+ window_style: Windows-only pywinstyles effect. Options include
137
+ 'mica', 'acrylic', 'aero', 'transparent', 'win7', etc.
138
+ If None (default), uses AppSettings.window_style.
139
+ """
140
+
141
+ def __init__(
142
+ self,
143
+ master: Master = None,
144
+ title: str = "bootstack",
145
+ content_builder: Optional[ContentBuilder] = None,
146
+ footer_builder: Optional[FooterBuilder] = None,
147
+ *,
148
+ buttons: Iterable[ButtonSpec] | None = None,
149
+ minsize: tuple[int, int] | None = None,
150
+ maxsize: tuple[int, int] | None = None,
151
+ resizable: tuple[bool, bool] | None = (False, False),
152
+ alert: bool = False,
153
+ mode: DialogMode = "modal",
154
+ frameless: bool = False,
155
+ window_style: str | None = None,
156
+ ):
157
+ import tkinter
158
+ self._master = master if master else tkinter._default_root
159
+ self._title = title
160
+ self._content_builder = content_builder
161
+ self._footer_builder = footer_builder
162
+ self._buttons: list[DialogButton] = self._normalize_buttons(buttons)
163
+
164
+ self._minsize = minsize
165
+ self._maxsize = maxsize
166
+ self._resizable = resizable
167
+ self._alert = alert
168
+ self._mode = mode
169
+ self._frameless = frameless
170
+ self._window_style = window_style
171
+
172
+ self._toplevel: Toplevel | None = None
173
+ self._content: bs.Frame | None = None
174
+ self._footer: bs.Frame | None = None
175
+ self._border_frame: bs.Frame | None = None
176
+
177
+ self.result: Any = None
178
+
179
+ # --------------------------------------------------------------- API
180
+
181
+ def show(
182
+ self,
183
+ position: Optional[Tuple[int, int]] = None,
184
+ modal: Optional[bool] = None,
185
+ *,
186
+ anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]] = None,
187
+ anchor_point: AnchorPoint = 'center',
188
+ window_point: AnchorPoint = 'center',
189
+ offset: Tuple[int, int] = (0, 0),
190
+ auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
191
+ ):
192
+ """Create and show the dialog with flexible positioning options.
193
+
194
+ Args:
195
+ position: Optional (x, y) coordinates to position the dialog.
196
+ If provided, takes precedence over anchor-based positioning.
197
+ modal: Override the mode's default modality.
198
+ - If None, uses mode:
199
+ - "modal": grab_set + wait_window
200
+ - "popover": no grab, but wait_window
201
+ anchor_to: Positioning target. Can be:
202
+ - Widget: Anchor to a specific widget
203
+ - "screen": Anchor to screen edges/corners
204
+ - "cursor": Anchor to mouse cursor location
205
+ - "parent": Anchor to parent window (same as widget)
206
+ - None: Centers on parent (default)
207
+ anchor_point: Point on the anchor target (n, s, e, w, ne, nw, se, sw, center).
208
+ Default 'center'.
209
+ window_point: Point on the dialog window (n, s, e, w, ne, nw, se, sw, center).
210
+ Default 'center'.
211
+ offset: Additional (x, y) offset in pixels from the anchor position.
212
+ auto_flip: Smart positioning to keep window on screen.
213
+ - False: No flipping (default)
214
+ - True: Flip both vertically and horizontally as needed
215
+ - 'vertical': Only flip up/down
216
+ - 'horizontal': Only flip left/right
217
+
218
+ Positioning Logic:
219
+ 1. If position is provided: Use explicit coordinates
220
+ 2. If anchor_to is provided: Use anchor-based positioning
221
+ 3. Default: Center on parent window
222
+ """
223
+ if modal is None:
224
+ modal = self._mode in ("modal", "sheet")
225
+
226
+ self.result = None
227
+ self._create_toplevel(modal=modal)
228
+ self._build_footer()
229
+ self._build_content()
230
+ self._position_dialog(
231
+ position=position,
232
+ anchor_to=anchor_to,
233
+ anchor_point=anchor_point,
234
+ window_point=window_point,
235
+ offset=offset,
236
+ auto_flip=auto_flip
237
+ )
238
+
239
+ if self._alert:
240
+ self._toplevel.bell()
241
+
242
+ if self._mode == "popover":
243
+ self._toplevel.bind("<FocusOut>", self._on_focus_out, add="+")
244
+
245
+ if modal:
246
+ # Sheets are inherently modal to their parent on Aqua via the
247
+ # sheet window class; calling grab_set on top of that is fine
248
+ # but unnecessary. Plain modal mode still uses grab to block
249
+ # interaction with the parent on platforms without a sheet.
250
+ if self._mode in ("modal", "sheet"):
251
+ self._toplevel.grab_set()
252
+ self._master.wait_window(self._toplevel)
253
+
254
+ @property
255
+ def toplevel(self) -> Toplevel | None:
256
+ """Read-only access to the underlying toplevel window."""
257
+ return self._toplevel
258
+
259
+ # --------------------------------------------------------------- Internals
260
+
261
+ def _normalize_buttons(
262
+ self,
263
+ buttons: Iterable[ButtonSpec] | None,
264
+ ) -> list[DialogButton]:
265
+ if not buttons:
266
+ return []
267
+
268
+ normalized: list[DialogButton] = []
269
+ for b in buttons:
270
+ if isinstance(b, DialogButton):
271
+ normalized.append(b)
272
+ else:
273
+ # assume mapping/dict
274
+ try:
275
+ normalized.append(DialogButton(**b)) # type: ignore[arg-type]
276
+ except TypeError as exc:
277
+ raise ValueError(
278
+ f"Invalid button mapping {b!r}: {exc}"
279
+ ) from exc
280
+ return normalized
281
+
282
+ def _create_toplevel(self, modal: bool = True):
283
+ # Pass transient to Toplevel so it's set before window_style is applied
284
+ # (required for mica effect to work on Windows)
285
+ self._toplevel = Toplevel(
286
+ master=self._master,
287
+ window_style=self._window_style,
288
+ transient=self._master if modal else None
289
+ )
290
+ self._toplevel.title(self._title)
291
+ self._toplevel.protocol("WM_DELETE_WINDOW", self._on_close_request)
292
+
293
+ try:
294
+ self._toplevel.withdraw()
295
+ except Exception:
296
+ pass
297
+
298
+ # Sheet mode: on Aqua, apply the Cocoa 'sheet' window class so the
299
+ # dialog renders chromeless and tied to its parent. Must be set
300
+ # before the window is mapped, hence here while still withdrawn.
301
+ # On non-Aqua, sheet mode is treated as plain modal — there's no
302
+ # cross-platform equivalent of a Cocoa sheet.
303
+ if self._mode == "sheet" and getattr(self._toplevel, 'winsys', None) == 'aqua':
304
+ try:
305
+ self._toplevel.tk.call(
306
+ '::tk::unsupported::MacWindowStyle', 'style',
307
+ self._toplevel, 'sheet', 'none',
308
+ )
309
+ except tkinter.TclError:
310
+ pass
311
+
312
+ if self._minsize:
313
+ self._toplevel.minsize(*self._minsize)
314
+ if self._maxsize:
315
+ self._toplevel.maxsize(*self._maxsize)
316
+ if self._resizable is not None:
317
+ self._toplevel.resizable(*self._resizable)
318
+
319
+ if self._frameless:
320
+ self._toplevel.overrideredirect(True)
321
+ self._border_frame = bs.Frame(self._toplevel, show_border=True, padding=2)
322
+ self._border_frame.pack(fill='both', expand=True)
323
+
324
+ def _build_content(self):
325
+ parent = self._border_frame if self._frameless else self._toplevel
326
+ padding = 2 if self._frameless else 0
327
+ self._content = bs.Frame(parent, padding=padding)
328
+
329
+ if self._frameless:
330
+ self._content.pack(fill="both", side="top", expand=False)
331
+ else:
332
+ self._content.pack(fill="both", side="top", expand=True)
333
+
334
+ if self._content_builder:
335
+ self._content_builder(self._content)
336
+
337
+ def _build_footer(self):
338
+ parent = self._border_frame if self._frameless else self._toplevel
339
+ footer_padding = 6 if self._frameless else 4
340
+
341
+ if self._footer_builder:
342
+ self._footer = bs.Frame(parent, padding=footer_padding)
343
+ self._footer.pack(side="bottom", fill="x")
344
+ bs.Separator(parent, orient="horizontal").pack(side="bottom", fill="x")
345
+ self._footer_builder(self._footer)
346
+ return
347
+
348
+ if not self._buttons:
349
+ return
350
+
351
+ self._footer = bs.Frame(parent, padding=footer_padding)
352
+ self._footer.pack(side="bottom", fill="x")
353
+ bs.Separator(parent, orient="horizontal").pack(side="bottom", fill="x")
354
+
355
+ self._create_standard_buttons(self._footer)
356
+
357
+ def _create_standard_buttons(self, parent: Widget):
358
+ """Create standardized footer buttons from self._buttons.
359
+
360
+ Buttons are packed right-to-left so first button appears rightmost.
361
+ """
362
+ default_button: bs.Button | None = None
363
+ cancel_button: bs.Button | None = None
364
+
365
+ for spec in reversed(self._buttons):
366
+ # Get accent/variant from spec or derive from role
367
+ if spec.accent or spec.variant:
368
+ accent, variant = spec.accent, spec.variant
369
+ else:
370
+ accent, variant = self._style_for_role(spec.role)
371
+
372
+ def make_command(s: DialogButton):
373
+ def cmd():
374
+ if s.command:
375
+ s.command(self)
376
+ if s.result is not None:
377
+ self.result = s.result
378
+ if s.closes and self._toplevel:
379
+ self._toplevel.destroy()
380
+
381
+ return cmd
382
+
383
+ btn = bs.Button(
384
+ parent,
385
+ text=spec.text,
386
+ accent=accent,
387
+ variant=variant,
388
+ command=make_command(spec),
389
+ icon=spec.icon,
390
+ compound="left" if spec.icon else "text",
391
+ )
392
+ btn.pack(side="right")
393
+
394
+ if spec.default and default_button is None:
395
+ default_button = btn
396
+ if spec.role == "cancel" and cancel_button is None:
397
+ cancel_button = btn
398
+
399
+ if self._toplevel is None:
400
+ return
401
+
402
+ if default_button is not None:
403
+ default_button.focus_set()
404
+ self._toplevel.bind("<Return>", lambda e, b=default_button: b.invoke())
405
+
406
+ if cancel_button is not None:
407
+ self._toplevel.bind("<Escape>", lambda e, b=cancel_button: b.invoke())
408
+ else:
409
+ self._toplevel.bind("<Escape>", lambda e: self._toplevel.destroy())
410
+
411
+ def _position_dialog(
412
+ self,
413
+ position: Optional[Tuple[int, int]] = None,
414
+ anchor_to: Optional[Union[Widget, Literal["screen", "cursor", "parent"]]] = None,
415
+ anchor_point: AnchorPoint = 'center',
416
+ window_point: AnchorPoint = 'center',
417
+ offset: Tuple[int, int] = (0, 0),
418
+ auto_flip: Union[bool, Literal['vertical', 'horizontal']] = False
419
+ ) -> None:
420
+ """Position the dialog window using consolidated positioning logic.
421
+
422
+ Positioning logic:
423
+ 1. If position is provided: Use explicit coordinates
424
+ 2. If anchor_to is provided: Use anchor-based positioning
425
+ 3. Default: Center on parent
426
+ """
427
+ if not self._toplevel:
428
+ return
429
+
430
+ # Priority 1: Explicit position coordinates
431
+ if position is not None:
432
+ x, y = position
433
+ x, y = WindowPositioning.ensure_on_screen(self._toplevel, int(x), int(y))
434
+ self._toplevel.geometry(f"+{x}+{y}")
435
+
436
+ # Priority 2: Anchor-based positioning
437
+ elif anchor_to is not None:
438
+ WindowPositioning.position_anchored(
439
+ window=self._toplevel,
440
+ anchor_to=anchor_to,
441
+ parent=self._master,
442
+ anchor_point=anchor_point,
443
+ window_point=window_point,
444
+ offset=offset,
445
+ auto_flip=auto_flip,
446
+ ensure_visible=True
447
+ )
448
+
449
+ # Priority 3: Default - center on parent
450
+ else:
451
+ WindowPositioning.position_window(
452
+ window=self._toplevel,
453
+ position=None,
454
+ parent=self._master,
455
+ center_on_parent=True,
456
+ ensure_visible=True
457
+ )
458
+
459
+ # Apply window style while still withdrawn, right before showing.
460
+ # The update() call is here so pywinstyles can attach to a fully
461
+ # realized HWND on Windows; on Aqua (and X11) it serves no purpose
462
+ # and can hang indefinitely flushing children's pending events
463
+ # (e.g. FontDialog's Treeview with hundreds of tag-configure font
464
+ # calls), so gate it on the platform that actually needs it.
465
+ self._toplevel.update_idletasks()
466
+ self._toplevel._apply_window_style()
467
+ if getattr(self._toplevel, 'winsys', None) == 'win32':
468
+ self._toplevel.update()
469
+ self._toplevel.deiconify()
470
+
471
+ # Second centering pass for default positioning (handles dynamic sizing)
472
+ if position is None and anchor_to is None:
473
+ try:
474
+ x, y = WindowPositioning.center_on_parent(self._toplevel, self._master)
475
+ x, y = WindowPositioning.ensure_on_screen(self._toplevel, x, y)
476
+ self._toplevel.geometry(f"+{x}+{y}")
477
+ except Exception:
478
+ pass
479
+
480
+ # --------------------------------------------------------------- Event Handlers
481
+
482
+ def _on_focus_out(self, _event):
483
+ """For popover mode: close when focus leaves the dialog."""
484
+ if self._mode != "popover" or not self._toplevel:
485
+ return
486
+
487
+ new_focus = self._toplevel.focus_get()
488
+
489
+ if new_focus is None:
490
+ self._toplevel.destroy()
491
+ return
492
+
493
+ if not str(new_focus).startswith(str(self._toplevel)):
494
+ self._toplevel.destroy()
495
+
496
+ def _on_close_request(self):
497
+ if self._toplevel:
498
+ self._toplevel.destroy()
499
+
500
+ # --------------------------------------------------------------- Helpers
501
+
502
+ def _style_for_role(self, role: ButtonRole) -> tuple[str | None, str | None]:
503
+ """Return (accent, variant) tuple for a button role."""
504
+ if role == "primary":
505
+ return ("primary", None)
506
+ if role == "secondary":
507
+ return ("secondary", None)
508
+ if role == "danger":
509
+ return ("danger", None)
510
+ if role == "cancel":
511
+ return ("secondary", "outline")
512
+ if role == "help":
513
+ return ("info", "link")
514
+ return ("secondary", None)