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,224 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, TYPE_CHECKING, overload
4
+
5
+ from bootstack.widgets._impl.primitives.button import Button as _InternalButton
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, ImageProperty
9
+ from bootstack.events import Event, Subscription
10
+ from bootstack.streams import Stream
11
+ from bootstack.widgets.types import AccentToken, WidgetDensity, IconPosition, ButtonVariant, IconSpec, LocalizeMode, SurfaceToken
12
+
13
+ if TYPE_CHECKING:
14
+ from bootstack.signals import Signal
15
+
16
+
17
+ class Button(IconProperty, ImageProperty, PublicWidgetBase):
18
+ """A clickable action trigger.
19
+
20
+ The button text is the first positional argument. All styling and
21
+ behavior options are keyword-only.
22
+
23
+ Args:
24
+ text: Text displayed on the button.
25
+ on_click: Called with no arguments when the button is clicked.
26
+ Equivalent to subscribing to the `'click'` event.
27
+ accent: Color intent token. Defaults to the theme's default button color.
28
+ variant: Visual weight token. Default `'solid'`.
29
+ icon: Bootstrap Icons name (e.g. `'save'`, `'trash'`) — see the full
30
+ catalog at https://icons.getbootstrap.com — or an `IconSpec` mapping
31
+ (`{'name', 'size', 'color'}`) for control over size and color.
32
+ icon_only: If `True`, show only the icon with no text. Inferred
33
+ automatically when `icon=` is set and no text is provided. Set
34
+ meaningful `text` anyway for accessibility.
35
+ icon_position: Position of the icon or image relative to the text.
36
+ Default `'left'`.
37
+ image: An `Image` handle (from `bootstack.images`) to display on the
38
+ button, for custom artwork rather than a Bootstrap Icon name. Also
39
+ accepts a `get_icon` result.
40
+ width: Button width in character units. Useful for making a row of
41
+ buttons uniform width (e.g. `width=10`).
42
+ textsignal: Reactive `Signal[str]` bound to the button text. Updates
43
+ automatically when the signal changes.
44
+ surface: The background the button is placed on — `'content'`, `'card'`,
45
+ `'card_raised'`, `'chrome'`, or `'overlay'`. Set it so a
46
+ `ghost`/`outline` button looks right on a non-default container (e.g.
47
+ a toolbar sets `'chrome'` on the buttons it builds). A build-time
48
+ setting, not a live property. Defaults to the theme's content
49
+ surface.
50
+ density: Padding density.
51
+ disabled: If `True`, the button is non-interactive and visually dimmed.
52
+ localize: Whether the text is translated through the catalog — `True`,
53
+ `False`, or `'auto'`. Defaults to the app's `localize_mode`. Set
54
+ `False` to keep a proper noun or identifier untranslated.
55
+ parent: Explicit parent widget. If omitted, the current context-stack
56
+ container is used.
57
+ **kwargs: Layout placement options applied by the parent container —
58
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
59
+ See :doc:`/tasks/layout`.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ text: str = "",
65
+ *,
66
+ on_click: Callable[[], Any] | None = None,
67
+ accent: AccentToken | str | None = None,
68
+ variant: ButtonVariant = "default",
69
+ icon: str | IconSpec | None = None,
70
+ icon_only: bool = False,
71
+ icon_position: IconPosition = "left",
72
+ image: Any = None,
73
+ width: int | None = None,
74
+ textsignal: "Signal[str] | None" = None,
75
+ surface: SurfaceToken | str | None = None,
76
+ density: WidgetDensity | None = None,
77
+ disabled: bool = False,
78
+ localize: LocalizeMode | None = None,
79
+ parent: Any = None,
80
+ **kwargs: Any,
81
+ ) -> None:
82
+ self._reject_signal_text(text)
83
+ self._parent = self._resolve_parent(parent)
84
+ layout_kw = self._split_layout_kwargs(kwargs)
85
+ tk_master = self._parent._child_master() if self._parent else None
86
+
87
+ if icon is not None and not text:
88
+ icon_only = True
89
+
90
+ # The internal command is always our dispatcher so that a click —
91
+ # whether by mouse, keyboard (Space/Return), or click()/invoke() — runs
92
+ # the on_click= callback AND emits <<Click>> for on_click() subscribers,
93
+ # consistently and only when the button is enabled (ttk gates the
94
+ # command on state, unlike a raw <Button-1> binding).
95
+ self._on_click_cmd = on_click
96
+
97
+ internal_kwargs: dict[str, Any] = {"text": text, "command": self._dispatch_click}
98
+ if not icon_only:
99
+ internal_kwargs["compound"] = icon_position
100
+ if accent is not None:
101
+ internal_kwargs["accent"] = accent
102
+ if variant is not None:
103
+ internal_kwargs["variant"] = variant
104
+ if icon is not None:
105
+ internal_kwargs["icon"] = icon
106
+ if icon_only:
107
+ internal_kwargs["icon_only"] = True
108
+ deferred_image = None
109
+ if image is not None:
110
+ from bootstack.images import Image as _ImageHandle
111
+
112
+ if isinstance(image, _ImageHandle):
113
+ deferred_image = image
114
+ else:
115
+ internal_kwargs["image"] = image
116
+ if width is not None:
117
+ internal_kwargs["width"] = width
118
+ if textsignal is not None:
119
+ internal_kwargs["textsignal"] = textsignal
120
+ if surface is not None:
121
+ internal_kwargs["surface"] = surface
122
+ if density is not None:
123
+ internal_kwargs["density"] = density
124
+ if disabled:
125
+ internal_kwargs["state"] = "disabled"
126
+ if localize is not None:
127
+ internal_kwargs["localize"] = localize
128
+
129
+ self._internal = _InternalButton(tk_master, **internal_kwargs)
130
+ if deferred_image is not None:
131
+ from bootstack.widgets._core.image_binding import bind_image
132
+
133
+ bind_image(self, self._internal, deferred_image)
134
+ self._attach_to_parent(layout_kw)
135
+
136
+ # ----- Properties -----
137
+
138
+ @property
139
+ def text(self) -> str:
140
+ """The button's text."""
141
+ # cget('text') reliably reflects a bound textvariable too (ttk keeps the
142
+ # option synced from the variable), so this is the live value either way.
143
+ return str(self._internal.cget("text"))
144
+
145
+ @text.setter
146
+ def text(self, value: str) -> None:
147
+ # When a textsignal/textvariable owns the text, ttk ignores the `text`
148
+ # option — write through the variable instead (which also keeps the
149
+ # bound signal in sync). Falls back to the option when unbound.
150
+ textvar = getattr(self._internal, "_textvariable", None)
151
+ if textvar is not None:
152
+ textvar.set(value)
153
+ else:
154
+ self._internal.configure(text=value)
155
+
156
+ @property
157
+ def disabled(self) -> bool:
158
+ """Whether the button is non-interactive."""
159
+ return str(self._internal.cget("state")) == "disabled"
160
+
161
+ @disabled.setter
162
+ def disabled(self, value: bool) -> None:
163
+ self._internal.configure(state="disabled" if value else "normal")
164
+
165
+ # ----- Methods -----
166
+
167
+ def click(self) -> None:
168
+ """Programmatically activate the button.
169
+
170
+ Runs the same path as a real click: the `on_click=` callback and any
171
+ `on_click()` handlers both fire. Does nothing while the button is
172
+ disabled.
173
+ """
174
+ self._internal.invoke()
175
+
176
+ def _dispatch_click(self) -> None:
177
+ """ttk command target — runs on every activation, never while disabled.
178
+
179
+ Fires on mouse release over the button, keyboard Space/Return, and
180
+ `click()`/`invoke()`. Runs the constructor `on_click=` callback, then
181
+ emits `<<Click>>` so `on_click()` subscribers see the same activation.
182
+ """
183
+ if self._on_click_cmd is not None:
184
+ self._on_click_cmd()
185
+ # `when='now'` delivers to subscribers synchronously, so a click()/invoke()
186
+ # notifies handlers before it returns (matches the on_click= callback).
187
+ self._internal.event_generate("<<Click>>", when="now")
188
+
189
+ # ----- Events -----
190
+
191
+ @overload
192
+ def on_click(self) -> Stream: ...
193
+ @overload
194
+ def on_click(self, handler: Callable[[Event], Any]) -> Subscription: ...
195
+ def on_click(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
196
+ """Register a callback for button click events.
197
+
198
+ Called with no handler, returns a composable `Stream`. Called with a
199
+ handler, binds it immediately and returns a `Subscription`.
200
+
201
+ Fires on activation — a mouse release over the button, a keyboard
202
+ Space/Return while focused, or :meth:`click`/`invoke` — and never while
203
+ the button is disabled, matching the `on_click=` constructor argument.
204
+ For a simple no-argument action prefer that constructor argument; use
205
+ this method (or its `Stream`) to compose the click event.
206
+
207
+ Args:
208
+ handler: Called with the click :class:`~bootstack.events.Event`.
209
+ The event marks the activation; because keyboard and
210
+ programmatic activations carry no pointer, treat position and
211
+ modifier fields as unset. Omit the handler to get a composable
212
+ :class:`~bootstack.streams.Stream`.
213
+
214
+ Returns:
215
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
216
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
217
+ """
218
+ return self.on("click", handler)
219
+
220
+
221
+ # "click" resolves to the <<Click>> virtual event emitted by the command
222
+ # dispatcher (see _dispatch_click) rather than the global <Button-1> mapping, so
223
+ # both the handler and Stream forms fire on activation and honor disabled state.
224
+ register_widget_events(Button, {"click": "<<Click>>"})
@@ -0,0 +1,239 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Literal, overload
4
+
5
+ from bootstack.widgets._impl.composites.buttongroup import ButtonGroup as _InternalButtonGroup
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets._core.events import register_widget_events
8
+ from bootstack.events import ButtonGroupClickEvent, Subscription
9
+ from bootstack.streams import Stream
10
+ from bootstack.widgets.types import AccentToken, Event, WidgetDensity, Orient, IconPosition, ButtonVariant
11
+
12
+ _BUTTONGROUP_EVENTS: dict[str, str] = {
13
+ "click": "<<BsButtonGroupClick>>",
14
+ }
15
+
16
+
17
+ class ButtonGroup(PublicWidgetBase):
18
+ """A row (or column) of visually-connected buttons sharing accent and variant.
19
+
20
+ Buttons are added via `add()` or `add_all()`. The group propagates
21
+ `accent`, `variant`, and `density` to every member automatically.
22
+ Listen for any button press with `on_click()`, which receives the key
23
+ of the clicked button.
24
+
25
+ Args:
26
+ orient: Layout orientation. Default `'horizontal'`.
27
+ accent: Accent token applied to all buttons.
28
+ variant: Style variant. Default `'solid'`.
29
+ density: Widget density. Default `'default'`.
30
+ disabled: If `True`, all buttons are non-interactive.
31
+ localize: Whether button labels are translated through the catalog —
32
+ `True`, `False`, or `'auto'` (translate when a translation is
33
+ registered, otherwise show the literal). Defaults to the app's
34
+ `localize_mode`. Set `False` to keep proper nouns untranslated;
35
+ override a single button with `add(..., localize=...)`.
36
+ parent: Override the context-stack parent.
37
+ **kwargs: Layout placement options applied by the parent container —
38
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
39
+ See :doc:`/tasks/layout`.
40
+ """
41
+
42
+ def __init__(
43
+ self,
44
+ orient: Orient = "horizontal",
45
+ *,
46
+ accent: AccentToken | str | None = None,
47
+ variant: ButtonVariant = "default",
48
+ density: WidgetDensity = "default",
49
+ disabled: bool = False,
50
+ localize: bool | Literal['auto'] | None = None,
51
+ parent: Any = None,
52
+ **kwargs: Any,
53
+ ) -> None:
54
+ self._localize = localize
55
+ self._parent = self._resolve_parent(parent)
56
+ layout_kw = self._split_layout_kwargs(kwargs)
57
+ tk_master = self._parent._child_master() if self._parent else None
58
+
59
+ internal_kwargs: dict[str, Any] = {
60
+ "orient": orient,
61
+ "variant": variant,
62
+ "density": density,
63
+ }
64
+ if accent is not None:
65
+ internal_kwargs["accent"] = accent
66
+ if disabled:
67
+ internal_kwargs["state"] = "disabled"
68
+
69
+ self._internal = _InternalButtonGroup(tk_master, **internal_kwargs)
70
+ self._key_counter = 0
71
+ self._attach_to_parent(layout_kw)
72
+
73
+ # ----- Item management -----
74
+
75
+ def add(
76
+ self,
77
+ label: str = "",
78
+ *,
79
+ key: str | None = None,
80
+ icon: str | None = None,
81
+ icon_position: IconPosition = "left",
82
+ disabled: bool = False,
83
+ localize: bool | Literal['auto'] | None = None,
84
+ **kwargs: Any,
85
+ ) -> str:
86
+ """Add a button to the group.
87
+
88
+ Args:
89
+ label: Button label text.
90
+ key: Unique string key. Auto-generated if omitted.
91
+ icon: Bootstrap Icons name (e.g. `'save'`, `'trash'`).
92
+ icon_position: Position of the icon relative to the label. Ignored
93
+ when `icon_only` is inferred or explicitly set. Default `'left'`.
94
+ disabled: If `True`, this button starts disabled.
95
+ localize: Translation mode for this button's label, overriding the
96
+ group's `localize=`. Defaults to the group setting.
97
+
98
+ Returns:
99
+ The key assigned to this button.
100
+ """
101
+ if key is None:
102
+ key = f"widget_{self._key_counter}"
103
+ self._key_counter += 1
104
+
105
+ icon_only = icon is not None and not label
106
+
107
+ btn_kwargs: dict[str, Any] = {}
108
+ if icon is not None:
109
+ btn_kwargs["icon"] = icon
110
+ if icon_only:
111
+ btn_kwargs["icon_only"] = True
112
+ elif icon is not None:
113
+ btn_kwargs["compound"] = icon_position
114
+ if disabled:
115
+ btn_kwargs["state"] = "disabled"
116
+ item_localize = localize if localize is not None else self._localize
117
+ if item_localize is not None:
118
+ btn_kwargs["localize"] = item_localize
119
+ btn_kwargs.update(kwargs)
120
+
121
+ _key = key
122
+ _group = self
123
+
124
+ def _command() -> None:
125
+ btn = _group._internal.item(_key)
126
+ _group.emit("click", data=ButtonGroupClickEvent(
127
+ key=_key,
128
+ text=str(btn.cget("text") or ""),
129
+ icon=btn.configure_style_options("icon"),
130
+ ))
131
+
132
+ self._internal.add(label or None, key=key, command=_command, **btn_kwargs)
133
+ return key
134
+
135
+ def add_all(self, items: list) -> list[str]:
136
+ """Add multiple buttons at once.
137
+
138
+ Each item is either a label string or a dict of keyword arguments
139
+ accepted by `add()`.
140
+
141
+ Args:
142
+ items: List of labels or dicts. A plain string is treated as
143
+ the button label with no other options.
144
+
145
+ Returns:
146
+ List of keys assigned to each button, in insertion order.
147
+ """
148
+ return [self.add(**item) if isinstance(item, dict) else self.add(item) for item in items]
149
+
150
+ def remove(self, key: str) -> None:
151
+ """Remove the button identified by `key`.
152
+
153
+ Args:
154
+ key: Key returned by `add()`.
155
+ """
156
+ self._internal.remove(key)
157
+
158
+ def update_item(self, key: str, **kwargs: Any) -> None:
159
+ """Reconfigure a button after creation.
160
+
161
+ Args:
162
+ key: Key returned by `add()`.
163
+ **kwargs: Options forwarded to the button's `configure()`.
164
+ """
165
+ self._internal.configure_item(key, **kwargs)
166
+
167
+ def query_item(self, key: str, option: str) -> Any:
168
+ """Return the current value of a configuration option for a button.
169
+
170
+ Args:
171
+ key: Key returned by `add()`.
172
+ option: Option name to query (e.g. `'text'`, `'state'`).
173
+ """
174
+ return self._internal.configure_item(key, option)
175
+
176
+ # ----- Queries -----
177
+
178
+ def item(self, key: str) -> Any:
179
+ """Return the button item object for `key`.
180
+
181
+ Args:
182
+ key: Key returned by `add()`.
183
+ """
184
+ return self._internal.item(key)
185
+
186
+ @property
187
+ def items(self) -> tuple[Any, ...]:
188
+ """All button items in insertion order."""
189
+ return self._internal.items()
190
+
191
+ @property
192
+ def keys(self) -> tuple[str, ...]:
193
+ """Keys of all buttons in insertion order."""
194
+ return self._internal.keys()
195
+
196
+ def __len__(self) -> int:
197
+ """Number of buttons in the group."""
198
+ return len(self._internal)
199
+
200
+ def __contains__(self, key: object) -> bool:
201
+ """True if a button with the given key exists in the group."""
202
+ return key in self._internal
203
+
204
+ # ----- Properties -----
205
+
206
+ @property
207
+ def disabled(self) -> bool:
208
+ """Whether all buttons are non-interactive."""
209
+ return str(self._internal.cget("state")) == "disabled"
210
+
211
+ @disabled.setter
212
+ def disabled(self, value: bool) -> None:
213
+ self._internal.configure(state="disabled" if value else "normal")
214
+
215
+ # ----- Events -----
216
+
217
+ @overload
218
+ def on_click(self) -> Stream: ...
219
+ @overload
220
+ def on_click(self, handler: Callable[[ButtonGroupClickEvent], Any]) -> Subscription: ...
221
+ def on_click(self, handler: Callable[[ButtonGroupClickEvent], Any] | None = None) -> Stream | Subscription:
222
+ """Register a callback fired when any button in the group is clicked.
223
+
224
+ Args:
225
+ handler: Called with a :class:`~bootstack.events.ButtonGroupClickEvent`
226
+ (`key`, `text`, `icon` of the clicked button). Omit to get a
227
+ composable :class:`~bootstack.streams.Stream`.
228
+
229
+ Returns:
230
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
231
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
232
+ """
233
+ if handler is None:
234
+ return self.on("click")
235
+
236
+ return self.on("click", handler)
237
+
238
+
239
+ register_widget_events(ButtonGroup, _BUTTONGROUP_EVENTS)
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date, datetime
4
+ from typing import overload, Any, Callable, Iterable, Literal
5
+
6
+ from bootstack.widgets._impl.composites.calendar import Calendar as _InternalCalendar
7
+ from bootstack.widgets._core.base import PublicWidgetBase
8
+ from bootstack.widgets._core.events import register_widget_events
9
+ from bootstack.events import DateSelectEvent, Subscription
10
+ from bootstack.streams import Stream
11
+ from bootstack.widgets.types import AccentToken, Event, Padding
12
+
13
+
14
+ class Calendar(PublicWidgetBase):
15
+ """An inline calendar for single-date or date-range selection.
16
+
17
+ Always visible — not a popup. Displays one month in `'single'` mode and
18
+ two months side-by-side in `'range'` mode.
19
+
20
+ Dates can be passed as `datetime.date` objects, `datetime.datetime`
21
+ objects, or ISO strings (`"2026-05-31"`).
22
+
23
+ Args:
24
+ value: Initially selected date (single mode only).
25
+ start_date: Initially selected range start date (range mode).
26
+ end_date: Initially selected range end date (range mode).
27
+ disabled_dates: Dates that cannot be selected. Displayed with a
28
+ strikethrough style.
29
+ selection_mode: `'single'` (default) for one date; `'range'`
30
+ for a start–end span.
31
+ min_date: Earliest selectable date. Earlier dates are disabled
32
+ and month navigation is blocked before this point.
33
+ max_date: Latest selectable date. Later dates are disabled
34
+ and month navigation is blocked past this point.
35
+ show_outside_days: Show days from adjacent months in the grid.
36
+ Defaults to `True` in single mode and `False` in range mode.
37
+ show_week_numbers: Display ISO 8601 week numbers in the leftmost
38
+ column. Defaults to `False`.
39
+ first_weekday: First day of the week as an integer (0=Monday,
40
+ 6=Sunday). If omitted, the locale default is used.
41
+ accent: Accent token applied to selected dates and highlights.
42
+ Defaults to `'primary'`.
43
+ padding: Space in pixels around the calendar grid.
44
+ parent: Explicit parent widget. If omitted, the current
45
+ context-stack container is used.
46
+ **kwargs: Layout placement options applied by the parent container —
47
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
48
+ See :doc:`/tasks/layout`.
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ value: date | datetime | str | None = None,
55
+ start_date: date | datetime | str | None = None,
56
+ end_date: date | datetime | str | None = None,
57
+ disabled_dates: Iterable[date | datetime | str] | None = None,
58
+ selection_mode: Literal["single", "range"] = "single",
59
+ max_date: date | datetime | str | None = None,
60
+ min_date: date | datetime | str | None = None,
61
+ show_outside_days: bool | None = None,
62
+ show_week_numbers: bool = False,
63
+ first_weekday: int | None = None,
64
+ accent: AccentToken | str | None = None,
65
+ padding: Padding | None = None,
66
+ parent: Any = None,
67
+ **kwargs: Any,
68
+ ) -> None:
69
+ self._parent = self._resolve_parent(parent)
70
+ layout_kw = self._split_layout_kwargs(kwargs)
71
+ tk_master = self._parent._child_master() if self._parent else None
72
+
73
+ kw: dict[str, Any] = {"selection_mode": selection_mode, "show_week_numbers": show_week_numbers}
74
+ if value is not None:
75
+ kw["value"] = value
76
+ if start_date is not None:
77
+ kw["start_date"] = start_date
78
+ if end_date is not None:
79
+ kw["end_date"] = end_date
80
+ if disabled_dates is not None:
81
+ kw["disabled_dates"] = disabled_dates
82
+ if max_date is not None:
83
+ kw["max_date"] = max_date
84
+ if min_date is not None:
85
+ kw["min_date"] = min_date
86
+ if show_outside_days is not None:
87
+ kw["show_outside_days"] = show_outside_days
88
+ if first_weekday is not None:
89
+ kw["first_weekday"] = first_weekday
90
+ if accent is not None:
91
+ kw["accent"] = accent
92
+ if padding is not None:
93
+ kw["padding"] = padding
94
+
95
+ self._internal = _InternalCalendar(tk_master, **kw)
96
+ self._attach_to_parent(layout_kw)
97
+
98
+ # ----- Value API -----
99
+
100
+ @property
101
+ def value(self) -> date | None:
102
+ """The currently selected date."""
103
+ return self._internal.value
104
+
105
+ @value.setter
106
+ def value(self, val: date | datetime | str | None) -> None:
107
+ self._internal.value = val
108
+
109
+ def get(self) -> date | None:
110
+ """Return the currently selected date."""
111
+ return self._internal.get()
112
+
113
+ def set(self, value: date | datetime | str | None) -> None:
114
+ """Set the selected date without emitting `<<DateSelect>>`."""
115
+ self._internal.set(value)
116
+
117
+ # ----- Range API -----
118
+
119
+ @property
120
+ def range(self) -> tuple[date | None, date | None]:
121
+ """The selected date range as (start, end)."""
122
+ return self._internal.range
123
+
124
+ @range.setter
125
+ def range(self, val: Any) -> None:
126
+ self._internal.range = val
127
+
128
+ def get_range(self) -> tuple[date | None, date | None]:
129
+ """Return the selected date range."""
130
+ return self._internal.get_range()
131
+
132
+ def set_range(
133
+ self,
134
+ start: date | datetime | str | None,
135
+ end: date | datetime | str | None = None,
136
+ ) -> None:
137
+ """Set the selected date range without emitting `<<DateSelect>>`."""
138
+ self._internal.set_range(start, end)
139
+
140
+ # ----- Date constraints (live) -----
141
+
142
+ @property
143
+ def min_date(self) -> date | None:
144
+ """Earliest selectable date. Assigning updates the calendar immediately; `None` clears the bound."""
145
+ return self._internal._min_date
146
+
147
+ @min_date.setter
148
+ def min_date(self, value: date | datetime | str | None) -> None:
149
+ self._internal.set_min_date(value)
150
+
151
+ @property
152
+ def max_date(self) -> date | None:
153
+ """Latest selectable date. Assigning updates the calendar immediately; `None` clears the bound."""
154
+ return self._internal._max_date
155
+
156
+ @max_date.setter
157
+ def max_date(self, value: date | datetime | str | None) -> None:
158
+ self._internal.set_max_date(value)
159
+
160
+ @property
161
+ def disabled_dates(self) -> tuple[date, ...]:
162
+ """The non-selectable dates. Assigning a new iterable updates the calendar immediately."""
163
+ return tuple(sorted(self._internal._disabled_dates))
164
+
165
+ @disabled_dates.setter
166
+ def disabled_dates(self, value: Iterable[date | datetime | str] | None) -> None:
167
+ self._internal.set_disabled_dates(value)
168
+
169
+ # ----- Events -----
170
+
171
+ @overload
172
+ def on_select(self) -> Stream: ...
173
+ @overload
174
+ def on_select(self, handler: Callable[[DateSelectEvent], Any]) -> Subscription: ...
175
+ def on_select(self, handler: Callable[[DateSelectEvent], Any] | None = None) -> Stream | Subscription:
176
+ """Register a callback fired when a date or range is selected.
177
+
178
+ In single mode the handler fires on every date click. In range mode
179
+ it fires after each click — check `calendar.range` to see whether
180
+ a complete range has been set (`end` is `None` while the second
181
+ date is still pending).
182
+
183
+ Args:
184
+ handler: Called with a :class:`~bootstack.events.DateSelectEvent`.
185
+ Omit to get a composable :class:`~bootstack.streams.Stream`
186
+ instead.
187
+
188
+ Returns:
189
+ A cancellable :class:`~bootstack.events.Subscription` when a
190
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
191
+ """
192
+ return self.on("<<DateSelect>>", handler)
193
+
194
+
195
+ register_widget_events(Calendar, {})