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,413 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from typing import overload, Any, Callable, TYPE_CHECKING
5
+
6
+ from bootstack.widgets._impl.composites.spinnerentry import SpinnerEntry as _InternalSpinnerEntry
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.field_mixin import FieldAddonMixin
10
+ from bootstack.events import ChangeEvent, InputEvent, Subscription, ValidationEvent
11
+ from bootstack.streams import Stream
12
+ from bootstack.widgets.textfield import _INNER_ENTRY_SEQUENCES
13
+ from bootstack.widgets.types import AccentToken, Event, Justify, WidgetDensity
14
+
15
+ if TYPE_CHECKING:
16
+ from bootstack.signals import Signal
17
+
18
+ _SPINNER_FIELD_EVENTS: dict[str, str] = {
19
+ "input": "<<Input>>",
20
+ "change": "<<Change>>",
21
+ "valid": "<<Valid>>",
22
+ "invalid": "<<Invalid>>",
23
+ "validate": "<<Validate>>",
24
+ "submit": "<Return>",
25
+ "focus": "<FocusIn>",
26
+ "blur": "<FocusOut>",
27
+ }
28
+
29
+
30
+ class SpinnerField(FieldAddonMixin, PublicWidgetBase):
31
+ """A text-entry field with spin buttons for stepping through values.
32
+
33
+ Supports two modes: a fixed list of text values (`options=`) or a numeric
34
+ range (`min_value=` / `max_value=`). Exactly one mode should be used at
35
+ a time.
36
+
37
+ Args:
38
+ value: Initial value — a string (text mode) or number (numeric mode).
39
+ options: Fixed list of string values to step through (text mode).
40
+ min_value: Minimum value for numeric range mode.
41
+ max_value: Maximum value for numeric range mode.
42
+ step: Increment step size for numeric mode. Default `1`.
43
+ wrap: If True, values wrap around at the boundaries.
44
+ label: Label displayed above the field.
45
+ message: Hint text displayed below the field.
46
+ placeholder: Ghost text shown when the field is empty and unfocused.
47
+ textsignal: Reactive `Signal[str]` bound to the field text. The
48
+ field value and signal stay in sync automatically.
49
+ required: If True, field cannot be left empty.
50
+ disabled: If True, field is non-interactive.
51
+ read_only: If True, value is visible but not editable.
52
+ width: Width in character cells.
53
+ justify: Text alignment. Default `'left'`.
54
+ font: Semantic font token (e.g. `'body'`, `'code'`). See
55
+ :doc:`/reference/typography`.
56
+ value_format: Format applied when displaying a numeric value — a named
57
+ preset (e.g. `'decimal'`, `'currency'`, `'percent'`) or a custom
58
+ pattern (e.g. `'#,##0.00'`). Requires localization enabled. See
59
+ :ref:`format specs <value-formats>`.
60
+ accent: Accent token applied to the focus ring.
61
+ density: Widget density.
62
+ parent: Override the context-stack parent.
63
+ **kwargs: Layout placement options applied by the parent container —
64
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
65
+ See :doc:`/tasks/layout`.
66
+ """
67
+
68
+ def __init__(
69
+ self,
70
+ value: int | float | str = "",
71
+ *,
72
+ options: list[str] | None = None,
73
+ min_value: int | float | None = None,
74
+ max_value: int | float | None = None,
75
+ step: int | float = 1,
76
+ wrap: bool = False,
77
+ label: str | None = None,
78
+ message: str | None = None,
79
+ placeholder: str | None = None,
80
+ textsignal: "Signal[str] | None" = None,
81
+ required: bool = False,
82
+ disabled: bool = False,
83
+ read_only: bool = False,
84
+ width: int | None = None,
85
+ justify: Justify | None = None,
86
+ font: str | None = None,
87
+ value_format: str | None = None,
88
+ accent: AccentToken | str | None = None,
89
+ density: WidgetDensity | None = None,
90
+ parent: Any = None,
91
+ **kwargs: Any,
92
+ ) -> None:
93
+ self._parent = self._resolve_parent(parent)
94
+ layout_kw = self._split_layout_kwargs(kwargs)
95
+ tk_master = self._parent._child_master() if self._parent else None
96
+
97
+ internal_kwargs: dict[str, Any] = {
98
+ "value": value,
99
+ "increment": step,
100
+ "wrap": wrap,
101
+ }
102
+ if options is not None:
103
+ internal_kwargs["values"] = options
104
+ if min_value is not None:
105
+ internal_kwargs["minvalue"] = min_value
106
+ if max_value is not None:
107
+ internal_kwargs["maxvalue"] = max_value
108
+ if label is not None:
109
+ internal_kwargs["label"] = label
110
+ if message is not None:
111
+ internal_kwargs["message"] = message
112
+ if placeholder is not None:
113
+ internal_kwargs["placeholder"] = placeholder
114
+ if textsignal is not None:
115
+ internal_kwargs["textsignal"] = textsignal
116
+ if required:
117
+ internal_kwargs["required"] = True
118
+ if disabled:
119
+ internal_kwargs["state"] = "disabled"
120
+ elif read_only:
121
+ internal_kwargs["state"] = "readonly"
122
+ if width is not None:
123
+ internal_kwargs["width"] = width
124
+ if justify is not None:
125
+ internal_kwargs["justify"] = justify
126
+ if font is not None:
127
+ internal_kwargs["font"] = font
128
+ if value_format is not None:
129
+ internal_kwargs["value_format"] = value_format
130
+ if accent is not None:
131
+ internal_kwargs["accent"] = accent
132
+ if density is not None:
133
+ internal_kwargs["density"] = density
134
+
135
+ self._internal = _InternalSpinnerEntry(tk_master, **internal_kwargs)
136
+ self._attach_to_parent(layout_kw)
137
+
138
+ # ----- Event routing -----
139
+
140
+ def _entry_widget(self) -> tkinter.Misc:
141
+ return self._internal._entry
142
+
143
+ @overload
144
+ def on(self, event: str) -> Stream: ...
145
+ @overload
146
+ def on(self, event: str, handler: Callable[[Event], Any]) -> Subscription: ...
147
+ def on(self, event: str, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
148
+ """Register a callback for an event by name.
149
+
150
+ A generic, string-keyed escape hatch — prefer the typed `on_*`
151
+ shorthands (e.g. `on_change`), which carry the precise payload type.
152
+ Called with no handler, returns a composable `Stream`; with a handler,
153
+ binds it and returns a `Subscription`.
154
+
155
+ Args:
156
+ event: Event name (for example `'change'` or `'focus'`).
157
+ handler: Called with the event payload. Omit to get a composable
158
+ :class:`~bootstack.streams.Stream` instead.
159
+
160
+ Returns:
161
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
162
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
163
+ """
164
+ sequence = resolve_event(self, str(event))
165
+ target = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
166
+ if handler is None:
167
+ def _source(h):
168
+ t = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
169
+ bid = t.bind(sequence, adapt_handler(h), add="+")
170
+ return Subscription(t, sequence, bid)
171
+ return Stream(self._internal, _source=_source)
172
+ bid = target.bind(sequence, adapt_handler(handler), add="+")
173
+ return Subscription(target, sequence, bid)
174
+
175
+ # ----- Properties -----
176
+
177
+ @property
178
+ def value(self) -> str:
179
+ """Current value as a string."""
180
+ return self._internal.value
181
+
182
+ @value.setter
183
+ def value(self, v: Any) -> None:
184
+ self._internal.value = v
185
+
186
+ @property
187
+ def options(self) -> list[str] | None:
188
+ """The fixed list of text values, or `None` in numeric mode."""
189
+ return self._internal._values
190
+
191
+ @options.setter
192
+ def options(self, v: list[str]) -> None:
193
+ self._internal.configure(values=v)
194
+
195
+ @property
196
+ def signal(self) -> "Signal[str] | None":
197
+ """The reactive `Signal` bound to this field, or `None`."""
198
+ return getattr(self._internal, "signal", None)
199
+
200
+ @property
201
+ def disabled(self) -> bool:
202
+ """Whether the field is fully non-interactive."""
203
+ return self._internal._entry.instate(("disabled",))
204
+
205
+ @disabled.setter
206
+ def disabled(self, v: bool) -> None:
207
+ self._internal.configure(state="disabled" if v else "normal")
208
+
209
+ @property
210
+ def read_only(self) -> bool:
211
+ """Whether the field is visible but not editable."""
212
+ return str(self._entry_widget().cget("state")) == "readonly"
213
+
214
+ @read_only.setter
215
+ def read_only(self, v: bool) -> None:
216
+ self._internal.configure(state="readonly" if v else "normal")
217
+
218
+ # ----- Methods -----
219
+
220
+ def increment(self, steps: int = 1) -> None:
221
+ """Step the value up by `steps` positions.
222
+
223
+ In numeric mode this adds `steps` × the step size; in text mode it
224
+ advances `steps` positions through `options`. Honors the field's
225
+ bounds and `wrap` setting.
226
+
227
+ Args:
228
+ steps: Number of steps to increment. Defaults to `1`.
229
+ """
230
+ self._internal.step(steps)
231
+
232
+ def decrement(self, steps: int = 1) -> None:
233
+ """Step the value down by `steps` positions.
234
+
235
+ In numeric mode this subtracts `steps` × the step size; in text mode it
236
+ moves back `steps` positions through `options`. Honors the field's
237
+ bounds and `wrap` setting.
238
+
239
+ Args:
240
+ steps: Number of steps to decrement. Defaults to `1`.
241
+ """
242
+ self._internal.step(-steps)
243
+
244
+ def focus(self) -> None:
245
+ """Give keyboard focus to this field."""
246
+ self._entry_widget().focus_set()
247
+
248
+ def clear(self) -> None:
249
+ """Clear the field value."""
250
+ self._internal.value = ""
251
+
252
+ def validate(self) -> bool:
253
+ """Run validation rules against the current value.
254
+
255
+ Returns:
256
+ `True` if all rules pass, `False` otherwise.
257
+ """
258
+ entry = self._internal._entry
259
+ return entry.validate(entry._get_validation_value(), trigger="manual")
260
+
261
+ def select_all(self) -> None:
262
+ """Select all text in the field."""
263
+ self._entry_widget().selection_range(0, "end")
264
+
265
+ def select_range(self, start: int, end: int) -> None:
266
+ """Select text between `start` and `end` character positions.
267
+
268
+ Args:
269
+ start: Start index (0-based, inclusive).
270
+ end: End index (exclusive).
271
+ """
272
+ self._entry_widget().selection_range(start, end)
273
+
274
+ # ----- Event shorthands -----
275
+
276
+ @overload
277
+ def on_input(self) -> Stream: ...
278
+ @overload
279
+ def on_input(self, handler: Callable[[InputEvent], Any]) -> Subscription: ...
280
+ def on_input(self, handler: Callable[[InputEvent], Any] | None = None) -> Stream | Subscription:
281
+ """Register a callback fired on every keystroke.
282
+
283
+ Args:
284
+ handler: Called with an :class:`~bootstack.events.InputEvent`. Omit to
285
+ get a composable :class:`~bootstack.streams.Stream` instead.
286
+
287
+ Returns:
288
+ A cancellable :class:`~bootstack.events.Subscription` when a
289
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
290
+ """
291
+ return self.on("input", handler)
292
+
293
+ @overload
294
+ def on_change(self) -> Stream: ...
295
+ @overload
296
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
297
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
298
+ """Register a callback fired when the value changes.
299
+
300
+ Args:
301
+ handler: Called with a :class:`~bootstack.events.ChangeEvent`. Omit to
302
+ get a composable :class:`~bootstack.streams.Stream` instead.
303
+
304
+ Returns:
305
+ A cancellable :class:`~bootstack.events.Subscription` when a
306
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
307
+ """
308
+ return self.on("change", handler)
309
+
310
+ @overload
311
+ def on_submit(self) -> Stream: ...
312
+ @overload
313
+ def on_submit(self, handler: Callable[[Event], Any]) -> Subscription: ...
314
+ def on_submit(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
315
+ """Register a callback fired when the user presses Enter.
316
+
317
+ Args:
318
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
319
+ get a composable :class:`~bootstack.streams.Stream` instead.
320
+
321
+ Returns:
322
+ A cancellable :class:`~bootstack.events.Subscription` when a
323
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
324
+ """
325
+ return self.on("submit", handler)
326
+
327
+ @overload
328
+ def on_focus(self) -> Stream: ...
329
+ @overload
330
+ def on_focus(self, handler: Callable[[Event], Any]) -> Subscription: ...
331
+ def on_focus(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
332
+ """Register a callback fired when the field gains focus.
333
+
334
+ Args:
335
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
336
+ get a composable :class:`~bootstack.streams.Stream` instead.
337
+
338
+ Returns:
339
+ A cancellable :class:`~bootstack.events.Subscription` when a
340
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
341
+ """
342
+ return self.on("focus", handler)
343
+
344
+ @overload
345
+ def on_blur(self) -> Stream: ...
346
+ @overload
347
+ def on_blur(self, handler: Callable[[Event], Any]) -> Subscription: ...
348
+ def on_blur(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
349
+ """Register a callback fired when the field loses focus.
350
+
351
+ Args:
352
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
353
+ get a composable :class:`~bootstack.streams.Stream` instead.
354
+
355
+ Returns:
356
+ A cancellable :class:`~bootstack.events.Subscription` when a
357
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
358
+ """
359
+ return self.on("blur", handler)
360
+
361
+ @overload
362
+ def on_valid(self) -> Stream: ...
363
+ @overload
364
+ def on_valid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
365
+ def on_valid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
366
+ """Register a callback fired when validation passes.
367
+
368
+ Args:
369
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
370
+ get a composable :class:`~bootstack.streams.Stream` instead.
371
+
372
+ Returns:
373
+ A cancellable :class:`~bootstack.events.Subscription` when a
374
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
375
+ """
376
+ return self.on("valid", handler)
377
+
378
+ @overload
379
+ def on_invalid(self) -> Stream: ...
380
+ @overload
381
+ def on_invalid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
382
+ def on_invalid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
383
+ """Register a callback fired when validation fails.
384
+
385
+ Args:
386
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
387
+ get a composable :class:`~bootstack.streams.Stream` instead.
388
+
389
+ Returns:
390
+ A cancellable :class:`~bootstack.events.Subscription` when a
391
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
392
+ """
393
+ return self.on("invalid", handler)
394
+
395
+ @overload
396
+ def on_validate(self) -> Stream: ...
397
+ @overload
398
+ def on_validate(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
399
+ def on_validate(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
400
+ """Register a callback fired after any validation run.
401
+
402
+ Args:
403
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
404
+ get a composable :class:`~bootstack.streams.Stream` instead.
405
+
406
+ Returns:
407
+ A cancellable :class:`~bootstack.events.Subscription` when a
408
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
409
+ """
410
+ return self.on("validate", handler)
411
+
412
+
413
+ register_widget_events(SpinnerField, _SPINNER_FIELD_EVENTS)