bootstack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bootstack/__init__.py +157 -0
- bootstack/__main__.py +5 -0
- bootstack/_core/__init__.py +21 -0
- bootstack/_core/capabilities/__init__.py +45 -0
- bootstack/_core/capabilities/after.py +103 -0
- bootstack/_core/capabilities/bind.py +192 -0
- bootstack/_core/capabilities/bindtags.py +112 -0
- bootstack/_core/capabilities/busy.py +72 -0
- bootstack/_core/capabilities/clipboard.py +89 -0
- bootstack/_core/capabilities/focus.py +118 -0
- bootstack/_core/capabilities/grab.py +65 -0
- bootstack/_core/capabilities/grid.py +211 -0
- bootstack/_core/capabilities/localization.py +231 -0
- bootstack/_core/capabilities/pack.py +140 -0
- bootstack/_core/capabilities/place.py +113 -0
- bootstack/_core/capabilities/selection.py +136 -0
- bootstack/_core/capabilities/signals.py +244 -0
- bootstack/_core/capabilities/winfo.py +315 -0
- bootstack/_core/colorutils.py +234 -0
- bootstack/_core/exceptions.py +25 -0
- bootstack/_core/images.py +463 -0
- bootstack/_core/mixins/__init__.py +1 -0
- bootstack/_core/mixins/ttk_state.py +35 -0
- bootstack/_core/mixins/widget.py +132 -0
- bootstack/_core/paths.py +49 -0
- bootstack/_core/publisher.py +149 -0
- bootstack/_core/variables.py +62 -0
- bootstack/_runtime/__init__.py +3 -0
- bootstack/_runtime/app.py +930 -0
- bootstack/_runtime/base_window.py +945 -0
- bootstack/_runtime/events.py +399 -0
- bootstack/_runtime/shortcuts.py +496 -0
- bootstack/_runtime/tk_patch.py +43 -0
- bootstack/_runtime/toplevel.py +276 -0
- bootstack/_runtime/utility.py +457 -0
- bootstack/_runtime/visual_focus.py +240 -0
- bootstack/_runtime/window_utilities.py +1123 -0
- bootstack/assets/__init__.py +21 -0
- bootstack/assets/bootstack.ico +0 -0
- bootstack/assets/bootstack.png +0 -0
- bootstack/assets/elements/__init__.py +0 -0
- bootstack/assets/elements/badge-pill.png +0 -0
- bootstack/assets/elements/badge-square.png +0 -0
- bootstack/assets/elements/button-compact.png +0 -0
- bootstack/assets/elements/button-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-after-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-before-v-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-h-default.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-compact.png +0 -0
- bootstack/assets/elements/buttongroup-center-v-default.png +0 -0
- bootstack/assets/elements/card.png +0 -0
- bootstack/assets/elements/checkbox-checked.png +0 -0
- bootstack/assets/elements/checkbox-indeterminate.png +0 -0
- bootstack/assets/elements/checkbox-unchecked.png +0 -0
- bootstack/assets/elements/field.png +0 -0
- bootstack/assets/elements/input-addon-compact.png +0 -0
- bootstack/assets/elements/input-addon-default.png +0 -0
- bootstack/assets/elements/input-compact.png +0 -0
- bootstack/assets/elements/input-default.png +0 -0
- bootstack/assets/elements/list-item-separated.png +0 -0
- bootstack/assets/elements/list-item.png +0 -0
- bootstack/assets/elements/listrow-compact.png +0 -0
- bootstack/assets/elements/listrow-default.png +0 -0
- bootstack/assets/elements/manifest.toml +361 -0
- bootstack/assets/elements/menu-item.png +0 -0
- bootstack/assets/elements/navitem-compact.png +0 -0
- bootstack/assets/elements/navitem-default.png +0 -0
- bootstack/assets/elements/progressbar-h-compact.png +0 -0
- bootstack/assets/elements/progressbar-h-default.png +0 -0
- bootstack/assets/elements/progressbar-v-compact.png +0 -0
- bootstack/assets/elements/progressbar-v-default.png +0 -0
- bootstack/assets/elements/radiobutton.png +0 -0
- bootstack/assets/elements/scrollbar-horizontal.png +0 -0
- bootstack/assets/elements/scrollbar-vertical.png +0 -0
- bootstack/assets/elements/slider-handle.png +0 -0
- bootstack/assets/elements/slider-track-h.png +0 -0
- bootstack/assets/elements/slider-track-v.png +0 -0
- bootstack/assets/elements/switch-off.png +0 -0
- bootstack/assets/elements/switch-on.png +0 -0
- bootstack/assets/elements/tab-h.png +0 -0
- bootstack/assets/elements/tab-v.png +0 -0
- bootstack/assets/icons/bootstrap.ttf +0 -0
- bootstack/assets/icons/glyphmap.json +2080 -0
- bootstack/assets/icons/icon_metrics.json +12470 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ar/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/bg/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/cs/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/da/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/de/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/en/LC_MESSAGES/bootstack.po +878 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/es/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/fr/LC_MESSAGES/bootstack.po +856 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/he/LC_MESSAGES/bootstack.po +854 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/hi/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/it/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ja/LC_MESSAGES/bootstack.po +917 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/ko/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nb/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/nl/LC_MESSAGES/bootstack.po +844 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/pt_BR/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sl/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/sv/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/tr/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_CN/LC_MESSAGES/bootstack.po +845 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.mo +0 -0
- bootstack/assets/locales/zh_TW/LC_MESSAGES/bootstack.po +845 -0
- bootstack/cli/__init__.py +133 -0
- bootstack/cli/__main__.py +6 -0
- bootstack/cli/add.py +395 -0
- bootstack/cli/appicon.py +285 -0
- bootstack/cli/build.py +115 -0
- bootstack/cli/config.py +313 -0
- bootstack/cli/demo.py +564 -0
- bootstack/cli/dev.py +153 -0
- bootstack/cli/doctor.py +195 -0
- bootstack/cli/icons.py +98 -0
- bootstack/cli/promote.py +120 -0
- bootstack/cli/pyinstaller.py +268 -0
- bootstack/cli/run.py +95 -0
- bootstack/cli/start.py +117 -0
- bootstack/cli/templates/__init__.py +931 -0
- bootstack/clipboard.py +48 -0
- bootstack/constants.py +318 -0
- bootstack/data/README.md +615 -0
- bootstack/data/__init__.py +78 -0
- bootstack/data/_observable.py +276 -0
- bootstack/data/base.py +780 -0
- bootstack/data/file_source.py +367 -0
- bootstack/data/memory_source.py +450 -0
- bootstack/data/query.py +367 -0
- bootstack/data/readers.py +289 -0
- bootstack/data/sqlite_source.py +869 -0
- bootstack/data/types.py +354 -0
- bootstack/data/writers.py +232 -0
- bootstack/dev/__init__.py +36 -0
- bootstack/dev/_capture.py +141 -0
- bootstack/dev/_env.py +43 -0
- bootstack/dev/_registry.py +140 -0
- bootstack/dev/_reloader.py +351 -0
- bootstack/dev/_reset.py +50 -0
- bootstack/dev/_watcher.py +91 -0
- bootstack/dialogs/__init__.py +948 -0
- bootstack/dialogs/_impl/__init__.py +47 -0
- bootstack/dialogs/_impl/colorchooser.py +588 -0
- bootstack/dialogs/_impl/datedialog.py +450 -0
- bootstack/dialogs/_impl/dialog.py +594 -0
- bootstack/dialogs/_impl/filterdialog.py +358 -0
- bootstack/dialogs/_impl/fontdialog.py +364 -0
- bootstack/dialogs/_impl/formdialog.py +564 -0
- bootstack/dialogs/_impl/message.py +486 -0
- bootstack/dialogs/_impl/query.py +570 -0
- bootstack/errors.py +67 -0
- bootstack/events/__init__.py +111 -0
- bootstack/events/_event.py +135 -0
- bootstack/events/_payloads.py +539 -0
- bootstack/events/_subscription.py +38 -0
- bootstack/i18n/README.md +77 -0
- bootstack/i18n/__init__.py +23 -0
- bootstack/i18n/catalog.py +121 -0
- bootstack/i18n/intl_format.py +584 -0
- bootstack/i18n/msgcat.py +425 -0
- bootstack/i18n/specs.py +156 -0
- bootstack/images.py +563 -0
- bootstack/py.typed +1 -0
- bootstack/scheduling/__init__.py +11 -0
- bootstack/scheduling/_schedule.py +218 -0
- bootstack/shortcuts.py +21 -0
- bootstack/signals/README.md +98 -0
- bootstack/signals/__init__.py +10 -0
- bootstack/signals/integration.py +100 -0
- bootstack/signals/signal.py +353 -0
- bootstack/signals/types.py +6 -0
- bootstack/store.py +286 -0
- bootstack/streams/__init__.py +12 -0
- bootstack/streams/_stream.py +321 -0
- bootstack/style/__init__.py +38 -0
- bootstack/style/builders/__init__.py +51 -0
- bootstack/style/builders/badge.py +46 -0
- bootstack/style/builders/button.py +339 -0
- bootstack/style/builders/buttongroup.py +311 -0
- bootstack/style/builders/calendar.py +271 -0
- bootstack/style/builders/checkbutton.py +110 -0
- bootstack/style/builders/combobox.py +113 -0
- bootstack/style/builders/contextmenu.py +268 -0
- bootstack/style/builders/entry.py +82 -0
- bootstack/style/builders/expander.py +148 -0
- bootstack/style/builders/field.py +335 -0
- bootstack/style/builders/frame.py +50 -0
- bootstack/style/builders/label.py +28 -0
- bootstack/style/builders/labelframe.py +34 -0
- bootstack/style/builders/listview.py +369 -0
- bootstack/style/builders/menubar.py +91 -0
- bootstack/style/builders/menubutton.py +359 -0
- bootstack/style/builders/panedwindow.py +25 -0
- bootstack/style/builders/progressbar.py +67 -0
- bootstack/style/builders/radiobutton.py +99 -0
- bootstack/style/builders/scale.py +64 -0
- bootstack/style/builders/scrollbar.py +225 -0
- bootstack/style/builders/separator.py +49 -0
- bootstack/style/builders/sidenav.py +643 -0
- bootstack/style/builders/sizegrip.py +15 -0
- bootstack/style/builders/spinbox.py +119 -0
- bootstack/style/builders/switch.py +70 -0
- bootstack/style/builders/tabitem.py +204 -0
- bootstack/style/builders/togglegroup.py +294 -0
- bootstack/style/builders/toolbutton.py +275 -0
- bootstack/style/builders/tooltip.py +26 -0
- bootstack/style/builders/treeview.py +193 -0
- bootstack/style/builders/utils.py +455 -0
- bootstack/style/builders_tk/__init__.py +16 -0
- bootstack/style/builders_tk/defaults.py +229 -0
- bootstack/style/element.py +173 -0
- bootstack/style/fonts.py +123 -0
- bootstack/style/style.py +609 -0
- bootstack/style/style_builder_base.py +716 -0
- bootstack/style/style_builder_mixed.py +93 -0
- bootstack/style/style_builder_tk.py +109 -0
- bootstack/style/style_builder_ttk.py +353 -0
- bootstack/style/style_resolver.py +447 -0
- bootstack/style/theme.py +245 -0
- bootstack/style/theme_provider.py +471 -0
- bootstack/style/themes/__init__.py +128 -0
- bootstack/style/tk_patch.py +5 -0
- bootstack/style/token_maps.py +41 -0
- bootstack/style/types.py +32 -0
- bootstack/style/typography.py +523 -0
- bootstack/style/utility.py +746 -0
- bootstack/types.py +39 -0
- bootstack/validation/__init__.py +6 -0
- bootstack/validation/types.py +15 -0
- bootstack/validation/validation_result.py +17 -0
- bootstack/validation/validation_rules.py +205 -0
- bootstack/widgets/__init__.py +74 -0
- bootstack/widgets/_core/__init__.py +31 -0
- bootstack/widgets/_core/app_config.py +266 -0
- bootstack/widgets/_core/base.py +461 -0
- bootstack/widgets/_core/container.py +334 -0
- bootstack/widgets/_core/context.py +35 -0
- bootstack/widgets/_core/events.py +130 -0
- bootstack/widgets/_core/field_mixin.py +353 -0
- bootstack/widgets/_core/icon_image_props.py +72 -0
- bootstack/widgets/_core/image_binding.py +93 -0
- bootstack/widgets/_core/navmodel.py +345 -0
- bootstack/widgets/_core/options.py +210 -0
- bootstack/widgets/_core/selection_group.py +130 -0
- bootstack/widgets/_core/window_controls.py +96 -0
- bootstack/widgets/_core/window_menu.py +246 -0
- bootstack/widgets/_impl/__init__.py +1 -0
- bootstack/widgets/_impl/_internal/__init__.py +0 -0
- bootstack/widgets/_impl/_internal/wrapper_base.py +307 -0
- bootstack/widgets/_impl/_parts/__init__.py +11 -0
- bootstack/widgets/_impl/_parts/numberentry_part.py +385 -0
- bootstack/widgets/_impl/_parts/spinnerentry_part.py +434 -0
- bootstack/widgets/_impl/_parts/textentry_part.py +406 -0
- bootstack/widgets/_impl/composites/__init__.py +33 -0
- bootstack/widgets/_impl/composites/_dateutils.py +33 -0
- bootstack/widgets/_impl/composites/_image_fit.py +105 -0
- bootstack/widgets/_impl/composites/accordion.py +381 -0
- bootstack/widgets/_impl/composites/avatar.py +191 -0
- bootstack/widgets/_impl/composites/buttongroup.py +371 -0
- bootstack/widgets/_impl/composites/calendar.py +972 -0
- bootstack/widgets/_impl/composites/carousel.py +567 -0
- bootstack/widgets/_impl/composites/chart.py +882 -0
- bootstack/widgets/_impl/composites/compositeframe.py +298 -0
- bootstack/widgets/_impl/composites/contextmenu.py +1951 -0
- bootstack/widgets/_impl/composites/dateentry.py +404 -0
- bootstack/widgets/_impl/composites/dropdownbutton.py +325 -0
- bootstack/widgets/_impl/composites/expander.py +515 -0
- bootstack/widgets/_impl/composites/field.py +670 -0
- bootstack/widgets/_impl/composites/form.py +1066 -0
- bootstack/widgets/_impl/composites/gallery.py +551 -0
- bootstack/widgets/_impl/composites/list/__init__.py +15 -0
- bootstack/widgets/_impl/composites/list/listitem.py +802 -0
- bootstack/widgets/_impl/composites/list/listview.py +1433 -0
- bootstack/widgets/_impl/composites/menu/__init__.py +18 -0
- bootstack/widgets/_impl/composites/menu/model.py +358 -0
- bootstack/widgets/_impl/composites/menu/render_native.py +134 -0
- bootstack/widgets/_impl/composites/menu/render_themed.py +134 -0
- bootstack/widgets/_impl/composites/meter.py +860 -0
- bootstack/widgets/_impl/composites/numericentry.py +201 -0
- bootstack/widgets/_impl/composites/pagestack.py +395 -0
- bootstack/widgets/_impl/composites/passwordentry.py +142 -0
- bootstack/widgets/_impl/composites/pathentry.py +168 -0
- bootstack/widgets/_impl/composites/picture.py +289 -0
- bootstack/widgets/_impl/composites/radiogroup.py +511 -0
- bootstack/widgets/_impl/composites/scrolledtext.py +375 -0
- bootstack/widgets/_impl/composites/scrolledtext.pyi +186 -0
- bootstack/widgets/_impl/composites/scrollview.py +764 -0
- bootstack/widgets/_impl/composites/selectbox.py +1026 -0
- bootstack/widgets/_impl/composites/shell/__init__.py +40 -0
- bootstack/widgets/_impl/composites/shell/content_host.py +52 -0
- bootstack/widgets/_impl/composites/shell/layout.py +335 -0
- bootstack/widgets/_impl/composites/shell/nav_panel.py +345 -0
- bootstack/widgets/_impl/composites/shell/providers.py +558 -0
- bootstack/widgets/_impl/composites/shell/rail.py +117 -0
- bootstack/widgets/_impl/composites/shell/shell.py +581 -0
- bootstack/widgets/_impl/composites/shell/workspace.py +273 -0
- bootstack/widgets/_impl/composites/sidenav/__init__.py +16 -0
- bootstack/widgets/_impl/composites/sidenav/header.py +81 -0
- bootstack/widgets/_impl/composites/sidenav/separator.py +44 -0
- bootstack/widgets/_impl/composites/slider/__init__.py +7 -0
- bootstack/widgets/_impl/composites/slider/_shared.py +195 -0
- bootstack/widgets/_impl/composites/slider/rangeslider.py +982 -0
- bootstack/widgets/_impl/composites/slider/slider.py +851 -0
- bootstack/widgets/_impl/composites/spinnerentry.py +185 -0
- bootstack/widgets/_impl/composites/tableview/__init__.py +5 -0
- bootstack/widgets/_impl/composites/tableview/tableview.py +3388 -0
- bootstack/widgets/_impl/composites/tableview/types.py +169 -0
- bootstack/widgets/_impl/composites/tabs/__init__.py +23 -0
- bootstack/widgets/_impl/composites/tabs/tabitem.py +389 -0
- bootstack/widgets/_impl/composites/tabs/tabs.py +974 -0
- bootstack/widgets/_impl/composites/tabs/tabview.py +650 -0
- bootstack/widgets/_impl/composites/textarea/__init__.py +26 -0
- bootstack/widgets/_impl/composites/textarea/change.py +46 -0
- bootstack/widgets/_impl/composites/textarea/codeeditor.py +526 -0
- bootstack/widgets/_impl/composites/textarea/core.py +495 -0
- bootstack/widgets/_impl/composites/textarea/decoration.py +42 -0
- bootstack/widgets/_impl/composites/textarea/diff.py +127 -0
- bootstack/widgets/_impl/composites/textarea/extensions/__init__.py +1 -0
- bootstack/widgets/_impl/composites/textarea/extensions/bracket_matcher.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/indent_guides.py +158 -0
- bootstack/widgets/_impl/composites/textarea/extensions/line_numbers.py +138 -0
- bootstack/widgets/_impl/composites/textarea/extensions/pygments_highlighter.py +312 -0
- bootstack/widgets/_impl/composites/textarea/extensions/smart_indent.py +177 -0
- bootstack/widgets/_impl/composites/textarea/filter.py +171 -0
- bootstack/widgets/_impl/composites/textarea/search_overlay.py +459 -0
- bootstack/widgets/_impl/composites/textarea/sidebar.py +88 -0
- bootstack/widgets/_impl/composites/textarea/style_registry.py +178 -0
- bootstack/widgets/_impl/composites/textarea/textarea.py +606 -0
- bootstack/widgets/_impl/composites/textarea/undo.py +217 -0
- bootstack/widgets/_impl/composites/textentry.py +57 -0
- bootstack/widgets/_impl/composites/timeentry.py +176 -0
- bootstack/widgets/_impl/composites/toast.py +390 -0
- bootstack/widgets/_impl/composites/toast_stack.py +156 -0
- bootstack/widgets/_impl/composites/togglegroup.py +418 -0
- bootstack/widgets/_impl/composites/toolbar.py +608 -0
- bootstack/widgets/_impl/composites/tooltip.py +491 -0
- bootstack/widgets/_impl/composites/tree/__init__.py +7 -0
- bootstack/widgets/_impl/composites/tree/source_binding.py +136 -0
- bootstack/widgets/_impl/composites/tree/treeitem.py +393 -0
- bootstack/widgets/_impl/composites/tree/treenode.py +174 -0
- bootstack/widgets/_impl/composites/tree/treeview.py +841 -0
- bootstack/widgets/_impl/mixins/__init__.py +24 -0
- bootstack/widgets/_impl/mixins/configure_mixin.py +216 -0
- bootstack/widgets/_impl/mixins/entry_mixin.py +134 -0
- bootstack/widgets/_impl/mixins/font_mixin.py +368 -0
- bootstack/widgets/_impl/mixins/icon_mixin.py +61 -0
- bootstack/widgets/_impl/mixins/localization_mixin.py +253 -0
- bootstack/widgets/_impl/mixins/signal_mixin.py +268 -0
- bootstack/widgets/_impl/mixins/validation_mixin.py +226 -0
- bootstack/widgets/_impl/primitives/__init__.py +49 -0
- bootstack/widgets/_impl/primitives/_menubutton.py +107 -0
- bootstack/widgets/_impl/primitives/badge.py +45 -0
- bootstack/widgets/_impl/primitives/button.py +76 -0
- bootstack/widgets/_impl/primitives/card.py +45 -0
- bootstack/widgets/_impl/primitives/checkbutton.py +124 -0
- bootstack/widgets/_impl/primitives/checktoggle.py +62 -0
- bootstack/widgets/_impl/primitives/combobox.py +156 -0
- bootstack/widgets/_impl/primitives/entry.py +87 -0
- bootstack/widgets/_impl/primitives/flexframe.py +448 -0
- bootstack/widgets/_impl/primitives/frame.py +185 -0
- bootstack/widgets/_impl/primitives/gridframe.py +546 -0
- bootstack/widgets/_impl/primitives/label.py +84 -0
- bootstack/widgets/_impl/primitives/labelframe.py +54 -0
- bootstack/widgets/_impl/primitives/optionmenu.py +387 -0
- bootstack/widgets/_impl/primitives/packframe.py +227 -0
- bootstack/widgets/_impl/primitives/panedwindow.py +45 -0
- bootstack/widgets/_impl/primitives/progressbar.py +83 -0
- bootstack/widgets/_impl/primitives/radiobutton.py +115 -0
- bootstack/widgets/_impl/primitives/radiotoggle.py +54 -0
- bootstack/widgets/_impl/primitives/scrollbar.py +42 -0
- bootstack/widgets/_impl/primitives/separator.py +43 -0
- bootstack/widgets/_impl/primitives/sizegrip.py +33 -0
- bootstack/widgets/_impl/primitives/spinbox.py +95 -0
- bootstack/widgets/_impl/primitives/switch.py +44 -0
- bootstack/widgets/_impl/primitives/treeview.py +69 -0
- bootstack/widgets/app.py +371 -0
- bootstack/widgets/appshell.py +1179 -0
- bootstack/widgets/avatar.py +140 -0
- bootstack/widgets/boolean_controls.py +455 -0
- bootstack/widgets/button.py +224 -0
- bootstack/widgets/buttongroup.py +239 -0
- bootstack/widgets/calendar.py +195 -0
- bootstack/widgets/card.py +159 -0
- bootstack/widgets/carousel.py +241 -0
- bootstack/widgets/chart.py +302 -0
- bootstack/widgets/codeeditor.py +675 -0
- bootstack/widgets/contextmenu.py +371 -0
- bootstack/widgets/datatable.py +688 -0
- bootstack/widgets/datefield.py +395 -0
- bootstack/widgets/divider.py +60 -0
- bootstack/widgets/expander.py +579 -0
- bootstack/widgets/form.py +200 -0
- bootstack/widgets/gallery.py +250 -0
- bootstack/widgets/gauge.py +168 -0
- bootstack/widgets/grid.py +121 -0
- bootstack/widgets/groupbox.py +162 -0
- bootstack/widgets/label.py +229 -0
- bootstack/widgets/listview.py +354 -0
- bootstack/widgets/menubutton.py +419 -0
- bootstack/widgets/numberfield.py +431 -0
- bootstack/widgets/pagestack.py +345 -0
- bootstack/widgets/passwordfield.py +383 -0
- bootstack/widgets/pathfield.py +454 -0
- bootstack/widgets/picture.py +251 -0
- bootstack/widgets/progressbar.py +106 -0
- bootstack/widgets/radio_variants.py +271 -0
- bootstack/widgets/radiogroup.py +228 -0
- bootstack/widgets/scrollbar.py +64 -0
- bootstack/widgets/scrollview.py +186 -0
- bootstack/widgets/select.py +302 -0
- bootstack/widgets/selectbutton.py +182 -0
- bootstack/widgets/sidebar_toggle.py +130 -0
- bootstack/widgets/sizegrip.py +42 -0
- bootstack/widgets/slider.py +408 -0
- bootstack/widgets/spinbox.py +147 -0
- bootstack/widgets/spinnerfield.py +413 -0
- bootstack/widgets/splash.py +367 -0
- bootstack/widgets/splitview.py +482 -0
- bootstack/widgets/stacks.py +249 -0
- bootstack/widgets/statusbar.py +189 -0
- bootstack/widgets/tabs.py +425 -0
- bootstack/widgets/textarea.py +459 -0
- bootstack/widgets/textfield.py +410 -0
- bootstack/widgets/theme_toggle.py +92 -0
- bootstack/widgets/timefield.py +340 -0
- bootstack/widgets/toast.py +314 -0
- bootstack/widgets/togglegroup.py +231 -0
- bootstack/widgets/toolbar.py +347 -0
- bootstack/widgets/tooltip.py +94 -0
- bootstack/widgets/tree.py +648 -0
- bootstack/widgets/types.py +305 -0
- bootstack/widgets/window.py +307 -0
- bootstack-0.1.0.dist-info/METADATA +301 -0
- bootstack-0.1.0.dist-info/RECORD +471 -0
- bootstack-0.1.0.dist-info/WHEEL +5 -0
- bootstack-0.1.0.dist-info/entry_points.txt +2 -0
- bootstack-0.1.0.dist-info/licenses/LICENSE +21 -0
- bootstack-0.1.0.dist-info/licenses/NOTICE +10 -0
- bootstack-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TYPE_CHECKING, overload
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.primitives.button import Button as _InternalButton
|
|
6
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
7
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
8
|
+
from bootstack.widgets._core.icon_image_props import IconProperty, ImageProperty
|
|
9
|
+
from bootstack.events import Event, Subscription
|
|
10
|
+
from bootstack.streams import Stream
|
|
11
|
+
from bootstack.widgets.types import AccentToken, WidgetDensity, IconPosition, ButtonVariant, IconSpec, LocalizeMode, SurfaceToken
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from bootstack.signals import Signal
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Button(IconProperty, ImageProperty, PublicWidgetBase):
|
|
18
|
+
"""A clickable action trigger.
|
|
19
|
+
|
|
20
|
+
The button text is the first positional argument. All styling and
|
|
21
|
+
behavior options are keyword-only.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
text: Text displayed on the button.
|
|
25
|
+
on_click: Called with no arguments when the button is clicked.
|
|
26
|
+
Equivalent to subscribing to the `'click'` event.
|
|
27
|
+
accent: Color intent token. Defaults to the theme's default button color.
|
|
28
|
+
variant: Visual weight token. Default `'solid'`.
|
|
29
|
+
icon: Bootstrap Icons name (e.g. `'save'`, `'trash'`) — see the full
|
|
30
|
+
catalog at https://icons.getbootstrap.com — or an `IconSpec` mapping
|
|
31
|
+
(`{'name', 'size', 'color'}`) for control over size and color.
|
|
32
|
+
icon_only: If `True`, show only the icon with no text. Inferred
|
|
33
|
+
automatically when `icon=` is set and no text is provided. Set
|
|
34
|
+
meaningful `text` anyway for accessibility.
|
|
35
|
+
icon_position: Position of the icon or image relative to the text.
|
|
36
|
+
Default `'left'`.
|
|
37
|
+
image: An `Image` handle (from `bootstack.images`) to display on the
|
|
38
|
+
button, for custom artwork rather than a Bootstrap Icon name. Also
|
|
39
|
+
accepts a `get_icon` result.
|
|
40
|
+
width: Button width in character units. Useful for making a row of
|
|
41
|
+
buttons uniform width (e.g. `width=10`).
|
|
42
|
+
textsignal: Reactive `Signal[str]` bound to the button text. Updates
|
|
43
|
+
automatically when the signal changes.
|
|
44
|
+
surface: The background the button is placed on — `'content'`, `'card'`,
|
|
45
|
+
`'card_raised'`, `'chrome'`, or `'overlay'`. Set it so a
|
|
46
|
+
`ghost`/`outline` button looks right on a non-default container (e.g.
|
|
47
|
+
a toolbar sets `'chrome'` on the buttons it builds). A build-time
|
|
48
|
+
setting, not a live property. Defaults to the theme's content
|
|
49
|
+
surface.
|
|
50
|
+
density: Padding density.
|
|
51
|
+
disabled: If `True`, the button is non-interactive and visually dimmed.
|
|
52
|
+
localize: Whether the text is translated through the catalog — `True`,
|
|
53
|
+
`False`, or `'auto'`. Defaults to the app's `localize_mode`. Set
|
|
54
|
+
`False` to keep a proper noun or identifier untranslated.
|
|
55
|
+
parent: Explicit parent widget. If omitted, the current context-stack
|
|
56
|
+
container is used.
|
|
57
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
58
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
59
|
+
See :doc:`/tasks/layout`.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
text: str = "",
|
|
65
|
+
*,
|
|
66
|
+
on_click: Callable[[], Any] | None = None,
|
|
67
|
+
accent: AccentToken | str | None = None,
|
|
68
|
+
variant: ButtonVariant = "default",
|
|
69
|
+
icon: str | IconSpec | None = None,
|
|
70
|
+
icon_only: bool = False,
|
|
71
|
+
icon_position: IconPosition = "left",
|
|
72
|
+
image: Any = None,
|
|
73
|
+
width: int | None = None,
|
|
74
|
+
textsignal: "Signal[str] | None" = None,
|
|
75
|
+
surface: SurfaceToken | str | None = None,
|
|
76
|
+
density: WidgetDensity | None = None,
|
|
77
|
+
disabled: bool = False,
|
|
78
|
+
localize: LocalizeMode | None = None,
|
|
79
|
+
parent: Any = None,
|
|
80
|
+
**kwargs: Any,
|
|
81
|
+
) -> None:
|
|
82
|
+
self._reject_signal_text(text)
|
|
83
|
+
self._parent = self._resolve_parent(parent)
|
|
84
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
85
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
86
|
+
|
|
87
|
+
if icon is not None and not text:
|
|
88
|
+
icon_only = True
|
|
89
|
+
|
|
90
|
+
# The internal command is always our dispatcher so that a click —
|
|
91
|
+
# whether by mouse, keyboard (Space/Return), or click()/invoke() — runs
|
|
92
|
+
# the on_click= callback AND emits <<Click>> for on_click() subscribers,
|
|
93
|
+
# consistently and only when the button is enabled (ttk gates the
|
|
94
|
+
# command on state, unlike a raw <Button-1> binding).
|
|
95
|
+
self._on_click_cmd = on_click
|
|
96
|
+
|
|
97
|
+
internal_kwargs: dict[str, Any] = {"text": text, "command": self._dispatch_click}
|
|
98
|
+
if not icon_only:
|
|
99
|
+
internal_kwargs["compound"] = icon_position
|
|
100
|
+
if accent is not None:
|
|
101
|
+
internal_kwargs["accent"] = accent
|
|
102
|
+
if variant is not None:
|
|
103
|
+
internal_kwargs["variant"] = variant
|
|
104
|
+
if icon is not None:
|
|
105
|
+
internal_kwargs["icon"] = icon
|
|
106
|
+
if icon_only:
|
|
107
|
+
internal_kwargs["icon_only"] = True
|
|
108
|
+
deferred_image = None
|
|
109
|
+
if image is not None:
|
|
110
|
+
from bootstack.images import Image as _ImageHandle
|
|
111
|
+
|
|
112
|
+
if isinstance(image, _ImageHandle):
|
|
113
|
+
deferred_image = image
|
|
114
|
+
else:
|
|
115
|
+
internal_kwargs["image"] = image
|
|
116
|
+
if width is not None:
|
|
117
|
+
internal_kwargs["width"] = width
|
|
118
|
+
if textsignal is not None:
|
|
119
|
+
internal_kwargs["textsignal"] = textsignal
|
|
120
|
+
if surface is not None:
|
|
121
|
+
internal_kwargs["surface"] = surface
|
|
122
|
+
if density is not None:
|
|
123
|
+
internal_kwargs["density"] = density
|
|
124
|
+
if disabled:
|
|
125
|
+
internal_kwargs["state"] = "disabled"
|
|
126
|
+
if localize is not None:
|
|
127
|
+
internal_kwargs["localize"] = localize
|
|
128
|
+
|
|
129
|
+
self._internal = _InternalButton(tk_master, **internal_kwargs)
|
|
130
|
+
if deferred_image is not None:
|
|
131
|
+
from bootstack.widgets._core.image_binding import bind_image
|
|
132
|
+
|
|
133
|
+
bind_image(self, self._internal, deferred_image)
|
|
134
|
+
self._attach_to_parent(layout_kw)
|
|
135
|
+
|
|
136
|
+
# ----- Properties -----
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def text(self) -> str:
|
|
140
|
+
"""The button's text."""
|
|
141
|
+
# cget('text') reliably reflects a bound textvariable too (ttk keeps the
|
|
142
|
+
# option synced from the variable), so this is the live value either way.
|
|
143
|
+
return str(self._internal.cget("text"))
|
|
144
|
+
|
|
145
|
+
@text.setter
|
|
146
|
+
def text(self, value: str) -> None:
|
|
147
|
+
# When a textsignal/textvariable owns the text, ttk ignores the `text`
|
|
148
|
+
# option — write through the variable instead (which also keeps the
|
|
149
|
+
# bound signal in sync). Falls back to the option when unbound.
|
|
150
|
+
textvar = getattr(self._internal, "_textvariable", None)
|
|
151
|
+
if textvar is not None:
|
|
152
|
+
textvar.set(value)
|
|
153
|
+
else:
|
|
154
|
+
self._internal.configure(text=value)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def disabled(self) -> bool:
|
|
158
|
+
"""Whether the button is non-interactive."""
|
|
159
|
+
return str(self._internal.cget("state")) == "disabled"
|
|
160
|
+
|
|
161
|
+
@disabled.setter
|
|
162
|
+
def disabled(self, value: bool) -> None:
|
|
163
|
+
self._internal.configure(state="disabled" if value else "normal")
|
|
164
|
+
|
|
165
|
+
# ----- Methods -----
|
|
166
|
+
|
|
167
|
+
def click(self) -> None:
|
|
168
|
+
"""Programmatically activate the button.
|
|
169
|
+
|
|
170
|
+
Runs the same path as a real click: the `on_click=` callback and any
|
|
171
|
+
`on_click()` handlers both fire. Does nothing while the button is
|
|
172
|
+
disabled.
|
|
173
|
+
"""
|
|
174
|
+
self._internal.invoke()
|
|
175
|
+
|
|
176
|
+
def _dispatch_click(self) -> None:
|
|
177
|
+
"""ttk command target — runs on every activation, never while disabled.
|
|
178
|
+
|
|
179
|
+
Fires on mouse release over the button, keyboard Space/Return, and
|
|
180
|
+
`click()`/`invoke()`. Runs the constructor `on_click=` callback, then
|
|
181
|
+
emits `<<Click>>` so `on_click()` subscribers see the same activation.
|
|
182
|
+
"""
|
|
183
|
+
if self._on_click_cmd is not None:
|
|
184
|
+
self._on_click_cmd()
|
|
185
|
+
# `when='now'` delivers to subscribers synchronously, so a click()/invoke()
|
|
186
|
+
# notifies handlers before it returns (matches the on_click= callback).
|
|
187
|
+
self._internal.event_generate("<<Click>>", when="now")
|
|
188
|
+
|
|
189
|
+
# ----- Events -----
|
|
190
|
+
|
|
191
|
+
@overload
|
|
192
|
+
def on_click(self) -> Stream: ...
|
|
193
|
+
@overload
|
|
194
|
+
def on_click(self, handler: Callable[[Event], Any]) -> Subscription: ...
|
|
195
|
+
def on_click(self, handler: Callable[[Event], Any] | None = None) -> Stream | Subscription:
|
|
196
|
+
"""Register a callback for button click events.
|
|
197
|
+
|
|
198
|
+
Called with no handler, returns a composable `Stream`. Called with a
|
|
199
|
+
handler, binds it immediately and returns a `Subscription`.
|
|
200
|
+
|
|
201
|
+
Fires on activation — a mouse release over the button, a keyboard
|
|
202
|
+
Space/Return while focused, or :meth:`click`/`invoke` — and never while
|
|
203
|
+
the button is disabled, matching the `on_click=` constructor argument.
|
|
204
|
+
For a simple no-argument action prefer that constructor argument; use
|
|
205
|
+
this method (or its `Stream`) to compose the click event.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
handler: Called with the click :class:`~bootstack.events.Event`.
|
|
209
|
+
The event marks the activation; because keyboard and
|
|
210
|
+
programmatic activations carry no pointer, treat position and
|
|
211
|
+
modifier fields as unset. Omit the handler to get a composable
|
|
212
|
+
:class:`~bootstack.streams.Stream`.
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
216
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
217
|
+
"""
|
|
218
|
+
return self.on("click", handler)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# "click" resolves to the <<Click>> virtual event emitted by the command
|
|
222
|
+
# dispatcher (see _dispatch_click) rather than the global <Button-1> mapping, so
|
|
223
|
+
# both the handler and Stream forms fire on activation and honor disabled state.
|
|
224
|
+
register_widget_events(Button, {"click": "<<Click>>"})
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Literal, overload
|
|
4
|
+
|
|
5
|
+
from bootstack.widgets._impl.composites.buttongroup import ButtonGroup as _InternalButtonGroup
|
|
6
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
7
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
8
|
+
from bootstack.events import ButtonGroupClickEvent, Subscription
|
|
9
|
+
from bootstack.streams import Stream
|
|
10
|
+
from bootstack.widgets.types import AccentToken, Event, WidgetDensity, Orient, IconPosition, ButtonVariant
|
|
11
|
+
|
|
12
|
+
_BUTTONGROUP_EVENTS: dict[str, str] = {
|
|
13
|
+
"click": "<<BsButtonGroupClick>>",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ButtonGroup(PublicWidgetBase):
|
|
18
|
+
"""A row (or column) of visually-connected buttons sharing accent and variant.
|
|
19
|
+
|
|
20
|
+
Buttons are added via `add()` or `add_all()`. The group propagates
|
|
21
|
+
`accent`, `variant`, and `density` to every member automatically.
|
|
22
|
+
Listen for any button press with `on_click()`, which receives the key
|
|
23
|
+
of the clicked button.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
orient: Layout orientation. Default `'horizontal'`.
|
|
27
|
+
accent: Accent token applied to all buttons.
|
|
28
|
+
variant: Style variant. Default `'solid'`.
|
|
29
|
+
density: Widget density. Default `'default'`.
|
|
30
|
+
disabled: If `True`, all buttons are non-interactive.
|
|
31
|
+
localize: Whether button labels are translated through the catalog —
|
|
32
|
+
`True`, `False`, or `'auto'` (translate when a translation is
|
|
33
|
+
registered, otherwise show the literal). Defaults to the app's
|
|
34
|
+
`localize_mode`. Set `False` to keep proper nouns untranslated;
|
|
35
|
+
override a single button with `add(..., localize=...)`.
|
|
36
|
+
parent: Override the context-stack parent.
|
|
37
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
38
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
39
|
+
See :doc:`/tasks/layout`.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
orient: Orient = "horizontal",
|
|
45
|
+
*,
|
|
46
|
+
accent: AccentToken | str | None = None,
|
|
47
|
+
variant: ButtonVariant = "default",
|
|
48
|
+
density: WidgetDensity = "default",
|
|
49
|
+
disabled: bool = False,
|
|
50
|
+
localize: bool | Literal['auto'] | None = None,
|
|
51
|
+
parent: Any = None,
|
|
52
|
+
**kwargs: Any,
|
|
53
|
+
) -> None:
|
|
54
|
+
self._localize = localize
|
|
55
|
+
self._parent = self._resolve_parent(parent)
|
|
56
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
57
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
58
|
+
|
|
59
|
+
internal_kwargs: dict[str, Any] = {
|
|
60
|
+
"orient": orient,
|
|
61
|
+
"variant": variant,
|
|
62
|
+
"density": density,
|
|
63
|
+
}
|
|
64
|
+
if accent is not None:
|
|
65
|
+
internal_kwargs["accent"] = accent
|
|
66
|
+
if disabled:
|
|
67
|
+
internal_kwargs["state"] = "disabled"
|
|
68
|
+
|
|
69
|
+
self._internal = _InternalButtonGroup(tk_master, **internal_kwargs)
|
|
70
|
+
self._key_counter = 0
|
|
71
|
+
self._attach_to_parent(layout_kw)
|
|
72
|
+
|
|
73
|
+
# ----- Item management -----
|
|
74
|
+
|
|
75
|
+
def add(
|
|
76
|
+
self,
|
|
77
|
+
label: str = "",
|
|
78
|
+
*,
|
|
79
|
+
key: str | None = None,
|
|
80
|
+
icon: str | None = None,
|
|
81
|
+
icon_position: IconPosition = "left",
|
|
82
|
+
disabled: bool = False,
|
|
83
|
+
localize: bool | Literal['auto'] | None = None,
|
|
84
|
+
**kwargs: Any,
|
|
85
|
+
) -> str:
|
|
86
|
+
"""Add a button to the group.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
label: Button label text.
|
|
90
|
+
key: Unique string key. Auto-generated if omitted.
|
|
91
|
+
icon: Bootstrap Icons name (e.g. `'save'`, `'trash'`).
|
|
92
|
+
icon_position: Position of the icon relative to the label. Ignored
|
|
93
|
+
when `icon_only` is inferred or explicitly set. Default `'left'`.
|
|
94
|
+
disabled: If `True`, this button starts disabled.
|
|
95
|
+
localize: Translation mode for this button's label, overriding the
|
|
96
|
+
group's `localize=`. Defaults to the group setting.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
The key assigned to this button.
|
|
100
|
+
"""
|
|
101
|
+
if key is None:
|
|
102
|
+
key = f"widget_{self._key_counter}"
|
|
103
|
+
self._key_counter += 1
|
|
104
|
+
|
|
105
|
+
icon_only = icon is not None and not label
|
|
106
|
+
|
|
107
|
+
btn_kwargs: dict[str, Any] = {}
|
|
108
|
+
if icon is not None:
|
|
109
|
+
btn_kwargs["icon"] = icon
|
|
110
|
+
if icon_only:
|
|
111
|
+
btn_kwargs["icon_only"] = True
|
|
112
|
+
elif icon is not None:
|
|
113
|
+
btn_kwargs["compound"] = icon_position
|
|
114
|
+
if disabled:
|
|
115
|
+
btn_kwargs["state"] = "disabled"
|
|
116
|
+
item_localize = localize if localize is not None else self._localize
|
|
117
|
+
if item_localize is not None:
|
|
118
|
+
btn_kwargs["localize"] = item_localize
|
|
119
|
+
btn_kwargs.update(kwargs)
|
|
120
|
+
|
|
121
|
+
_key = key
|
|
122
|
+
_group = self
|
|
123
|
+
|
|
124
|
+
def _command() -> None:
|
|
125
|
+
btn = _group._internal.item(_key)
|
|
126
|
+
_group.emit("click", data=ButtonGroupClickEvent(
|
|
127
|
+
key=_key,
|
|
128
|
+
text=str(btn.cget("text") or ""),
|
|
129
|
+
icon=btn.configure_style_options("icon"),
|
|
130
|
+
))
|
|
131
|
+
|
|
132
|
+
self._internal.add(label or None, key=key, command=_command, **btn_kwargs)
|
|
133
|
+
return key
|
|
134
|
+
|
|
135
|
+
def add_all(self, items: list) -> list[str]:
|
|
136
|
+
"""Add multiple buttons at once.
|
|
137
|
+
|
|
138
|
+
Each item is either a label string or a dict of keyword arguments
|
|
139
|
+
accepted by `add()`.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
items: List of labels or dicts. A plain string is treated as
|
|
143
|
+
the button label with no other options.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
List of keys assigned to each button, in insertion order.
|
|
147
|
+
"""
|
|
148
|
+
return [self.add(**item) if isinstance(item, dict) else self.add(item) for item in items]
|
|
149
|
+
|
|
150
|
+
def remove(self, key: str) -> None:
|
|
151
|
+
"""Remove the button identified by `key`.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
key: Key returned by `add()`.
|
|
155
|
+
"""
|
|
156
|
+
self._internal.remove(key)
|
|
157
|
+
|
|
158
|
+
def update_item(self, key: str, **kwargs: Any) -> None:
|
|
159
|
+
"""Reconfigure a button after creation.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
key: Key returned by `add()`.
|
|
163
|
+
**kwargs: Options forwarded to the button's `configure()`.
|
|
164
|
+
"""
|
|
165
|
+
self._internal.configure_item(key, **kwargs)
|
|
166
|
+
|
|
167
|
+
def query_item(self, key: str, option: str) -> Any:
|
|
168
|
+
"""Return the current value of a configuration option for a button.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
key: Key returned by `add()`.
|
|
172
|
+
option: Option name to query (e.g. `'text'`, `'state'`).
|
|
173
|
+
"""
|
|
174
|
+
return self._internal.configure_item(key, option)
|
|
175
|
+
|
|
176
|
+
# ----- Queries -----
|
|
177
|
+
|
|
178
|
+
def item(self, key: str) -> Any:
|
|
179
|
+
"""Return the button item object for `key`.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
key: Key returned by `add()`.
|
|
183
|
+
"""
|
|
184
|
+
return self._internal.item(key)
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def items(self) -> tuple[Any, ...]:
|
|
188
|
+
"""All button items in insertion order."""
|
|
189
|
+
return self._internal.items()
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def keys(self) -> tuple[str, ...]:
|
|
193
|
+
"""Keys of all buttons in insertion order."""
|
|
194
|
+
return self._internal.keys()
|
|
195
|
+
|
|
196
|
+
def __len__(self) -> int:
|
|
197
|
+
"""Number of buttons in the group."""
|
|
198
|
+
return len(self._internal)
|
|
199
|
+
|
|
200
|
+
def __contains__(self, key: object) -> bool:
|
|
201
|
+
"""True if a button with the given key exists in the group."""
|
|
202
|
+
return key in self._internal
|
|
203
|
+
|
|
204
|
+
# ----- Properties -----
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def disabled(self) -> bool:
|
|
208
|
+
"""Whether all buttons are non-interactive."""
|
|
209
|
+
return str(self._internal.cget("state")) == "disabled"
|
|
210
|
+
|
|
211
|
+
@disabled.setter
|
|
212
|
+
def disabled(self, value: bool) -> None:
|
|
213
|
+
self._internal.configure(state="disabled" if value else "normal")
|
|
214
|
+
|
|
215
|
+
# ----- Events -----
|
|
216
|
+
|
|
217
|
+
@overload
|
|
218
|
+
def on_click(self) -> Stream: ...
|
|
219
|
+
@overload
|
|
220
|
+
def on_click(self, handler: Callable[[ButtonGroupClickEvent], Any]) -> Subscription: ...
|
|
221
|
+
def on_click(self, handler: Callable[[ButtonGroupClickEvent], Any] | None = None) -> Stream | Subscription:
|
|
222
|
+
"""Register a callback fired when any button in the group is clicked.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
handler: Called with a :class:`~bootstack.events.ButtonGroupClickEvent`
|
|
226
|
+
(`key`, `text`, `icon` of the clicked button). Omit to get a
|
|
227
|
+
composable :class:`~bootstack.streams.Stream`.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
A cancellable :class:`~bootstack.events.Subscription` when a handler
|
|
231
|
+
is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
232
|
+
"""
|
|
233
|
+
if handler is None:
|
|
234
|
+
return self.on("click")
|
|
235
|
+
|
|
236
|
+
return self.on("click", handler)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
register_widget_events(ButtonGroup, _BUTTONGROUP_EVENTS)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from typing import overload, Any, Callable, Iterable, Literal
|
|
5
|
+
|
|
6
|
+
from bootstack.widgets._impl.composites.calendar import Calendar as _InternalCalendar
|
|
7
|
+
from bootstack.widgets._core.base import PublicWidgetBase
|
|
8
|
+
from bootstack.widgets._core.events import register_widget_events
|
|
9
|
+
from bootstack.events import DateSelectEvent, Subscription
|
|
10
|
+
from bootstack.streams import Stream
|
|
11
|
+
from bootstack.widgets.types import AccentToken, Event, Padding
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Calendar(PublicWidgetBase):
|
|
15
|
+
"""An inline calendar for single-date or date-range selection.
|
|
16
|
+
|
|
17
|
+
Always visible — not a popup. Displays one month in `'single'` mode and
|
|
18
|
+
two months side-by-side in `'range'` mode.
|
|
19
|
+
|
|
20
|
+
Dates can be passed as `datetime.date` objects, `datetime.datetime`
|
|
21
|
+
objects, or ISO strings (`"2026-05-31"`).
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
value: Initially selected date (single mode only).
|
|
25
|
+
start_date: Initially selected range start date (range mode).
|
|
26
|
+
end_date: Initially selected range end date (range mode).
|
|
27
|
+
disabled_dates: Dates that cannot be selected. Displayed with a
|
|
28
|
+
strikethrough style.
|
|
29
|
+
selection_mode: `'single'` (default) for one date; `'range'`
|
|
30
|
+
for a start–end span.
|
|
31
|
+
min_date: Earliest selectable date. Earlier dates are disabled
|
|
32
|
+
and month navigation is blocked before this point.
|
|
33
|
+
max_date: Latest selectable date. Later dates are disabled
|
|
34
|
+
and month navigation is blocked past this point.
|
|
35
|
+
show_outside_days: Show days from adjacent months in the grid.
|
|
36
|
+
Defaults to `True` in single mode and `False` in range mode.
|
|
37
|
+
show_week_numbers: Display ISO 8601 week numbers in the leftmost
|
|
38
|
+
column. Defaults to `False`.
|
|
39
|
+
first_weekday: First day of the week as an integer (0=Monday,
|
|
40
|
+
6=Sunday). If omitted, the locale default is used.
|
|
41
|
+
accent: Accent token applied to selected dates and highlights.
|
|
42
|
+
Defaults to `'primary'`.
|
|
43
|
+
padding: Space in pixels around the calendar grid.
|
|
44
|
+
parent: Explicit parent widget. If omitted, the current
|
|
45
|
+
context-stack container is used.
|
|
46
|
+
**kwargs: Layout placement options applied by the parent container —
|
|
47
|
+
`fill`, `expand`, `anchor`, `margin`, `row`, `column`, `sticky`.
|
|
48
|
+
See :doc:`/tasks/layout`.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
value: date | datetime | str | None = None,
|
|
55
|
+
start_date: date | datetime | str | None = None,
|
|
56
|
+
end_date: date | datetime | str | None = None,
|
|
57
|
+
disabled_dates: Iterable[date | datetime | str] | None = None,
|
|
58
|
+
selection_mode: Literal["single", "range"] = "single",
|
|
59
|
+
max_date: date | datetime | str | None = None,
|
|
60
|
+
min_date: date | datetime | str | None = None,
|
|
61
|
+
show_outside_days: bool | None = None,
|
|
62
|
+
show_week_numbers: bool = False,
|
|
63
|
+
first_weekday: int | None = None,
|
|
64
|
+
accent: AccentToken | str | None = None,
|
|
65
|
+
padding: Padding | None = None,
|
|
66
|
+
parent: Any = None,
|
|
67
|
+
**kwargs: Any,
|
|
68
|
+
) -> None:
|
|
69
|
+
self._parent = self._resolve_parent(parent)
|
|
70
|
+
layout_kw = self._split_layout_kwargs(kwargs)
|
|
71
|
+
tk_master = self._parent._child_master() if self._parent else None
|
|
72
|
+
|
|
73
|
+
kw: dict[str, Any] = {"selection_mode": selection_mode, "show_week_numbers": show_week_numbers}
|
|
74
|
+
if value is not None:
|
|
75
|
+
kw["value"] = value
|
|
76
|
+
if start_date is not None:
|
|
77
|
+
kw["start_date"] = start_date
|
|
78
|
+
if end_date is not None:
|
|
79
|
+
kw["end_date"] = end_date
|
|
80
|
+
if disabled_dates is not None:
|
|
81
|
+
kw["disabled_dates"] = disabled_dates
|
|
82
|
+
if max_date is not None:
|
|
83
|
+
kw["max_date"] = max_date
|
|
84
|
+
if min_date is not None:
|
|
85
|
+
kw["min_date"] = min_date
|
|
86
|
+
if show_outside_days is not None:
|
|
87
|
+
kw["show_outside_days"] = show_outside_days
|
|
88
|
+
if first_weekday is not None:
|
|
89
|
+
kw["first_weekday"] = first_weekday
|
|
90
|
+
if accent is not None:
|
|
91
|
+
kw["accent"] = accent
|
|
92
|
+
if padding is not None:
|
|
93
|
+
kw["padding"] = padding
|
|
94
|
+
|
|
95
|
+
self._internal = _InternalCalendar(tk_master, **kw)
|
|
96
|
+
self._attach_to_parent(layout_kw)
|
|
97
|
+
|
|
98
|
+
# ----- Value API -----
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def value(self) -> date | None:
|
|
102
|
+
"""The currently selected date."""
|
|
103
|
+
return self._internal.value
|
|
104
|
+
|
|
105
|
+
@value.setter
|
|
106
|
+
def value(self, val: date | datetime | str | None) -> None:
|
|
107
|
+
self._internal.value = val
|
|
108
|
+
|
|
109
|
+
def get(self) -> date | None:
|
|
110
|
+
"""Return the currently selected date."""
|
|
111
|
+
return self._internal.get()
|
|
112
|
+
|
|
113
|
+
def set(self, value: date | datetime | str | None) -> None:
|
|
114
|
+
"""Set the selected date without emitting `<<DateSelect>>`."""
|
|
115
|
+
self._internal.set(value)
|
|
116
|
+
|
|
117
|
+
# ----- Range API -----
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def range(self) -> tuple[date | None, date | None]:
|
|
121
|
+
"""The selected date range as (start, end)."""
|
|
122
|
+
return self._internal.range
|
|
123
|
+
|
|
124
|
+
@range.setter
|
|
125
|
+
def range(self, val: Any) -> None:
|
|
126
|
+
self._internal.range = val
|
|
127
|
+
|
|
128
|
+
def get_range(self) -> tuple[date | None, date | None]:
|
|
129
|
+
"""Return the selected date range."""
|
|
130
|
+
return self._internal.get_range()
|
|
131
|
+
|
|
132
|
+
def set_range(
|
|
133
|
+
self,
|
|
134
|
+
start: date | datetime | str | None,
|
|
135
|
+
end: date | datetime | str | None = None,
|
|
136
|
+
) -> None:
|
|
137
|
+
"""Set the selected date range without emitting `<<DateSelect>>`."""
|
|
138
|
+
self._internal.set_range(start, end)
|
|
139
|
+
|
|
140
|
+
# ----- Date constraints (live) -----
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def min_date(self) -> date | None:
|
|
144
|
+
"""Earliest selectable date. Assigning updates the calendar immediately; `None` clears the bound."""
|
|
145
|
+
return self._internal._min_date
|
|
146
|
+
|
|
147
|
+
@min_date.setter
|
|
148
|
+
def min_date(self, value: date | datetime | str | None) -> None:
|
|
149
|
+
self._internal.set_min_date(value)
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def max_date(self) -> date | None:
|
|
153
|
+
"""Latest selectable date. Assigning updates the calendar immediately; `None` clears the bound."""
|
|
154
|
+
return self._internal._max_date
|
|
155
|
+
|
|
156
|
+
@max_date.setter
|
|
157
|
+
def max_date(self, value: date | datetime | str | None) -> None:
|
|
158
|
+
self._internal.set_max_date(value)
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def disabled_dates(self) -> tuple[date, ...]:
|
|
162
|
+
"""The non-selectable dates. Assigning a new iterable updates the calendar immediately."""
|
|
163
|
+
return tuple(sorted(self._internal._disabled_dates))
|
|
164
|
+
|
|
165
|
+
@disabled_dates.setter
|
|
166
|
+
def disabled_dates(self, value: Iterable[date | datetime | str] | None) -> None:
|
|
167
|
+
self._internal.set_disabled_dates(value)
|
|
168
|
+
|
|
169
|
+
# ----- Events -----
|
|
170
|
+
|
|
171
|
+
@overload
|
|
172
|
+
def on_select(self) -> Stream: ...
|
|
173
|
+
@overload
|
|
174
|
+
def on_select(self, handler: Callable[[DateSelectEvent], Any]) -> Subscription: ...
|
|
175
|
+
def on_select(self, handler: Callable[[DateSelectEvent], Any] | None = None) -> Stream | Subscription:
|
|
176
|
+
"""Register a callback fired when a date or range is selected.
|
|
177
|
+
|
|
178
|
+
In single mode the handler fires on every date click. In range mode
|
|
179
|
+
it fires after each click — check `calendar.range` to see whether
|
|
180
|
+
a complete range has been set (`end` is `None` while the second
|
|
181
|
+
date is still pending).
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
handler: Called with a :class:`~bootstack.events.DateSelectEvent`.
|
|
185
|
+
Omit to get a composable :class:`~bootstack.streams.Stream`
|
|
186
|
+
instead.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
A cancellable :class:`~bootstack.events.Subscription` when a
|
|
190
|
+
handler is given, otherwise a :class:`~bootstack.streams.Stream`.
|
|
191
|
+
"""
|
|
192
|
+
return self.on("<<DateSelect>>", handler)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
register_widget_events(Calendar, {})
|