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,454 @@
1
+ from __future__ import annotations
2
+
3
+ import tkinter
4
+ from typing import overload, Any, Callable, Literal, TYPE_CHECKING
5
+
6
+ from bootstack.widgets._impl.composites.pathentry import PathEntry as _InternalPathEntry
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, WidgetDensity
14
+
15
+ if TYPE_CHECKING:
16
+ from bootstack.signals import Signal
17
+
18
+ _PATHFIELD_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 PathField(FieldAddonMixin, PublicWidgetBase):
31
+ """A text field with a browse button that opens a native file/directory dialog.
32
+
33
+ After the user picks a path, the field text is updated and a `change` event
34
+ fires. The `dialog_result` property holds the raw result from the dialog (a
35
+ string for single-file dialogs, a tuple of strings for multi-file dialogs).
36
+
37
+ Args:
38
+ value: Initial path string.
39
+ mode: Dialog type. Default `'open'`.
40
+ dialog_title: Title shown in the native picker window.
41
+ start_dir: Directory the dialog opens in.
42
+ file_filters: File type filters, e.g. `[('Images', '*.png *.jpg')]`.
43
+ Not used for `'directory'` mode.
44
+ default_extension: Extension appended automatically if omitted by the
45
+ user. `'save'` mode only.
46
+ default_filename: Suggested filename pre-filled in the dialog.
47
+ `'save'` mode only.
48
+ label: Label displayed above the field.
49
+ message: Hint text displayed below the field.
50
+ placeholder: Ghost text shown when the field is empty and unfocused.
51
+ textsignal: Reactive `Signal[str]` bound to the field text. The
52
+ field value and signal stay in sync automatically.
53
+ required: Mark field as required.
54
+ disabled: If True, field is non-interactive.
55
+ read_only: If True, value is visible but not editable.
56
+ width: Width in character units.
57
+ accent: Accent token applied to the focus ring.
58
+ density: Widget density.
59
+ parent: Override the context-stack parent.
60
+ **kwargs: Layout placement options applied by the parent container —
61
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
62
+ See :doc:`/tasks/layout`.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ value: str = "",
68
+ *,
69
+ mode: Literal["open", "open_multiple", "save", "directory"] = "open",
70
+ dialog_title: str | None = None,
71
+ start_dir: str | None = None,
72
+ file_filters: list[tuple[str, str]] | None = None,
73
+ default_extension: str | None = None,
74
+ default_filename: str | None = None,
75
+ label: str | None = None,
76
+ message: str | None = None,
77
+ placeholder: str | None = None,
78
+ textsignal: "Signal[str] | None" = None,
79
+ required: bool = False,
80
+ disabled: bool = False,
81
+ read_only: bool = False,
82
+ width: int | None = None,
83
+ accent: AccentToken | str | None = None,
84
+ density: WidgetDensity | None = None,
85
+ parent: Any = None,
86
+ **kwargs: Any,
87
+ ) -> None:
88
+ self._parent = self._resolve_parent(parent)
89
+ layout_kw = self._split_layout_kwargs(kwargs)
90
+
91
+ tk_master = self._parent._child_master() if self._parent else None
92
+
93
+ internal_kwargs: dict[str, Any] = {"mode": mode}
94
+ if value:
95
+ internal_kwargs["value"] = value
96
+ if dialog_title is not None:
97
+ internal_kwargs["dialog_title"] = dialog_title
98
+ if start_dir is not None:
99
+ internal_kwargs["start_dir"] = start_dir
100
+ if file_filters is not None:
101
+ internal_kwargs["file_filters"] = file_filters
102
+ if default_extension is not None:
103
+ internal_kwargs["default_extension"] = default_extension
104
+ if default_filename is not None:
105
+ internal_kwargs["default_filename"] = default_filename
106
+ if label is not None:
107
+ internal_kwargs["label"] = label
108
+ if message is not None:
109
+ internal_kwargs["message"] = message
110
+ if placeholder is not None:
111
+ internal_kwargs["placeholder"] = placeholder
112
+ if textsignal is not None:
113
+ internal_kwargs["textsignal"] = textsignal
114
+ if required:
115
+ internal_kwargs["required"] = True
116
+ if disabled:
117
+ internal_kwargs["state"] = "disabled"
118
+ elif read_only:
119
+ internal_kwargs["state"] = "readonly"
120
+ if width is not None:
121
+ internal_kwargs["width"] = width
122
+ if accent is not None:
123
+ internal_kwargs["accent"] = accent
124
+ if density is not None:
125
+ internal_kwargs["density"] = density
126
+
127
+ self._internal = _InternalPathEntry(tk_master, **internal_kwargs)
128
+ self._attach_to_parent(layout_kw)
129
+
130
+ # ----- Event routing -----
131
+
132
+ def _entry_widget(self) -> tkinter.Misc:
133
+ return self._internal._entry
134
+
135
+ @overload
136
+ def on(self, event: str) -> Stream: ...
137
+ @overload
138
+ def on(self, event: str, handler: Callable[[Event], Any]) -> Subscription: ...
139
+ def on(self, event: str, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
140
+ """Register a callback for an event by name.
141
+
142
+ A generic, string-keyed escape hatch — prefer the typed `on_*`
143
+ shorthands (e.g. `on_change`), which carry the precise payload type.
144
+ Called with no handler, returns a composable `Stream`; with a handler,
145
+ binds it and returns a `Subscription`.
146
+
147
+ Args:
148
+ event: Event name (for example `'change'` or `'focus'`).
149
+ handler: Called with the event payload. Omit to get a composable
150
+ :class:`~bootstack.streams.Stream` instead.
151
+
152
+ Returns:
153
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
154
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
155
+ """
156
+ sequence = resolve_event(self, str(event))
157
+ target = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
158
+ if handler is None:
159
+ def _source(h):
160
+ t = self._entry_widget() if sequence in _INNER_ENTRY_SEQUENCES else self._internal
161
+ bid = t.bind(sequence, adapt_handler(h), add="+")
162
+ return Subscription(t, sequence, bid)
163
+ return Stream(self._internal, _source=_source)
164
+ bid = target.bind(sequence, adapt_handler(handler), add="+")
165
+ return Subscription(target, sequence, bid)
166
+
167
+ # ----- Properties -----
168
+
169
+ @property
170
+ def value(self) -> str:
171
+ """The current path string shown in the field."""
172
+ return self._internal.value
173
+
174
+ @value.setter
175
+ def value(self, v: str) -> None:
176
+ self._internal.value = v
177
+
178
+ @property
179
+ def dialog_result(self) -> "str | tuple[str, ...] | None":
180
+ """Raw result from the most recent dialog (string or tuple of strings)."""
181
+ return self._internal.dialog_result
182
+
183
+ # ----- Live dialog configuration -----
184
+
185
+ @property
186
+ def mode(self) -> Literal["open", "open_multiple", "save", "directory"]:
187
+ """Which native dialog the browse button opens."""
188
+ return self._internal.cget("mode")
189
+
190
+ @mode.setter
191
+ def mode(self, v: Literal["open", "open_multiple", "save", "directory"]) -> None:
192
+ self._internal.configure(mode=v)
193
+
194
+ @property
195
+ def dialog_title(self) -> str | None:
196
+ """Title shown in the native picker window."""
197
+ return self._internal.cget("dialog_title")
198
+
199
+ @dialog_title.setter
200
+ def dialog_title(self, v: str | None) -> None:
201
+ self._internal.configure(dialog_title=v)
202
+
203
+ @property
204
+ def start_dir(self) -> str | None:
205
+ """Directory the dialog opens in."""
206
+ return self._internal.cget("start_dir")
207
+
208
+ @start_dir.setter
209
+ def start_dir(self, v: str | None) -> None:
210
+ self._internal.configure(start_dir=v)
211
+
212
+ @property
213
+ def file_filters(self) -> "list[tuple[str, str]] | None":
214
+ """File-type filters, e.g. `[('Images', '*.png *.jpg')]`. Not used for `'directory'` mode."""
215
+ return self._internal.cget("file_filters")
216
+
217
+ @file_filters.setter
218
+ def file_filters(self, v: "list[tuple[str, str]] | None") -> None:
219
+ self._internal.configure(file_filters=v)
220
+
221
+ @property
222
+ def default_extension(self) -> str | None:
223
+ """Extension appended automatically when omitted (`'save'` mode only)."""
224
+ return self._internal.cget("default_extension")
225
+
226
+ @default_extension.setter
227
+ def default_extension(self, v: str | None) -> None:
228
+ self._internal.configure(default_extension=v)
229
+
230
+ @property
231
+ def default_filename(self) -> str | None:
232
+ """Suggested filename pre-filled in the dialog (`'save'` mode only)."""
233
+ return self._internal.cget("default_filename")
234
+
235
+ @default_filename.setter
236
+ def default_filename(self, v: str | None) -> None:
237
+ self._internal.configure(default_filename=v)
238
+
239
+ @property
240
+ def signal(self) -> "Signal[str] | None":
241
+ """The reactive `Signal` bound to this field, or `None`."""
242
+ return getattr(self._internal, "signal", None)
243
+
244
+ @property
245
+ def disabled(self) -> bool:
246
+ """Whether the field is fully non-interactive."""
247
+ return str(self._entry_widget().cget("state")) == "disabled"
248
+
249
+ @disabled.setter
250
+ def disabled(self, v: bool) -> None:
251
+ self._internal.configure(state="disabled" if v else "normal")
252
+
253
+ @property
254
+ def read_only(self) -> bool:
255
+ """Whether the field is visible but not editable."""
256
+ return str(self._entry_widget().cget("state")) == "readonly"
257
+
258
+ @read_only.setter
259
+ def read_only(self, v: bool) -> None:
260
+ self._internal.configure(state="readonly" if v else "normal")
261
+
262
+ # ----- Methods -----
263
+
264
+ def focus(self) -> None:
265
+ """Give keyboard focus to this field."""
266
+ self._entry_widget().focus_set()
267
+
268
+ def clear(self) -> None:
269
+ """Clear the field text."""
270
+ self._internal.value = ""
271
+
272
+ def validate(self) -> bool:
273
+ """Run validation rules against the current value.
274
+
275
+ Returns:
276
+ `True` if all rules pass, `False` otherwise.
277
+ """
278
+ entry = self._internal._entry
279
+ return entry.validate(entry._get_validation_value(), trigger="manual")
280
+
281
+ def select_all(self) -> None:
282
+ """Select all text in the field."""
283
+ self._entry_widget().selection_range(0, "end")
284
+
285
+ def select_range(self, start: int, end: int) -> None:
286
+ """Select text between `start` and `end` character positions.
287
+
288
+ Args:
289
+ start: Start index (0-based, inclusive).
290
+ end: End index (exclusive).
291
+ """
292
+ self._entry_widget().selection_range(start, end)
293
+
294
+ def insert(self, index: int, text: str) -> None:
295
+ """Insert `text` at `index`.
296
+
297
+ Args:
298
+ index: Character position to insert at.
299
+ text: Text to insert.
300
+ """
301
+ self._entry_widget().insert(index, text)
302
+
303
+ def delete(self, start: int, end: int | None = None) -> None:
304
+ """Delete characters from `start` to `end`.
305
+
306
+ Args:
307
+ start: Start index (inclusive).
308
+ end: End index (exclusive). If `None`, deletes to end of field.
309
+ """
310
+ self._entry_widget().delete(start, "end" if end is None else end)
311
+
312
+ # ----- Event shorthands -----
313
+
314
+ @overload
315
+ def on_change(self) -> Stream: ...
316
+ @overload
317
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
318
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
319
+ """Register a callback fired when the path value changes.
320
+
321
+ Fires after the user picks a path via the dialog or edits the field
322
+ directly and commits (blur or Enter).
323
+
324
+ Args:
325
+ handler: Called with a :class:`~bootstack.events.ChangeEvent`. Omit to
326
+ get a composable :class:`~bootstack.streams.Stream` instead.
327
+
328
+ Returns:
329
+ A cancellable :class:`~bootstack.events.Subscription` when a
330
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
331
+ """
332
+ return self.on("change", handler)
333
+
334
+ @overload
335
+ def on_input(self) -> Stream: ...
336
+ @overload
337
+ def on_input(self, handler: Callable[[InputEvent], Any]) -> Subscription: ...
338
+ def on_input(self, handler: Callable[[InputEvent], Any] | None = None) -> Stream | Subscription:
339
+ """Register a callback fired on every keystroke in the text portion.
340
+
341
+ Args:
342
+ handler: Called with an :class:`~bootstack.events.InputEvent`. Omit to
343
+ get a composable :class:`~bootstack.streams.Stream` instead.
344
+
345
+ Returns:
346
+ A cancellable :class:`~bootstack.events.Subscription` when a
347
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
348
+ """
349
+ return self.on("input", handler)
350
+
351
+ @overload
352
+ def on_submit(self) -> Stream: ...
353
+ @overload
354
+ def on_submit(self, handler: Callable[[Event], Any]) -> Subscription: ...
355
+ def on_submit(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
356
+ """Register a callback fired when the user presses Enter.
357
+
358
+ Args:
359
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
360
+ get a composable :class:`~bootstack.streams.Stream` instead.
361
+
362
+ Returns:
363
+ A cancellable :class:`~bootstack.events.Subscription` when a
364
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
365
+ """
366
+ return self.on("submit", handler)
367
+
368
+ @overload
369
+ def on_focus(self) -> Stream: ...
370
+ @overload
371
+ def on_focus(self, handler: Callable[[Event], Any]) -> Subscription: ...
372
+ def on_focus(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
373
+ """Register a callback fired when the field gains focus.
374
+
375
+ Args:
376
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
377
+ get a composable :class:`~bootstack.streams.Stream` instead.
378
+
379
+ Returns:
380
+ A cancellable :class:`~bootstack.events.Subscription` when a
381
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
382
+ """
383
+ return self.on("focus", handler)
384
+
385
+ @overload
386
+ def on_blur(self) -> Stream: ...
387
+ @overload
388
+ def on_blur(self, handler: Callable[[Event], Any]) -> Subscription: ...
389
+ def on_blur(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
390
+ """Register a callback fired when the field loses focus.
391
+
392
+ Args:
393
+ handler: Called with an :class:`~bootstack.events.Event`. Omit to
394
+ get a composable :class:`~bootstack.streams.Stream` instead.
395
+
396
+ Returns:
397
+ A cancellable :class:`~bootstack.events.Subscription` when a
398
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
399
+ """
400
+ return self.on("blur", handler)
401
+
402
+ @overload
403
+ def on_valid(self) -> Stream: ...
404
+ @overload
405
+ def on_valid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
406
+ def on_valid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
407
+ """Register a callback fired when validation passes.
408
+
409
+ Args:
410
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
411
+ get a composable :class:`~bootstack.streams.Stream` instead.
412
+
413
+ Returns:
414
+ A cancellable :class:`~bootstack.events.Subscription` when a
415
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
416
+ """
417
+ return self.on("valid", handler)
418
+
419
+ @overload
420
+ def on_invalid(self) -> Stream: ...
421
+ @overload
422
+ def on_invalid(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
423
+ def on_invalid(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
424
+ """Register a callback fired when validation fails.
425
+
426
+ Args:
427
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
428
+ get a composable :class:`~bootstack.streams.Stream` instead.
429
+
430
+ Returns:
431
+ A cancellable :class:`~bootstack.events.Subscription` when a
432
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
433
+ """
434
+ return self.on("invalid", handler)
435
+
436
+ @overload
437
+ def on_validate(self) -> Stream: ...
438
+ @overload
439
+ def on_validate(self, handler: Callable[[ValidationEvent], Any]) -> Subscription: ...
440
+ def on_validate(self, handler: Callable[[ValidationEvent], Any] | None = None) -> Stream | Subscription:
441
+ """Register a callback fired after any validation run.
442
+
443
+ Args:
444
+ handler: Called with a :class:`~bootstack.events.ValidationEvent`. Omit to
445
+ get a composable :class:`~bootstack.streams.Stream` instead.
446
+
447
+ Returns:
448
+ A cancellable :class:`~bootstack.events.Subscription` when a
449
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
450
+ """
451
+ return self.on("validate", handler)
452
+
453
+
454
+ register_widget_events(PathField, _PATHFIELD_EVENTS)