bootstack 0.1.7__tar.gz → 0.2.0__tar.gz

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 (492) hide show
  1. {bootstack-0.1.7/src/bootstack.egg-info → bootstack-0.2.0}/PKG-INFO +1 -1
  2. {bootstack-0.1.7 → bootstack-0.2.0}/pyproject.toml +2 -2
  3. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/events.py +156 -9
  4. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/utility.py +14 -2
  5. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/errors.py +12 -0
  6. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/typography.py +15 -6
  7. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/base.py +14 -4
  8. bootstack-0.2.0/src/bootstack/widgets/_core/choices.py +56 -0
  9. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/container.py +57 -0
  10. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/field_mixin.py +7 -0
  11. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/_parts/spinnerentry_part.py +5 -3
  12. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/_parts/textentry_part.py +9 -3
  13. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/dateentry.py +30 -7
  14. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/field.py +7 -1
  15. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/form.py +16 -4
  16. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/layout.py +33 -28
  17. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/shell.py +3 -3
  18. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/flexframe.py +74 -15
  19. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/appshell.py +12 -9
  20. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/calendar.py +2 -0
  21. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/card.py +4 -11
  22. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/codeeditor.py +2 -0
  23. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/datatable.py +4 -0
  24. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/datefield.py +2 -0
  25. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/expander.py +7 -21
  26. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/form.py +5 -1
  27. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/gallery.py +2 -0
  28. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/groupbox.py +4 -11
  29. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/listview.py +2 -0
  30. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/pagestack.py +4 -11
  31. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/pathfield.py +2 -0
  32. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/scrollview.py +3 -0
  33. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/select.py +7 -0
  34. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/sidebar_toggle.py +2 -0
  35. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/splitview.py +4 -11
  36. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/stacks.py +16 -7
  37. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/tabs.py +4 -11
  38. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/textarea.py +2 -0
  39. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/togglegroup.py +2 -0
  40. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/tree.py +2 -0
  41. {bootstack-0.1.7 → bootstack-0.2.0/src/bootstack.egg-info}/PKG-INFO +1 -1
  42. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack.egg-info/SOURCES.txt +1 -0
  43. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_public_surface.py +1 -1
  44. {bootstack-0.1.7 → bootstack-0.2.0}/LICENSE +0 -0
  45. {bootstack-0.1.7 → bootstack-0.2.0}/MANIFEST.in +0 -0
  46. {bootstack-0.1.7 → bootstack-0.2.0}/NOTICE +0 -0
  47. {bootstack-0.1.7 → bootstack-0.2.0}/README.md +0 -0
  48. {bootstack-0.1.7 → bootstack-0.2.0}/setup.cfg +0 -0
  49. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/__init__.py +0 -0
  50. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/__main__.py +0 -0
  51. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/__init__.py +0 -0
  52. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/__init__.py +0 -0
  53. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/after.py +0 -0
  54. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/bind.py +0 -0
  55. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/bindtags.py +0 -0
  56. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/busy.py +0 -0
  57. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/clipboard.py +0 -0
  58. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/focus.py +0 -0
  59. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/grab.py +0 -0
  60. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/grid.py +0 -0
  61. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/localization.py +0 -0
  62. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/pack.py +0 -0
  63. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/place.py +0 -0
  64. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/selection.py +0 -0
  65. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/signals.py +0 -0
  66. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/capabilities/winfo.py +0 -0
  67. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/colorutils.py +0 -0
  68. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/exceptions.py +0 -0
  69. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/images.py +0 -0
  70. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/mixins/__init__.py +0 -0
  71. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/mixins/ttk_state.py +0 -0
  72. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/mixins/widget.py +0 -0
  73. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/paths.py +0 -0
  74. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/publisher.py +0 -0
  75. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_core/variables.py +0 -0
  76. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/__init__.py +0 -0
  77. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/app.py +0 -0
  78. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/base_window.py +0 -0
  79. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/shortcuts.py +0 -0
  80. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/tk_patch.py +0 -0
  81. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/toplevel.py +0 -0
  82. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/visual_focus.py +0 -0
  83. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/wheel.py +0 -0
  84. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/_runtime/window_utilities.py +0 -0
  85. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/__init__.py +0 -0
  86. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/bootstack.ico +0 -0
  87. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/bootstack.png +0 -0
  88. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/__init__.py +0 -0
  89. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/badge-pill.png +0 -0
  90. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/badge-square.png +0 -0
  91. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/button-compact.png +0 -0
  92. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/button-default.png +0 -0
  93. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
  94. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
  95. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
  96. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
  97. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
  98. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
  99. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
  100. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
  101. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
  102. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
  103. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
  104. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
  105. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/card.png +0 -0
  106. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/checkbox-checked.png +0 -0
  107. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/checkbox-indeterminate.png +0 -0
  108. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/checkbox-unchecked.png +0 -0
  109. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/field.png +0 -0
  110. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/input-addon-compact.png +0 -0
  111. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/input-addon-default.png +0 -0
  112. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/input-compact.png +0 -0
  113. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/input-default.png +0 -0
  114. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/list-item-separated.png +0 -0
  115. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/list-item.png +0 -0
  116. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/listrow-compact.png +0 -0
  117. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/listrow-default.png +0 -0
  118. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/manifest.toml +0 -0
  119. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/menu-item.png +0 -0
  120. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/navitem-compact.png +0 -0
  121. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/navitem-default.png +0 -0
  122. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/progressbar-h-compact.png +0 -0
  123. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/progressbar-h-default.png +0 -0
  124. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/progressbar-v-compact.png +0 -0
  125. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/progressbar-v-default.png +0 -0
  126. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/radiobutton.png +0 -0
  127. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/scrollbar-horizontal.png +0 -0
  128. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/scrollbar-vertical.png +0 -0
  129. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/slider-handle.png +0 -0
  130. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/slider-track-h.png +0 -0
  131. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/slider-track-v.png +0 -0
  132. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/switch-off.png +0 -0
  133. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/switch-on.png +0 -0
  134. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/tab-h.png +0 -0
  135. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/elements/tab-v.png +0 -0
  136. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/icons/bootstrap.ttf +0 -0
  137. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/icons/glyphmap.json +0 -0
  138. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/icons/icon_metrics.json +0 -0
  139. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
  140. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +0 -0
  141. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
  142. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +0 -0
  143. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
  144. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +0 -0
  145. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
  146. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +0 -0
  147. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
  148. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +0 -0
  149. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
  150. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +0 -0
  151. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
  152. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +0 -0
  153. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
  154. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +0 -0
  155. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
  156. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +0 -0
  157. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
  158. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +0 -0
  159. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
  160. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +0 -0
  161. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
  162. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +0 -0
  163. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
  164. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +0 -0
  165. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
  166. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +0 -0
  167. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
  168. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +0 -0
  169. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
  170. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +0 -0
  171. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
  172. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +0 -0
  173. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
  174. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +0 -0
  175. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
  176. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +0 -0
  177. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
  178. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +0 -0
  179. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
  180. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +0 -0
  181. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
  182. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +0 -0
  183. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
  184. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +0 -0
  185. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/__init__.py +0 -0
  186. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/__main__.py +0 -0
  187. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/add.py +0 -0
  188. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/appicon.py +0 -0
  189. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/build.py +0 -0
  190. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/config.py +0 -0
  191. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/demo.py +0 -0
  192. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/dev.py +0 -0
  193. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/doctor.py +0 -0
  194. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/icons.py +0 -0
  195. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/promote.py +0 -0
  196. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/pyinstaller.py +0 -0
  197. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/run.py +0 -0
  198. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/start.py +0 -0
  199. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/cli/templates/__init__.py +0 -0
  200. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/clipboard.py +0 -0
  201. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/constants.py +0 -0
  202. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/README.md +0 -0
  203. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/__init__.py +0 -0
  204. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/_observable.py +0 -0
  205. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/base.py +0 -0
  206. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/file_source.py +0 -0
  207. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/memory_source.py +0 -0
  208. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/query.py +0 -0
  209. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/readers.py +0 -0
  210. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/sqlite_source.py +0 -0
  211. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/types.py +0 -0
  212. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/data/writers.py +0 -0
  213. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dev/__init__.py +0 -0
  214. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dev/_capture.py +0 -0
  215. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dev/_env.py +0 -0
  216. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dev/_registry.py +0 -0
  217. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dev/_reloader.py +0 -0
  218. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dev/_reset.py +0 -0
  219. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dev/_watcher.py +0 -0
  220. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/__init__.py +0 -0
  221. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/__init__.py +0 -0
  222. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/colorchooser.py +0 -0
  223. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/datedialog.py +0 -0
  224. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/dialog.py +0 -0
  225. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/filterdialog.py +0 -0
  226. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/fontdialog.py +0 -0
  227. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/formdialog.py +0 -0
  228. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/message.py +0 -0
  229. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/dialogs/_impl/query.py +0 -0
  230. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/events/__init__.py +0 -0
  231. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/events/_event.py +0 -0
  232. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/events/_payloads.py +0 -0
  233. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/events/_subscription.py +0 -0
  234. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/i18n/README.md +0 -0
  235. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/i18n/__init__.py +0 -0
  236. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/i18n/catalog.py +0 -0
  237. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/i18n/intl_format.py +0 -0
  238. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/i18n/msgcat.py +0 -0
  239. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/i18n/specs.py +0 -0
  240. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/images.py +0 -0
  241. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/py.typed +0 -0
  242. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/scheduling/__init__.py +0 -0
  243. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/scheduling/_schedule.py +0 -0
  244. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/shortcuts.py +0 -0
  245. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/signals/README.md +0 -0
  246. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/signals/__init__.py +0 -0
  247. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/signals/integration.py +0 -0
  248. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/signals/signal.py +0 -0
  249. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/signals/types.py +0 -0
  250. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/store.py +0 -0
  251. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/streams/__init__.py +0 -0
  252. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/streams/_stream.py +0 -0
  253. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/__init__.py +0 -0
  254. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/__init__.py +0 -0
  255. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/badge.py +0 -0
  256. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/button.py +0 -0
  257. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/buttongroup.py +0 -0
  258. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/calendar.py +0 -0
  259. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/checkbutton.py +0 -0
  260. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/combobox.py +0 -0
  261. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/contextmenu.py +0 -0
  262. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/entry.py +0 -0
  263. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/expander.py +0 -0
  264. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/field.py +0 -0
  265. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/frame.py +0 -0
  266. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/label.py +0 -0
  267. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/labelframe.py +0 -0
  268. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/listview.py +0 -0
  269. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/menubar.py +0 -0
  270. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/menubutton.py +0 -0
  271. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/panedwindow.py +0 -0
  272. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/progressbar.py +0 -0
  273. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/radiobutton.py +0 -0
  274. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/scale.py +0 -0
  275. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/scrollbar.py +0 -0
  276. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/separator.py +0 -0
  277. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/sidenav.py +0 -0
  278. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/sizegrip.py +0 -0
  279. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/spinbox.py +0 -0
  280. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/switch.py +0 -0
  281. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/tabitem.py +0 -0
  282. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/togglegroup.py +0 -0
  283. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/toolbutton.py +0 -0
  284. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/tooltip.py +0 -0
  285. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/treeview.py +0 -0
  286. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders/utils.py +0 -0
  287. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders_tk/__init__.py +0 -0
  288. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/builders_tk/defaults.py +0 -0
  289. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/element.py +0 -0
  290. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/fonts.py +0 -0
  291. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/style.py +0 -0
  292. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/style_builder_base.py +0 -0
  293. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/style_builder_mixed.py +0 -0
  294. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/style_builder_tk.py +0 -0
  295. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/style_builder_ttk.py +0 -0
  296. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/style_resolver.py +0 -0
  297. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/theme.py +0 -0
  298. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/theme_provider.py +0 -0
  299. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/themes/__init__.py +0 -0
  300. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/tk_patch.py +0 -0
  301. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/token_maps.py +0 -0
  302. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/types.py +0 -0
  303. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/style/utility.py +0 -0
  304. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/types.py +0 -0
  305. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/validation/__init__.py +0 -0
  306. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/validation/types.py +0 -0
  307. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/validation/validation_result.py +0 -0
  308. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/validation/validation_rules.py +0 -0
  309. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/__init__.py +0 -0
  310. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/__init__.py +0 -0
  311. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/app_config.py +0 -0
  312. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/context.py +0 -0
  313. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/events.py +0 -0
  314. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/icon_image_props.py +0 -0
  315. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/image_binding.py +0 -0
  316. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/kwargs.py +0 -0
  317. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/navmodel.py +0 -0
  318. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/options.py +0 -0
  319. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/selection_group.py +0 -0
  320. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/window_controls.py +0 -0
  321. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_core/window_menu.py +0 -0
  322. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/__init__.py +0 -0
  323. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/_internal/__init__.py +0 -0
  324. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/_internal/wrapper_base.py +0 -0
  325. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/_parts/__init__.py +0 -0
  326. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/_parts/numberentry_part.py +0 -0
  327. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/__init__.py +0 -0
  328. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/_dateutils.py +0 -0
  329. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/_image_fit.py +0 -0
  330. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/accordion.py +0 -0
  331. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/avatar.py +0 -0
  332. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/buttongroup.py +0 -0
  333. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/calendar.py +0 -0
  334. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/carousel.py +0 -0
  335. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/chart.py +0 -0
  336. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/compositeframe.py +0 -0
  337. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/contextmenu.py +0 -0
  338. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/dropdownbutton.py +0 -0
  339. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/expander.py +0 -0
  340. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/gallery.py +0 -0
  341. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/list/__init__.py +0 -0
  342. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/list/listitem.py +0 -0
  343. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/list/listview.py +0 -0
  344. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/menu/__init__.py +0 -0
  345. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/menu/model.py +0 -0
  346. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/menu/render_native.py +0 -0
  347. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/menu/render_themed.py +0 -0
  348. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/meter.py +0 -0
  349. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/numericentry.py +0 -0
  350. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/pagestack.py +0 -0
  351. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/passwordentry.py +0 -0
  352. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/pathentry.py +0 -0
  353. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/picture.py +0 -0
  354. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/radiogroup.py +0 -0
  355. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/scrolledtext.py +0 -0
  356. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/scrolledtext.pyi +0 -0
  357. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/scrollview.py +0 -0
  358. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/selectbox.py +0 -0
  359. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/__init__.py +0 -0
  360. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/content_host.py +0 -0
  361. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/nav_panel.py +0 -0
  362. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/providers.py +0 -0
  363. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/rail.py +0 -0
  364. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/shell/workspace.py +0 -0
  365. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/sidenav/__init__.py +0 -0
  366. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/sidenav/header.py +0 -0
  367. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/sidenav/separator.py +0 -0
  368. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/slider/__init__.py +0 -0
  369. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/slider/_shared.py +0 -0
  370. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/slider/rangeslider.py +0 -0
  371. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/slider/slider.py +0 -0
  372. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/spinnerentry.py +0 -0
  373. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tableview/__init__.py +0 -0
  374. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tableview/tableview.py +0 -0
  375. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tableview/types.py +0 -0
  376. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tabs/__init__.py +0 -0
  377. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tabs/tabitem.py +0 -0
  378. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tabs/tabs.py +0 -0
  379. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tabs/tabview.py +0 -0
  380. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/__init__.py +0 -0
  381. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/change.py +0 -0
  382. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/codeeditor.py +0 -0
  383. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/core.py +0 -0
  384. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/decoration.py +0 -0
  385. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/diff.py +0 -0
  386. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +0 -0
  387. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +0 -0
  388. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +0 -0
  389. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +0 -0
  390. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +0 -0
  391. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +0 -0
  392. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/filter.py +0 -0
  393. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/search_overlay.py +0 -0
  394. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/sidebar.py +0 -0
  395. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/style_registry.py +0 -0
  396. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/textarea.py +0 -0
  397. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textarea/undo.py +0 -0
  398. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/textentry.py +0 -0
  399. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/timeentry.py +0 -0
  400. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/toast.py +0 -0
  401. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/toast_stack.py +0 -0
  402. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/togglegroup.py +0 -0
  403. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/toolbar.py +0 -0
  404. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tooltip.py +0 -0
  405. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tree/__init__.py +0 -0
  406. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tree/source_binding.py +0 -0
  407. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tree/treeitem.py +0 -0
  408. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tree/treenode.py +0 -0
  409. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/composites/tree/treeview.py +0 -0
  410. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/__init__.py +0 -0
  411. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/configure_mixin.py +0 -0
  412. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/entry_mixin.py +0 -0
  413. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/font_mixin.py +0 -0
  414. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/icon_mixin.py +0 -0
  415. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/localization_mixin.py +0 -0
  416. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/signal_mixin.py +0 -0
  417. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/mixins/validation_mixin.py +0 -0
  418. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/__init__.py +0 -0
  419. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/_menubutton.py +0 -0
  420. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/badge.py +0 -0
  421. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/button.py +0 -0
  422. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/card.py +0 -0
  423. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/checkbutton.py +0 -0
  424. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/checktoggle.py +0 -0
  425. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/combobox.py +0 -0
  426. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/entry.py +0 -0
  427. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/frame.py +0 -0
  428. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/gridframe.py +0 -0
  429. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/label.py +0 -0
  430. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/labelframe.py +0 -0
  431. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/optionmenu.py +0 -0
  432. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/packframe.py +0 -0
  433. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/panedwindow.py +0 -0
  434. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/progressbar.py +0 -0
  435. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/radiobutton.py +0 -0
  436. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/radiotoggle.py +0 -0
  437. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/scrollbar.py +0 -0
  438. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/separator.py +0 -0
  439. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/sizegrip.py +0 -0
  440. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/spinbox.py +0 -0
  441. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/switch.py +0 -0
  442. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/_impl/primitives/treeview.py +0 -0
  443. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/app.py +0 -0
  444. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/avatar.py +0 -0
  445. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/boolean_controls.py +0 -0
  446. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/button.py +0 -0
  447. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/buttongroup.py +0 -0
  448. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/carousel.py +0 -0
  449. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/chart.py +0 -0
  450. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/contextmenu.py +0 -0
  451. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/divider.py +0 -0
  452. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/gauge.py +0 -0
  453. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/grid.py +0 -0
  454. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/label.py +0 -0
  455. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/menubutton.py +0 -0
  456. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/numberfield.py +0 -0
  457. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/passwordfield.py +0 -0
  458. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/picture.py +0 -0
  459. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/progressbar.py +0 -0
  460. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/radio_variants.py +0 -0
  461. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/radiogroup.py +0 -0
  462. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/scrollbar.py +0 -0
  463. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/selectbutton.py +0 -0
  464. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/sizegrip.py +0 -0
  465. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/slider.py +0 -0
  466. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/spinbox.py +0 -0
  467. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/spinnerfield.py +0 -0
  468. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/splash.py +0 -0
  469. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/statusbar.py +0 -0
  470. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/textfield.py +0 -0
  471. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/theme_toggle.py +0 -0
  472. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/timefield.py +0 -0
  473. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/toast.py +0 -0
  474. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/toolbar.py +0 -0
  475. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/tooltip.py +0 -0
  476. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/types.py +0 -0
  477. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack/widgets/window.py +0 -0
  478. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack.egg-info/dependency_links.txt +0 -0
  479. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack.egg-info/entry_points.txt +0 -0
  480. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack.egg-info/requires.txt +0 -0
  481. {bootstack-0.1.7 → bootstack-0.2.0}/src/bootstack.egg-info/top_level.txt +0 -0
  482. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_dialog_anchor.py +0 -0
  483. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_events_doc_coverage.py +0 -0
  484. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_hot_reload.py +0 -0
  485. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_images.py +0 -0
  486. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_images_gui.py +0 -0
  487. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_intl_format.py +0 -0
  488. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_navmodel.py +0 -0
  489. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_on_color_contrast.py +0 -0
  490. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_store.py +0 -0
  491. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_theme_from_existing.py +0 -0
  492. {bootstack-0.1.7 → bootstack-0.2.0}/tests/test_validation_rules.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bootstack
3
- Version: 0.1.7
3
+ Version: 0.2.0
4
4
  Summary: Build desktop GUI apps in pure Python and ship them as a standalone executable — built on Tk, no Tkinter boilerplate.
5
5
  Author-email: Israel Dryer <israel.dryer@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bootstack"
7
- version = "0.1.7"
7
+ version = "0.2.0"
8
8
  description = "Build desktop GUI apps in pure Python and ship them as a standalone executable — built on Tk, no Tkinter boilerplate."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -114,7 +114,7 @@ where = ["src"]
114
114
  ]
115
115
 
116
116
  [tool.bumpversion]
117
- current_version = "0.1.7"
117
+ current_version = "0.2.0"
118
118
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)((?P<pre>a|b|rc)(?P<pre_n>\\d+))?"
119
119
  serialize = [
120
120
  "{major}.{minor}.{patch}{pre}{pre_n}",
@@ -32,6 +32,7 @@ Note:
32
32
  applied globally and affect all tkinter widgets in the application.
33
33
  """
34
34
 
35
+ import itertools
35
36
  import tkinter as tk
36
37
  import uuid
37
38
  import weakref
@@ -43,9 +44,35 @@ from typing import Any, Callable, Optional, Union
43
44
  _event_data_cache: OrderedDict[str, Any] = OrderedDict()
44
45
  _MAX_CACHE_SIZE = 100 # Prevent unbounded growth
45
46
 
47
+ # Serial number used to give every registered handler a unique name. See
48
+ # `_unique_name` for why the stock naming scheme is not safe here.
49
+ _binding_seq = itertools.count()
50
+
51
+
52
+ def _unique_name(prefix: str) -> str:
53
+ """Return a handler name that can never collide with a previous one.
54
+
55
+ Tkinter names the Tcl command behind a binding `repr(id(f)) + f.__name__`,
56
+ where `f` is a bound method created for that registration alone. Nothing
57
+ else refers to it, so releasing the command frees the method and CPython is
58
+ free to hand the very same address to the next one — measured at 498 out of
59
+ 499 consecutive bind/cancel/bind cycles. Every wrapper registered here has
60
+ the same `__name__`, so the whole name would repeat with it.
61
+
62
+ That is harmless while removals delete the command immediately, but
63
+ `_patched_unbind` defers the deletion to the next idle point. A repeated
64
+ name means a binding created in the meantime inherits the name a deletion is
65
+ already pending on, and that deletion then takes out a live handler — the
66
+ #392 failure again, on a new trigger. A serial number in the name keeps the
67
+ two apart no matter what the allocator does.
68
+ """
69
+ return '%s%d' % (prefix, next(_binding_seq))
70
+
71
+
46
72
  # Store original tkinter methods before patching
47
73
  _original_event_generate: Callable = tk.Misc.event_generate
48
74
  _original_bind: Callable = tk.Misc.bind
75
+ _original_unbind: Callable = tk.Misc.unbind
49
76
 
50
77
 
51
78
  def _patched_event_generate(
@@ -219,23 +246,60 @@ def _patched_bind(
219
246
  # Attach custom data (don't pop yet - multiple handlers may need it)
220
247
  if data_guid and data_guid in _event_data_cache:
221
248
  event.data = _event_data_cache[data_guid]
222
- # Schedule cleanup after all handlers have run
223
- widget.after_idle(lambda: _event_data_cache.pop(data_guid, None))
249
+ # Schedule cleanup after all handlers have run. On the root,
250
+ # not on `widget`: deferred work owned by a widget is torn
251
+ # down with it, so a handler that destroys its own widget
252
+ # would leave this callback pointing at a command destroy()
253
+ # had already swept — a silent background error. The root
254
+ # outlives every widget. (Same rule as `_patched_unbind`.)
255
+ try:
256
+ widget._root().after_idle(
257
+ lambda: _event_data_cache.pop(data_guid, None)
258
+ )
259
+ except (tk.TclError, AttributeError):
260
+ # No event loop left to defer onto; the LRU cap reclaims
261
+ # the entry instead.
262
+ pass
224
263
  else:
225
264
  # Data might have been evicted from cache already
226
265
  event.data = None
227
266
 
228
267
  return func(event)
229
268
 
230
- # Register wrapper function with tkinter
269
+ # Register wrapper function with tkinter. The name tkinter builds
270
+ # ends with the function's `__name__`, so making that unique is
271
+ # enough to keep a deferred deletion off a later binding.
272
+ wrapper.__name__ = _unique_name('bsvirtual')
231
273
  name = self._register(wrapper)
232
274
 
233
- # Build command with all substitutions including %d for data GUID
275
+ # Build the binding script. Its exact shape is load-bearing, and
276
+ # this must stay byte-for-byte what stock `Misc._bind` emits:
277
+ #
278
+ # * Removing a single handler works by matching the line prefix
279
+ # `if {"[<funcid> ` — note the TRAILING SPACE, which means the
280
+ # substitution list below must never be empty. A script in any
281
+ # other shape is invisible to the matcher, so the handler
282
+ # survives while its command is deleted, leaving an orphan that
283
+ # aborts every handler after it. All handlers for an event share
284
+ # ONE concatenated script, so that took out every handler
285
+ # registered after the cancelled one (#392).
286
+ # * The `if {...} == "break"` test is what lets a handler return
287
+ # 'break' to stop the remaining handlers for that event.
288
+ #
289
+ # (The trailing newline only separates this line from the next
290
+ # handler's; Tk inserts its own separator when appending a `+`
291
+ # script. It is kept to match stock exactly.)
292
+ #
293
+ # The substitution list carries %d (our data token) ahead of the
294
+ # standard values; its order must match `wrapper`'s parameters.
234
295
  # Substitution codes: https://tcl.tk/man/tcl8.6/TkCmd/bind.htm
235
- cmd = f'{name} %d %# %b %h %w %k %s %t %x %y %X %Y %A %K %N %T %E %D'
296
+ subst = '%d %# %b %h %w %k %s %t %x %y %X %Y %A %K %N %T %E %D'
297
+ cmd = '%sif {"[%s %s]" == "break"} break\n' % (
298
+ '+' if add else '', name, subst
299
+ )
236
300
 
237
301
  # Bind to widget
238
- self.tk.call('bind', self._w, sequence, cmd if not add else '+' + cmd)
302
+ self.tk.call('bind', self._w, sequence, cmd)
239
303
 
240
304
  return name
241
305
  else:
@@ -246,12 +310,92 @@ def _patched_bind(
246
310
  event.data = None
247
311
  return func(event)
248
312
 
313
+ # Real events are removed by the same patched `unbind`, so they need
314
+ # the same protection from a recycled name. See `_unique_name`.
315
+ regular_wrapper.__name__ = _unique_name('bsregular')
249
316
  return _original_bind(self, sequence, regular_wrapper, add)
250
317
 
251
318
  # No function or sequence provided, pass through to original
252
319
  return _original_bind(self, sequence, func, add)
253
320
 
254
321
 
322
+ def _patched_unbind(
323
+ self: tk.Misc,
324
+ sequence: str,
325
+ funcid: Optional[str] = None
326
+ ) -> None:
327
+ """Enhanced unbind that cannot disturb the other handlers for an event.
328
+
329
+ Stock tkinter removes one handler in two steps: rewrite the binding script
330
+ without that handler's line, then delete the Tcl command behind it. The
331
+ rewrite is correct, but the deletion is immediate — and all handlers for an
332
+ event share ONE script, so if the removal happens *while that script is
333
+ running* (a handler cancelling another handler's subscription), execution
334
+ later reaches the deleted command and the whole script aborts. Every handler
335
+ after the cancelled one is skipped for that dispatch, and the error surfaces
336
+ nowhere Python can see it.
337
+
338
+ This version neutralizes the command in place instead, then deletes it once
339
+ the current dispatch has finished. An already-running script then evaluates
340
+ a harmless no-op where the cancelled handler used to be and carries on to
341
+ the handlers after it.
342
+
343
+ Args:
344
+ self: The widget instance (automatically provided)
345
+ sequence: Event sequence the handler was bound to
346
+ funcid: Identifier returned by `bind`. When omitted, every handler for
347
+ the sequence is removed and the original implementation is used.
348
+ """
349
+ if funcid is None:
350
+ # Removing everything replaces the whole script, so there is no
351
+ # surviving line that could reference a deleted command.
352
+ return _original_unbind(self, sequence)
353
+
354
+ what = ('bind', self._w, sequence)
355
+ try:
356
+ script = self.tk.call(what)
357
+ lines = str(script).split('\n')
358
+ # Must match what `_patched_bind` emits (and stock `Misc._bind`).
359
+ prefix = 'if {"[%s ' % funcid
360
+ keep = [line for line in lines if not line.startswith(prefix)]
361
+ if len(keep) == len(lines):
362
+ # This funcid is not bound to this widget and sequence, so it is
363
+ # not ours to delete — the live binding may be somewhere else, and
364
+ # deleting its command would orphan it. Leave everything alone.
365
+ return
366
+ remaining = '\n'.join(keep)
367
+ self.tk.call(*what, remaining if remaining.strip() else '')
368
+ # Replace the command rather than deleting it, so an in-flight copy of
369
+ # the concatenated script has something harmless to call.
370
+ self.tk.createcommand(funcid, lambda *args: '')
371
+ except tk.TclError:
372
+ return
373
+
374
+ def _delete_command() -> None:
375
+ try:
376
+ self.deletecommand(funcid)
377
+ except (tk.TclError, AttributeError):
378
+ # Already swept, most likely because the widget was destroyed
379
+ # before the interpreter went idle. `AttributeError` is that same
380
+ # case seen from the other side: destroy() clears the widget's
381
+ # command bookkeeping, which `deletecommand` then cannot update.
382
+ pass
383
+
384
+ try:
385
+ # Deliberately scheduled on the root, not on `self`. Deferred work owned
386
+ # by a widget is torn down with it, and destroying a widget between the
387
+ # cancellation and the next idle point would leave the pending callback
388
+ # referring to a command that destroy() had already deleted — a silent
389
+ # error of exactly the kind this function exists to avoid. The root
390
+ # outlives every widget, and when the root itself goes the pending
391
+ # callback goes with it, which is equally fine: there is nothing left to
392
+ # clean up.
393
+ self._root().after_idle(_delete_command)
394
+ except (tk.TclError, AttributeError):
395
+ # No event loop left to defer onto; nothing can be mid-dispatch either.
396
+ _delete_command()
397
+
398
+
255
399
  def cleanup_event_cache() -> None:
256
400
  """Manually clear the entire event data cache.
257
401
 
@@ -342,9 +486,10 @@ def get_cache_size() -> int:
342
486
  def install_enhanced_events() -> None:
343
487
  """Install the enhanced event system by patching tkinter methods.
344
488
 
345
- This function applies monkey patches to tkinter's Misc class,
346
- replacing event_generate and bind with enhanced versions that
347
- support passing custom data through virtual events.
489
+ This function applies monkey patches to tkinter's Misc class, replacing
490
+ event_generate and bind with enhanced versions that support passing custom
491
+ data through virtual events, and unbind with a version that cannot disturb
492
+ the other handlers for an event while one is being removed.
348
493
 
349
494
  The patches are applied globally and affect all tkinter widgets
350
495
  in the application. This function is called automatically when
@@ -364,6 +509,7 @@ def install_enhanced_events() -> None:
364
509
  """
365
510
  tk.Misc.event_generate = _patched_event_generate
366
511
  tk.Misc.bind = _patched_bind
512
+ tk.Misc.unbind = _patched_unbind
367
513
 
368
514
 
369
515
  def uninstall_enhanced_events() -> None:
@@ -387,6 +533,7 @@ def uninstall_enhanced_events() -> None:
387
533
  """
388
534
  tk.Misc.event_generate = _original_event_generate
389
535
  tk.Misc.bind = _original_bind
536
+ tk.Misc.unbind = _original_unbind
390
537
  cleanup_event_cache()
391
538
 
392
539
 
@@ -30,11 +30,18 @@ def _platform_baseline() -> float:
30
30
 
31
31
  Tk scaling is measured in pixels-per-point (72 points per inch).
32
32
  - Windows/Linux default DPI is 96 → baseline = 96/72 ≈ 1.334
33
- - macOS default DPI is 72 → baseline = 72/72 = 1.0
33
+ - macOS default DPI is 72 on Tk 8.6 → baseline = 72/72 = 1.0
34
34
  (Retina pixel-doubling is handled by the OS, not Tk scaling)
35
+
36
+ Tk 9 changed Aqua's nominal resolution from 72 to 96 DPI, aligning it with
37
+ Windows/X11 (#375). The pixel grid is unchanged — only the physical-size
38
+ claim moved — so the baseline must key off the Tk version, not the platform
39
+ alone. Keying off platform alone reports ui_scale ~1.333 instead of 1.0 on
40
+ macOS + Tk 9 and inflates every DPI-derived measurement by a third.
35
41
  """
36
42
  import platform
37
- if platform.system() == 'Darwin':
43
+ import tkinter
44
+ if platform.system() == 'Darwin' and tkinter.TkVersion < 9.0:
38
45
  return 1.0
39
46
  return 1.33398982438864281 # 96 DPI / 72
40
47
 
@@ -54,6 +61,11 @@ class _ScalingState:
54
61
  """Get the current global scale factor."""
55
62
  return cls._scale_factor
56
63
 
64
+ @classmethod
65
+ def get_baseline(cls) -> float:
66
+ """Get the platform's nominal Tk scaling (pixels per point)."""
67
+ return cls._baseline
68
+
57
69
  @classmethod
58
70
  def get_ui_scale(cls) -> float:
59
71
  """Get the UI scale factor (relative to baseline)."""
@@ -31,6 +31,17 @@ class SerializationError(BootstackError):
31
31
  """
32
32
 
33
33
 
34
+ class InvalidChoiceError(BootstackError, ValueError):
35
+ """Raised when a keyword argument is given a value outside its valid set.
36
+
37
+ Applies to arguments that name a behavior mode drawn from a closed set,
38
+ such as `selection_mode` or `scrollbars`. The message names the value that
39
+ was rejected and lists the values that are accepted.
40
+
41
+ Also a `ValueError`, so either `BootstackError` or `ValueError` catches it.
42
+ """
43
+
44
+
34
45
  class NavigationError(BootstackError):
35
46
  """Raised when a navigation operation references a wrong key.
36
47
 
@@ -58,6 +69,7 @@ class StyleBuilderError(BootstackError):
58
69
  __all__ = [
59
70
  "BootstackError",
60
71
  "DuplicateIdError",
72
+ "InvalidChoiceError",
61
73
  "NavigationError",
62
74
  "ParentResolutionError",
63
75
  "SerializationError",
@@ -8,6 +8,8 @@ from tkinter import Misc, font as tkfont
8
8
  from tkinter.font import Font as TkFont
9
9
  from typing import Any, Literal, NamedTuple
10
10
 
11
+ from bootstack._runtime.utility import _ScalingState
12
+
11
13
 
12
14
  # =============================================================================
13
15
  # Token model
@@ -90,12 +92,19 @@ def build_desktop_tokens(
90
92
  elif system == "darwin":
91
93
  default_ui = "SF Pro Text"
92
94
  default_mono = "Menlo"
93
- # Tk's pt-to-pixel conversion uses the active scaling factor.
94
- # macOS defaults to 1.0 (72 DPI baseline) vs ~1.334 on Win/Linux,
95
- # so the same point size renders ~25% smaller on Mac. Bump the
96
- # base to keep visual parity across platforms and align with the
97
- # macOS HIG default control text size (13pt).
98
- base = _clamp_base_size(base + 2)
95
+ # Tk's pt-to-pixel conversion uses the active scaling factor. On Tk 8.6
96
+ # macOS defaults to 1.0 (72 DPI baseline) vs ~1.334 on Win/Linux, so
97
+ # the same point size renders ~25% smaller on Mac. Bump the base to
98
+ # align with the macOS HIG default control text size (13pt).
99
+ #
100
+ # Tk 9 moved Aqua to a 96 DPI baseline (#375), so a "point" is no
101
+ # longer the same physical size and the raw bump double-counts (a
102
+ # `body` label rendered at 20px linespace instead of 16px). The target
103
+ # is a physical size, so express it in the active point unit: divide by
104
+ # the baseline ratio. Tk 8.6 -> 13pt (unchanged), Tk 9 -> 10pt, which
105
+ # is the same rendered size and matches the point size Tk 9 itself
106
+ # picks for TkDefaultFont.
107
+ base = _clamp_base_size(round((base + 2) / _ScalingState.get_baseline()))
99
108
  else:
100
109
  default_ui = "DejaVu Sans"
101
110
  default_mono = "DejaVu Sans Mono"
@@ -13,20 +13,30 @@ if TYPE_CHECKING:
13
13
  from bootstack.streams import Stream
14
14
 
15
15
 
16
- def adapt_handler(handler: Callable[[Any], Any]) -> Callable[[Any], Any]:
16
+ def adapt_handler(handler: Callable[[Any], Any]) -> Callable[[Any], None]:
17
17
  """Wrap a public handler so it receives a bootstack event, not a raw one.
18
18
 
19
19
  Data-carrying events (emitted with `data=<payload>`) deliver the payload
20
20
  object directly — the handler argument *is* the payload. Native/context
21
21
  events carry no payload, so the raw toolkit event is curated into a clean
22
22
  :class:`~bootstack.events.Event` first.
23
+
24
+ A handler's return value is deliberately discarded. The underlying toolkit
25
+ treats one particular return value as "stop the remaining handlers for this
26
+ event", which would make any handler that happens to return that value drop
27
+ its siblings — silently, and with nothing in the public API to explain it.
28
+ Suppressing later handlers is a capability worth designing on purpose rather
29
+ than inheriting, so nothing is promised here. Handlers bound internally do
30
+ not pass through this wrapper and are unaffected.
23
31
  """
24
32
 
25
- def _wrapped(raw: Any) -> Any:
33
+ def _wrapped(raw: Any) -> None:
26
34
  payload = getattr(raw, "data", None)
27
35
  if payload is not None:
28
- return handler(payload)
29
- return handler(Event._from_tk(raw))
36
+ handler(payload)
37
+ else:
38
+ handler(Event._from_tk(raw))
39
+ return None
30
40
 
31
41
  return _wrapped
32
42
 
@@ -0,0 +1,56 @@
1
+ """Construction-time checking for keyword arguments with a closed value set.
2
+
3
+ Many widget keyword arguments name a behavior mode drawn from a small, closed
4
+ set — `selection_mode`, `sorting_mode`, `scrollbars`. The widgets read those
5
+ values by comparing against one literal (`mode == 'multi'`), so a near-miss
6
+ spelling does not fail: it just takes the other branch. `selection_mode='multiple'`
7
+ turns multi-select off and reports nothing, which reads as a broken widget rather
8
+ than a typo.
9
+
10
+ `validate_choice` closes that gap. It runs at construction, names the value it
11
+ rejected and the set it accepts, and raises `InvalidChoiceError` — both a
12
+ `BootstackError` and a `ValueError`, so either is a valid thing to catch. Call it
13
+ before anything else in `__init__`, ahead of parent resolution: a bad value
14
+ should be reported as a bad value, not masked by whatever fails next.
15
+
16
+ Only use it for a genuinely closed set. Arguments that widen their literal with
17
+ `| str` — `accent`, `surface` — accept values the alias does not spell out
18
+ (`'primary[+1]'`, `'primary[500]'`) and must not be checked this way.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from typing import Any, Sequence, TypeVar, get_args
23
+
24
+ from bootstack.errors import InvalidChoiceError
25
+ from bootstack.widgets.types import SelectionMode
26
+
27
+ T = TypeVar("T")
28
+
29
+ SELECTION_MODES = get_args(SelectionMode)
30
+ """Accepted `selection_mode` values, read from the `SelectionMode` alias so the
31
+ check cannot drift from the type."""
32
+
33
+
34
+ def validate_choice(value: T, valid: Sequence[Any], *, param: str, widget: str) -> T:
35
+ """Return `value` if it is in `valid`, otherwise raise.
36
+
37
+ Args:
38
+ value: The value the caller supplied.
39
+ valid: The accepted values, in the order they should be listed back.
40
+ param: Name of the keyword argument being checked, e.g.
41
+ `'selection_mode'`.
42
+ widget: Name of the public widget or method the caller used, e.g.
43
+ `'DataTable'`, used to open the message.
44
+
45
+ Returns:
46
+ The value, unchanged.
47
+
48
+ Raises:
49
+ InvalidChoiceError: If `value` is not in `valid`.
50
+ """
51
+ if value in valid:
52
+ return value
53
+ listed = ", ".join(repr(v) for v in valid)
54
+ raise InvalidChoiceError(
55
+ f"{widget}({param}={value!r}) is not a valid {param}. Valid values: {listed}."
56
+ )
@@ -255,17 +255,74 @@ def grid_sticky(horizontal: str | None, vertical: str | None) -> str:
255
255
  return sticky
256
256
 
257
257
 
258
+ def resolve_layout_items(
259
+ layout: str,
260
+ horizontal_items: str | None,
261
+ vertical_items: str | None,
262
+ *,
263
+ column_horizontal: str | None = None,
264
+ ) -> tuple[str | None, str | None]:
265
+ """Fill in a multi-mode container's unset `*_items` for its layout mode.
266
+
267
+ Containers that can lay out as a column, a row, or a grid (`Card`, `GroupBox`,
268
+ `Expander`, `Tabs`, `PageStack`, `SplitView`, the AppShell page) take
269
+ `horizontal_items`/`vertical_items` as `None`-means-unset and need a default
270
+ that depends on the mode.
271
+
272
+ A **grid** gets concrete values, because the grid engine has no notion of an
273
+ unset axis — cells stretch unless told otherwise. A **column** or **row**
274
+ keeps `None` and lets `FlexFrame` resolve it: the engine's own defaults are
275
+ exactly what these containers used to hard-code (the stacking axis starts at
276
+ its leading edge, the cross axis centers), and leaving the cross axis unset is
277
+ what allows a child to contribute its own default — which is how a row of
278
+ input fields stays aligned whether or not each field carries a validation
279
+ message (#394). Pre-resolving to `'center'` here silently suppressed that.
280
+
281
+ Args:
282
+ layout: The container's layout mode — `'column'`, `'row'` or `'grid'`.
283
+ horizontal_items: The x-axis value as given, or None if unset.
284
+ vertical_items: The y-axis value as given, or None if unset.
285
+ column_horizontal: Cross-axis override for column mode, for a container
286
+ whose column should not center — the AppShell page stretches instead.
287
+ Defaults to None (use the engine default).
288
+
289
+ Returns:
290
+ The pair to hand the layout frame. Either element may be None in column or
291
+ row mode, meaning "unset — let the engine decide"; both are concrete for a
292
+ grid. The None is only ever passed to `FlexFrame`; the grid path
293
+ (`_merge_layout_options`) runs solely in grid mode, where both are set.
294
+ """
295
+ if layout == "grid":
296
+ return (horizontal_items or "stretch", vertical_items or "stretch")
297
+ if layout == "column" and horizontal_items is None:
298
+ horizontal_items = column_horizontal
299
+ return (horizontal_items, vertical_items)
300
+
301
+
258
302
  def _flex_child_opts(child: PublicWidgetBase, layout_kw: dict) -> dict:
259
303
  """Build the FlexFrame per-child opts dict from resolved layout kwargs.
260
304
 
261
305
  Expects `_expand_margin` to have already converted margins to padx/pady.
262
306
  Carries spacer metadata when `child` is a `Spacer` (duck-typed to avoid a
263
307
  circular import).
308
+
309
+ A widget may also declare its own preferred cross-axis alignment via a
310
+ `_flex_horizontal_default` / `_flex_vertical_default` class attribute (same
311
+ duck-typing). That is a *soft* default: it applies only when neither the
312
+ child nor the container was given an explicit alignment, so it can never
313
+ override an instruction the author actually wrote. The field family uses it
314
+ to sit top-aligned in a Row instead of centering, since a field carrying a
315
+ validation message is taller than one that isn't (#394).
264
316
  """
265
317
  opts: dict[str, Any] = {}
266
318
  for key in ("grow", "horizontal", "vertical", "padx", "pady"):
267
319
  if key in layout_kw:
268
320
  opts[key] = layout_kw[key]
321
+ for axis in ("horizontal", "vertical"):
322
+ if axis not in opts:
323
+ declared = getattr(child, f"_flex_{axis}_default", None)
324
+ if declared is not None:
325
+ opts[f"_{axis}_default"] = declared
269
326
  if getattr(child, "_is_spacer", False):
270
327
  opts["_spacer"] = True
271
328
  opts["_spacer_size"] = getattr(child, "_spacer_size", None)
@@ -23,6 +23,13 @@ class FieldAddonMixin:
23
23
  #: field wrappers override it (`'number'`, `'date'`, `'time'`).
24
24
  _VALIDATION_KIND: str = "text"
25
25
 
26
+ #: Cross-axis alignment a field contributes when the author set none. A
27
+ #: field that shows a validation message is taller than one that doesn't, so
28
+ #: centering them in a Row leaves the shorter ones sitting low; top-aligning
29
+ #: lines their labels and entries up instead. Soft — an explicit `vertical`
30
+ #: on the field or `vertical_items` on the container still wins (#394).
31
+ _flex_vertical_default: str = "top"
32
+
26
33
  _ADDON_TYPES = {
27
34
  "button": "bootstack.widgets._impl.primitives.button::Button",
28
35
  "label": "bootstack.widgets._impl.primitives.label::Label",
@@ -13,6 +13,7 @@ from bootstack.i18n import MessageCatalog, IntlFormatter
13
13
  from bootstack.widgets._impl.primitives.spinbox import Spinbox
14
14
  from bootstack.widgets._impl.mixins import ValidationMixin
15
15
  from bootstack.widgets._impl.mixins.configure_mixin import configure_delegate
16
+ from bootstack.widgets._impl._parts.textentry_part import _UNSET
16
17
 
17
18
 
18
19
  class SpinnerEntryPart(ValidationMixin, Spinbox):
@@ -307,16 +308,17 @@ class SpinnerEntryPart(ValidationMixin, Spinbox):
307
308
  """Remove callback from <<Change>> event."""
308
309
  self.unbind('<<Change>>', bind_id)
309
310
 
310
- def value(self, value=None):
311
+ def value(self, value=_UNSET):
311
312
  """Get or set the parsed/committed value.
312
313
 
313
314
  Args:
314
- value: If provided, sets the display text and internal value with formatting
315
+ value: If provided, sets the display text and internal value with
316
+ formatting. Pass `None` to clear the field.
315
317
 
316
318
  Returns:
317
319
  Current parsed value if no argument provided, None otherwise
318
320
  """
319
- if value is None:
321
+ if value is _UNSET:
320
322
  return self._value
321
323
  else:
322
324
  # Store the value and format it for display
@@ -7,6 +7,11 @@ from bootstack.widgets._impl.primitives.entry import Entry
7
7
  from bootstack.widgets._impl.mixins import ValidationMixin
8
8
  from bootstack.widgets._impl.mixins.configure_mixin import configure_delegate
9
9
 
10
+ #: Sentinel marking "no argument passed" for the combined get/set accessors below.
11
+ #: `None` cannot serve as that sentinel — it is a real value meaning "empty", and
12
+ #: using it to mean "get" made `value(None)` a silent no-op instead of clearing.
13
+ _UNSET: Any = object()
14
+
10
15
 
11
16
  class TextEntryPart(ValidationMixin, Entry):
12
17
  """Internationalization-aware entry widget with deferred parsing and formatting.
@@ -250,16 +255,17 @@ class TextEntryPart(ValidationMixin, Entry):
250
255
  """Unbind from `<<Change>>`."""
251
256
  self.unbind('<<Change>>', bind_id)
252
257
 
253
- def value(self, value=None):
258
+ def value(self, value=_UNSET):
254
259
  """Get or set the parsed/committed value.
255
260
 
256
261
  Args:
257
- value: If provided, sets the display text and internal value with formatting
262
+ value: If provided, sets the display text and internal value with
263
+ formatting. Pass `None` to clear the field.
258
264
 
259
265
  Returns:
260
266
  Current parsed value if no argument provided, None otherwise
261
267
  """
262
- if value is None:
268
+ if value is _UNSET:
263
269
  return self._value
264
270
  else:
265
271
  # Store the value and format it for display
@@ -269,6 +269,31 @@ class DateEntry(Field):
269
269
  self.date_picker_button.pack_forget()
270
270
  return None
271
271
 
272
+ def _apply_picked(self, val) -> None:
273
+ """Apply a value chosen in the picker and announce it as a user commit.
274
+
275
+ Choosing a date is a commit, exactly like typing one and pressing
276
+ Return, so it has to emit `<<Change>>` — bound signals, `on_change`
277
+ handlers, and form change tracking all listen for that event. Assigning
278
+ `value` alone does not emit it: the event comes from the entry's own
279
+ change detection, which otherwise only runs on FocusOut and Return.
280
+
281
+ `_check_if_changed` also advances the entry's last-seen value, so a
282
+ later focus-out cannot fire a second `<<Change>>` for this same
283
+ selection — which hand-building the event here would allow whenever the
284
+ entry already held focus. It is safe to call twice: the second call
285
+ sees no difference and does nothing.
286
+
287
+ Range mode is left alone; `_set_range` already emits its own event.
288
+ """
289
+ if self._selection_mode == "range":
290
+ self.value = val
291
+ return
292
+ if isinstance(val, datetime):
293
+ val = val.date()
294
+ self.value = val
295
+ self._entry._check_if_changed()
296
+
272
297
  def _show_date_picker(self):
273
298
  """Open the calendar picker dialog.
274
299
 
@@ -336,10 +361,10 @@ class DateEntry(Field):
336
361
  result = payload.get('result') if isinstance(payload, dict) else payload
337
362
  if self._selection_mode == "range":
338
363
  if isinstance(result, tuple) and len(result) == 2:
339
- self.value = result
364
+ self._apply_picked(result)
340
365
  else:
341
366
  if isinstance(result, (date, datetime)):
342
- self.value = result
367
+ self._apply_picked(result)
343
368
 
344
369
  dialog.on_result(lambda x: _on_result(x))
345
370
  dialog.show(
@@ -366,12 +391,10 @@ class DateEntry(Field):
366
391
  selected = dialog.result
367
392
  if self._selection_mode == "range":
368
393
  if isinstance(selected, tuple) and len(selected) == 2:
369
- self.value = selected
394
+ self._apply_picked(selected)
370
395
  else:
371
- if isinstance(selected, datetime):
372
- selected = selected.date()
373
- if isinstance(selected, date):
374
- self.value = selected
396
+ if isinstance(selected, (date, datetime)):
397
+ self._apply_picked(selected)
375
398
 
376
399
  # Return focus to the entry after dialog closes
377
400
  # Note: focus_force() is needed on Windows after override-redirect dialogs