Skip to content

feat: add useAttribute hook for boolean property ↔ attribute reflection#99

Draft
cristinecula wants to merge 1 commit into
mainfrom
feature/neo-1055-add-useattribute-hook
Draft

feat: add useAttribute hook for boolean property ↔ attribute reflection#99
cristinecula wants to merge 1 commit into
mainfrom
feature/neo-1055-add-useattribute-hook

Conversation

@cristinecula

@cristinecula cristinecula commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

Note: This is one of three alternative implementations for NEO-1055. See also:

Summary

Adds a standalone useAttribute hook for bidirectional boolean property ↔ attribute sync with built-in loop prevention. Addresses NEO-1055.

API

const [opened, setOpened] = useAttribute('opened');
  • Boolean-only (v1): attribute present = true, absent = false
  • Always reflects: setOpened(true)setAttribute('opened', ''), setOpened(false)removeAttribute('opened')
  • Dispatches cancelable opened-changed event (same convention as useProperty)
  • Supports function updater: setOpened(prev => !prev)
  • Supports lift() pattern for parent-controlled state
  • Reads initial attribute value from host element
  • Respects parent-provided .prop= bindings
  • Handles kebab-case ↔ camelCase conversion (open-on-focusopenOnFocus)
  • Throws when used in virtual components

Consumer usage

import { component, useAttribute, html } from '@pionjs/pion';

function MyDropdown() {
  const [opened, setOpened] = useAttribute('opened');
  return html`<div>${opened ? 'open' : 'closed'}</div>`;
}

customElements.define('my-dropdown', component(MyDropdown, {
  observedAttributes: ['opened']
}));

// CSS selectors work:    :host([opened]) { ... }
// Declarative HTML:      <my-dropdown opened></my-dropdown>
// Programmatic JS:       dropdown.opened = true
// Query selectors:       document.querySelector('[opened]')

Changes

File Change
src/use-attribute.ts NewuseAttribute hook implementation
src/symbols.ts Add reflectingSymbol for loop prevention
src/component.ts Add reflectingSymbol guard + null→false coercion in attributeChangedCallback
src/core.ts Export useAttribute
test/use-attribute.test.ts New — 13 tests covering all behaviors

How it differs from useProperty

Aspect useProperty useAttribute
Attribute reflection No (must add manually) Always reflects
Type Any Boolean only (v1)
Default value Explicit second arg false (boolean semantics)
API intent General property management Attribute-first, signals CSS/HTML intent

Test results

All 81 tests pass (13 new + 68 existing), 96.5% code coverage, zero regressions.

Add a standalone useAttribute hook that provides bidirectional boolean
property ↔ attribute sync with built-in loop prevention.

API: const [opened, setOpened] = useAttribute('opened');

- Always reflects: true → setAttribute('', ''), false → removeAttribute
- Dispatches cancelable *-changed events (same convention as useProperty)
- Supports function updater: setter(prev => !prev)
- Supports lift() pattern for parent-controlled state
- Reads initial attribute value from host element
- Respects parent-provided .prop= bindings
- Throws when used in virtual components
- Handles kebab-case ↔ camelCase conversion

Also adds reflectingSymbol guard + null→false coercion to
attributeChangedCallback in component.ts to prevent feedback loops
when the hook reflects property changes back to attributes.

Closes NEO-1055
@changeset-bot

changeset-bot Bot commented Feb 16, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7aa2d3b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify

netlify Bot commented Feb 16, 2026

Copy link
Copy Markdown

Deploy Preview for pionjs ready!

Name Link
🔨 Latest commit 7aa2d3b
🔍 Latest deploy log https://app.netlify.com/projects/pionjs/deploys/6992bea9ec80e100089c3691
😎 Deploy Preview https://deploy-preview-99--pionjs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant