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,408 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import overload, Any, Callable, TYPE_CHECKING
4
+
5
+ from bootstack.widgets._impl.composites.slider.slider import Slider as _InternalSlider
6
+ from bootstack.widgets._impl.composites.slider.rangeslider import RangeSlider as _InternalRangeSlider
7
+ from bootstack.widgets._core.base import PublicWidgetBase
8
+ from bootstack.widgets._core.events import register_widget_events
9
+ from bootstack.events import RangeSliderCommitEvent, RangeSliderEvent, SliderCommitEvent, SliderEvent, Subscription
10
+ from bootstack.streams import Stream
11
+ from bootstack.widgets.types import AccentToken, Event, Orient
12
+
13
+ if TYPE_CHECKING:
14
+ from bootstack.signals import Signal
15
+
16
+ _SLIDER_EVENTS: dict[str, str] = {
17
+ "change": "<<Change>>",
18
+ "commit": "<<Commit>>",
19
+ }
20
+
21
+
22
+ class Slider(PublicWidgetBase):
23
+ """A single-handle slider for selecting a numeric value within a range.
24
+
25
+ The initial value is the first positional argument. All options are
26
+ keyword-only.
27
+
28
+ Args:
29
+ value: Initial value. Defaults to `0`.
30
+ min_value: Minimum of the range. Defaults to `0`.
31
+ max_value: Maximum of the range. Defaults to `100`.
32
+ signal: Reactive `Signal[float]` linked to the slider value. The
33
+ slider and signal stay in sync automatically.
34
+ orient: Track orientation. Default `'horizontal'`.
35
+ show_value: If `True`, shows a floating badge with the current
36
+ value above the handle. Defaults to `False`.
37
+ show_minmax: If `True`, shows min/max labels at the track ends.
38
+ Defaults to `False`.
39
+ tick_step: Spacing between major tick marks. `None` disables
40
+ ticks entirely.
41
+ minor_ticks: Number of minor tick marks between each major tick.
42
+ Defaults to `0`.
43
+ tick_labels: If `True`, shows numeric labels at major tick
44
+ positions. Defaults to `True`.
45
+ tick_format: Python format string applied to tick and badge labels.
46
+ Defaults to `'{:.0f}'`.
47
+ step: Snap increment. When set, click/drag values snap to multiples of
48
+ `step` (measured from `min_value`) and the arrow keys move by `step`.
49
+ `None` (the default) leaves the value continuous. Independent of
50
+ `tick_step`, which only places tick marks.
51
+ accent: Accent token controlling the fill, handle, and focus ring
52
+ color. Defaults to `'primary'`.
53
+ disabled: If `True`, slider is non-interactive. Defaults to
54
+ `False`.
55
+ parent: Explicit parent widget. If omitted, the current
56
+ context-stack container is used.
57
+ **kwargs: Layout placement options applied by the parent container —
58
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
59
+ See :doc:`/tasks/layout`.
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ value: float = 0,
65
+ *,
66
+ min_value: float = 0,
67
+ max_value: float = 100,
68
+ signal: "Signal[float] | None" = None,
69
+ orient: Orient = "horizontal",
70
+ show_value: bool = False,
71
+ show_minmax: bool = False,
72
+ tick_step: float | None = None,
73
+ minor_ticks: int = 0,
74
+ tick_labels: bool = True,
75
+ tick_format: str = "{:.0f}",
76
+ step: float | None = None,
77
+ accent: AccentToken | str | None = None,
78
+ disabled: bool = False,
79
+ parent: Any = None,
80
+ **kwargs: Any,
81
+ ) -> None:
82
+ self._parent = self._resolve_parent(parent)
83
+ layout_kw = self._split_layout_kwargs(kwargs)
84
+ tk_master = self._parent._child_master() if self._parent else None
85
+
86
+ internal_kwargs: dict[str, Any] = {
87
+ "value": value,
88
+ "minvalue": min_value,
89
+ "maxvalue": max_value,
90
+ "orient": orient,
91
+ "show_value": show_value,
92
+ "show_minmax": show_minmax,
93
+ "tick_interval": tick_step,
94
+ "minor_ticks": minor_ticks,
95
+ "tick_labels": tick_labels,
96
+ "tick_format": tick_format,
97
+ "step": step,
98
+ }
99
+ if signal is not None:
100
+ internal_kwargs["signal"] = signal
101
+ if accent is not None:
102
+ internal_kwargs["accent"] = accent
103
+ if disabled:
104
+ internal_kwargs["state"] = "disabled"
105
+
106
+ self._internal = _InternalSlider(tk_master, **internal_kwargs)
107
+ self._attach_to_parent(layout_kw)
108
+
109
+ # ----- Properties -----
110
+
111
+ @property
112
+ def value(self) -> float:
113
+ """The current slider value."""
114
+ return self._internal.value
115
+
116
+ @value.setter
117
+ def value(self, v: float) -> None:
118
+ # Route through set() so the value is clamped to [min, max] — a bare
119
+ # signal write would store an out-of-range value the getter reads back.
120
+ self._internal.set(float(v))
121
+
122
+ @property
123
+ def signal(self) -> "Signal[float]":
124
+ """The reactive `Signal[float]` linked to this slider."""
125
+ return self._internal.signal
126
+
127
+ @property
128
+ def min_value(self) -> float:
129
+ """The minimum value of the slider range."""
130
+ return self._internal._minvalue
131
+
132
+ @min_value.setter
133
+ def min_value(self, v: float) -> None:
134
+ self._internal.configure(minvalue=float(v))
135
+
136
+ @property
137
+ def max_value(self) -> float:
138
+ """The maximum value of the slider range."""
139
+ return self._internal._maxvalue
140
+
141
+ @max_value.setter
142
+ def max_value(self, v: float) -> None:
143
+ self._internal.configure(maxvalue=float(v))
144
+
145
+ @property
146
+ def step(self) -> float | None:
147
+ """Snap increment, or `None` when the value is continuous."""
148
+ return self._internal._step_size
149
+
150
+ @step.setter
151
+ def step(self, v: float | None) -> None:
152
+ self._internal.configure(step=v)
153
+
154
+ @property
155
+ def disabled(self) -> bool:
156
+ """Whether the slider is non-interactive."""
157
+ return self._internal._state == "disabled"
158
+
159
+ @disabled.setter
160
+ def disabled(self, v: bool) -> None:
161
+ self._internal.configure(state="disabled" if v else "normal")
162
+
163
+ # ----- Event shorthands -----
164
+
165
+ @overload
166
+ def on_change(self) -> Stream: ...
167
+ @overload
168
+ def on_change(self, handler: Callable[[SliderEvent], Any]) -> Subscription: ...
169
+ def on_change(self, handler: Callable[[SliderEvent], Any] | None = None) -> Stream | Subscription:
170
+ """Register a callback fired continuously as the handle moves.
171
+
172
+ The current value is available via `slider.value` inside the handler.
173
+
174
+ Args:
175
+ handler: Called with a :class:`~bootstack.events.SliderEvent`. Omit to
176
+ get a composable :class:`~bootstack.streams.Stream` instead.
177
+
178
+ Returns:
179
+ A cancellable :class:`~bootstack.events.Subscription` when a
180
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
181
+ """
182
+ return self.on("change", handler)
183
+
184
+ @overload
185
+ def on_commit(self) -> Stream: ...
186
+ @overload
187
+ def on_commit(self, handler: Callable[[SliderCommitEvent], Any]) -> Subscription: ...
188
+ def on_commit(self, handler: Callable[[SliderCommitEvent], Any] | None = None) -> Stream | Subscription:
189
+ """Register a callback fired when the handle is released.
190
+
191
+ Args:
192
+ handler: Called with a :class:`~bootstack.events.SliderCommitEvent`. Omit to
193
+ get a composable :class:`~bootstack.streams.Stream` instead.
194
+
195
+ Returns:
196
+ A cancellable :class:`~bootstack.events.Subscription` when a
197
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
198
+ """
199
+ return self.on("commit", handler)
200
+
201
+
202
+ register_widget_events(Slider, _SLIDER_EVENTS)
203
+
204
+
205
+ class RangeSlider(PublicWidgetBase):
206
+ """A two-handle slider for selecting a low/high value range.
207
+
208
+ The initial handle positions are the first two positional arguments.
209
+ All options are keyword-only.
210
+
211
+ Args:
212
+ low_value: Initial low-handle value. Defaults to `0`.
213
+ high_value: Initial high-handle value. Defaults to `100`.
214
+ min_value: Minimum of the range. Defaults to `0`.
215
+ max_value: Maximum of the range. Defaults to `100`.
216
+ low_signal: Reactive `Signal[float]` linked to the low handle.
217
+ high_signal: Reactive `Signal[float]` linked to the high handle.
218
+ orient: Track orientation. Default `'horizontal'`.
219
+ show_value: If `True`, shows floating badges on both handles.
220
+ Defaults to `False`.
221
+ tick_step: Spacing between major tick marks. `None` disables
222
+ ticks entirely.
223
+ minor_ticks: Number of minor tick marks between each major tick.
224
+ Defaults to `0`.
225
+ tick_labels: If `True`, shows numeric labels at major tick
226
+ positions. Defaults to `True`.
227
+ tick_format: Python format string applied to tick and badge labels.
228
+ Defaults to `'{:.0f}'`.
229
+ step: Snap increment. When set, handle values snap to multiples of
230
+ `step` (measured from `min_value`) and the arrow keys move by `step`.
231
+ `None` (the default) leaves the values continuous. Independent of
232
+ `tick_step`, which only places tick marks.
233
+ accent: Accent token controlling the fill, handles, and focus ring
234
+ color. Defaults to `'primary'`.
235
+ disabled: If `True`, slider is non-interactive. Defaults to
236
+ `False`.
237
+ parent: Explicit parent widget. If omitted, the current
238
+ context-stack container is used.
239
+ **kwargs: Layout placement options applied by the parent container —
240
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
241
+ See :doc:`/tasks/layout`.
242
+ """
243
+
244
+ def __init__(
245
+ self,
246
+ low_value: float = 0,
247
+ high_value: float = 100,
248
+ *,
249
+ min_value: float = 0,
250
+ max_value: float = 100,
251
+ low_signal: "Signal[float] | None" = None,
252
+ high_signal: "Signal[float] | None" = None,
253
+ orient: Orient = "horizontal",
254
+ show_value: bool = False,
255
+ tick_step: float | None = None,
256
+ minor_ticks: int = 0,
257
+ tick_labels: bool = True,
258
+ tick_format: str = "{:.0f}",
259
+ step: float | None = None,
260
+ accent: AccentToken | str | None = None,
261
+ disabled: bool = False,
262
+ parent: Any = None,
263
+ **kwargs: Any,
264
+ ) -> None:
265
+ self._parent = self._resolve_parent(parent)
266
+ layout_kw = self._split_layout_kwargs(kwargs)
267
+ tk_master = self._parent._child_master() if self._parent else None
268
+
269
+ internal_kwargs: dict[str, Any] = {
270
+ "lovalue": low_value,
271
+ "hivalue": high_value,
272
+ "minvalue": min_value,
273
+ "maxvalue": max_value,
274
+ "orient": orient,
275
+ "show_value": show_value,
276
+ "tick_interval": tick_step,
277
+ "minor_ticks": minor_ticks,
278
+ "tick_labels": tick_labels,
279
+ "tick_format": tick_format,
280
+ "step": step,
281
+ }
282
+ if low_signal is not None:
283
+ internal_kwargs["lo_signal"] = low_signal
284
+ if high_signal is not None:
285
+ internal_kwargs["hi_signal"] = high_signal
286
+ if accent is not None:
287
+ internal_kwargs["accent"] = accent
288
+ if disabled:
289
+ internal_kwargs["state"] = "disabled"
290
+
291
+ self._internal = _InternalRangeSlider(tk_master, **internal_kwargs)
292
+ self._attach_to_parent(layout_kw)
293
+
294
+ # ----- Properties -----
295
+
296
+ @property
297
+ def low_value(self) -> float:
298
+ """The current low-handle value."""
299
+ return self._internal.lovalue
300
+
301
+ @low_value.setter
302
+ def low_value(self, v: float) -> None:
303
+ # Route through set_lo so the value is clamped to [min, high] — a bare
304
+ # signal write would let the low handle cross above the high handle.
305
+ self._internal.set_lo(float(v))
306
+
307
+ @property
308
+ def high_value(self) -> float:
309
+ """The current high-handle value."""
310
+ return self._internal.hivalue
311
+
312
+ @high_value.setter
313
+ def high_value(self, v: float) -> None:
314
+ # Route through set_hi so the value is clamped to [low, max].
315
+ self._internal.set_hi(float(v))
316
+
317
+ @property
318
+ def value(self) -> tuple[float, float]:
319
+ """Current `(low, high)` values as a tuple."""
320
+ return (self._internal.lovalue, self._internal.hivalue)
321
+
322
+ @property
323
+ def low_signal(self) -> "Signal[float]":
324
+ """The reactive `Signal[float]` linked to the low handle."""
325
+ return self._internal._lo_signal
326
+
327
+ @property
328
+ def high_signal(self) -> "Signal[float]":
329
+ """The reactive `Signal[float]` linked to the high handle."""
330
+ return self._internal._hi_signal
331
+
332
+ @property
333
+ def min_value(self) -> float:
334
+ """The minimum value of the slider range."""
335
+ return self._internal._minvalue
336
+
337
+ @min_value.setter
338
+ def min_value(self, v: float) -> None:
339
+ self._internal.configure(minvalue=float(v))
340
+
341
+ @property
342
+ def max_value(self) -> float:
343
+ """The maximum value of the slider range."""
344
+ return self._internal._maxvalue
345
+
346
+ @max_value.setter
347
+ def max_value(self, v: float) -> None:
348
+ self._internal.configure(maxvalue=float(v))
349
+
350
+ @property
351
+ def step(self) -> float | None:
352
+ """Snap increment, or `None` when the values are continuous."""
353
+ return self._internal._step_size
354
+
355
+ @step.setter
356
+ def step(self, v: float | None) -> None:
357
+ self._internal.configure(step=v)
358
+
359
+ @property
360
+ def disabled(self) -> bool:
361
+ """Whether the slider is non-interactive."""
362
+ return self._internal._state == "disabled"
363
+
364
+ @disabled.setter
365
+ def disabled(self, v: bool) -> None:
366
+ self._internal.configure(state="disabled" if v else "normal")
367
+
368
+ # ----- Event shorthands -----
369
+
370
+ @overload
371
+ def on_change(self) -> Stream: ...
372
+ @overload
373
+ def on_change(self, handler: Callable[[RangeSliderEvent], Any]) -> Subscription: ...
374
+ def on_change(self, handler: Callable[[RangeSliderEvent], Any] | None = None) -> Stream | Subscription:
375
+ """Register a callback fired continuously as either handle moves.
376
+
377
+ The current values are available via `slider.low_value` and
378
+ `slider.high_value` inside the handler.
379
+
380
+ Args:
381
+ handler: Called with a :class:`~bootstack.events.RangeSliderEvent`. Omit to
382
+ get a composable :class:`~bootstack.streams.Stream` instead.
383
+
384
+ Returns:
385
+ A cancellable :class:`~bootstack.events.Subscription` when a
386
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
387
+ """
388
+ return self.on("change", handler)
389
+
390
+ @overload
391
+ def on_commit(self) -> Stream: ...
392
+ @overload
393
+ def on_commit(self, handler: Callable[[RangeSliderCommitEvent], Any]) -> Subscription: ...
394
+ def on_commit(self, handler: Callable[[RangeSliderCommitEvent], Any] | None = None) -> Stream | Subscription:
395
+ """Register a callback fired when a handle is released.
396
+
397
+ Args:
398
+ handler: Called with a :class:`~bootstack.events.RangeSliderCommitEvent`. Omit to
399
+ get a composable :class:`~bootstack.streams.Stream` instead.
400
+
401
+ Returns:
402
+ A cancellable :class:`~bootstack.events.Subscription` when a
403
+ handler is given, otherwise a :class:`~bootstack.streams.Stream`.
404
+ """
405
+ return self.on("commit", handler)
406
+
407
+
408
+ register_widget_events(RangeSlider, _SLIDER_EVENTS)
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import overload, Any, Callable, TYPE_CHECKING
4
+
5
+ from bootstack.widgets._impl.primitives.spinbox import Spinbox as _InternalSpinbox
6
+ from bootstack.widgets._core.base import PublicWidgetBase
7
+ from bootstack.widgets._core.events import register_widget_events
8
+ from bootstack.events import ChangeEvent, Subscription
9
+ from bootstack.streams import Stream
10
+ from bootstack.widgets.types import Event, AccentToken, WidgetDensity
11
+
12
+ if TYPE_CHECKING:
13
+ from bootstack.signals import Signal
14
+
15
+ _SPINBOX_EVENTS: dict[str, str] = {
16
+ "change": "<KeyRelease>",
17
+ "submit": "<Return>",
18
+ }
19
+
20
+
21
+ class Spinbox(PublicWidgetBase):
22
+ """A spin-box that cycles through a fixed list or numeric range.
23
+
24
+ Pass either `options=` for a discrete list, or `min_value=`/`max_value=`
25
+ for a numeric range. `options=` takes precedence if both are provided.
26
+
27
+ Args:
28
+ value: Initial display value.
29
+ options: Fixed list of values to cycle through.
30
+ min_value: Minimum numeric value (used when `options=` is not set).
31
+ max_value: Maximum numeric value.
32
+ step: Increment between values. Default `1`.
33
+ wrap: If True, wraps from max back to min and vice versa.
34
+ value_format: Display format string (e.g. `'%.2f'`).
35
+ textsignal: Reactive `Signal` linked to the displayed text.
36
+ width: Width in character cells.
37
+ disabled: If True, widget is non-interactive.
38
+ density: Padding density.
39
+ accent: Accent token applied to the focus ring.
40
+ parent: Override the context-stack parent.
41
+ **kwargs: Layout placement options applied by the parent container —
42
+ `fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
43
+ See :doc:`/tasks/layout`.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ value: Any = "",
49
+ *,
50
+ options: list[Any] | None = None,
51
+ min_value: float | None = None,
52
+ max_value: float | None = None,
53
+ step: float = 1,
54
+ wrap: bool = False,
55
+ value_format: str | None = None,
56
+ textsignal: "Signal | None" = None,
57
+ width: int | None = None,
58
+ disabled: bool = False,
59
+ density: WidgetDensity | None = None,
60
+ accent: AccentToken | str | None = None,
61
+ parent: Any = None,
62
+ **kwargs: Any,
63
+ ) -> None:
64
+ self._parent = self._resolve_parent(parent)
65
+ layout_kw = self._split_layout_kwargs(kwargs)
66
+
67
+ tk_master = self._parent._child_master() if self._parent else None
68
+
69
+ internal_kwargs: dict[str, Any] = {"wrap": wrap}
70
+ if options is not None:
71
+ internal_kwargs["values"] = [str(o) for o in options]
72
+ elif min_value is not None or max_value is not None:
73
+ if min_value is not None:
74
+ internal_kwargs["from_"] = min_value
75
+ if max_value is not None:
76
+ internal_kwargs["to"] = max_value
77
+ internal_kwargs["increment"] = step
78
+ if value_format is not None:
79
+ internal_kwargs["format"] = value_format
80
+ if textsignal is not None:
81
+ internal_kwargs["textsignal"] = textsignal
82
+ if width is not None:
83
+ internal_kwargs["width"] = width
84
+ if disabled:
85
+ internal_kwargs["state"] = "disabled"
86
+ if density is not None:
87
+ internal_kwargs["density"] = density
88
+ if accent is not None:
89
+ internal_kwargs["accent"] = accent
90
+ # Clean up any None values accidentally set. (**kwargs is layout-only —
91
+ # already split out above — so it is NOT forwarded to the internal widget.)
92
+ internal_kwargs = {k: v for k, v in internal_kwargs.items() if v is not None}
93
+
94
+ self._internal = _InternalSpinbox(tk_master, **internal_kwargs)
95
+
96
+ # Seed the display value after construction in all cases.
97
+ if value != "":
98
+ self._internal.set(str(value))
99
+
100
+ self._attach_to_parent(layout_kw)
101
+
102
+ # ----- Properties -----
103
+
104
+ @property
105
+ def value(self) -> str:
106
+ return self._internal.get()
107
+
108
+ @value.setter
109
+ def value(self, v: Any) -> None:
110
+ self._internal.set(str(v))
111
+
112
+ @property
113
+ def disabled(self) -> bool:
114
+ return str(self._internal.cget("state")) == "disabled"
115
+
116
+ @disabled.setter
117
+ def disabled(self, v: bool) -> None:
118
+ self._internal.configure(state="disabled" if v else "normal")
119
+
120
+ # ----- Event shorthands -----
121
+
122
+ @overload
123
+ def on_change(self) -> Stream: ...
124
+ @overload
125
+ def on_change(self, handler: Callable[[ChangeEvent], Any]) -> Subscription: ...
126
+ def on_change(self, handler: Callable[[ChangeEvent], Any] | None = None) -> Stream | Subscription:
127
+ """Register a callback fired on each keystroke or spin-button click.
128
+
129
+ Returns:
130
+ Subscription — call `.cancel()` to unsubscribe.
131
+ """
132
+ return self.on("change", handler)
133
+
134
+ @overload
135
+ def on_submit(self) -> Stream: ...
136
+ @overload
137
+ def on_submit(self, handler: Callable[[Event], Any]) -> Subscription: ...
138
+ def on_submit(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
139
+ """Register a callback fired on Enter key.
140
+
141
+ Returns:
142
+ Subscription — call `.cancel()` to unsubscribe.
143
+ """
144
+ return self.on("submit", handler)
145
+
146
+
147
+ register_widget_events(Spinbox, _SPINBOX_EVENTS)