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,606 @@
1
+ """TextArea — labeled multi-line text input with validation support."""
2
+ from __future__ import annotations
3
+
4
+ import tkinter as tk
5
+ from typing import Callable
6
+
7
+ from bootstack.events import ChangeEvent, InputEvent, ValidationEvent
8
+ from bootstack.widgets._impl.primitives.frame import Frame
9
+ from bootstack.widgets._impl.primitives.gridframe import GridFrame
10
+ from bootstack.widgets._impl.primitives.label import Label
11
+ from bootstack.widgets._impl.composites.textarea.core import _MultilineCore, ScrollbarMode
12
+ from bootstack.widgets.types import Master, AccentToken
13
+ from bootstack._runtime.utility import scale_padding_floor
14
+
15
+ from bootstack.signals import Signal
16
+
17
+ from typing import TYPE_CHECKING
18
+ if TYPE_CHECKING:
19
+ from bootstack.validation.validation_rules import ValidationRule
20
+
21
+
22
+ class TextArea(GridFrame):
23
+ """A labeled, scrollable multi-line text input with validation support.
24
+
25
+ `TextArea` is the multi-line counterpart to `TextEntry`. It provides the
26
+ same field chrome (label, message, focus border, validation) while wrapping
27
+ a multi-line `tk.Text` widget with undo/redo, scrollbars, and reactive
28
+ signal binding.
29
+
30
+ Use this for comment boxes, note fields, and any other multi-line prose
31
+ input in a form context. For code editing see `CodeEditor`.
32
+
33
+ Replaces `bs.ScrolledText`.
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ master: Master = None,
39
+ *,
40
+ value: str = "",
41
+ textsignal: Signal | None = None,
42
+ label: str | None = None,
43
+ message: str | None = None,
44
+ show_message: bool = False,
45
+ required: bool = False,
46
+ placeholder: str | None = None,
47
+ max_length: int | None = None,
48
+ read_only: bool = False,
49
+ wrap: bool = True,
50
+ height: int = 4,
51
+ width: int | None = None,
52
+ scrollbars: ScrollbarMode = "auto",
53
+ font: str = "body",
54
+ accent: AccentToken | str = "primary",
55
+ show_border: bool = True,
56
+ on_input: Callable | None = None,
57
+ on_changed: Callable | None = None,
58
+ on_blur: Callable | None = None,
59
+ on_valid: Callable | None = None,
60
+ on_invalid: Callable | None = None,
61
+ on_validated: Callable | None = None,
62
+ ) -> None:
63
+ """Create a TextArea widget.
64
+
65
+ Args:
66
+ master: Parent widget. If None, uses the default root window.
67
+ value: Initial text content.
68
+ textsignal: Reactive `Signal[str]` — the TextArea displays and
69
+ tracks the signal's value.
70
+ label: Optional label displayed above the text area.
71
+ message: Optional hint text shown below the text area. Replaced
72
+ by validation error messages when validation fails.
73
+ show_message: If True, the message area is always visible. Auto-
74
+ enabled when `message=` or `required=True` is set.
75
+ required: If True, marks the field as required and adds a
76
+ validation rule that rejects empty input.
77
+ placeholder: Placeholder text shown when the widget is empty.
78
+ Cleared on first focus.
79
+ max_length: If set, inserts beyond this character count are blocked.
80
+ read_only: If True, the text is not editable.
81
+ wrap: If True (default), long lines wrap at the widget boundary.
82
+ If False, horizontal scrolling is used.
83
+ height: Visible row count. Default is 4.
84
+ scrollbars: Scrollbar visibility mode — `"auto"` (shown when
85
+ needed), `"vertical"`, `"both"`, or `"none"`.
86
+ font: Font token. Defaults to `"body"`.
87
+ accent: Accent token for the focus border. Defaults to `"primary"`.
88
+ show_border: If True (default), wraps the text area in a themed
89
+ border that gains a focus ring on interaction.
90
+ on_input: Callback invoked on every edit. Receives `event.data`
91
+ of type `InputEvent`.
92
+ on_changed: Callback invoked when the widget loses focus. Receives
93
+ `event.data` of type `InputEvent`.
94
+ on_blur: Callback invoked when the widget loses focus (alias for
95
+ `on_changed` — use either, not both).
96
+ on_valid: Callback invoked after successful validation. Receives
97
+ `event.data` of type `ValidationEvent`.
98
+ on_invalid: Callback invoked after failed validation. Receives
99
+ `event.data` of type `ValidationEvent`.
100
+ on_validated: Callback invoked after any validation, pass or fail.
101
+ Receives `event.data` of type `ValidationEvent`.
102
+ """
103
+ # GridFrame rows: auto (label) | 1 (field, expands) | auto (message)
104
+ super().__init__(master, rows=["auto", 1, "auto"], columns=[1],
105
+ auto_flow="none", gap=(0, 2))
106
+
107
+ self._accent = accent
108
+ self._placeholder_text = placeholder
109
+ self._max_length = max_length
110
+ self._showing_placeholder = False
111
+ self._original_message = message or ""
112
+ self._rules: list[ValidationRule] = []
113
+ self._message_showing = False
114
+ self._valid_signal: Signal = Signal(True)
115
+ self._error_signal: Signal = Signal("")
116
+
117
+ # ── label (row 0) ─────────────────────────────────────────────────
118
+ if label:
119
+ _lbl_text = f"{label} *" if required else label
120
+ Label(self, text=_lbl_text, font="caption").grid(
121
+ row=0, column=0, sticky="w", padx=(4, 0),
122
+ )
123
+
124
+ # ── core (row 1) ──────────────────────────────────────────────────
125
+ if show_border:
126
+ # Padding must grow with the DPI-scaled TField border slice, else the
127
+ # text core overpaints the border at high DPI (#90, same as Field).
128
+ self._field_frame = Frame(
129
+ self, accent=accent, padding=scale_padding_floor(5), ttk_class="TField",
130
+ )
131
+ self._field_frame.grid(row=1, column=0, sticky="nsew")
132
+ core_parent = self._field_frame
133
+ else:
134
+ self._field_frame = None
135
+ core_parent = self
136
+
137
+ core_kwargs: dict = {}
138
+ if width is not None:
139
+ core_kwargs["width"] = width
140
+ self._core = _MultilineCore(
141
+ core_parent,
142
+ value=value,
143
+ wrap=wrap,
144
+ height=height,
145
+ scrollbars=scrollbars,
146
+ font=font,
147
+ read_only=read_only,
148
+ **core_kwargs,
149
+ )
150
+
151
+ if show_border:
152
+ self._core.pack(fill="both", expand=True)
153
+ self._core.text.bind(
154
+ "<FocusIn>",
155
+ lambda _: self._field_frame.state(["focus"]),
156
+ add="+",
157
+ )
158
+ self._core.text.bind(
159
+ "<FocusOut>",
160
+ lambda _: self._field_frame.state(["!focus"]),
161
+ add="+",
162
+ )
163
+ else:
164
+ self._core.grid(row=1, column=0, sticky="nsew")
165
+
166
+ # ── Tab moves focus (not a keyboard trap) ─────────────────────────
167
+ # A multi-line tk.Text inserts a literal tab on <Tab> and keeps focus,
168
+ # which traps keyboard users in a form field. Rebind Tab/Shift-Tab to
169
+ # move focus like every other field. (CodeEditor keeps Tab for indent —
170
+ # it is a separate composite and is unaffected.)
171
+ self._core.text.bind("<Tab>", self._focus_next, add="+")
172
+ self._core.text.bind("<Shift-Tab>", self._focus_prev, add="+")
173
+ self._core.text.bind("<ISO_Left_Tab>", self._focus_prev, add="+")
174
+
175
+ # ── message label (row 2) ─────────────────────────────────────────
176
+ self._message_lbl = Label(self, text=message or "", font="caption",
177
+ accent="secondary")
178
+ self._message_lbl.grid(row=2, column=0, sticky="w")
179
+ self._message_lbl.grid_remove()
180
+
181
+ if show_message or message or required:
182
+ self._show_message_area()
183
+
184
+ # ── signal binding ────────────────────────────────────────────────
185
+ if textsignal is not None:
186
+ self._core.bind_signal(textsignal)
187
+
188
+ # ── placeholder ───────────────────────────────────────────────────
189
+ self._default_fg = self._core.text.cget("foreground")
190
+ if placeholder and not value and textsignal is None:
191
+ self._show_placeholder()
192
+ if placeholder:
193
+ self._core.text.bind("<FocusIn>", self._on_focus_in_placeholder, add="+")
194
+
195
+ # ── max_length filter ─────────────────────────────────────────────
196
+ if max_length is not None:
197
+ from bootstack.widgets._impl.composites.textarea.filter import EditFilter
198
+
199
+ _limit = max_length
200
+
201
+ class _MaxLenFilter(EditFilter):
202
+ def insert(self, index, chars, tags=None):
203
+ current = len(self.get("1.0", "end-1c"))
204
+ allowed = max(0, _limit - current)
205
+ if allowed > 0:
206
+ self.delegate.insert(index, chars[:allowed], tags)
207
+
208
+ self._core.add_filter(_MaxLenFilter())
209
+
210
+ # ── required rule ─────────────────────────────────────────────────
211
+ if required:
212
+ self.add_validation_rule("required")
213
+
214
+ # ── internal event wiring ─────────────────────────────────────────
215
+ # Fire <<Input>> on every edit, <<Changed>> + validation on blur.
216
+ self._core.text.bind("<<Change>>", self._on_core_change, add="+")
217
+ self._core.text.bind("<FocusOut>", self._on_focus_out, add="+")
218
+
219
+ # ── constructor callbacks ─────────────────────────────────────────
220
+ if on_input is not None:
221
+ self.on_input(on_input)
222
+ if on_changed is not None:
223
+ self.on_changed(on_changed)
224
+ if on_blur is not None:
225
+ self.on_changed(on_blur)
226
+ if on_valid is not None:
227
+ self.on_valid(on_valid)
228
+ if on_invalid is not None:
229
+ self.on_invalid(on_invalid)
230
+ if on_validated is not None:
231
+ self.on_validated(on_validated)
232
+
233
+ # ── placeholder ───────────────────────────────────────────────────────
234
+
235
+ def _show_placeholder(self) -> None:
236
+ self._showing_placeholder = True
237
+ was_read_only = self._core._read_only
238
+ self._core.text.configure(state=tk.NORMAL)
239
+ self._core.text.insert("1.0", self._placeholder_text)
240
+ self._core.text.configure(foreground="grey")
241
+ # Restore read-only state — showing a placeholder must not silently make
242
+ # a read-only field editable.
243
+ if was_read_only:
244
+ self._core.text.configure(state=tk.DISABLED)
245
+
246
+ def _hide_placeholder(self) -> None:
247
+ self._showing_placeholder = False
248
+ was_read_only = self._core._read_only
249
+ if was_read_only:
250
+ self._core.text.configure(state=tk.NORMAL)
251
+ self._core.text.delete("1.0", tk.END)
252
+ self._core.text.configure(foreground=self._default_fg)
253
+ if was_read_only:
254
+ self._core.text.configure(state=tk.DISABLED)
255
+
256
+ def _on_focus_in_placeholder(self, _event: tk.Event) -> None:
257
+ if self._showing_placeholder:
258
+ self._hide_placeholder()
259
+
260
+ # ── focus traversal ───────────────────────────────────────────────────
261
+
262
+ def _focus_next(self, event: tk.Event) -> str:
263
+ event.widget.tk_focusNext().focus_set()
264
+ return "break"
265
+
266
+ def _focus_prev(self, event: tk.Event) -> str:
267
+ event.widget.tk_focusPrev().focus_set()
268
+ return "break"
269
+
270
+ # ── message / error area ──────────────────────────────────────────────
271
+
272
+ def _show_message_area(self) -> None:
273
+ if not self._message_showing:
274
+ self._message_lbl.grid()
275
+ self._message_showing = True
276
+
277
+ def _show_error(self, message: str) -> None:
278
+ self._show_message_area()
279
+ self._message_lbl.configure(text=message, accent="danger")
280
+
281
+ def _clear_error(self) -> None:
282
+ self._message_lbl.configure(text=self._original_message, accent="secondary")
283
+
284
+ # ── validation ────────────────────────────────────────────────────────
285
+
286
+ def add_validation_rule(
287
+ self,
288
+ rule_type: str,
289
+ message: str | None = None,
290
+ **kwargs,
291
+ ) -> None:
292
+ """Add a validation rule to this field.
293
+
294
+ Args:
295
+ rule_type: Rule name — `"required"`, `"pattern"`, `"stringLength"`,
296
+ `"email"`, `"custom"`, etc.
297
+ message: Custom error message. If None, the rule's default is used.
298
+ **kwargs: Rule-specific parameters (e.g. `minLength=`, `pattern=`,
299
+ `func=` for custom rules).
300
+ """
301
+ from bootstack.validation.validation_rules import ValidationRule
302
+ self._rules.append(ValidationRule(rule_type, message=message, **kwargs))
303
+ self._show_message_area()
304
+
305
+ def validate(self) -> bool:
306
+ """Run all validation rules immediately and return True if valid.
307
+
308
+ Triggers the same `<<Valid>>` / `<<Invalid>>` / `<<Validate>>` events
309
+ as blur-triggered validation.
310
+ """
311
+ return self._run_validation(trigger="manual")
312
+
313
+ def _set_validity(self, is_valid: bool, message: str) -> None:
314
+ self._error_signal.set("" if is_valid else message)
315
+ self._valid_signal.set(is_valid)
316
+
317
+ def _run_validation(self, trigger: str = "blur") -> bool:
318
+ value = self.value
319
+ for rule in self._rules:
320
+ if trigger != "manual" and rule.trigger not in ("always", trigger):
321
+ continue
322
+ result = rule.validate(value)
323
+ if not result.is_valid:
324
+ self._set_validity(False, result.message)
325
+ self._show_error(result.message)
326
+ data = ValidationEvent(
327
+ value=value, is_valid=False, message=result.message,
328
+ )
329
+ self.event_generate("<<Invalid>>", data=data, when="tail")
330
+ self.event_generate("<<Validate>>", data=data, when="tail")
331
+ return False
332
+ self._set_validity(True, "")
333
+ self._clear_error()
334
+ data = ValidationEvent(value=value, is_valid=True, message="")
335
+ self.event_generate("<<Valid>>", data=data, when="tail")
336
+ self.event_generate("<<Validate>>", data=data, when="tail")
337
+ return True
338
+
339
+ # ── internal event dispatch ───────────────────────────────────────────
340
+
341
+ def _on_core_change(self, _event: tk.Event) -> None:
342
+ if not self._showing_placeholder:
343
+ self.event_generate(
344
+ "<<Input>>",
345
+ data=InputEvent(text=self.value),
346
+ when="tail",
347
+ )
348
+
349
+ def _on_focus_out(self, _event: tk.Event) -> None:
350
+ if self._placeholder_text and not self._core.value:
351
+ self._show_placeholder()
352
+ if not self._showing_placeholder:
353
+ self.event_generate(
354
+ "<<Changed>>",
355
+ data=ChangeEvent(value=self.value, text=self.value),
356
+ when="tail",
357
+ )
358
+ if self._rules:
359
+ self._run_validation(trigger="blur")
360
+
361
+ # ── public value API ──────────────────────────────────────────────────
362
+
363
+ @property
364
+ def value(self) -> str:
365
+ """Full text content (trailing newline stripped)."""
366
+ if self._showing_placeholder:
367
+ return ""
368
+ return self._core.value
369
+
370
+ @value.setter
371
+ def value(self, text: str) -> None:
372
+ if self._showing_placeholder:
373
+ self._hide_placeholder()
374
+ self._core.value = text
375
+
376
+ @property
377
+ def is_dirty(self) -> bool:
378
+ """True if the content has changed since last save."""
379
+ return self._core.is_dirty
380
+
381
+ def mark_saved(self) -> None:
382
+ """Mark the current state as the saved baseline."""
383
+ self._core.mark_saved()
384
+
385
+ def undo(self) -> None:
386
+ """Undo the last edit."""
387
+ self._core.undo()
388
+
389
+ def redo(self) -> None:
390
+ """Redo the last undone edit."""
391
+ self._core.redo()
392
+
393
+ def undo_block_start(self) -> None:
394
+ """Begin a compound undo block."""
395
+ self._core.undo_block_start()
396
+
397
+ def undo_block_stop(self) -> None:
398
+ """End a compound undo block."""
399
+ self._core.undo_block_stop()
400
+
401
+ def focus_set(self, *args, **kwargs) -> None:
402
+ """Set focus to the text area."""
403
+ self._core.focus_set()
404
+
405
+ @property
406
+ def core(self) -> _MultilineCore:
407
+ """The internal `_MultilineCore` — for advanced filter/decoration use."""
408
+ return self._core
409
+
410
+ # ── event subscriptions ───────────────────────────────────────────────
411
+
412
+ def on_input(self, callback: Callable[[InputEvent], None]) -> str:
413
+ """Register a callback for `<<Input>>` events.
414
+
415
+ Fires on every edit. `event.data.text` is the current text.
416
+
417
+ Args:
418
+ callback: Receives the Tk event. `event.data` is
419
+ `InputEvent`.
420
+
421
+ Returns:
422
+ Bind ID — pass to `off_input()` to unsubscribe.
423
+ """
424
+ return self.bind("<<Input>>", callback, add="+")
425
+
426
+ def off_input(self, bind_id: str | None = None) -> None:
427
+ """Unsubscribe from `<<Input>>`.
428
+
429
+ Args:
430
+ bind_id: ID returned by `on_input()`. If None, removes all.
431
+ """
432
+ self.unbind("<<Input>>", bind_id)
433
+
434
+ def on_changed(self, callback: Callable[[InputEvent], None]) -> str:
435
+ """Register a callback for `<<Changed>>` events.
436
+
437
+ Fires when the widget loses focus. `event.data.value` is the
438
+ current text.
439
+
440
+ Args:
441
+ callback: Receives the Tk event. `event.data` is
442
+ `InputEvent`.
443
+
444
+ Returns:
445
+ Bind ID — pass to `off_changed()` to unsubscribe.
446
+ """
447
+ return self.bind("<<Changed>>", callback, add="+")
448
+
449
+ def off_changed(self, bind_id: str | None = None) -> None:
450
+ """Unsubscribe from `<<Changed>>`.
451
+
452
+ Args:
453
+ bind_id: ID returned by `on_changed()`. If None, removes all.
454
+ """
455
+ self.unbind("<<Changed>>", bind_id)
456
+
457
+ def on_blur(self, callback: Callable) -> str:
458
+ """Register a callback for focus-out events.
459
+
460
+ Args:
461
+ callback: Called when the text area loses focus.
462
+
463
+ Returns:
464
+ Bind ID — pass to `off_blur()` to unsubscribe.
465
+ """
466
+ return self._core.text.bind("<FocusOut>", callback, add="+")
467
+
468
+ def off_blur(self, bind_id: str | None = None) -> None:
469
+ """Unsubscribe from focus-out events.
470
+
471
+ Args:
472
+ bind_id: ID returned by `on_blur()`. If None, removes all.
473
+ """
474
+ self._core.text.unbind("<FocusOut>", bind_id)
475
+
476
+ def on_valid(self, callback: Callable) -> str:
477
+ """Register a callback for `<<Valid>>` events.
478
+
479
+ Fires after all validation rules pass.
480
+
481
+ Args:
482
+ callback: Receives the Tk event. `event.data` is
483
+ `ValidationEvent`.
484
+
485
+ Returns:
486
+ Bind ID — pass to `off_valid()` to unsubscribe.
487
+ """
488
+ return self.bind("<<Valid>>", callback, add="+")
489
+
490
+ def off_valid(self, bind_id: str | None = None) -> None:
491
+ """Unsubscribe from `<<Valid>>`.
492
+
493
+ Args:
494
+ bind_id: ID returned by `on_valid()`. If None, removes all.
495
+ """
496
+ self.unbind("<<Valid>>", bind_id)
497
+
498
+ def on_invalid(self, callback: Callable) -> str:
499
+ """Register a callback for `<<Invalid>>` events.
500
+
501
+ Fires when any validation rule fails.
502
+
503
+ Args:
504
+ callback: Receives the Tk event. `event.data` is
505
+ `ValidationEvent`.
506
+
507
+ Returns:
508
+ Bind ID — pass to `off_invalid()` to unsubscribe.
509
+ """
510
+ return self.bind("<<Invalid>>", callback, add="+")
511
+
512
+ def off_invalid(self, bind_id: str | None = None) -> None:
513
+ """Unsubscribe from `<<Invalid>>`.
514
+
515
+ Args:
516
+ bind_id: ID returned by `on_invalid()`. If None, removes all.
517
+ """
518
+ self.unbind("<<Invalid>>", bind_id)
519
+
520
+ def on_validated(self, callback: Callable) -> str:
521
+ """Register a callback for `<<Validate>>` events.
522
+
523
+ Fires after any validation pass, whether the result is valid or not.
524
+
525
+ Args:
526
+ callback: Receives the Tk event. `event.data` is
527
+ `ValidationEvent`.
528
+
529
+ Returns:
530
+ Bind ID — pass to `off_validated()` to unsubscribe.
531
+ """
532
+ return self.bind("<<Validate>>", callback, add="+")
533
+
534
+ def off_validated(self, bind_id: str | None = None) -> None:
535
+ """Unsubscribe from `<<Validate>>`.
536
+
537
+ Args:
538
+ bind_id: ID returned by `on_validated()`. If None, removes all.
539
+ """
540
+ self.unbind("<<Validate>>", bind_id)
541
+
542
+ def on_modified(self, callback: Callable) -> str:
543
+ """Register a callback for `<<TextModified>>` events.
544
+
545
+ Fires when the dirty state changes. `event.data.is_dirty` is the
546
+ new state.
547
+
548
+ Args:
549
+ callback: Receives the Tk event.
550
+
551
+ Returns:
552
+ Bind ID — pass to `off_modified()` to unsubscribe.
553
+ """
554
+ return self._core.on_modified(callback)
555
+
556
+ def off_modified(self, bind_id: str | None = None) -> None:
557
+ """Unsubscribe from `<<TextModified>>`.
558
+
559
+ Args:
560
+ bind_id: ID returned by `on_modified()`. If None, removes all.
561
+ """
562
+ self._core.off_modified(bind_id)
563
+
564
+ def on_undo(self, callback: Callable) -> str:
565
+ """Register a callback for `<<TextUndo>>` events.
566
+
567
+ Fires after an undo completes. `event.data.text` is the text
568
+ content after the undo.
569
+
570
+ Args:
571
+ callback: Receives the Tk event.
572
+
573
+ Returns:
574
+ Bind ID — pass to `off_undo()` to unsubscribe.
575
+ """
576
+ return self._core.on_undo(callback)
577
+
578
+ def off_undo(self, bind_id: str | None = None) -> None:
579
+ """Unsubscribe from `<<TextUndo>>`.
580
+
581
+ Args:
582
+ bind_id: ID returned by `on_undo()`. If None, removes all.
583
+ """
584
+ self._core.off_undo(bind_id)
585
+
586
+ def on_redo(self, callback: Callable) -> str:
587
+ """Register a callback for `<<TextRedo>>` events.
588
+
589
+ Fires after a redo completes. `event.data.text` is the text
590
+ content after the redo.
591
+
592
+ Args:
593
+ callback: Receives the Tk event.
594
+
595
+ Returns:
596
+ Bind ID — pass to `off_redo()` to unsubscribe.
597
+ """
598
+ return self._core.on_redo(callback)
599
+
600
+ def off_redo(self, bind_id: str | None = None) -> None:
601
+ """Unsubscribe from `<<TextRedo>>`.
602
+
603
+ Args:
604
+ bind_id: ID returned by `on_redo()`. If None, removes all.
605
+ """
606
+ self._core.off_redo(bind_id)