Conversation
86d1faf to
a89e0d6
Compare
There was a problem hiding this comment.
Pull request overview
This PR implements scoped CSS support for Vue Single File Components (SFCs) in ipyvue, addressing issue #102 where styles leak between components. The implementation follows the httpVueLoader scoping approach and adds a new scoped trait to allow programmatic control of scoping behavior.
Changes:
- Implements CSS scoping by transforming selectors to include unique data attributes
- Adds optional
scopedtrait to VueTemplate to override template-level scoping - Includes UI tests verifying scoped styles work correctly and don't affect unscoped elements
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| ipyvue/VueTemplateWidget.py | Adds new scoped Bool trait to control CSS scoping behavior |
| js/src/VueTemplateModel.js | Adds scoped property to the JavaScript model defaults |
| js/src/VueTemplateRenderer.js | Implements core scoping logic: selector transformation, scope ID generation, and lifecycle integration |
| tests/ui/test_template.py | Adds comprehensive tests for both template-based and trait-based scoped CSS |
| .gitignore | Adds ipyvue/static/ to ignored directories for build artifacts |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
js/src/VueTemplateRenderer.js
Outdated
| scopedSelectors.push(`${segments[1]}${scopeSelector}${segments[2] || ''}`); | ||
| } | ||
| }); | ||
| const scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length); |
There was a problem hiding this comment.
The substr method is deprecated in JavaScript. Consider using substring or slice instead. Replace rule.cssText.substr(rule.selectorText.length) with rule.cssText.substring(rule.selectorText.length) for better future compatibility.
| const scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length); | |
| const scopedRule = scopedSelectors.join(',') + rule.cssText.substring(rule.selectorText.length); |
js/src/VueTemplateRenderer.js
Outdated
| const scopedRule = scopedSelectors.join(',') + rule.cssText.substr(rule.selectorText.length); | ||
| deleteRule(i); | ||
| insertRule(scopedRule, i); | ||
| continue; |
There was a problem hiding this comment.
The continue statement after modifying the rule is unnecessary and potentially confusing. When you delete and insert a rule at the same index, the loop counter already moves to the next rule naturally. The continue statement is redundant here and could mislead readers into thinking there's a reason to skip further processing. Consider removing it for clarity.
| continue; |
| } else { | ||
| if (style.innerHTML !== css) { | ||
| style.innerHTML = css; | ||
| } | ||
| style.removeAttribute('data-ipyvue-scope'); | ||
| } |
There was a problem hiding this comment.
When multiple component instances share the same cssId but have different scoped values, there's a bug in the CSS processing. For example, if instance A uses scoped CSS and instance B (created later with the same cssId) uses unscoped CSS, instance B will reuse the same style element. At line 124, the check style.innerHTML !== css may fail if the CSS content is the same, causing the code to skip updating the innerHTML. Then at line 127, the scope attribute is removed, but the CSS rules in the style element remain scoped from instance A, breaking instance A's styling. To fix this, the condition at line 124 should also check if the current scoping state differs from the desired state, or the innerHTML should always be reset when transitioning from scoped to unscoped.
| ipywidgets_runner, page_session: playwright.sync_api.Page | ||
| ): | ||
| def kernel_code(): | ||
| from test_template import ScopedStyleTemplate |
There was a problem hiding this comment.
The module 'test_template' imports itself.
The module 'tests.ui.test_template' imports itself.
| ipywidgets_runner, page_session: playwright.sync_api.Page | ||
| ): | ||
| def kernel_code(): | ||
| from test_template import ScopedCssTemplate |
There was a problem hiding this comment.
The module 'test_template' imports itself.
The module 'tests.ui.test_template' imports itself.
|
Regarding the review note about redundant ipyvue import: that import is inside kernel_code that runs in the notebook kernel context, so it isn't redundant with the test module import; it ensures ipyvue is available in the kernel. We'll ignore that comment. |
a89e0d6 to
4adef12
Compare
|
Addressed the remaining review notes in latest push: switched to substring, removed redundant continue, and handled scoped→unscoped reuse by resetting style content when prior scope was set. |
dff5cff to
9d60d1e
Compare
.gitignore
Outdated
| # Compiled javascript | ||
| ipyvue/labextension/ | ||
| ipyvue/nbextension/ | ||
| ipyvue/static/ |
There was a problem hiding this comment.
This doesn't seem related.
9d60d1e to
e0b9e79
Compare
- Implement scoped CSS handling for Vue SFC styles - Add scoped trait for css property - Rewrite CSS selectors with unique data-v-* attributes - Add example notebook demonstrating scoped styles - Add UI tests for scoped CSS functionality
Add scoped_css_support trait to VueTemplate that controls whether <style scoped> in templates is processed. This is a backwards-compatible change that defaults to False to avoid breaking existing code. Configuration options: - Environment variable: IPYVUE_SCOPED_CSS_SUPPORT=1 - Global setting: ipyvue.scoped_css_support = True - Per-widget: VueTemplate(..., scoped_css_support=True) The explicit scoped=True/False trait (used with the css trait) still works independently of this setting.
e0b9e79 to
1b37d8f
Compare

Summary
Issue
Closes #102