4227 shoelace to webawesome#4262
Conversation
Use the WebAwesome shoelace.css compatibility theme to preserve existing CSS variable names and minimize the scope of this migration.
- Rename sl-* tags to wa-* (e.g., sl-button -> wa-button) - Rename sl-range to wa-slider (component was redesigned and renamed) - Unprefix custom events: sl-change -> change, sl-input -> input - Replace divider vertical attribute with orientation="vertical"
- Dialog: replace .show()/.hide() with .open property (https://www.webawesome.com/docs/components/dialog) - Menu: remove sl-menu wrapper; wa-dropdown now takes wa-dropdown-item children directly (https://www.webawesome.com/docs/components/dropdown) - Button variant: primary -> brand (https://www.webawesome.com/docs/components/button)
|
As you've noted, the #3891 already exists, ready to land, as a fix for the naming of the CSS file; it would make sense for this PR to clean up the rest of the |
Briefcase no longer uses the style_framework key; the web backend now loads WebAwesome via insert files instead. Refs beeware#3822.
There was a problem hiding this comment.
This file is actually a stray - it shouldn't exist in the repo at all. It's evidently some cruft that got accidentally committed when we first added Shoelace support.
The dist/ build requires a bundler to resolve bare module specifiers; dist-cdn/ bundles all dependencies for direct browser use.
WebAwesome registers custom elements asynchronously, so native element properties (.value, .checked, .min, .max, etc.) may not exist when Python code runs during widget init. Add _get_native_attr/_set_native_attr helpers on the base Widget class that catch AttributeError and warn instead of crashing. Apply to Switch, Selection, and Slider.
| return getattr(self.native, attr) | ||
| except AttributeError: | ||
| tag = getattr(self.native, "tagName", "unknown") | ||
| warnings.warn( |
There was a problem hiding this comment.
all of this verbosity exists because I was trained by a past security team and now I have a reflexive need to not have blank except blocks without at least logging. It could probably just be pass
There was a problem hiding this comment.
Understandable (and good practice); however, as noted above, I'd like to have a better idea of why this will occur at all.
Status@freakboy3742 FYI I'm putting this PR down for two weeks. But it's probably ready for a preliminary review to see if things are on the right track I've made an effort to write the description to be clear about what I've done as far as changes and testing. I've also commented on various lines that I felt would benefit the reviewer in understanding certain changes. As mentioned, when testing the different examples, I now find the 19 examples mostly work the same on this branch as on main. In some cases, they crash on both branches. In other cases, they're slightly broken in the same way. The main difference is the styling is just...different. I haven't yet figured out how to get them to match better, but I'm not sure how important it is. Here is an example of one of the most striking differences. This is
The main difference that exists just about everywhere is that black/gray vs. blue. That's the part I haven't figured out how to fix yet and can't judge the severity of that difference. |
freakboy3742
left a comment
There was a problem hiding this comment.
Looks like good progress!
I've flagged my concerns with the async attribute getter/setter tooling; your explanation makes sense, but as noted inline, I'd like to have a better understanding of the why - and in particular, if there's anything we can do to avoid the problem.
The change in color is an odd one. The differences are pretty stark - I don't think it matters too much if there are some cosmetic differences, but the broad look and feel should be retained to the extent possible.
| return getattr(self.native, attr) | ||
| except AttributeError: | ||
| tag = getattr(self.native, "tagName", "unknown") | ||
| warnings.warn( |
There was a problem hiding this comment.
Understandable (and good practice); however, as noted above, I'd like to have a better idea of why this will occur at all.
|
The test failures don't appear to be your problem - GitHub has clearly changed something in their Linux configuration over the last day or so. |
| @@ -1,5 +1,6 @@ | |||
| from __future__ import annotations | |||
|
|
|||
| import warnings | |||
There was a problem hiding this comment.
Looks like my linter got this though it's not related to the change of this PR. I'll change that back if it's not common practice.
So However, in Toga's widget constructors, we first create the element, then start setting attributes (some of which don't exist on vanilla
I think so. I'll start working on a pattern that:
LMK if you have any thoughts on that approach |
Ok - that makes sense (or, at the very least, explains the behavior we're seeing here :-) ) Thanks for that investigation.
I think the general approach is on the right track. We do a broadly similar thing for WebView on a couple of platforms (Windows, for example) - we can't set the URL on a webview until the webview is fully initialized, so we have a That works well for setter methods; getters need a little more handling to ensure that appropriate defaults are returned (or the getter has a way to get the "current" value if the element isn't initialized). The other approach that might work is a proxy wrapper for the I don't have any strong opinions on which of those approaches we should use, though. |
The last approach, _get_native_attr / _set_native_attr, handled WebAwesome's async upgrade by silently dropping pre-upgrade writes, so widgets lost any state configured before upgrade. Replace with a NativeProxy that buffers pre-upgrade sets and replays them from a customElements.whenDefined callback after force-upgrading the element. Widget code reverts to direct self.native.foo access with local AttributeError fallbacks where reads can happen before writes. Callers that pass .native into appendChild/insertBefore now use .unwrap() to reach the underlying JsProxy, since Pyodide does not auto-unwrap Python wrappers.
|
|
||
| def add_child(self, child): | ||
| self.native.appendChild(child.native) | ||
| self.native.appendChild(child.native.unwrap()) |
There was a problem hiding this comment.
We can't send the Python object in to these JS calls, so this unwrap method sends in the JsProxy
freakboy3742
left a comment
There was a problem hiding this comment.
I've taken a quick review pass; a couple of notes about possible cleanups and edge cases of the "no default yet" handling, but otherwise, this definitely looks like it's on the right track.
toga.css referenced --sl-* tokens that no longer exist in WA's shoelace theme, leaving the header unstyled. Replace with --wa-* equivalents. wa-button defaults to appearance="accent" which rendered as a dark filled pill; switch to "outlined" to match the previous Shoelace look and let per-widget background colors show through.
Instead of needing to call .unwrap when we’re calling into a JS method, this adds to our __getattr__ override to, if the method we’re calling is a JS method, automatically unwrap any NativeProxy arguments
|
I've added the auto-unwrap and re-tested the examples. Ready for re-review when you have time @freakboy3742 |
freakboy3742
left a comment
There was a problem hiding this comment.
My review of the code all looks great; I still need to do a functional review to make sure everything still works. I should get a chance to do that early next week.
As a heads up, |
freakboy3742
left a comment
There was a problem hiding this comment.
I've had a chance to take the examples for a spin - I only spotted a handful of issues, and the answer for most of them might be "lets treat that as a future problem" unless you can find a quick solution:
- ActivityIndicator and TimeInput aren't registered as web widgets.
- Titlebar color is
rgb(0, 63, 156), rather thanrgb(7, 89, 133). ActvityIndicator- Starts in a "visible and active" state.Box- Disabling the "yellow" button doesn't disable or hide the yellow button.Slider- Release message doesn't work unless you're hovering over the handle when you release the mouse button (if you press, and move horizontally and vertically, you don't get the release message).- I think I'm seeing a FOUC (Flash of Unstyled Content), especially when there's a multiple widgets on the page.
(1) was a pre-existing problem; I've pushed an update to correct it as part of this PR.
(2) may just be a theming issue. It seems weird that the Shoelace compatibility theme doesn't actually maintain complete compatibility; but if that's what WA gives us, then I guess that's what we've got. It's not worth spending any time on.
(3)-(5) are at least partially pre-existing problems. They are either in the shoelace version of the demos, or there's an analogous problem. Since we're in the area and updating things, it's worth having a quick poke around to see if we can fix those problems, but don't burn too much time on it.
(6) is a bigger problem. I'm guessing this is related to the delayed-load handling. If you've got any ideas on how to minimize the FOUC (possibly by deferring the window.show() until such time as all the content has been loaded?), then it might be worth tackling now - but I can also accept this as a longer term problem to be resolved.
|
Thanks. Just wanted to acknowledge that I got this feedback and plan to work through it. I don't know when that will be, but wanted to put this here so it wasn't like I was ghosting. |
…a into 4227-shoelace-to-webawesome
insert_child was ignoring the index and always appending; remove_child was only clearing the Python container reference without touching the DOM. In the Box example, removing the yellow button does not reset the background color — the Switch controls button availability, not color state.
The shoelace.css compatibility theme was loading but silently doing nothing: its palette and theme rules are scoped to .wa-palette-shoelace and .wa-theme-shoelace respectively, and neither class was present on <html>. Added a synchronous script to index.html~head that sets both classes before CSS is evaluated. With the shoelace palette now active, --wa-color-brand-40 (rgb(0, 93, 147)) is closer to the original Shoelace --sl-color-primary-800 (rgb(7, 89, 133)) than --wa-color-brand-30 was.
|
Thanks for running through the examples. Here's where I've landed on each item:
|
_reapply_style replays style.cssText on element upgrade, which erases any inline visibility set by stop(). Use a CSS class instead so the two mechanisms don't interfere.
|
I implied this above, but in the interest of transparency, for the purposes of this PR if I ran across something that seemed odd to me but it was the same in both Some of them you mentioned above. Another example is that in progress bar, If you'd like, I can spend some time cataloging these and, after making a brief attempt to solve, log them as an issue. For some, I wasn't able to determine what they were supposed to do. scrollcontainer is an example like this. Spending more time with the code I probably could, but, like I said, my MO for this PR was that if it worked the same across branches it was good. Happy to adjust. UPDATE: After writing that I kept going with tests and found the text color difference in the switch example was fixable. It also seems to have fixed button text color that we'd been ignoring (couldn't find an existing Issue for it, but one of the buttons in the example has blue text not visible in |
wa-slider is built without pointer capture (sl-range wrapped a native <input type=range> which the browser captures implicitly). Without capture, pointerup fires on whatever element is under the pointer at release time, so on_release could miss entirely or fire on a different slider. Move the listener to document with a _dragging guard.
Both components set text color via CSS variables inside their shadow DOM (--wa-form-control-value-color for wa-switch, --wa-color-on-quiet for wa-button), so the host element's inherited 'color' property never reached the label text. Override _reapply_style in each to forward the Pack color as the appropriate CSS variable in the inline style string. Switch: restores feature parity with sl-switch, which let the host color cascade through naturally. Button: fixes a pre-existing bug -- sl-button also blocked inherited color the same way, so button text color was silently ignored on both branches.
Completely agreed that constraining scope to "reproduce the errors that are already there" makes sense. I only flagged the ones above because there was an off chance they were simple oversights; if a fix didn't fall out immediately, then it's not worth it. If you've got a list of known wrinkles, then it might be worth turning them into a bug report; but it's not worth going through a serious bug hunt looking for them. The problem we have is that verification is difficult without a web testbed (see #2662). When we are eventually able to add that testing, having a clear catalog of know bugs will be useful - because each of them will be a test failure that can be resolved. At that point, it will be worth opening bugs, because we will have clear success criteria (and a basis to prevent future regression). |
freakboy3742
left a comment
There was a problem hiding this comment.
Thanks for those fixes. The FOUC issue is likely exacerbated by my geography - being in Perth is a really good way to reveal when there's a download delay :-)
However, we don't need to resolve that now. What is here works at least as well as what was there previously, and gets us away from the deprecated Shoelace. Nice work - thanks for the contribution!
Nah, I wasn't organized enough for that... |

Changes
Component Migration
Migrates the web backend from Shoelace (now in maintenance mode) to WebAwesome, its official successor maintained by the Font Awesome team.
There is no official migration guide from Shoelace to WebAwesome. Changes were verified against individual WebAwesome component docs:
sl-*wa-*wa-sl-rangewa-slidersl-menu+sl-menu-itemwa-dropdown-itemdirectlyvariant="primary"variant="brand"appearance="outlined"sl-change,sl-inputchange,input.show()/.hide().openpropertyverticalattributeorientation="vertical"--sl-font-sans,--sl-color-primary-800, etc.--wa-font-family-body,--wa-color-brand-40, etc.--sl-*tokens intoga.cssto native--wa-*equivalentsdist/dist-cdn/dist-cdnis required for direct browser loading without a bundlershoelace.jsbundlewebawesome.loader.jsautoloaderLoads WebAwesome's
shoelace.csscompatibility theme. The theme's palette and overrides are scoped to.wa-palette-shoelaceand.wa-theme-shoelaceon the<html>element; a synchronous script inindex.html~headsets both classes before CSS is evaluated. All CSS token references intoga.csshave been migrated to native--wa-*variables.Async element upgrade workarounds
WebAwesome registers custom elements asynchronously via a lazy loader. Unlike Shoelace's synchronous bundle, element properties (
.value,.checked,.min,.max, etc.) may not exist when Python code runs during widget init. This causesAttributeErrorcrashes on Switch, Selection, and Slider.shoelace.jswas a pretty sizeable file that loaded/defined every custom element in the shoelace library. WA doesn't do that. Instead it loads a very small autoloader that watches for mutations in the DOM and, if it sees a new custom element in the DOM that is in its library, then it downloads that code and defines the element and upgrades it to an element of the custom element classHowever, in Toga's widget constructors, we first create the element, then start setting attributes (some of which don't exist on vanilla
HTMLElement), then later it gets added to the DOM. This causesAttributeErrorsin both sets and gets.The workaround is to wrap the native element in a proxy that:
Since we're wrapping the native element in a Python object, we provide an
unwrapmethod that returns the native element for cases where it's necessary (i.e., when we call a JS method (e.g.,appendChild,insertBefore)).We can auto-unwrap any
NativeProxys sent as arguments when the receiver is itself aNativeProxyby modifying our__getattr__override to inspect any callables and arguments and unwrap where necessary. One.unwrapis still necessary when callingappendChildonWindow'snativesinceWindowis not itself aNativeProxy.Cleanup
style_frameworkfrom example pyproject.toml files. This key was deprecated in Briefcase and is no longer consumed.Pre-existing bug fixes
These fix bugs that predate the Shoelace→WA conversion and are unrelated to the migration itself:
db21b903b, freakboy3742):ActivityIndicatorandTimeInputwere missing from the entry points inweb/pyproject.tomland were not registered as web widgets.Box.remove_childwas a DOM no-op (only cleared the Python container reference without touching the DOM), andBox.insert_childwas ignoring the index and always appending. Added properremoveChild/insertBeforeoverrides inbox.py.wa-button(likesl-buttonbefore it) sets text color via a shadow DOM CSS variable, so the host element's inherited color property was silently ignored. Fixed by forwarding the Pack color as--wa-color-on-quietin the inline style. Also restores feature parity forwa-switchlabel color via--wa-form-control-value-color.Testing
Tested 19 web-compatible examples against both
main(Shoelace) and this branch (WebAwesome) using manual interactive testing. All work the same on both branches.What problem does this solve?
Shoelace is in maintenance mode and will not receive new features. WebAwesome is its actively developed successor.
Fixes #4227
PR Checklist:
Trailers
Assisted-by: Claude Sonnet 4.6