bootstack 0.1.0a1__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 (419) hide show
  1. bootstack/__init__.py +249 -0
  2. bootstack/__main__.py +5 -0
  3. bootstack/api/__init__.py +127 -0
  4. bootstack/api/app.py +30 -0
  5. bootstack/api/constants.py +3 -0
  6. bootstack/api/data.py +23 -0
  7. bootstack/api/dialogs.py +44 -0
  8. bootstack/api/i18n.py +17 -0
  9. bootstack/api/localization.py +16 -0
  10. bootstack/api/menu.py +7 -0
  11. bootstack/api/style.py +23 -0
  12. bootstack/api/utils.py +24 -0
  13. bootstack/api/widgets.py +137 -0
  14. bootstack/assets/__init__.py +24 -0
  15. bootstack/assets/bootstack-transparent.png +0 -0
  16. bootstack/assets/bootstack.ico +0 -0
  17. bootstack/assets/bootstack.png +0 -0
  18. bootstack/assets/elements/__init__.py +0 -0
  19. bootstack/assets/elements/badge-pill.png +0 -0
  20. bootstack/assets/elements/badge-square.png +0 -0
  21. bootstack/assets/elements/border.png +0 -0
  22. bootstack/assets/elements/button-compact.png +0 -0
  23. bootstack/assets/elements/button-default.png +0 -0
  24. bootstack/assets/elements/button-group-horizontal-after-compact.png +0 -0
  25. bootstack/assets/elements/button-group-horizontal-after-default.png +0 -0
  26. bootstack/assets/elements/button-group-horizontal-before-compact.png +0 -0
  27. bootstack/assets/elements/button-group-horizontal-before-default.png +0 -0
  28. bootstack/assets/elements/button-group-horizontal-center-compact.png +0 -0
  29. bootstack/assets/elements/button-group-horizontal-center-default.png +0 -0
  30. bootstack/assets/elements/button-group-vertical-after-compact.png +0 -0
  31. bootstack/assets/elements/button-group-vertical-after-default.png +0 -0
  32. bootstack/assets/elements/button-group-vertical-before-compact.png +0 -0
  33. bootstack/assets/elements/button-group-vertical-before-default.png +0 -0
  34. bootstack/assets/elements/button-group-vertical-center-compact.png +0 -0
  35. bootstack/assets/elements/button-group-vertical-center-default.png +0 -0
  36. bootstack/assets/elements/checkbox-checked.png +0 -0
  37. bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  38. bootstack/assets/elements/checkbox-unchecked.png +0 -0
  39. bootstack/assets/elements/field.png +0 -0
  40. bootstack/assets/elements/input-after-compact.png +0 -0
  41. bootstack/assets/elements/input-after-default.png +0 -0
  42. bootstack/assets/elements/input-before-compact.png +0 -0
  43. bootstack/assets/elements/input-before-default.png +0 -0
  44. bootstack/assets/elements/input-compact.png +0 -0
  45. bootstack/assets/elements/input-default.png +0 -0
  46. bootstack/assets/elements/list-item-separated.png +0 -0
  47. bootstack/assets/elements/list-item.png +0 -0
  48. bootstack/assets/elements/manifest.toml +480 -0
  49. bootstack/assets/elements/menu-item.png +0 -0
  50. bootstack/assets/elements/nav-button-compact.png +0 -0
  51. bootstack/assets/elements/nav-button-default.png +0 -0
  52. bootstack/assets/elements/nav-icon-button-compact.png +0 -0
  53. bootstack/assets/elements/nav-icon-button-default.png +0 -0
  54. bootstack/assets/elements/notebook-client-border.png +0 -0
  55. bootstack/assets/elements/notebook-tab-active.png +0 -0
  56. bootstack/assets/elements/notebook-tab-bar.png +0 -0
  57. bootstack/assets/elements/notebook-tab-normal.png +0 -0
  58. bootstack/assets/elements/notebook-tab-pill.png +0 -0
  59. bootstack/assets/elements/progress-bar-horizontal-striped.png +0 -0
  60. bootstack/assets/elements/progress-bar-solid.png +0 -0
  61. bootstack/assets/elements/progress-bar-thin.png +0 -0
  62. bootstack/assets/elements/progress-bar-vertical-striped.png +0 -0
  63. bootstack/assets/elements/radio-selected.png +0 -0
  64. bootstack/assets/elements/radio-unselected.png +0 -0
  65. bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  66. bootstack/assets/elements/scrollbar-vertical.png +0 -0
  67. bootstack/assets/elements/slider-handle-focus.png +0 -0
  68. bootstack/assets/elements/slider-handle.png +0 -0
  69. bootstack/assets/elements/slider-track-horizontal.png +0 -0
  70. bootstack/assets/elements/slider-track-vertical.png +0 -0
  71. bootstack/assets/elements/switch-off.png +0 -0
  72. bootstack/assets/elements/switch-on.png +0 -0
  73. bootstack/assets/elements/tabs-bar-horizontal.png +0 -0
  74. bootstack/assets/elements/tabs-bar-vertical.png +0 -0
  75. bootstack/assets/elements/tabs-pill.png +0 -0
  76. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  77. bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +853 -0
  78. bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +875 -0
  79. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  80. bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +853 -0
  81. bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  82. bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +853 -0
  83. bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  84. bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +853 -0
  85. bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  86. bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +875 -0
  87. bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  88. bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +853 -0
  89. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  90. bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +853 -0
  91. bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  92. bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +851 -0
  93. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  94. bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +842 -0
  95. bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  96. bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +841 -0
  97. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  98. bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +914 -0
  99. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  100. bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +842 -0
  101. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  102. bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +841 -0
  103. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  104. bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +841 -0
  105. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  106. bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +842 -0
  107. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  108. bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +842 -0
  109. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  110. bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +842 -0
  111. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  112. bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +842 -0
  113. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  114. bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +842 -0
  115. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  116. bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +842 -0
  117. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  118. bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +842 -0
  119. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  120. bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +842 -0
  121. bootstack/assets/themes/__init__.py +0 -0
  122. bootstack/assets/themes/amber-dark.json +32 -0
  123. bootstack/assets/themes/amber-light.json +32 -0
  124. bootstack/assets/themes/aurora-dark.json +32 -0
  125. bootstack/assets/themes/aurora-light.json +32 -0
  126. bootstack/assets/themes/bootstrap-dark.json +32 -0
  127. bootstack/assets/themes/bootstrap-light.json +32 -0
  128. bootstack/assets/themes/classic-dark.json +32 -0
  129. bootstack/assets/themes/classic-light.json +32 -0
  130. bootstack/assets/themes/docs-dark.json +32 -0
  131. bootstack/assets/themes/docs-light.json +32 -0
  132. bootstack/assets/themes/forest-dark.json +32 -0
  133. bootstack/assets/themes/forest-light.json +32 -0
  134. bootstack/assets/themes/ocean-dark.json +32 -0
  135. bootstack/assets/themes/ocean-light.json +32 -0
  136. bootstack/assets/themes/rose-dark.json +32 -0
  137. bootstack/assets/themes/rose-light.json +32 -0
  138. bootstack/assets/widgets/__init__.py +0 -0
  139. bootstack/assets/widgets/badge-default.png +0 -0
  140. bootstack/assets/widgets/badge-pill.png +0 -0
  141. bootstack/assets/widgets/border.png +0 -0
  142. bootstack/assets/widgets/button-group-horizontal-after.png +0 -0
  143. bootstack/assets/widgets/button-group-horizontal-before.png +0 -0
  144. bootstack/assets/widgets/button-group-horizontal-center.png +0 -0
  145. bootstack/assets/widgets/button-group-vertical-after.png +0 -0
  146. bootstack/assets/widgets/button-group-vertical-before.png +0 -0
  147. bootstack/assets/widgets/button-group-vertical-center.png +0 -0
  148. bootstack/assets/widgets/button.png +0 -0
  149. bootstack/assets/widgets/checkbox-checked.png +0 -0
  150. bootstack/assets/widgets/checkbox-indeterminate.png +0 -0
  151. bootstack/assets/widgets/checkbox-unchecked.png +0 -0
  152. bootstack/assets/widgets/field.png +0 -0
  153. bootstack/assets/widgets/icon-button.png +0 -0
  154. bootstack/assets/widgets/input-inner.png +0 -0
  155. bootstack/assets/widgets/input-prefix.png +0 -0
  156. bootstack/assets/widgets/input-suffix.png +0 -0
  157. bootstack/assets/widgets/input.png +0 -0
  158. bootstack/assets/widgets/list-item-focus.png +0 -0
  159. bootstack/assets/widgets/list-item-separated.png +0 -0
  160. bootstack/assets/widgets/menu-item-separated.png +0 -0
  161. bootstack/assets/widgets/notebook-client-border.png +0 -0
  162. bootstack/assets/widgets/notebook-pill-active.png +0 -0
  163. bootstack/assets/widgets/notebook-pill-inactive.png +0 -0
  164. bootstack/assets/widgets/notebook-tab-active.png +0 -0
  165. bootstack/assets/widgets/notebook-tab-border.png +0 -0
  166. bootstack/assets/widgets/notebook-tab-normal.png +0 -0
  167. bootstack/assets/widgets/notebook-underline.png +0 -0
  168. bootstack/assets/widgets/progress-bar-horizontal-default.png +0 -0
  169. bootstack/assets/widgets/progress-bar-horizontal-striped.png +0 -0
  170. bootstack/assets/widgets/progress-bar-vertical-default.png +0 -0
  171. bootstack/assets/widgets/progress-bar-vertical-striped.png +0 -0
  172. bootstack/assets/widgets/progress-trough-horizontal.png +0 -0
  173. bootstack/assets/widgets/progress-trough-vertical.png +0 -0
  174. bootstack/assets/widgets/radio-selected.png +0 -0
  175. bootstack/assets/widgets/radio-unselected.png +0 -0
  176. bootstack/assets/widgets/scrollbar-horizontal-rounded.png +0 -0
  177. bootstack/assets/widgets/scrollbar-vertical-rounded.png +0 -0
  178. bootstack/assets/widgets/separator-horizontal.png +0 -0
  179. bootstack/assets/widgets/separator-vertical.png +0 -0
  180. bootstack/assets/widgets/slider-handle-focus.png +0 -0
  181. bootstack/assets/widgets/slider-handle.png +0 -0
  182. bootstack/assets/widgets/slider-track-horizontal.png +0 -0
  183. bootstack/assets/widgets/slider-track-vertical.png +0 -0
  184. bootstack/assets/widgets/switch-off.png +0 -0
  185. bootstack/assets/widgets/switch-on.png +0 -0
  186. bootstack/assets/widgets/tabs-bar-horizontal.png +0 -0
  187. bootstack/assets/widgets/tabs-bar-vertical.png +0 -0
  188. bootstack/assets/widgets/tabs-pill.png +0 -0
  189. bootstack/cli/__init__.py +124 -0
  190. bootstack/cli/__main__.py +6 -0
  191. bootstack/cli/add.py +439 -0
  192. bootstack/cli/build.py +115 -0
  193. bootstack/cli/config.py +287 -0
  194. bootstack/cli/demo.py +1267 -0
  195. bootstack/cli/doctor.py +195 -0
  196. bootstack/cli/list_cmd.py +71 -0
  197. bootstack/cli/promote.py +120 -0
  198. bootstack/cli/pyinstaller.py +246 -0
  199. bootstack/cli/run.py +99 -0
  200. bootstack/cli/start.py +105 -0
  201. bootstack/cli/templates/__init__.py +861 -0
  202. bootstack/constants.py +325 -0
  203. bootstack/core/__init__.py +34 -0
  204. bootstack/core/capabilities/__init__.py +45 -0
  205. bootstack/core/capabilities/after.py +103 -0
  206. bootstack/core/capabilities/bind.py +154 -0
  207. bootstack/core/capabilities/bindtags.py +112 -0
  208. bootstack/core/capabilities/busy.py +61 -0
  209. bootstack/core/capabilities/clipboard.py +88 -0
  210. bootstack/core/capabilities/focus.py +118 -0
  211. bootstack/core/capabilities/grab.py +65 -0
  212. bootstack/core/capabilities/grid.py +188 -0
  213. bootstack/core/capabilities/localization.py +231 -0
  214. bootstack/core/capabilities/pack.py +119 -0
  215. bootstack/core/capabilities/place.py +92 -0
  216. bootstack/core/capabilities/selection.py +136 -0
  217. bootstack/core/capabilities/signals.py +242 -0
  218. bootstack/core/capabilities/winfo.py +315 -0
  219. bootstack/core/colorutils.py +234 -0
  220. bootstack/core/exceptions.py +95 -0
  221. bootstack/core/images.py +283 -0
  222. bootstack/core/localization/README.md +90 -0
  223. bootstack/core/localization/__init__.py +13 -0
  224. bootstack/core/localization/intl_format.py +580 -0
  225. bootstack/core/localization/msgcat.py +425 -0
  226. bootstack/core/localization/specs.py +143 -0
  227. bootstack/core/mixins/__init__.py +1 -0
  228. bootstack/core/mixins/ttk_state.py +35 -0
  229. bootstack/core/mixins/widget.py +132 -0
  230. bootstack/core/publisher.py +147 -0
  231. bootstack/core/signals/README.md +112 -0
  232. bootstack/core/signals/__init__.py +8 -0
  233. bootstack/core/signals/integration.py +100 -0
  234. bootstack/core/signals/signal.py +317 -0
  235. bootstack/core/signals/types.py +4 -0
  236. bootstack/core/validation/__init__.py +5 -0
  237. bootstack/core/validation/types.py +13 -0
  238. bootstack/core/validation/validation_result.py +17 -0
  239. bootstack/core/validation/validation_rules.py +112 -0
  240. bootstack/core/variables.py +62 -0
  241. bootstack/datasource/README.md +607 -0
  242. bootstack/datasource/__init__.py +51 -0
  243. bootstack/datasource/base.py +474 -0
  244. bootstack/datasource/file_source.py +541 -0
  245. bootstack/datasource/memory_source.py +482 -0
  246. bootstack/datasource/sqlite_source.py +453 -0
  247. bootstack/datasource/types.py +259 -0
  248. bootstack/dialogs/__init__.py +56 -0
  249. bootstack/dialogs/colorchooser.py +674 -0
  250. bootstack/dialogs/colordropper.py +257 -0
  251. bootstack/dialogs/datedialog.py +404 -0
  252. bootstack/dialogs/dialog.py +514 -0
  253. bootstack/dialogs/filterdialog.py +358 -0
  254. bootstack/dialogs/fontdialog.py +339 -0
  255. bootstack/dialogs/formdialog.py +541 -0
  256. bootstack/dialogs/message.py +489 -0
  257. bootstack/dialogs/query.py +561 -0
  258. bootstack/py.typed +1 -0
  259. bootstack/runtime/__init__.py +3 -0
  260. bootstack/runtime/app.py +879 -0
  261. bootstack/runtime/base_window.py +786 -0
  262. bootstack/runtime/events.py +399 -0
  263. bootstack/runtime/menu.py +510 -0
  264. bootstack/runtime/shortcuts.py +423 -0
  265. bootstack/runtime/tk_patch.py +31 -0
  266. bootstack/runtime/toplevel.py +131 -0
  267. bootstack/runtime/utility.py +371 -0
  268. bootstack/runtime/visual_focus.py +228 -0
  269. bootstack/runtime/window_utilities.py +1043 -0
  270. bootstack/style/__init__.py +5498 -0
  271. bootstack/style/bootstyle.py +507 -0
  272. bootstack/style/bootstyle_builder_base.py +752 -0
  273. bootstack/style/bootstyle_builder_mixed.py +93 -0
  274. bootstack/style/bootstyle_builder_tk.py +109 -0
  275. bootstack/style/bootstyle_builder_ttk.py +354 -0
  276. bootstack/style/builders/__init__.py +51 -0
  277. bootstack/style/builders/badge.py +44 -0
  278. bootstack/style/builders/button.py +453 -0
  279. bootstack/style/builders/buttongroup.py +344 -0
  280. bootstack/style/builders/calendar.py +271 -0
  281. bootstack/style/builders/checkbutton.py +95 -0
  282. bootstack/style/builders/combobox.py +112 -0
  283. bootstack/style/builders/contextmenu.py +268 -0
  284. bootstack/style/builders/entry.py +83 -0
  285. bootstack/style/builders/expander.py +171 -0
  286. bootstack/style/builders/field.py +312 -0
  287. bootstack/style/builders/frame.py +27 -0
  288. bootstack/style/builders/label.py +28 -0
  289. bootstack/style/builders/labelframe.py +41 -0
  290. bootstack/style/builders/listview.py +267 -0
  291. bootstack/style/builders/menubar.py +74 -0
  292. bootstack/style/builders/menubutton.py +408 -0
  293. bootstack/style/builders/notebook.py +316 -0
  294. bootstack/style/builders/panedwindow.py +25 -0
  295. bootstack/style/builders/progressbar.py +71 -0
  296. bootstack/style/builders/radiobutton.py +68 -0
  297. bootstack/style/builders/scale.py +66 -0
  298. bootstack/style/builders/scrollbar.py +360 -0
  299. bootstack/style/builders/separator.py +45 -0
  300. bootstack/style/builders/sidenav.py +313 -0
  301. bootstack/style/builders/sizegrip.py +15 -0
  302. bootstack/style/builders/spinbox.py +119 -0
  303. bootstack/style/builders/switch.py +67 -0
  304. bootstack/style/builders/tabitem.py +205 -0
  305. bootstack/style/builders/toolbutton.py +260 -0
  306. bootstack/style/builders/tooltip.py +26 -0
  307. bootstack/style/builders/treeview.py +269 -0
  308. bootstack/style/builders/utils.py +404 -0
  309. bootstack/style/builders_tk/__init__.py +16 -0
  310. bootstack/style/builders_tk/defaults.py +229 -0
  311. bootstack/style/element.py +173 -0
  312. bootstack/style/style.py +499 -0
  313. bootstack/style/theme_provider.py +449 -0
  314. bootstack/style/tk_patch.py +5 -0
  315. bootstack/style/token_maps.py +42 -0
  316. bootstack/style/types.py +32 -0
  317. bootstack/style/typography.py +527 -0
  318. bootstack/style/utility.py +696 -0
  319. bootstack/themes/__init__.py +12 -0
  320. bootstack/themes/standard.py +415 -0
  321. bootstack/themes/user.py +45 -0
  322. bootstack/widgets/__init__.py +53 -0
  323. bootstack/widgets/composites/__init__.py +38 -0
  324. bootstack/widgets/composites/accordion.py +385 -0
  325. bootstack/widgets/composites/appshell.py +445 -0
  326. bootstack/widgets/composites/buttongroup.py +391 -0
  327. bootstack/widgets/composites/calendar.py +914 -0
  328. bootstack/widgets/composites/compositeframe.py +282 -0
  329. bootstack/widgets/composites/contextmenu.py +1754 -0
  330. bootstack/widgets/composites/dateentry.py +261 -0
  331. bootstack/widgets/composites/dropdownbutton.py +190 -0
  332. bootstack/widgets/composites/expander.py +508 -0
  333. bootstack/widgets/composites/field.py +448 -0
  334. bootstack/widgets/composites/floodgauge.py +434 -0
  335. bootstack/widgets/composites/form.py +983 -0
  336. bootstack/widgets/composites/labeledscale.py +209 -0
  337. bootstack/widgets/composites/list/__init__.py +15 -0
  338. bootstack/widgets/composites/list/listitem.py +733 -0
  339. bootstack/widgets/composites/list/listview.py +1507 -0
  340. bootstack/widgets/composites/menubar.py +303 -0
  341. bootstack/widgets/composites/meter.py +882 -0
  342. bootstack/widgets/composites/numericentry.py +183 -0
  343. bootstack/widgets/composites/pagestack.py +330 -0
  344. bootstack/widgets/composites/passwordentry.py +149 -0
  345. bootstack/widgets/composites/pathentry.py +223 -0
  346. bootstack/widgets/composites/radiogroup.py +466 -0
  347. bootstack/widgets/composites/scrolledtext.py +388 -0
  348. bootstack/widgets/composites/scrolledtext.pyi +186 -0
  349. bootstack/widgets/composites/scrollview.py +675 -0
  350. bootstack/widgets/composites/selectbox.py +544 -0
  351. bootstack/widgets/composites/sidenav/__init__.py +24 -0
  352. bootstack/widgets/composites/sidenav/group.py +485 -0
  353. bootstack/widgets/composites/sidenav/header.py +83 -0
  354. bootstack/widgets/composites/sidenav/item.py +413 -0
  355. bootstack/widgets/composites/sidenav/separator.py +51 -0
  356. bootstack/widgets/composites/sidenav/view.py +919 -0
  357. bootstack/widgets/composites/spinnerentry.py +232 -0
  358. bootstack/widgets/composites/tableview/__init__.py +5 -0
  359. bootstack/widgets/composites/tableview/tableview.py +2254 -0
  360. bootstack/widgets/composites/tableview/types.py +169 -0
  361. bootstack/widgets/composites/tabs/__init__.py +6 -0
  362. bootstack/widgets/composites/tabs/tabitem.py +372 -0
  363. bootstack/widgets/composites/tabs/tabs.py +478 -0
  364. bootstack/widgets/composites/tabs/tabview.py +352 -0
  365. bootstack/widgets/composites/textentry.py +90 -0
  366. bootstack/widgets/composites/timeentry.py +189 -0
  367. bootstack/widgets/composites/toast.py +364 -0
  368. bootstack/widgets/composites/togglegroup.py +382 -0
  369. bootstack/widgets/composites/toolbar.py +393 -0
  370. bootstack/widgets/composites/tooltip.py +404 -0
  371. bootstack/widgets/internal/__init__.py +0 -0
  372. bootstack/widgets/internal/wrapper_base.py +304 -0
  373. bootstack/widgets/mixins/__init__.py +25 -0
  374. bootstack/widgets/mixins/configure_mixin.py +186 -0
  375. bootstack/widgets/mixins/entry_mixin.py +70 -0
  376. bootstack/widgets/mixins/font_mixin.py +346 -0
  377. bootstack/widgets/mixins/icon_mixin.py +38 -0
  378. bootstack/widgets/mixins/localization_mixin.py +255 -0
  379. bootstack/widgets/mixins/signal_mixin.py +272 -0
  380. bootstack/widgets/mixins/validation_mixin.py +204 -0
  381. bootstack/widgets/parts/__init__.py +11 -0
  382. bootstack/widgets/parts/numberentry_part.py +345 -0
  383. bootstack/widgets/parts/spinnerentry_part.py +394 -0
  384. bootstack/widgets/parts/textentry_part.py +344 -0
  385. bootstack/widgets/primitives/__init__.py +55 -0
  386. bootstack/widgets/primitives/badge.py +44 -0
  387. bootstack/widgets/primitives/button.py +89 -0
  388. bootstack/widgets/primitives/card.py +66 -0
  389. bootstack/widgets/primitives/checkbutton.py +124 -0
  390. bootstack/widgets/primitives/checktoggle.py +53 -0
  391. bootstack/widgets/primitives/combobox.py +165 -0
  392. bootstack/widgets/primitives/entry.py +98 -0
  393. bootstack/widgets/primitives/frame.py +206 -0
  394. bootstack/widgets/primitives/gridframe.py +479 -0
  395. bootstack/widgets/primitives/label.py +95 -0
  396. bootstack/widgets/primitives/labelframe.py +63 -0
  397. bootstack/widgets/primitives/menubutton.py +118 -0
  398. bootstack/widgets/primitives/notebook.py +551 -0
  399. bootstack/widgets/primitives/optionmenu.py +248 -0
  400. bootstack/widgets/primitives/packframe.py +228 -0
  401. bootstack/widgets/primitives/panedwindow.py +58 -0
  402. bootstack/widgets/primitives/progressbar.py +95 -0
  403. bootstack/widgets/primitives/radiobutton.py +115 -0
  404. bootstack/widgets/primitives/radiotoggle.py +50 -0
  405. bootstack/widgets/primitives/scale.py +85 -0
  406. bootstack/widgets/primitives/scrollbar.py +56 -0
  407. bootstack/widgets/primitives/separator.py +56 -0
  408. bootstack/widgets/primitives/sizegrip.py +47 -0
  409. bootstack/widgets/primitives/spinbox.py +91 -0
  410. bootstack/widgets/primitives/switch.py +41 -0
  411. bootstack/widgets/primitives/treeview.py +77 -0
  412. bootstack/widgets/types.py +20 -0
  413. bootstack-0.1.0a1.dist-info/METADATA +196 -0
  414. bootstack-0.1.0a1.dist-info/RECORD +419 -0
  415. bootstack-0.1.0a1.dist-info/WHEEL +5 -0
  416. bootstack-0.1.0a1.dist-info/entry_points.txt +2 -0
  417. bootstack-0.1.0a1.dist-info/licenses/LICENSE +22 -0
  418. bootstack-0.1.0a1.dist-info/licenses/NOTICE +10 -0
  419. bootstack-0.1.0a1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,544 @@
1
+ from tkinter import Toplevel
2
+
3
+ from typing_extensions import Unpack
4
+
5
+ from bootstack.widgets.primitives.button import Button
6
+ from bootstack.widgets.primitives.frame import Frame
7
+ from bootstack.widgets.composites.scrollview import ScrollView
8
+ from bootstack.widgets.composites.field import Field, FieldOptions
9
+ from bootstack.widgets.mixins import configure_delegate
10
+ from bootstack.widgets.types import Master
11
+
12
+
13
+ class SelectBox(Field):
14
+ """Dropdown-like field widget built on top of Field.
15
+
16
+ Renders a field with a suffix button that opens a popup list of available
17
+ items. Selecting an item updates the field value and emits `<<Change>>`.
18
+
19
+ !!! note "Events"
20
+
21
+ `<<Change>>`: Fired when the selection changes (user or code).
22
+ Provides `event.data` with keys: `value`, `prev_value`, `text`.
23
+
24
+ Attributes:
25
+ entry_widget (TextEntryPart): The underlying entry widget.
26
+ label_widget (Label): The label widget.
27
+ message_widget (Label): The message label widget.
28
+ addons (dict[str, Widget]): Dictionary of inserted addon widgets by name.
29
+ variable (Variable): Tkinter Variable linked to entry text.
30
+ signal (Signal): Signal object for reactive updates.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ master: Master = None,
36
+ value: str = None,
37
+ items: list[str] = None,
38
+ label: str = None,
39
+ message: str = None,
40
+ allow_custom_values: bool = False,
41
+ show_dropdown_button: bool = True,
42
+ dropdown_button_icon: str = None,
43
+ enable_search: bool = False,
44
+ **kwargs: Unpack[FieldOptions]
45
+ ):
46
+ """Create a SelectBox widget.
47
+
48
+ Args:
49
+ master: Parent widget. If None, uses the default root window.
50
+ value (str): Initial selected value; should typically be present in `items`.
51
+ items (list): Sequence of string options to present in the popup list.
52
+ label (str): Optional label text shown above the field.
53
+ message (str): Optional helper/error message shown below the field.
54
+ allow_custom_values (bool): If True, the entry is editable so users can type
55
+ arbitrary values in addition to choosing from the list.
56
+ show_dropdown_button (bool): If True (default), the dropdown button is shown. This option is
57
+ ignored if custom values are allowed.
58
+ dropdown_button_icon (str): The icon to display on the dropdown button.
59
+ enable_search (bool): If True, allows typing in the entry to filter the popup list.
60
+ When combined with allow_custom_values=False, the first filtered item is selected
61
+ when the popup closes. With allow_custom_values=True, any typed value is kept.
62
+
63
+ Other Parameters:
64
+ allow_blank (bool): If True, empty input is allowed.
65
+ accent (str): Accent token for styling the focus ring and active border.
66
+ value_format (str): ICU format pattern for parsing/formatting.
67
+ font (str): Font for text display.
68
+ foreground (str): Text color.
69
+ initial_focus (bool): If True, widget receives focus when created.
70
+ justify (str): Text justification ('left', 'center', 'right').
71
+ show_message (bool): If True, displays message text below the field.
72
+ padding (str | int | tuple): Padding around the entry widget.
73
+ state (str): The widget state ('normal', 'disabled', 'readonly').
74
+ textvariable (Variable): Tkinter Variable to link with the entry text.
75
+ textsignal (Signal): Signal object for reactive text updates.
76
+ width (int): Width of the entry in characters.
77
+ required (bool): If True, field cannot be empty.
78
+ """
79
+ super().__init__(master, value=value, label=label, message=message, **kwargs)
80
+
81
+ self._allow_custom_values = allow_custom_values
82
+ self._search_enabled = enable_search
83
+ self._items = items or []
84
+ self._last_selected_value = value
85
+ self._popup_open = False
86
+ self._dropdown_button_icon = dropdown_button_icon or 'chevron-down'
87
+ self._popup_frame = None
88
+ self._item_labels = []
89
+
90
+ # Add dropdown button if needed
91
+ if allow_custom_values or show_dropdown_button:
92
+ self.insert_addon(
93
+ Button,
94
+ position="after",
95
+ name="dropdown",
96
+ icon=self._dropdown_button_icon,
97
+ icon_only=True,
98
+ command=self._on_dropdown_click
99
+ )
100
+
101
+ # Configure entry state based on search and custom value settings
102
+ if allow_custom_values or enable_search:
103
+ self.entry_widget.state(['!readonly'])
104
+ else:
105
+ # Set entry to readonly but keep dropdown button enabled
106
+ self.entry_widget.state(['readonly'])
107
+ self.after_idle(self._bind_readonly_selection_on_click)
108
+
109
+ def _on_dropdown_click(self):
110
+ """Handle dropdown button click by focusing entry then showing popup."""
111
+ self.entry_widget.focus_set()
112
+ self._show_selection_options()
113
+
114
+ def _bind_readonly_selection_on_click(self):
115
+ """Bind entry click to show popup when in readonly mode."""
116
+ self.entry_widget.bind('<Button-1>', lambda _: self.after_idle(self._show_selection_options), add='+')
117
+
118
+ def _show_selection_options(self):
119
+ """Create and display the popup list of selectable items."""
120
+ if not self._items or self._popup_open:
121
+ return
122
+
123
+ self._popup_open = True
124
+ self.update_idletasks()
125
+
126
+ # Create popup toplevel
127
+ toplevel = self._create_popup_toplevel()
128
+
129
+ # Create tracking variables for popup state
130
+ popup_state = {
131
+ 'item_was_selected': False,
132
+ 'popup_closed': False,
133
+ 'first_filtered_item': None,
134
+ 'entry_focus_handler': None,
135
+ 'key_bindings': [],
136
+ 'highlighted_index': max(0, self.selected_index),
137
+ }
138
+
139
+ # Setup close handler
140
+ def close_popup(event=None):
141
+ self._close_popup(toplevel, popup_state)
142
+
143
+ # Create popup content frame with items
144
+ self._popup_frame = self._create_popup_frame(toplevel, popup_state)
145
+
146
+ # Setup event bindings based on mode
147
+ self._setup_popup_bindings(toplevel, popup_state, close_popup)
148
+
149
+ # Show popup and set focus
150
+ toplevel.deiconify()
151
+ toplevel.lift()
152
+
153
+ if self._search_enabled:
154
+ self.entry_widget.focus_force()
155
+ self.entry_widget.icursor('end')
156
+ else:
157
+ toplevel.focus_force()
158
+
159
+ def _create_popup_toplevel(self):
160
+ """Create and position the popup toplevel window."""
161
+ x = self.winfo_rootx() + (1 if self._search_enabled else 3)
162
+ gap_below = 8 if self._search_enabled else 5
163
+ gap_above = 4 if self._search_enabled else 1
164
+ entry_top = self.entry_widget.winfo_rooty()
165
+ entry_bottom = entry_top + self.entry_widget.winfo_height()
166
+ width = self.winfo_width() - (2 if self._search_enabled else 6)
167
+ max_height = 200 # Maximum popup height in pixels
168
+
169
+ # Flip above the entry when there isn't enough room below — matches
170
+ # Tk's combobox PlacePopdown behavior.
171
+ screen_h = self.winfo_screenheight()
172
+ if entry_bottom + gap_below + max_height > screen_h and entry_top - gap_above - max_height >= 0:
173
+ y = entry_top - gap_above - max_height
174
+ else:
175
+ y = entry_bottom + gap_below
176
+
177
+ # The base_window.py warning about overrideredirect on Aqua applies
178
+ # to full app windows that combine it with grab/transient semantics;
179
+ # this popup uses neither (it dismisses via a root <Button-1>
180
+ # binding), so overrideredirect is safe here and matches Tk's own
181
+ # ttk::combobox::PlacePopdown approach for Mac popdowns.
182
+ toplevel = Toplevel(self)
183
+ toplevel.withdraw()
184
+ toplevel.overrideredirect(True)
185
+ toplevel.minsize(width, 0)
186
+ toplevel.maxsize(width * 2, max_height)
187
+ toplevel.geometry(f"{width}x{max_height}+{x}+{y}")
188
+
189
+ return toplevel
190
+
191
+ def _create_popup_frame(self, toplevel, popup_state):
192
+ """Create popup frame with scrollable item list."""
193
+ # Outer frame with border and padding — match the entry's input
194
+ # surface so the popup fill blends with the field background.
195
+ outer_frame = Frame(toplevel, padding=3, show_border=True, surface='content')
196
+ outer_frame.pack(fill='both', expand=True)
197
+
198
+ # Create scrollview inside the outer frame
199
+ scrollview = ScrollView(
200
+ outer_frame,
201
+ scroll_direction='vertical',
202
+ scrollbar_visibility='always',
203
+ )
204
+ scrollview.pack(fill='both', expand=True)
205
+
206
+ # Create inner frame for items
207
+ inner_frame = Frame(scrollview)
208
+ scrollview.add(inner_frame)
209
+
210
+ # Make inner frame fill the canvas width
211
+ def on_canvas_configure(event):
212
+ scrollview.canvas.itemconfig(scrollview._window_id, width=event.width)
213
+ scrollview.canvas.bind('<Configure>', on_canvas_configure, add='+')
214
+
215
+ # Expand scrollregion to include vertical padding so content doesn't clip borders
216
+ def on_inner_frame_configure(event):
217
+ bbox = scrollview.canvas.bbox('all')
218
+ if bbox:
219
+ x0, y0, x1, y1 = bbox
220
+ padding_y = 1
221
+ scrollview.canvas.configure(scrollregion=(x0, y0 - padding_y, x1, y1 + padding_y))
222
+ inner_frame.bind('<Configure>', on_inner_frame_configure, add='+')
223
+
224
+ self._item_labels = []
225
+ current_value = self.value
226
+
227
+ # Get accent from Field's _accent attribute, fallback to primary if None
228
+ accent = getattr(self, '_accent', None) or 'primary'
229
+
230
+ for i, item in enumerate(self._items):
231
+ btn = Button(
232
+ inner_frame,
233
+ text=item,
234
+ accent=accent,
235
+ variant='selectbox_item',
236
+ command=lambda v=item: self._on_item_click(v, toplevel, popup_state)
237
+ )
238
+ btn.pack(fill='x')
239
+
240
+ # Store item value on button for retrieval
241
+ btn._item_value = item
242
+ btn._item_index = i
243
+
244
+ # Apply selected state if this is the current value
245
+ if item == current_value:
246
+ btn.state(['selected'])
247
+
248
+ self._item_labels.append(btn)
249
+
250
+ return scrollview
251
+
252
+ def _on_item_click(self, value, toplevel, popup_state):
253
+ """Handle click on item."""
254
+ popup_state['item_was_selected'] = True
255
+ self._set_selected_value(value, toplevel, popup_state)
256
+
257
+ def _update_highlight(self, popup_state, new_index):
258
+ """Update the highlighted item in the popup."""
259
+ visible_buttons = [btn for btn in self._item_labels if btn.winfo_manager()]
260
+ if not visible_buttons:
261
+ return
262
+
263
+ # Clamp index to valid range
264
+ new_index = max(0, min(new_index, len(visible_buttons) - 1))
265
+ old_index = popup_state['highlighted_index']
266
+
267
+ # Remove highlight from old button
268
+ if 0 <= old_index < len(visible_buttons):
269
+ visible_buttons[old_index].state(['!selected'])
270
+
271
+ # Add highlight to new button
272
+ visible_buttons[new_index].state(['selected'])
273
+ popup_state['highlighted_index'] = new_index
274
+
275
+ # Scroll to make highlighted item visible
276
+ btn = visible_buttons[new_index]
277
+ if self._popup_frame and self._popup_frame.winfo_exists():
278
+ self._popup_frame.canvas.update_idletasks()
279
+ # Get button position relative to canvas
280
+ btn_y = btn.winfo_y()
281
+ btn_height = btn.winfo_height()
282
+ canvas_height = self._popup_frame.canvas.winfo_height()
283
+
284
+ # Scroll if needed
285
+ scroll_top = self._popup_frame.canvas.canvasy(0)
286
+ scroll_bottom = scroll_top + canvas_height
287
+
288
+ if btn_y < scroll_top:
289
+ self._popup_frame.canvas.yview_moveto(btn_y / self._popup_frame.canvas.bbox('all')[3])
290
+ elif btn_y + btn_height > scroll_bottom:
291
+ target = (btn_y + btn_height - canvas_height) / self._popup_frame.canvas.bbox('all')[3]
292
+ self._popup_frame.canvas.yview_moveto(target)
293
+
294
+ def _setup_popup_bindings(self, toplevel, popup_state, close_popup):
295
+ """Setup all event bindings for the popup."""
296
+ # Escape always closes
297
+ toplevel.bind("<Escape>", close_popup)
298
+
299
+ # Arrow key navigation
300
+ def on_arrow_down(event):
301
+ self._update_highlight(popup_state, popup_state['highlighted_index'] + 1)
302
+ return 'break'
303
+
304
+ def on_arrow_up(event):
305
+ self._update_highlight(popup_state, popup_state['highlighted_index'] - 1)
306
+ return 'break'
307
+
308
+ def on_enter(event):
309
+ visible_buttons = [btn for btn in self._item_labels if btn.winfo_manager()]
310
+ idx = popup_state['highlighted_index']
311
+ if 0 <= idx < len(visible_buttons):
312
+ popup_state['item_was_selected'] = True
313
+ self._set_selected_value(visible_buttons[idx]._item_value, toplevel, popup_state)
314
+ return 'break'
315
+
316
+ toplevel.bind("<Down>", on_arrow_down)
317
+ toplevel.bind("<Up>", on_arrow_up)
318
+ toplevel.bind("<Return>", on_enter)
319
+
320
+ # Also bind to entry widget for search mode
321
+ if self._search_enabled:
322
+ self._setup_search_bindings(toplevel, popup_state, close_popup)
323
+ entry_down = self.entry_widget.bind('<Down>', on_arrow_down, add='+')
324
+ entry_up = self.entry_widget.bind('<Up>', on_arrow_up, add='+')
325
+ entry_enter = self.entry_widget.bind('<Return>', on_enter, add='+')
326
+ popup_state['key_bindings'].append(('<Down>', entry_down))
327
+ popup_state['key_bindings'].append(('<Up>', entry_up))
328
+ popup_state['key_bindings'].append(('<Return>', entry_enter))
329
+ else:
330
+ toplevel.bind("<FocusOut>", close_popup)
331
+
332
+ # Apply initial highlight
333
+ self._update_highlight(popup_state, popup_state['highlighted_index'])
334
+
335
+ def _setup_search_bindings(self, toplevel, popup_state, close_popup):
336
+ """Setup search-specific event bindings."""
337
+ # Initialize first filtered item
338
+ if self._items:
339
+ popup_state['first_filtered_item'] = self._items[0]
340
+ popup_state['last_search_text'] = self.entry_widget.get().lower()
341
+
342
+ # Filter function
343
+ def filter_items(*args):
344
+ if popup_state['popup_closed'] or not self._popup_frame or not self._popup_frame.winfo_exists():
345
+ return
346
+
347
+ search_text = self.entry_widget.get().lower()
348
+
349
+ # Skip if search text hasn't changed (e.g., arrow key release)
350
+ if search_text == popup_state['last_search_text']:
351
+ return
352
+ popup_state['last_search_text'] = search_text
353
+
354
+ # Show/hide buttons based on filter
355
+ first_visible = None
356
+ for btn in self._item_labels:
357
+ if search_text in btn._item_value.lower():
358
+ btn.pack(fill='x')
359
+ if first_visible is None:
360
+ first_visible = btn
361
+ else:
362
+ btn.pack_forget()
363
+
364
+ # Track first filtered item for auto-select
365
+ popup_state['first_filtered_item'] = first_visible._item_value if first_visible else None
366
+
367
+ # Reset highlight to first visible item
368
+ self._update_highlight(popup_state, 0)
369
+
370
+ # Bind KeyRelease for filtering
371
+ keyrelease_binding = self.entry_widget.bind('<KeyRelease>', lambda e: filter_items(), add='+')
372
+ popup_state['key_bindings'].append(('<KeyRelease>', keyrelease_binding))
373
+
374
+ # Bind Tab to select highlighted item
375
+ def on_tab(event):
376
+ if popup_state['popup_closed']:
377
+ return
378
+ visible_buttons = [btn for btn in self._item_labels if btn.winfo_manager()]
379
+ idx = popup_state['highlighted_index']
380
+ if 0 <= idx < len(visible_buttons):
381
+ popup_state['item_was_selected'] = True
382
+ self._set_selected_value(visible_buttons[idx]._item_value, toplevel, popup_state)
383
+ return 'break'
384
+
385
+ tab_binding = self.entry_widget.bind('<Tab>', on_tab)
386
+ popup_state['key_bindings'].append(('<Tab>', tab_binding))
387
+
388
+ # Setup click-outside detection
389
+ def on_root_click(event):
390
+ x, y = event.x_root, event.y_root
391
+
392
+ # Check if click is inside entry widget
393
+ ex, ey = self.entry_widget.winfo_rootx(), self.entry_widget.winfo_rooty()
394
+ ew, eh = self.entry_widget.winfo_width(), self.entry_widget.winfo_height()
395
+ if ex <= x <= ex + ew and ey <= y <= ey + eh:
396
+ return
397
+
398
+ # Check if click is inside toplevel
399
+ if toplevel.winfo_exists():
400
+ tx, ty = toplevel.winfo_rootx(), toplevel.winfo_rooty()
401
+ tw, th = toplevel.winfo_width(), toplevel.winfo_height()
402
+ if tx <= x <= tx + tw and ty <= y <= ty + th:
403
+ return
404
+
405
+ close_popup()
406
+
407
+ def bind_click():
408
+ if popup_state['popup_closed']:
409
+ return
410
+ root = self.winfo_toplevel()
411
+ bind_id = root.bind('<Button-1>', on_root_click, add='+')
412
+ popup_state['entry_focus_handler'] = bind_id
413
+
414
+ self.after(100, bind_click)
415
+
416
+ def _close_popup(self, toplevel, popup_state):
417
+ """Close the popup and cleanup bindings."""
418
+ if popup_state['popup_closed']:
419
+ return
420
+
421
+ popup_state['popup_closed'] = True
422
+ self._popup_open = False
423
+
424
+ # Unbind handlers
425
+ if popup_state['entry_focus_handler'] is not None and self._search_enabled:
426
+ root = self.winfo_toplevel()
427
+ root.unbind('<Button-1>', popup_state['entry_focus_handler'])
428
+
429
+ # Unbind key bindings
430
+ for sequence, funcid in popup_state['key_bindings']:
431
+ self.entry_widget.unbind(sequence, funcid)
432
+
433
+ # Destroy toplevel
434
+ if toplevel.winfo_exists():
435
+ toplevel.destroy()
436
+
437
+ # Clean up popup references
438
+ self._popup_frame = None
439
+ self._item_labels = []
440
+
441
+ # Handle value selection for search mode without custom values
442
+ if self._search_enabled and not self._allow_custom_values:
443
+ if not popup_state['item_was_selected']:
444
+ if popup_state['first_filtered_item'] is not None:
445
+ self._last_selected_value = popup_state['first_filtered_item']
446
+ self.value = popup_state['first_filtered_item']
447
+
448
+ def _set_selected_value(self, selected_value, toplevel, popup_state):
449
+ """Set the selected value and close the popup."""
450
+ if selected_value is None:
451
+ return
452
+
453
+ self._last_selected_value = selected_value
454
+ self.value = selected_value
455
+ self._close_popup(toplevel, popup_state)
456
+
457
+ @configure_delegate('items')
458
+ def _delegate_items(self, value: list[str] = None):
459
+ """Get or set the available items for the SelectBox."""
460
+ if value is None:
461
+ return self._items
462
+ else:
463
+ self._items = value or []
464
+ return None
465
+
466
+ @configure_delegate('allow_custom_values')
467
+ def _delegate_allow_custom_values(self, value: bool = None):
468
+ """Get or set whether free-form text entry is allowed."""
469
+ if value is None:
470
+ return self._allow_custom_values
471
+ else:
472
+ self._allow_custom_values = value
473
+ if value or self._search_enabled:
474
+ self.entry_widget.state(['!readonly'])
475
+ else:
476
+ self.readonly(True)
477
+ return None
478
+
479
+ @configure_delegate('enable_search')
480
+ def _delegate_enable_search(self, value: bool = None):
481
+ """Get or set whether search filtering is enabled."""
482
+ if value is None:
483
+ return self._search_enabled
484
+ else:
485
+ self._search_enabled = value
486
+ if value or self._allow_custom_values:
487
+ self.entry_widget.state(['!readonly'])
488
+ else:
489
+ self.readonly(True)
490
+ return None
491
+
492
+ @configure_delegate('value')
493
+ def _delegate_value(self, value=None):
494
+ if value is not None:
495
+ return self.value
496
+ else:
497
+ self.value = value
498
+ return None
499
+
500
+ @property
501
+ def selected_index(self):
502
+ """Get or set the selected index.
503
+
504
+ Returns -1 if the current value is not in the items list.
505
+ Setting to -1 or None clears the selection.
506
+ """
507
+ try:
508
+ return self._items.index(self.value)
509
+ except (ValueError, TypeError):
510
+ return -1
511
+
512
+ @selected_index.setter
513
+ def selected_index(self, index):
514
+ if index is None or index == -1:
515
+ self.value = ""
516
+ elif 0 <= index < len(self._items):
517
+ self.value = self._items[index]
518
+ else:
519
+ raise IndexError(f"index {index} out of range for {len(self._items)} items")
520
+
521
+ @property
522
+ def value(self):
523
+ """Get or set the selected value."""
524
+ return Field.value.fget(self)
525
+
526
+ @value.setter
527
+ def value(self, value):
528
+ """Set the selected value and emit `<<Change>>` when it differs."""
529
+ prev_value = Field.value.fget(self)
530
+ Field.value.fset(self, value)
531
+ new_value = Field.value.fget(self)
532
+ if new_value != prev_value:
533
+ self.entry_widget._prev_changed_value = new_value
534
+ if not getattr(self, "_suppress_changed_event", False):
535
+ self.entry_widget.event_generate(
536
+ '<<Change>>',
537
+ data={
538
+ 'value': new_value,
539
+ 'prev_value': prev_value,
540
+ 'text': self.entry_widget.get()
541
+ },
542
+ when="tail"
543
+ )
544
+
@@ -0,0 +1,24 @@
1
+ """SideNav widgets for building navigation interfaces.
2
+
3
+ This package provides a complete navigation solution:
4
+
5
+ - SideNav: Main container with pane, header, content, and footer
6
+ - SideNavGroup: Collapsible group of items (expander in expanded mode, popup in compact)
7
+ - SideNavItem: Selectable navigation item with icon and text
8
+ - SideNavHeader: Non-selectable section label
9
+ - SideNavSeparator: Visual divider between groups
10
+ """
11
+
12
+ from bootstack.widgets.composites.sidenav.item import SideNavItem
13
+ from bootstack.widgets.composites.sidenav.group import SideNavGroup
14
+ from bootstack.widgets.composites.sidenav.header import SideNavHeader
15
+ from bootstack.widgets.composites.sidenav.separator import SideNavSeparator
16
+ from bootstack.widgets.composites.sidenav.view import SideNav
17
+
18
+ __all__ = [
19
+ 'SideNav',
20
+ 'SideNavGroup',
21
+ 'SideNavItem',
22
+ 'SideNavHeader',
23
+ 'SideNavSeparator',
24
+ ]