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,302 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from typing import overload, Any, Callable, Literal, TYPE_CHECKING
5
+
6
+ from bootstack.widgets._impl.composites.selectbox import SelectBox as _InternalSelectBox
7
+ from bootstack.widgets._core.base import PublicWidgetBase, adapt_handler
8
+ from bootstack.widgets._core.events import resolve_event, register_widget_events
9
+ from bootstack.widgets._core.options import record_to_dict
10
+ from bootstack.events import ChangeEvent, Subscription
11
+ from bootstack.streams import Stream
12
+ from bootstack.widgets.textfield import _INNER_ENTRY_SEQUENCES
13
+ from bootstack.widgets.types import AccentToken, Event, Option, OptionDict, WidgetDensity
14
+
15
+ if TYPE_CHECKING:
16
+ from bootstack.signals import Signal
17
+
18
+ _SELECT_EVENTS: dict[str, str] = {
19
+ "change": "<<Change>>",
20
+ }
21
+
22
+
23
+ class Select(PublicWidgetBase):
24
+ """A single-selection dropdown field.
25
+
26
+ The options list is the first positional argument. All options are
27
+ keyword-only.
28
+
29
+ Args:
30
+ options: Choices presented in the popup. Each item is a plain string,
31
+ a `(text, value)` tuple, or a `{'text': ..., 'value': ...}` dict —
32
+ so an option's displayed label can differ from its stored value.
33
+ A dict option may also carry `'icon'` (a glyph rendered beside the
34
+ row label) and `'disabled'` (when `True` the row is dimmed and
35
+ cannot be chosen); any other keys ride along as carried data on
36
+ `selection`. Defaults to an empty list.
37
+ value: Initially selected value (value-space — matches an option's
38
+ value, not its label).
39
+ signal: Reactive `Signal` two-way bound to the field's displayed text.
40
+ With decoupled options (text differs from value), bind to the value
41
+ via `on_change`/`value`; the signal carries the display text.
42
+ label: Label displayed above the field.
43
+ message: Hint or helper text displayed below the field.
44
+ required: If `True`, marks the field as required and prevents
45
+ empty submission.
46
+ searchable: If `True`, typing in the field filters the option
47
+ list. Defaults to `False`.
48
+ allow_custom_values: If `True`, users may type values not in
49
+ `options`. Defaults to `False`.
50
+ group_by: Name of an option field to cluster the popup rows under
51
+ non-selectable group headers (e.g. `'category'`). The field is read
52
+ from each option's flat record, so it may be any carried bag key (or
53
+ `'text'`/`'value'`). Groups appear in first-appearance order; an
54
+ option missing the field renders headerless. Grouping is
55
+ presentational only — `value`/`selection` are unaffected. `None`
56
+ (default) renders a flat list.
57
+ max_visible_items: Approximate number of option rows the popup shows
58
+ before it scrolls (height is `max_visible_items * row_height`).
59
+ Group headers and separators consume some of that budget, so the
60
+ count is approximate. `None` (default) uses the built-in cap.
61
+ read_only: If `True`, value is visible but the popup cannot be
62
+ opened.
63
+ disabled: If `True`, field is fully non-interactive and dimmed.
64
+ width: Width in character units.
65
+ accent: Accent token applied to the focus ring.
66
+ density: Padding density.
67
+ localize: Whether option labels (in the popup and the selected label
68
+ shown in the field) are translated through the catalog — `True`,
69
+ `False`, or `'auto'` (translate when a translation is registered,
70
+ otherwise show the literal). Defaults to the app's `localize_mode`.
71
+ Set `False` to keep proper nouns untranslated; override a single
72
+ option with its `localize` key. When searching is enabled, search
73
+ matches the displayed (translated) labels.
74
+ parent: Explicit parent widget. If omitted, the current
75
+ context-stack container is used.
76
+ **kwargs: Layout placement options applied by the parent container —
77
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
78
+ See :doc:`/tasks/layout`.
79
+ """
80
+
81
+ def __init__(
82
+ self,
83
+ options: list[Option] | None = None,
84
+ *,
85
+ value: Any = None,
86
+ signal: "Signal[str] | None" = None,
87
+ label: str | None = None,
88
+ message: str | None = None,
89
+ required: bool = False,
90
+ searchable: bool = False,
91
+ allow_custom_values: bool = False,
92
+ group_by: str | None = None,
93
+ max_visible_items: int | None = None,
94
+ read_only: bool = False,
95
+ disabled: bool = False,
96
+ width: int | None = None,
97
+ accent: AccentToken | str | None = None,
98
+ density: WidgetDensity | None = None,
99
+ localize: bool | Literal['auto'] | None = None,
100
+ parent: Any = None,
101
+ **kwargs: Any,
102
+ ) -> None:
103
+ self._parent = self._resolve_parent(parent)
104
+ layout_kw = self._split_layout_kwargs(kwargs)
105
+ tk_master = self._parent._child_master() if self._parent else None
106
+
107
+ internal_kwargs: dict[str, Any] = {
108
+ "items": options or [],
109
+ "enable_search": searchable,
110
+ "allow_custom_values": allow_custom_values,
111
+ "group_by": group_by,
112
+ "max_visible_items": max_visible_items,
113
+ # Public Select rejects an unknown value (unless custom values are
114
+ # allowed); internal/embedded SelectBoxes stay lenient.
115
+ "strict_value": not allow_custom_values,
116
+ }
117
+ if value is not None:
118
+ internal_kwargs["value"] = value
119
+ if signal is not None:
120
+ internal_kwargs["textsignal"] = signal
121
+ if label is not None:
122
+ internal_kwargs["label"] = label
123
+ if message is not None:
124
+ internal_kwargs["message"] = message
125
+ if required:
126
+ internal_kwargs["required"] = True
127
+ if disabled:
128
+ internal_kwargs["state"] = "disabled"
129
+ elif read_only:
130
+ internal_kwargs["state"] = "readonly"
131
+ if width is not None:
132
+ internal_kwargs["width"] = width
133
+ if accent is not None:
134
+ internal_kwargs["accent"] = accent
135
+ if density is not None:
136
+ internal_kwargs["density"] = density
137
+ if localize is not None:
138
+ internal_kwargs["localize"] = localize
139
+
140
+ self._internal = _InternalSelectBox(tk_master, **internal_kwargs)
141
+ self._attach_to_parent(layout_kw)
142
+
143
+ # ----- Event routing -----
144
+
145
+ def _entry_widget(self) -> tkinter.Misc:
146
+ return self._internal._entry
147
+
148
+ @overload
149
+ def on(self, event: str) -> Stream: ...
150
+ @overload
151
+ def on(self, event: str, handler: Callable[[Event], Any]) -> Subscription: ...
152
+ def on(self, event: str, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
153
+ """Register a callback for an event by name.
154
+
155
+ A generic, string-keyed escape hatch — prefer the typed `on_*`
156
+ shorthands (e.g. `on_change`), which carry the precise payload type.
157
+ Called with no handler, returns a composable `Stream`; with a handler,
158
+ binds it and returns a `Subscription`.
159
+
160
+ Args:
161
+ event: Event name (for example `'change'` or `'focus'`).
162
+ handler: Called with the event payload. Omit to get a composable
163
+ :class:`~bootstack.streams.Stream` instead.
164
+
165
+ Returns:
166
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
167
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
168
+ """
169
+ sequence = resolve_event(self, str(event))
170
+ target = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
171
+ if handler is None:
172
+ def _source(h):
173
+ t = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
174
+ bid = t.bind(sequence, adapt_handler(h), add="+")
175
+ return Subscription(t, sequence, bid)
176
+ return Stream(self._internal, _source=_source)
177
+ bid = target.bind(sequence, adapt_handler(handler), add="+")
178
+ return Subscription(target, sequence, bid)
179
+
180
+ # ----- Properties -----
181
+
182
+ @property
183
+ def value(self) -> Any:
184
+ """The currently selected value, or `None` if unselected.
185
+
186
+ This is the option's *value* (value-space). For the displayed label, see
187
+ `text`.
188
+ """
189
+ return self._internal.value
190
+
191
+ @value.setter
192
+ def value(self, v: Any) -> None:
193
+ self._internal.value = v
194
+
195
+ @property
196
+ def text(self) -> str:
197
+ """The label currently shown in the field — the selected option's text.
198
+
199
+ Read-only; the complement of `value` (the selection's value). Assign to
200
+ `value` to change the selection.
201
+ """
202
+ return self._internal.text
203
+
204
+ @property
205
+ def selection(self) -> dict | None:
206
+ """The selected option as a full record dict — the data bag — or `None`.
207
+
208
+ `{'text': ..., 'value': ..., ...any extra keys}`, indexed by key like
209
+ any record. `None` when nothing is selected or the value is a custom
210
+ off-list one. Read-only.
211
+ """
212
+ return self._internal.selection
213
+
214
+ @property
215
+ def options(self) -> list[OptionDict]:
216
+ """The available options as normalized `{'text', 'value'}` records.
217
+
218
+ Assigning a new list rebuilds the popup. Accepts the same `Option`
219
+ forms as the constructor (strings, `(text, value)` tuples, or dicts).
220
+ """
221
+ return [record_to_dict(r) for r in self._internal.cget("items")]
222
+
223
+ @options.setter
224
+ def options(self, items: list[Option]) -> None:
225
+ self._internal.configure(items=list(items))
226
+
227
+ @property
228
+ def group_by(self) -> str | None:
229
+ """Name of the option field the popup clusters rows under, or `None`.
230
+
231
+ Assigning a field name groups the popup the next time it opens; assign
232
+ `None` to render a flat list. Grouping is presentational only — it does
233
+ not affect `value`, `selection`, or `options`.
234
+ """
235
+ return self._internal.cget("group_by")
236
+
237
+ @group_by.setter
238
+ def group_by(self, field: str | None) -> None:
239
+ self._internal.configure(group_by=field or "")
240
+
241
+ @property
242
+ def max_visible_items(self) -> int | None:
243
+ """Approximate option-row cap before the popup scrolls, or `None`.
244
+
245
+ Assigning takes effect the next time the popup opens; assign `None` to
246
+ restore the built-in cap. Height is `max_visible_items * row_height`,
247
+ so group headers and separators make the count approximate.
248
+ """
249
+ return self._internal.cget("max_visible_items")
250
+
251
+ @max_visible_items.setter
252
+ def max_visible_items(self, n: int | None) -> None:
253
+ self._internal.configure(max_visible_items=n or 0)
254
+
255
+ @property
256
+ def selected_index(self) -> int:
257
+ """Zero-based index of the selected option, or `-1` if none selected."""
258
+ return self._internal.selected_index
259
+
260
+ @selected_index.setter
261
+ def selected_index(self, v: int) -> None:
262
+ self._internal.selected_index = v
263
+
264
+ @property
265
+ def read_only(self) -> bool:
266
+ """Whether the field is visible but the popup cannot be opened."""
267
+ return str(self._entry_widget().cget("state")) == "readonly"
268
+
269
+ @read_only.setter
270
+ def read_only(self, v: bool) -> None:
271
+ self._internal.configure(state="readonly" if v else "normal")
272
+
273
+ @property
274
+ def disabled(self) -> bool:
275
+ """Whether the field is fully non-interactive."""
276
+ return str(self._entry_widget().cget("state")) == "disabled"
277
+
278
+ @disabled.setter
279
+ def disabled(self, v: bool) -> None:
280
+ self._internal.configure(state="disabled" if v else "normal")
281
+
282
+ # ----- Event shorthands -----
283
+
284
+ @overload
285
+ def on_change(self) -> Stream: ...
286
+ @overload
287
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
288
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
289
+ """Register a callback fired when the selection changes.
290
+
291
+ Args:
292
+ handler: Called with a :class:`~bootstack.events.ChangeEvent`. Omit
293
+ to get a composable :class:`~bootstack.streams.Stream` instead.
294
+
295
+ Returns:
296
+ A cancellable :class:`~bootstack.events.Subscription` when a
297
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
298
+ """
299
+ return self.on("change", handler)
300
+
301
+
302
+ register_widget_events(Select, _SELECT_EVENTS)
@@ -0,0 +1,182 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import overload, Any, Callable, Literal, TYPE_CHECKING
4
+
5
+ from bootstack.widgets._impl.primitives.optionmenu import OptionMenu as _InternalOptionMenu
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets._core.events import register_widget_events
8
+ from bootstack.widgets._core.icon_image_props import IconProperty
9
+ from bootstack.widgets._core.options import record_to_dict
10
+ from bootstack.events import ChangeEvent, Subscription
11
+ from bootstack.streams import Stream
12
+ from bootstack.widgets.types import AccentToken, Event, Option, OptionDict, WidgetDensity, ButtonVariant, IconSpec
13
+
14
+ if TYPE_CHECKING:
15
+ from bootstack.signals import Signal
16
+
17
+ _SELECTBUTTON_EVENTS: dict[str, str] = {
18
+ "change": "<<Change>>",
19
+ }
20
+
21
+
22
+ class SelectButton(IconProperty, PublicWidgetBase):
23
+ """A button that opens a dropdown value list — a button-styled alternative to `Select`.
24
+
25
+ Clicking the button opens a popup list of options. The selected value is
26
+ displayed on the button face. Unlike `Select`, there is no text field —
27
+ the button is the only interaction point.
28
+
29
+ Args:
30
+ options: Choices shown in the popup. Each item is a plain string, a
31
+ `(text, value)` tuple, or a `{'text': ..., 'value': ...}` dict — so
32
+ an option's displayed label can differ from its stored value, e.g.
33
+ `["Light", "Dark"]` or `[("Light theme", "light")]`. A dict option
34
+ may also carry `'icon'` (a glyph rendered beside the menu label) and
35
+ `'disabled'` (when `True` the item is dimmed and cannot be chosen);
36
+ any other keys ride along as carried data on `selection`.
37
+ value: Initially selected value (value-space — matches an option's
38
+ value, not its label). Must match one of the options.
39
+ signal: Reactive `Signal[str]` controlling the selected value. When
40
+ provided, `value=` is ignored — seed the Signal directly.
41
+ disabled: If `True`, the button is non-interactive and dimmed.
42
+ Defaults to `False`.
43
+ accent: Accent token applied to the button.
44
+ variant: Button style variant. Default `'solid'`.
45
+ density: Padding density.
46
+ icon: Bootstrap Icons name shown on the button beside the value.
47
+ localize: Whether option labels (and the selected label on the button
48
+ face) are translated through the catalog — `True`, `False`, or
49
+ `'auto'` (translate when a translation is registered, otherwise show
50
+ the literal). Defaults to the app's `localize_mode`. Set `False` to
51
+ keep proper nouns untranslated; override a single option with its
52
+ `localize` key.
53
+ parent: Explicit parent widget. If omitted, the current
54
+ context-stack container is used.
55
+ **kwargs: Layout placement options applied by the parent container —
56
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
57
+ See :doc:`/tasks/layout`.
58
+ """
59
+
60
+ def __init__(
61
+ self,
62
+ options: list[Option] | None = None,
63
+ *,
64
+ value: Any = None,
65
+ signal: "Signal[str] | None" = None,
66
+ disabled: bool = False,
67
+ accent: AccentToken | str | None = None,
68
+ variant: ButtonVariant = "default",
69
+ density: WidgetDensity | None = None,
70
+ icon: str | IconSpec | None = None,
71
+ localize: bool | Literal['auto'] | None = None,
72
+ parent: Any = None,
73
+ **kwargs: Any,
74
+ ) -> None:
75
+ self._parent = self._resolve_parent(parent)
76
+ layout_kw = self._split_layout_kwargs(kwargs)
77
+ tk_master = self._parent._child_master() if self._parent else None
78
+
79
+ internal_kwargs: dict[str, Any] = {
80
+ "options": options or [],
81
+ }
82
+ if value is not None:
83
+ internal_kwargs["value"] = value
84
+ if signal is not None:
85
+ internal_kwargs["textsignal"] = signal
86
+ if disabled:
87
+ internal_kwargs["state"] = "disabled"
88
+ if accent is not None:
89
+ internal_kwargs["accent"] = accent
90
+ if variant is not None:
91
+ internal_kwargs["variant"] = variant
92
+ if density is not None:
93
+ internal_kwargs["density"] = density
94
+ if icon is not None:
95
+ internal_kwargs["icon"] = icon
96
+ if localize is not None:
97
+ internal_kwargs["localize"] = localize
98
+
99
+ self._internal = _InternalOptionMenu(tk_master, **internal_kwargs)
100
+ self._attach_to_parent(layout_kw)
101
+
102
+ # ----- Properties -----
103
+
104
+ @property
105
+ def value(self) -> Any:
106
+ """The currently selected value, or `None` if unselected.
107
+
108
+ This is the option's *value* (value-space). For the displayed label, see
109
+ `text`.
110
+ """
111
+ return self._internal.get()
112
+
113
+ @value.setter
114
+ def value(self, v: Any) -> None:
115
+ self._internal.set(v)
116
+
117
+ @property
118
+ def text(self) -> str:
119
+ """The label currently shown on the button — the selected option's text.
120
+
121
+ Read-only; the complement of `value`. Assign to `value` to change the
122
+ selection.
123
+ """
124
+ return self._internal.text
125
+
126
+ @property
127
+ def selection(self) -> dict | None:
128
+ """The selected option as a full record dict — the data bag — or `None`.
129
+
130
+ `{'text': ..., 'value': ..., ...any extra keys}`, indexed by key like
131
+ any record. Read-only.
132
+ """
133
+ return self._internal.selection
134
+
135
+ @property
136
+ def options(self) -> list[OptionDict]:
137
+ """The available options as normalized `{'text', 'value'}` records.
138
+
139
+ Assigning a new list rebuilds the dropdown. Accepts the same `Option`
140
+ forms as the constructor (strings, `(text, value)` tuples, or dicts).
141
+ """
142
+ return [record_to_dict(r) for r in self._internal.cget("options")]
143
+
144
+ @options.setter
145
+ def options(self, items: list[Option]) -> None:
146
+ self._internal.configure(options=list(items))
147
+
148
+ @property
149
+ def signal(self) -> "Signal[str] | None":
150
+ """The reactive `Signal` linked to this button, or `None`."""
151
+ return getattr(self._internal, 'textsignal', None)
152
+
153
+ @property
154
+ def disabled(self) -> bool:
155
+ """Whether the button is non-interactive."""
156
+ return str(self._internal.cget("state")) == "disabled"
157
+
158
+ @disabled.setter
159
+ def disabled(self, v: bool) -> None:
160
+ self._internal.configure(state="disabled" if v else "normal")
161
+
162
+ # ----- Event shorthands -----
163
+
164
+ @overload
165
+ def on_change(self) -> Stream: ...
166
+ @overload
167
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
168
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
169
+ """Register a callback fired whenever the selected value changes.
170
+
171
+ Args:
172
+ handler: Called with a :class:`~bootstack.events.ChangeEvent`. Omit
173
+ to get a composable :class:`~bootstack.streams.Stream` instead.
174
+
175
+ Returns:
176
+ A cancellable :class:`~bootstack.events.Subscription` when a
177
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
178
+ """
179
+ return self.on("change", handler)
180
+
181
+
182
+ register_widget_events(SelectButton, _SELECTBUTTON_EVENTS)
@@ -0,0 +1,130 @@
1
+ """SidebarToggle — a hamburger button that collapses/expands an AppShell sidebar.
2
+
3
+ This is not a standalone, place-anywhere widget like `ThemeToggle`: a sidebar
4
+ toggle only means anything in the context of one shell's sidebar. It is built by
5
+ `Toolbar.add_sidebar_toggle()` / `StatusBar.add_sidebar_toggle()`, which wire it
6
+ to the owning `AppShell`; it is not exported at the `bootstack` top level.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Literal
11
+
12
+ from bootstack.widgets.button import Button
13
+ from bootstack.widgets.types import AccentToken, ButtonVariant, WidgetDensity
14
+
15
+
16
+ class SidebarToggle(Button):
17
+ """A hamburger button that collapses and expands an AppShell's sidebar.
18
+
19
+ Clicking it toggles the shell's sidebar. With `collapse='compact'` (the
20
+ default — the desktop convention) it shrinks the sidebar to an icon rail when
21
+ that sidebar can show icons (a static `page_nav`), and falls back to fully
22
+ hiding it when it can't (a data-bound `list_nav`/`tree_nav`). With
23
+ `collapse='hidden'` it always toggles between hidden and expanded. Its icon
24
+ can reflect the current state — `expand_icon` is shown while the sidebar is
25
+ collapsed (clicking expands it) and `collapse_icon` while it is expanded
26
+ (clicking collapses it); each falls back to `icon` when not given.
27
+
28
+ Args:
29
+ shell: The `AppShell` whose sidebar this controls.
30
+ collapse: What "collapse" means — `'compact'` (shrink to the icon rail
31
+ where possible, else hide; the default) or `'hidden'` (always fully
32
+ hide).
33
+ icon: Base icon, used for both states unless a state-specific icon is
34
+ given. Defaults by mode — `'layout-sidebar'` (a panel glyph) for
35
+ `collapse='compact'`, `'list'` (a hamburger) for `collapse='hidden'`.
36
+ expand_icon: Icon shown while the sidebar is collapsed (the affordance to
37
+ expand it). Falls back to `icon`.
38
+ collapse_icon: Icon shown while the sidebar is expanded (the affordance to
39
+ collapse it). Falls back to `icon`.
40
+ variant: Button style variant. Default `'ghost'`.
41
+ density: Padding density — `'default'` or `'compact'`.
42
+ accent: Accent token applied to the icon. Defaults to the foreground.
43
+ parent: Explicit parent widget. If omitted, the current context-stack
44
+ container is used.
45
+ **kwargs: Layout placement options applied by the parent container.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ *,
51
+ shell: Any,
52
+ collapse: Literal["compact", "hidden"] = "compact",
53
+ icon: str | None = None,
54
+ expand_icon: str | None = None,
55
+ collapse_icon: str | None = None,
56
+ variant: ButtonVariant = "ghost",
57
+ density: WidgetDensity = "default",
58
+ accent: AccentToken | str | None = None,
59
+ parent: Any = None,
60
+ **kwargs: Any,
61
+ ) -> None:
62
+ self._shell = shell
63
+ self._collapse = collapse
64
+ # Default glyph follows the collapse mode: a panel for the icon rail, a
65
+ # hamburger for full hide.
66
+ if icon is None:
67
+ icon = "layout-sidebar" if collapse == "compact" else "list"
68
+ self._base_icon = icon
69
+ self._expand_icon = expand_icon
70
+ self._collapse_icon = collapse_icon
71
+ super().__init__(
72
+ on_click=self._toggle,
73
+ icon=self._state_icon(),
74
+ variant=variant,
75
+ density=density,
76
+ accent=accent,
77
+ parent=parent,
78
+ **kwargs,
79
+ )
80
+
81
+ # Keep the icon matching the live sidebar state — toggling here or
82
+ # elsewhere (Ctrl-B, another control) refreshes it. Setting the icon never
83
+ # fires a click, so there is no cycle.
84
+ self._subs = [
85
+ shell.on_sidebar_toggle(self._sync_icon),
86
+ shell.on_sidebar_mode_change(self._sync_icon),
87
+ ]
88
+ self.on_destroy(self._cleanup_sidebar_toggle)
89
+
90
+ def _is_expanded(self) -> bool:
91
+ return self._shell.sidebar_mode == "expanded"
92
+
93
+ def _state_icon(self) -> str:
94
+ if self._is_expanded():
95
+ return self._collapse_icon or self._base_icon
96
+ return self._expand_icon or self._base_icon
97
+
98
+ def _toggle(self) -> None:
99
+ if self._collapse == "hidden":
100
+ # Always fully hide (VS Code-style focus collapse).
101
+ self._shell.toggle_sidebar()
102
+ return
103
+ # "compact": collapse as far as is useful — to the icon rail when the
104
+ # active sidebar can show icons, otherwise hide it (a data list can't
105
+ # compact). Mirrors the shell's own Ctrl-B behavior.
106
+ if self._can_compact():
107
+ mode = self._shell.sidebar_mode
108
+ self._shell.sidebar_mode = "expanded" if mode == "compact" else "compact"
109
+ else:
110
+ self._shell.toggle_sidebar()
111
+
112
+ def _can_compact(self) -> bool:
113
+ """Whether the active sidebar can shrink to icons (vs. only hide)."""
114
+ try:
115
+ return bool(self._shell._internal._can_compact_active())
116
+ except Exception:
117
+ return False
118
+
119
+ def _sync_icon(self, _event: Any = None) -> None:
120
+ self.icon = self._state_icon()
121
+
122
+ def _cleanup_sidebar_toggle(self, _event: Any = None) -> None:
123
+ for sub in self._subs:
124
+ try:
125
+ sub.cancel()
126
+ except Exception:
127
+ pass
128
+
129
+
130
+ __all__ = ["SidebarToggle"]
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from bootstack.widgets._impl.primitives.sizegrip import SizeGrip as _InternalSizeGrip
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets.types import AccentToken
8
+
9
+
10
+ class SizeGrip(PublicWidgetBase):
11
+ """A resize handle placed in the corner of a window or panel.
12
+
13
+ Typically anchored to the bottom-right corner so users can drag to resize
14
+ the parent window.
15
+
16
+ Args:
17
+ accent: Accent token for styling.
18
+ parent: Override the context-stack parent.
19
+ **kwargs: Layout placement options applied by the parent container —
20
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
21
+ See :doc:`/tasks/layout`.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ *,
27
+ accent: AccentToken | str | None = None,
28
+ parent: Any = None,
29
+ **kwargs: Any,
30
+ ) -> None:
31
+ self._parent = self._resolve_parent(parent)
32
+ layout_kw = self._split_layout_kwargs(kwargs)
33
+
34
+ tk_master = self._parent._child_master() if self._parent else None
35
+
36
+ internal_kwargs: dict[str, Any] = {}
37
+ if accent is not None:
38
+ internal_kwargs["accent"] = accent
39
+ # **kwargs is layout-only (split out above) — not forwarded to the internal.
40
+
41
+ self._internal = _InternalSizeGrip(tk_master, **internal_kwargs)
42
+ self._attach_to_parent(layout_kw)