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,688 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Literal, Protocol, overload
4
+
5
+ from bootstack.widgets._impl.composites.tableview.tableview import (
6
+ TableView as _InternalTableView,
7
+ )
8
+ from bootstack.data.types import DataSourceProtocol
9
+ from bootstack.events import RowEvent, RowsEvent, SelectionEvent, ExportEvent, Subscription
10
+ from bootstack.streams import Stream
11
+ from bootstack.widgets._core.base import PublicWidgetBase
12
+ from bootstack.widgets._core.events import register_widget_events
13
+ from bootstack.widgets.types import (
14
+ ColumnSpec, FormOptions, WidgetDensity, ExportScope, ExportFormat, SelectionMode,
15
+ )
16
+
17
+
18
+ class ExportJob(Protocol):
19
+ """Handle for an in-progress async export (returned by `export_file_async`)."""
20
+
21
+ def cancel(self) -> None:
22
+ """Stop the export. The partial file is removed."""
23
+ ...
24
+
25
+
26
+ class DataTable(PublicWidgetBase):
27
+ """A feature-rich data table with sorting, filtering, search, and grouping.
28
+
29
+ Backed by an in-memory `SqliteDataSource` by default. Supply `rows=` to
30
+ pre-load data, or pass an existing `data_source=` to share any data source
31
+ that implements the data-source protocol (`SqliteDataSource`,
32
+ `MemoryDataSource`, `FileDataSource`, or your own).
33
+
34
+ Args:
35
+ columns: Column definitions — a list of field-key strings, or
36
+ `ColumnSpec` dicts (`'key'`, `'text'`, `'width'`, `'minwidth'`,
37
+ `'anchor'`, and the editor keys `'editor'`, `'editor_options'`,
38
+ `'dtype'`, `'readonly'`, `'required'` that shape the add/edit dialog).
39
+ rows: Initial data rows (list of dicts or sequences).
40
+ data_source: Existing data source to use instead of creating one. Any
41
+ object implementing the data-source protocol is accepted
42
+ (`SqliteDataSource`, `MemoryDataSource`, `FileDataSource`, or a
43
+ custom source).
44
+ selection_mode: Row selection behavior. Default `'single'`.
45
+ sorting_mode: Whether columns can be sorted. Default `'single'`.
46
+ searchable: Show the search bar. Default `True`.
47
+ broadcast_search: When `True`, the search box and column filters write
48
+ their combined condition to the shared data source (un-silenced),
49
+ so all other views bound to the same source — including `Chart`
50
+ widgets — re-render to match. Defaults to `False` (per-view only).
51
+
52
+ .. warning::
53
+
54
+ Only enable this on **one** `DataTable` per source. Two tables
55
+ with ``broadcast_search=True`` on the same source will clobber
56
+ each other's filters on every keystroke, reproducing the
57
+ source-mutation coupling that per-view isolation was designed
58
+ to prevent.
59
+ allow_filter: Enable column filtering. Default `True`.
60
+ paging_mode: `'standard'` paginates the rows; `'virtual'` scrolls them
61
+ in a single virtual view. Default `'standard'`.
62
+ page_size: Rows per page in standard paging mode. Default `25`.
63
+ allow_add: Allow adding rows via a form dialog. Default `False`.
64
+ allow_edit: Allow editing rows via a form dialog. Default `False`.
65
+ allow_delete: Allow deleting rows. Default `False`.
66
+ allow_export: Show an export menu (Copy to clipboard / Save to file).
67
+ The actions export the selected rows if any are selected, otherwise
68
+ all rows in the current filter/sort. Saves CSV; Excel (`.xlsx`) is
69
+ also offered when the optional `bootstack[excel]` dependency is
70
+ installed. For explicit control use `export_file()` / `to_csv()`
71
+ with `scope=`. Default `False`.
72
+ export_formats: Which formats the export menu offers. Default `('csv',)`.
73
+ Formats needing an optional dependency (`xlsx`→`bootstack[excel]`,
74
+ `parquet`/`feather`→`bootstack[parquet]`, `hdf5`→`bootstack[hdf5]`)
75
+ appear only when it is installed. Registry formats export the
76
+ displayed columns; for the full record set use `data_source.save(path)`.
77
+ striped: Alternate row background colors. Default `True`.
78
+ density: Row compactness — `'default'` or `'compact'` (tighter row
79
+ height, smaller body font and padding). Default `'default'`.
80
+ allow_group: Allow grouping rows by a column. Default `False`.
81
+ show_status_bar: Show the footer — the filter/sort/group status and the
82
+ pager. The pager auto-hides on a single page, and the whole footer
83
+ collapses when there's nothing to show. Default `True`.
84
+ show_column_chooser: Show a button to toggle column visibility.
85
+ Default `False`.
86
+ show_selection_controls: Show a per-row checkbox in the leading icon slot
87
+ while in `'multi'` selection mode — filled with the accent when
88
+ selected, a muted outline otherwise. With the checkboxes visible, a
89
+ plain click toggles the row (no Ctrl/Shift needed). Has no effect in
90
+ `'single'` mode (the row highlight is enough) or while grouped, where
91
+ the slot holds the group's expand/collapse control. Default `False`.
92
+ id_field: Record field used as the stable row identity. When your rows
93
+ carry this field (default `'id'`), its value becomes the row id used
94
+ by `select_rows`, events, and `update_rows`/`delete_rows`, so your own
95
+ ids round-trip; otherwise an id is auto-assigned. Ignored when you pass
96
+ your own `data_source` (set it on the source instead). Default `'id'`.
97
+ form: Layout options for the built-in add/edit dialog — a `FormOptions`
98
+ dict (`col_count`, `min_col_width`, `scrollable`, `resizable`).
99
+ parent: Override the context-stack parent.
100
+ **kwargs: Layout placement options applied by the parent container —
101
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
102
+ See :doc:`/tasks/layout`.
103
+ """
104
+
105
+ def __init__(
106
+ self,
107
+ *,
108
+ columns: list[str | ColumnSpec] | None = None,
109
+ rows: list | None = None,
110
+ data_source: DataSourceProtocol | None = None,
111
+ selection_mode: SelectionMode = "single",
112
+ sorting_mode: Literal["single", "none"] = "single",
113
+ searchable: bool = True,
114
+ broadcast_search: bool = False,
115
+ allow_filter: bool = True,
116
+ paging_mode: Literal["standard", "virtual"] = "standard",
117
+ page_size: int = 25,
118
+ allow_add: bool = False,
119
+ allow_edit: bool = False,
120
+ allow_delete: bool = False,
121
+ allow_export: bool = False,
122
+ export_formats: list[
123
+ Literal["csv", "tsv", "xlsx", "json", "jsonl", "xml", "parquet", "feather", "hdf5"]
124
+ ] | None = None,
125
+ striped: bool = True,
126
+ density: WidgetDensity = "default",
127
+ allow_group: bool = False,
128
+ show_status_bar: bool = True,
129
+ show_column_chooser: bool = False,
130
+ show_selection_controls: bool = False,
131
+ id_field: str = "id",
132
+ form: FormOptions | None = None,
133
+ parent: Any = None,
134
+ **kwargs: Any,
135
+ ) -> None:
136
+ self._parent = self._resolve_parent(parent)
137
+ self._selection_mode = selection_mode
138
+ layout_kw = self._split_layout_kwargs(kwargs)
139
+ tk_master = self._parent._child_master() if self._parent else None
140
+
141
+ internal_kwargs: dict[str, Any] = {
142
+ "selection_mode": selection_mode,
143
+ "sorting_mode": sorting_mode,
144
+ "enable_search": searchable,
145
+ "broadcast_search": broadcast_search,
146
+ "enable_filtering": allow_filter,
147
+ "paging_mode": paging_mode,
148
+ "page_size": page_size,
149
+ "enable_adding": allow_add,
150
+ "enable_editing": allow_edit,
151
+ "enable_deleting": allow_delete,
152
+ "enable_exporting": allow_export,
153
+ "striped": striped,
154
+ "density": density,
155
+ "allow_grouping": allow_group,
156
+ "show_table_status": show_status_bar,
157
+ "show_column_chooser": show_column_chooser,
158
+ "show_selection_controls": show_selection_controls,
159
+ "id_field": id_field,
160
+ }
161
+ if columns is not None:
162
+ internal_kwargs["columns"] = columns
163
+ if rows is not None:
164
+ internal_kwargs["rows"] = rows
165
+ if data_source is not None:
166
+ internal_kwargs["datasource"] = data_source
167
+ if form is not None:
168
+ internal_kwargs["form_options"] = form
169
+ if export_formats is not None:
170
+ internal_kwargs["export_formats"] = tuple(export_formats)
171
+
172
+ self._internal = _InternalTableView(tk_master, **internal_kwargs)
173
+ self._attach_to_parent(layout_kw)
174
+
175
+ # ----- Data -----
176
+
177
+ def set_rows(self, rows: list) -> None:
178
+ """Replace the entire dataset with `rows`.
179
+
180
+ Args:
181
+ rows: List of dicts (or sequences) to load.
182
+ """
183
+ self._internal.set_data(rows)
184
+
185
+ def insert_rows(self, rows: list) -> None:
186
+ """Insert new rows.
187
+
188
+ Args:
189
+ rows: List of dicts or sequences to insert.
190
+ """
191
+ self._internal.insert_rows(rows)
192
+
193
+ def update_rows(self, rows: list[dict]) -> None:
194
+ """Update existing rows by merging the given dicts.
195
+
196
+ Args:
197
+ rows: List of dicts, each with an `id` key and the fields to update.
198
+ """
199
+ self._internal.update_rows(rows)
200
+
201
+ def delete_rows(self, rows_or_ids: list) -> None:
202
+ """Delete rows by record `id` or by record dict.
203
+
204
+ Args:
205
+ rows_or_ids: List of record ids or record dicts with an `id` key.
206
+ """
207
+ self._internal.delete_rows(rows_or_ids)
208
+
209
+ def new_row(self, defaults: dict | None = None) -> dict | None:
210
+ """Open the built-in *New Record* dialog and insert on save.
211
+
212
+ Honors each column's editor configuration (`editor`, `dtype`,
213
+ `readonly`, `required`). On save the row is inserted and an `rows_insert`
214
+ event fires; the saved record is also returned here.
215
+
216
+ Args:
217
+ defaults: Field values to pre-fill the form with.
218
+
219
+ Returns:
220
+ The new record, or `None` if the dialog was cancelled.
221
+ """
222
+ return self._internal.new_row(defaults)
223
+
224
+ def edit_row(self, record_id: Any) -> dict | None:
225
+ """Open the built-in *Edit Record* dialog for a row and save on submit.
226
+
227
+ On save the row is updated and a `rows_update` event fires (or
228
+ `rows_delete` if the user deletes it); the saved record is also returned.
229
+
230
+ Args:
231
+ record_id: Record id of the row to edit.
232
+
233
+ Returns:
234
+ The updated record, or `None` if cancelled or deleted.
235
+ """
236
+ return self._internal.edit_row(record_id)
237
+
238
+ # ----- Selection -----
239
+
240
+ def select_rows(self, record_ids: list) -> None:
241
+ """Select rows by record `id`.
242
+
243
+ Only rows on the current page can be selected.
244
+
245
+ Args:
246
+ record_ids: Record ids of the rows to select.
247
+ """
248
+ self._internal.select_rows(record_ids)
249
+
250
+ def select_all(self) -> None:
251
+ """Select all rows in the current view."""
252
+ self._internal.select_all()
253
+
254
+ def deselect_rows(self, record_ids: list) -> None:
255
+ """Remove the given rows from the selection by record `id`.
256
+
257
+ Args:
258
+ record_ids: Record ids to deselect.
259
+ """
260
+ self._internal.deselect_rows(record_ids)
261
+
262
+ def clear_selection(self) -> None:
263
+ """Clear the selection.
264
+
265
+ Users can also press `Escape` over the table to clear it — useful in
266
+ single-select mode, where clicking cannot return to an empty selection.
267
+ """
268
+ self._internal.deselect_all()
269
+
270
+ @property
271
+ def selection(self) -> dict | list[dict] | None:
272
+ """The selected row(s) — the data bag.
273
+
274
+ In `'single'` mode, the selected record `dict` (or `None` when nothing
275
+ is selected). In `'multi'` mode, a `list` of record dicts (empty when
276
+ nothing is selected). Each record carries its non-displayed fields too,
277
+ indexed by key like any record. Read-only.
278
+ """
279
+ rows = self._internal.selected_rows
280
+ if self._selection_mode == "multi":
281
+ return rows
282
+ return rows[0] if rows else None
283
+
284
+ # ----- Scroll -----
285
+
286
+ def scroll_to_row(self, record_id: Any) -> None:
287
+ """Scroll the table so the row with the given `id` is visible.
288
+
289
+ Args:
290
+ record_id: Record id of the row.
291
+ """
292
+ self._internal.scroll_to_row(record_id)
293
+
294
+ # ----- Paging -----
295
+
296
+ def go_to_page(self, index: int) -> None:
297
+ """Show the page at the given zero-based index.
298
+
299
+ Args:
300
+ index: Zero-based page index.
301
+ """
302
+ self._internal.go_to_page(index)
303
+
304
+ def next_page(self) -> None:
305
+ """Advance to the next page (no-op on the last page)."""
306
+ self._internal.next_page()
307
+
308
+ def prev_page(self) -> None:
309
+ """Go to the previous page (no-op on the first page)."""
310
+ self._internal.previous_page()
311
+
312
+ @property
313
+ def current_page(self) -> int:
314
+ """Zero-based index of the page currently shown."""
315
+ return self._internal.current_page
316
+
317
+ @property
318
+ def page_count(self) -> int:
319
+ """Total number of pages for the current filter/search."""
320
+ return self._internal.page_count
321
+
322
+ # ----- Export -----
323
+
324
+ def to_rows(self, scope: ExportScope = "all", *, max_rows: int | None = 100_000) -> list[dict]:
325
+ """Return the data as a list of record dicts (materialized — small data).
326
+
327
+ Loads every matching row into memory. For large datasets use
328
+ `iter_rows()` (streaming) or `export_file()` instead.
329
+
330
+ Args:
331
+ scope: Which rows — `'all'` (the filtered set), `'page'` (current
332
+ page), or `'selection'` (selected rows).
333
+ max_rows: Raise if the row count exceeds this. Pass `None` to lift
334
+ the cap (at your own memory risk).
335
+ """
336
+ return self._internal.to_rows(scope, max_rows=max_rows)
337
+
338
+ def to_csv(self, scope: ExportScope = "all", *, max_rows: int | None = 100_000) -> str:
339
+ """Return the data as a CSV string (materialized — small data).
340
+
341
+ For large datasets use `export_file()`, which streams to disk.
342
+
343
+ Args:
344
+ scope: `'all'`, `'page'`, or `'selection'`.
345
+ max_rows: Raise if the row count exceeds this. `None` lifts the cap.
346
+ """
347
+ return self._internal.to_csv(scope, max_rows=max_rows)
348
+
349
+ def iter_rows(self, scope: ExportScope = "all", chunk_size: int = 1000):
350
+ """Lazily yield record dicts one at a time, paging the data source.
351
+
352
+ Memory stays flat regardless of size — suitable for very large exports.
353
+
354
+ Args:
355
+ scope: `'all'`, `'page'`, or `'selection'`.
356
+ chunk_size: Rows read from the source per batch.
357
+ """
358
+ return self._internal.iter_rows(scope, chunk_size)
359
+
360
+ def export_file(
361
+ self,
362
+ path: str,
363
+ scope: ExportScope = "all",
364
+ *,
365
+ format: ExportFormat | None = None,
366
+ chunk_size: int = 1000,
367
+ on_progress: Callable[[int, int], Any] | None = None,
368
+ ) -> int:
369
+ """Stream the data to `path`, paging so memory stays flat.
370
+
371
+ The format is inferred from the path extension (`.csv`, `.tsv`, `.xlsx`)
372
+ unless `format` is given. `.xlsx` requires `bootstack[excel]`.
373
+
374
+ Args:
375
+ path: Destination file path.
376
+ scope: `'all'`, `'page'`, or `'selection'`.
377
+ format: Override the format — `'csv'`, `'tsv'`, or `'xlsx'`.
378
+ chunk_size: Rows read/written per batch.
379
+ on_progress: Called as `on_progress(written, total)` after each batch.
380
+
381
+ Returns:
382
+ The number of rows written.
383
+ """
384
+ return self._internal.export_file(
385
+ path, scope, format=format, chunk_size=chunk_size, on_progress=on_progress
386
+ )
387
+
388
+ def export_file_async(
389
+ self,
390
+ path: str,
391
+ scope: ExportScope = "all",
392
+ *,
393
+ format: ExportFormat | None = None,
394
+ chunk_size: int = 1000,
395
+ on_progress: Callable[[int, int], Any] | None = None,
396
+ on_done: Callable[[str, int, Any], Any] | None = None,
397
+ ) -> ExportJob:
398
+ """Stream the data to `path` without blocking the UI; return a job.
399
+
400
+ Writes one chunk per event-loop idle tick, so the UI stays responsive
401
+ and the export can be cancelled mid-run via the returned job's
402
+ `cancel()`. A cancelled or failed export removes the partial file.
403
+
404
+ Args:
405
+ path: Destination file path.
406
+ scope: `'all'`, `'page'`, or `'selection'`.
407
+ format: Override the format — `'csv'`, `'tsv'`, or `'xlsx'`.
408
+ chunk_size: Rows read/written per idle tick.
409
+ on_progress: Called as `on_progress(written, total)` after each chunk.
410
+ on_done: Called as `on_done(status, written, error)` at the end, with
411
+ `status` one of `'completed'`, `'cancelled'`, or `'error'`.
412
+
413
+ Returns:
414
+ A job handle with a `cancel()` method.
415
+ """
416
+ return self._internal.export_file_async(
417
+ path, scope, format=format, chunk_size=chunk_size,
418
+ on_progress=on_progress, on_done=on_done,
419
+ )
420
+
421
+ # ----- Search -----
422
+
423
+ def get_search(self) -> str:
424
+ """Return the active free-text search term."""
425
+ return self._internal.get_search()
426
+
427
+ def set_search(self, text: str) -> None:
428
+ """Set the free-text search term.
429
+
430
+ Searches across all columns. Leaves any active column filters intact.
431
+
432
+ Args:
433
+ text: The search term. Pass an empty string to clear the search.
434
+ """
435
+ self._internal.set_search(text)
436
+
437
+ def clear_search(self) -> None:
438
+ """Clear the free-text search term (leaves column filters intact)."""
439
+ self._internal.clear_search()
440
+
441
+ # ----- Filter / sort / group -----
442
+
443
+ def get_filters(self) -> dict[str, list]:
444
+ """Return the active column filters as `{column_key: allowed_values}`."""
445
+ return self._internal.get_filters()
446
+
447
+ def clear_filters(self) -> None:
448
+ """Remove all active column filters (leaves the search term intact)."""
449
+ self._internal.clear_filters()
450
+
451
+ def set_filter(self, column: str, values: list | None = None) -> None:
452
+ """Filter a column to the given values, or clear it when `values` is None.
453
+
454
+ Composes with the search term and other column filters.
455
+
456
+ Args:
457
+ column: Column key to filter.
458
+ values: Allowed values; `None` clears this column's filter.
459
+ """
460
+ self._internal.set_filter(column, values)
461
+
462
+ def get_sorting(self) -> dict[str, bool]:
463
+ """Return the current sort state as `{column_key: ascending}` dict."""
464
+ return self._internal.get_sorting()
465
+
466
+ def clear_sorting(self) -> None:
467
+ """Remove all active sort orders."""
468
+ self._internal.clear_sorting()
469
+
470
+ def sort_by(self, column: str, ascending: bool = True) -> None:
471
+ """Sort the table by a column.
472
+
473
+ Args:
474
+ column: Column key to sort by.
475
+ ascending: Sort ascending (default) or descending.
476
+ """
477
+ self._internal.set_sorting(column, ascending)
478
+
479
+ def get_grouping(self) -> str | None:
480
+ """Return the currently grouped column key, or `None`."""
481
+ return self._internal.get_grouping()
482
+
483
+ def clear_grouping(self) -> None:
484
+ """Remove the active grouping."""
485
+ self._internal.clear_grouping()
486
+
487
+ def group_by(self, column: str) -> None:
488
+ """Group rows by a column (collapsible group headers).
489
+
490
+ Args:
491
+ column: Column key to group by.
492
+ """
493
+ self._internal.set_grouping(column)
494
+
495
+ def expand_all(self) -> None:
496
+ """Expand all groups."""
497
+ self._internal.expand_all()
498
+
499
+ def collapse_all(self) -> None:
500
+ """Collapse all groups."""
501
+ self._internal.collapse_all()
502
+
503
+ # ----- Properties -----
504
+
505
+ @property
506
+ def data_source(self) -> DataSourceProtocol:
507
+ """The underlying data source instance."""
508
+ return self._internal._datasource
509
+
510
+ # ----- Events -----
511
+
512
+ @overload
513
+ def on_select(self) -> Stream: ...
514
+ @overload
515
+ def on_select(self, handler: Callable[[SelectionEvent], Any]) -> Subscription: ...
516
+ def on_select(self, handler: Callable[[SelectionEvent], Any] | None = None) -> Stream | Subscription:
517
+ """Fired when the set of selected rows changes.
518
+
519
+ Args:
520
+ handler: Called with a :class:`~bootstack.events.SelectionEvent`
521
+ (`records`, `ids`). Omit to get a composable
522
+ :class:`~bootstack.streams.Stream` instead.
523
+
524
+ Returns:
525
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
526
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
527
+ """
528
+ return self.on("select", handler)
529
+
530
+ @overload
531
+ def on_row_click(self) -> Stream: ...
532
+ @overload
533
+ def on_row_click(self, handler: Callable[[RowEvent], Any]) -> Subscription: ...
534
+ def on_row_click(self, handler: Callable[[RowEvent], Any] | None = None) -> Stream | Subscription:
535
+ """Fired when a row is clicked.
536
+
537
+ Args:
538
+ handler: Called with a :class:`~bootstack.events.RowEvent` (`record`,
539
+ `id`). Omit to get a composable
540
+ :class:`~bootstack.streams.Stream` instead.
541
+
542
+ Returns:
543
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
544
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
545
+ """
546
+ return self.on("row_click", handler)
547
+
548
+ @overload
549
+ def on_row_double_click(self) -> Stream: ...
550
+ @overload
551
+ def on_row_double_click(self, handler: Callable[[RowEvent], Any]) -> Subscription: ...
552
+ def on_row_double_click(self, handler: Callable[[RowEvent], Any] | None = None) -> Stream | Subscription:
553
+ """Fired when a row is double-clicked.
554
+
555
+ Args:
556
+ handler: Called with a :class:`~bootstack.events.RowEvent` (`record`,
557
+ `id`). Omit to get a composable
558
+ :class:`~bootstack.streams.Stream` instead.
559
+
560
+ Returns:
561
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
562
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
563
+ """
564
+ return self.on("row_double_click", handler)
565
+
566
+ @overload
567
+ def on_row_right_click(self) -> Stream: ...
568
+ @overload
569
+ def on_row_right_click(self, handler: Callable[[RowEvent], Any]) -> Subscription: ...
570
+ def on_row_right_click(self, handler: Callable[[RowEvent], Any] | None = None) -> Stream | Subscription:
571
+ """Fired when a row is right-clicked.
572
+
573
+ Args:
574
+ handler: Called with a :class:`~bootstack.events.RowEvent` (`record`,
575
+ `id`). Omit to get a composable
576
+ :class:`~bootstack.streams.Stream` instead.
577
+
578
+ Returns:
579
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
580
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
581
+ """
582
+ return self.on("row_right_click", handler)
583
+
584
+ @overload
585
+ def on_rows_insert(self) -> Stream: ...
586
+ @overload
587
+ def on_rows_insert(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
588
+ def on_rows_insert(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
589
+ """Fired after rows are inserted.
590
+
591
+ Args:
592
+ handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
593
+ the inserted `records`. Omit to get a composable
594
+ :class:`~bootstack.streams.Stream` instead.
595
+
596
+ Returns:
597
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
598
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
599
+ """
600
+ return self.on("rows_insert", handler)
601
+
602
+ @overload
603
+ def on_rows_update(self) -> Stream: ...
604
+ @overload
605
+ def on_rows_update(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
606
+ def on_rows_update(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
607
+ """Fired after rows are updated.
608
+
609
+ Args:
610
+ handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
611
+ the updated `records`. Omit to get a composable
612
+ :class:`~bootstack.streams.Stream` instead.
613
+
614
+ Returns:
615
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
616
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
617
+ """
618
+ return self.on("rows_update", handler)
619
+
620
+ @overload
621
+ def on_rows_delete(self) -> Stream: ...
622
+ @overload
623
+ def on_rows_delete(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
624
+ def on_rows_delete(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
625
+ """Fired after rows are deleted.
626
+
627
+ Args:
628
+ handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
629
+ the deleted `records`. Omit to get a composable
630
+ :class:`~bootstack.streams.Stream` instead.
631
+
632
+ Returns:
633
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
634
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
635
+ """
636
+ return self.on("rows_delete", handler)
637
+
638
+ @overload
639
+ def on_rows_move(self) -> Stream: ...
640
+ @overload
641
+ def on_rows_move(self, handler: Callable[[RowsEvent], Any]) -> Subscription: ...
642
+ def on_rows_move(self, handler: Callable[[RowsEvent], Any] | None = None) -> Stream | Subscription:
643
+ """Fired after rows are reordered.
644
+
645
+ Args:
646
+ handler: Called with a :class:`~bootstack.events.RowsEvent` carrying
647
+ the moved `records`. Omit to get a composable
648
+ :class:`~bootstack.streams.Stream` instead.
649
+
650
+ Returns:
651
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
652
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
653
+ """
654
+ return self.on("rows_move", handler)
655
+
656
+ @overload
657
+ def on_export(self) -> Stream: ...
658
+ @overload
659
+ def on_export(self, handler: Callable[[ExportEvent], Any]) -> Subscription: ...
660
+ def on_export(self, handler: Callable[[ExportEvent], Any] | None = None) -> Stream | Subscription:
661
+ """Fired after the data is exported (copied or saved).
662
+
663
+ Args:
664
+ handler: Called with an :class:`~bootstack.events.ExportEvent`
665
+ (`count`, `target` — `'clipboard'` or `'file'` — `format`, and
666
+ `path`). Omit to get a composable
667
+ :class:`~bootstack.streams.Stream` instead.
668
+
669
+ Returns:
670
+ A cancellable :class:`~bootstack.events.Subscription` when a handler
671
+ is given, otherwise a :class:`~bootstack.streams.Stream`.
672
+ """
673
+ return self.on("export", handler)
674
+
675
+
676
+ _DATATABLE_EVENTS: dict[str, str] = {
677
+ "select": "<<SelectionChange>>",
678
+ "row_click": "<<RowClick>>",
679
+ "row_double_click": "<<RowDoubleClick>>",
680
+ "row_right_click": "<<RowRightClick>>",
681
+ "rows_insert": "<<RowsInsert>>",
682
+ "rows_update": "<<RowsUpdate>>",
683
+ "rows_delete": "<<RowsDelete>>",
684
+ "rows_move": "<<RowsMove>>",
685
+ "export": "<<Export>>",
686
+ }
687
+
688
+ register_widget_events(DataTable, _DATATABLE_EVENTS)