feat: component attribute handling and handler state scoping#49
Merged
Conversation
…ndling
Rewrite the component directive processing to match the Node.js reference
implementation for entry stream and component template stream handling.
Entry stream changes:
- Emit opening tag, attributes, closing bracket as separate fragments
- Add process_component_tag_attributes for component-specific attribute logic
- Static attrs on components emit as attribute fragments with rawValue
- First dynamic/static attribute gets attrStart marker
- Skipped attrs (class, style, role, data-*, aria-*) get attrSkip
- Process slot children with full directive support
Component template stream changes:
- Add process_component_template for shadow DOM wrapping
- Skip double-wrapping when template already has <template> tag
- Inject stylesheet link after opening template tag
- Strip @/:/?-prefixed runtime attributes from template tags
- Derive CSS path as {tag_name}.css when component has CSS content
Update existing tests and add new tests for:
- No double-wrap of existing template elements
- Styled component template with existing template tag
- Runtime attribute stripping from template tags
- Component with static attributes and slots
- Legacy components without styles
- Self-closing components with dynamic attributes
- Nested self-closing tags in component slots
- Boolean attribute with attrStart marking
- Meta/link tags with dynamic attributes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
d609cf2 to
7e3a3b4
Compare
Merge the duplicated attribute-processing methods into a single process_tag_attributes that accepts an is_component flag. Extract maybe_mark_attr_start helper to set attr_start on the first non-skipped component attribute. Add test_component_attr_skip and test_component_multiple_nested tests ported from the Node prototype. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
7e3a3b4 to
e8209ad
Compare
- Add resolve_value helper: checks local_vars first, then global state - Add evaluate_condition helper: merges local vars into state for condition evaluation - Fix process_for_loop to resolve collections from local vars (enables nested for loops like outerItem.innerItems) - Fix process_attribute to resolve values from local vars and handle numeric/bool types properly - Fix process_if to evaluate conditions against merged local+global state - Simplify process_signal to delegate to resolve_value 13 new tests: boolean attrs (true/false/missing/multiple), simple attrs (string/numeric), template attrs, raw signals, nested for loops, for+if state scoping. 20 total handler tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add component_attrs field to WebUIProcessContext for accumulating attribute values between attrStart and the component fragment - Add convert_hyphen_to_camel_case helper for data-* attribute names - Update process_attribute to accumulate attrs: boolean, raw_value, dynamic, complex, and template attribute types - Add render_template_attr_value for raw (unescaped) template rendering - Update process_component to scope local_vars from accumulated attrs with proper save/restore for parent scope isolation - Add component_attr_name helper for stripping : prefix and camelCase - Add 6 tests covering simple, template, camelCase, complex, boolean, and parent scope isolation scenarios Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add fragment matcher DSL to webui-test-utils with constructors: raw(), signal(), attr(), bool_attr(), attr_raw(), attr_skip(), attr_complex(), component(), for_loop(), if_cond() and variants with attr_start. Macros assert_fragments! and assert_stream! provide: - Count verification with descriptive fragment dump on mismatch - Per-index field-level assertions with clear error messages - Compact syntax mirroring the Node prototype test style Migrate 3 parser tests as demonstration (15 lines → 5 lines each). Remaining tests can be migrated incrementally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert 44 parser tests from verbose matches! patterns to the (use .contains() patterns not supported by the macro). matches! calls: 195 → 17 File size: 2601 → 2260 lines (-341) All 81 tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
janechu
approved these changes
Feb 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parser
attrStart,attrSkip, andrawValuesemanticsprocess_tag_attributesfor regular and component elements<template><link>into component templates@/:/?-prefixed runtime attributes from template tags using tree-sitterHandler
resolve_valuehelper: checks local_vars first, then global stateevaluate_conditionhelper: merges local vars for expression evaluationouterItem.innerItems)attrStartand component fragment, makes them available as local vars inside the component templatedata-title→dataTitle):prefix) state resolution — passes objects to component scopeattrSkip) excluded from component stateTests