Version Packages - #86
Open
github-actions[bot] wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
🟢 Approval recommended
The changes are limited to standard release bookkeeping (version bump, changelog update, and changeset consumption) with no functional code modifications.
Pull request overview
Release/versioning PR generated by Changesets to publish @interline-io/catenary@0.14.0, reflecting the previously merged cat-tooltip accessibility and behavior improvements.
Changes:
- Bumps package version from
0.13.0to0.14.0. - Adds the
0.14.0release notes toCHANGELOG.md. - Removes the consumed changeset file now included in the changelog.
File summaries
| File | Description |
|---|---|
| package.json | Updates the published package version to 0.14.0. |
| CHANGELOG.md | Adds the 0.14.0 entry describing the cat-tooltip changes included in the release. |
| .changeset/tooltip-hover-dismiss.md | Deletes the changeset file after it’s been incorporated into the changelog/version bump. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Deploying catenary with
|
| Latest commit: |
147aa1c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1d1e0d0b.catenary-5bw.pages.dev |
| Branch Preview URL: | https://changeset-release-main.catenary-5bw.pages.dev |
github-actions
Bot
force-pushed
the
changeset-release/main
branch
3 times, most recently
from
September 1, 2026 03:43
ea7dc59 to
e887b8e
Compare
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
September 1, 2026 05:08
e887b8e to
147aa1c
Compare
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.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@interline-io/catenary@0.14.0
Minor Changes
#92
45df370Thanks @drewda! -cat-checkboxaccessibility review, plus API additions modelled on Reka UI. Part of #64. All additive — no existing prop, event or behavior changes.aria-labelandaria-describedbynow reach the native input. The component's root is the<label>, so undirected fallthrough attributes landed there — wherearia-labeldoes nothing for the input's accessible name. A checkbox with no visible text was therefore impossible to name from outside, which matters for the common case of a row selector in a table. There is now anariaLabelprop, and fallthrougharia-*/data-*are routed to the input.class,styleand listeners stay on the wrapper, exactly where they already applied — moving a spacing class onto the box would shift the layout of existing call sites.The mixed state survives a click. The browser clears the DOM
indeterminateproperty as soon as the box is clicked. Since theindeterminateprop had not changed, the watcher never refired, so a parent checkbox whose children were unchanged silently lost its mixed state in the accessibility tree. It is now restored after the change event.No
aria-checked="mixed"is set: a native checkbox maps.indeterminateto a mixed state on its own, and redundant ARIA over working native semantics is a regression rather than an improvement. The APG example that setsaria-checkedis for customrole="checkbox"widgets.Reka UI-style additions.
trueValue/falseValue— emit something other than a boolean. They only take effect when set, so a caller binding a truthy non-boolean keeps reading as checked.name,valueandrequiredon the native input, so a checkbox can take part in native form submission and validation. In array-binding mode the nativevaluefalls back tonativeValue, which is already the option's semantic value and is whatcat-radiobinds — otherwise pairingnamewith the existing array API would submit the browser default of"on"for every checked box.Existing usage renders byte-identically with one exception: an array-binding checkbox now carries
value="<nativeValue>"on its input. That is inert unless the checkbox is inside a native<form>, but it will show up in a DOM snapshot.The playground page gains the
demo-a11ysection it was missing, along with demos for the unnamed-row-selector case, custom true/false values, and native form submission.#91
695bd54Thanks @drewda! -cat-fieldaccessibility review. Part of #64.The default slot now exposes
idanddescribedby.cat-fieldrenders a real<label for>, but only the controls that injectFieldIdKey—cat-input,cat-select,cat-textarea,cat-slider,cat-taginput, and anything built on them — ever carried that id. Wrapping anything else left the label attached to nothing: visually correct, and unnamed to a screen reader. There was no way for a raw control to reach the id, which made the component's own documented example (<cat-field label="Name"><input class="input"></cat-field>) one of the broken cases. Now:cat-fieldwarns in development when its label names nothing. Three distinct cases, each naming its own fix: the label resolves to no element (bind the id from the slot, or usecat-fieldset), to more than one (duplicate DOM ids — give every control after the first an explicitid), or the field wraps no form control at all (a<label>is the wrong element for a caption).Expect these warnings when you upgrade. A label over a group of controls is the common one — one
<label>cannot name a set, so a label above several checkboxes, radios, switches or buttons is orphaned.cat-fieldsetis the component for that: its<legend>names the group.cat-checkbox,cat-radioandcat-switchdeliberately do not take the field id, since they render their own wrapping<label>and a second name would be concatenated onto the first.The playground's own field page demonstrated three of these bugs — a label over three buttons, and two pairs of inputs sharing one id. All now use
cat-fieldsetwith per-control names, and the registration form routes its validation error throughvariant/messageinstead of a hand-written<p class="help">, so the error is linked byaria-describedbyand the input is markedaria-invalid. The page gains thedemo-a11ysection it was missing.#90
d989974Thanks @drewda! - Four form-control accessibility fixes. Closes #49.cat-radiogroups properly. Radios are grouped by a sharedname, not by sharing av-model— without one, each radio is its own group of one: arrow keys do not move between them, every radio is a separate tab stop, and a screen reader announces each as "1 of 1". A mouse user sees nothing wrong, which is why it goes unnoticed.A radio that resolves to no name warns in development, naming the two ways to fix it. Expect this warning when you upgrade — it is the point of the change, and it fires once per unnamed radio on mount. Either give every radio in a group the same
name, or wrap the group in the new<cat-fieldset radio-group>, which supplies a generated one. An explicitnamealways wins.radio-groupis opt-in rather than automatic because a fieldset is a generic grouping wrapper: naming every descendant radio would merge two independent questions under one legend into a single group, and the resulting bug is silent — picking "Red" makes the browser natively uncheck "Small", and sincesizenever changed, Vue's patch skips the DOM write, so the size selection vanishes from the UI while state still says it is selected.The playground's own radio demos shipped the ungrouped bug — 27 radios, none named. All are grouped now, which is also what the documentation should have been showing.
cat-selectreadonlyno longer removes the control from the tab order. It was implemented asdisabled, so a keyboard or screen reader user could not reach it to read the current value, and it was announced as unavailable rather than read-only. It now stays focusable, carriesaria-readonly, gets a muted surface so it does not look editable, and refuses to change — while leaving Enter (so a form still submits) and Cmd/Ctrl shortcuts (copy, select-all) alone. If a change reaches it by another route it restores the previous selection, including every option of amultipleselect.cat-inputandcat-textareaaccept an explicitid. Acat-fieldprovides one id, so a grouped field containing more than one control gave them all the same one: duplicate DOM ids, and a<label for>resolving to whichever came first.cat-selecthas carried this escape hatch; these two did not.cat-textareaacceptsariaLabel, matchingcat-inputandcat-select, so a textarea used without a labelledcat-fieldcan be named understrictTemplates.#89
3fc741cThanks @drewda! -cat-tabskeys its tablist registry on each item's stable id rather than itsvalue, which fixes five ways the tablist could drift from the template.The registry was keyed on
value— a prop the consumer can change — so a changed value read as a different tab. That single decision produced a cluster of failures, all of which go away when identity and content are separated:tabIdis minted once per item withuseId()and outlives every prop change.Fixed:
v-iftab in the middle of a list appended to the end of the tablist while its panel rendered in the middle. Live in production. The registry is sorted by panel document position after each registration, so an unkeyed list that grows, shrinks or inserts mid-way settles correctly — and a keyed list reordered without any prop change corrects itself as soon as anything registers again.focusTabAtindexed av-fortemplate-ref array, which Vue fills in mount order. Once the tablist is ordered by document position, indexing one by the other moves DOM focus to one button while activating another — the worse half of the ordering bug. Resolved from the DOM instead.v-forshrinking no longer empties the tablist. Re-registering under a value a sibling still owned overwrote that sibling's entry, which unmounting then deleted;['a','b','c']→['b','c']left zero tabs and two orphaned panels.aria-labelledbynames a tab that is not rendered.tab.value, so changing one destroyed and recreated the button and dropped focus to<body>. Keyed ontabId, the button is patched in place.Two tabs sharing a
valuenow render one selected tab rather than two: selection is resolved by the item'stabId, since keying the registry ontabIdremoved the de-duplication that keying onvalueused to provide.Also: a bound
modelValuethat names no tab now falls back to the first tab rather than leaving a tablist with nothing selected and every panel hidden. An unbound tablist is unchanged — none selected, first still keyboard-reachable — since that is an unmade choice rather than a broken state.The parent/child contract moved to a typed
InjectionKey, matchingcat-steps. The string-keyedprovide/injectit replaces carried the registration signature hand-copied into the child, so the two sides were asserted to agree rather than checked — and this change alters that signature.The fallback emits
update:modelValue, sov-modelfollows what is displayed. Without it, a consumer that renders content from the model — panels outside the tabs gated onactiveTab === '...'— showed a highlighted tab above an empty region once the active tab unmounted.cat-stepsdoes not emit; the two differ here deliberately.Two tabs sharing a
valuenow warn in development. Selection resolves a value to the first tab carrying it while focus moves by index, so a duplicate leaves the second unreachable and puts focus and selection on different buttons. That is not fixable from inside the component, sincev-modelcarries a value and two tabs sharing one are indistinguishable to it.One known gap: a keyed list reordered with no other change re-sorts only once something registers again, so a pure reorder with no accompanying prop change is corrected on the next registration rather than immediately.
cat-stepsshares this registry's shape and most of these faults; it is not changed here. Extracting the two into one implementation is the real fix and is worth doing separately, since porting between them by hand is what letcat-tabsship the ordering bug for months aftercat-stepsfixed it in #66.#85
85940d2Thanks @drewda! -cat-tooltipconforms to WCAG 1.4.13, gains an affordance and motion control, and stops sticking after a click. Closes #50 and #79.Content on Hover or Focus (WCAG 1.4.13)
The success criterion asks for three things and the bubble now does all three.
Hoverable. The bubble carried
pointer-events: none, so it was never a pointer target: any move toward it firedmouseleaveon the wrapper and dismissed it. Anyone using magnification, or wanting to select a long tooltip's text, could not reach the content. It is nowpointer-events: autowhile visible, with a short close delay bridging the gap between trigger and bubble so crossing it does not dismiss — position-agnostic, unlike a transparent CSS bridge which would need geometry per side.Making it hoverable moved the dismissal boundary, so the bubble also handles its own
mouseleave. Without that the wrapper'smouseleavehad already fired when the pointer crossed onto the bubble, and nothing was left to dismiss it once the pointer left again — a tooltip stuck open would have been worse than the bug being fixed.Dismissible without moving the pointer. Escape was bound on the wrapper, so it only fired with focus inside; a tooltip opened by hover while focus sat elsewhere could only be dismissed by moving the pointer, which the criterion explicitly disallows. Escape now goes through the shared LIFO dismiss stack, which also means the keypress is consumed — a tooltip open inside a
cat-modaltakes the first press and the dialog stays open, instead of both closing at once.No longer sticks after a click
When the slot has no focusable child the wrapper takes a
tabindexof its own, so clicking it focused it and the bubble stayed up after the pointer had left. Focus now shows the tooltip only when the focus did not come from a pointer press, tracked frompointerdownrather than:focus-visible— the same distinction, but:focus-visibleis a rendering hint that jsdom reports false for synthetic focus, which would make the behaviour untestable and environment-dependent. Keyboard focus still holds the bubble open when the pointer wanders away.This is the dominant usage pattern in the consumer apps: 91 of their 129
cat-tooltipcall sites wrap a non-focusable icon or span.New props
affordance—cursor: helpand a dotted underline on the trigger. Opt-in, since it changes how slot content looks at every existing call site. Without it nothing on screen tells a pointer user a tooltip exists; two playground demos had already hand-rolled exactly these declarations inline.animated(defaulttrue) — turn the fade off. Suppressed automatically underprefers-reduced-motion: reduceregardless of the prop, matchingcat-collapse,cat-stepsandcat-msg, which had the blockcat-tooltiplacked.Playground
The tooltip page had 20 icon-only triggers with no accessible name at all — a critical axe
button-nameviolation on the page that documents the component, teaching the opposite of whatcat-button's own docs say.aria-describedbysupplies a description, not a name. Each now usescat-button'siconprop with anaria-label, which is both the documented pattern and the correct icon sizing. With the two hand-rolled affordances replaced by the prop (removing ahas-text-infospan that failed contrast), the page goes from 21 axe violations to zero in both themes.