feat(react,ui): PretableTextInput and PretableCheckbox — the fourteen fields and checkboxes on the kit (components SP3) - #585
Merged
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e input and checkbox sites Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The kit's checkbox, one component for the model the row-select cell, the
column toggle and the boolean cell already drew by hand: a
`button[role="checkbox"]` carrying `data-pretable-checkbox` and
`aria-checked`, with the grid's CheckIcon / MinusIcon as the glyph. A
button rather than a styled native input because mixed state, the roving
tabindex in body cells and a themed glyph are all plain on a button and a
fight on an `<input>`.
The consumer's `onClick` runs first and may `preventDefault()` to veto the
toggle — how a shift-click range select keeps its click without a second
write. Keyboard is the native button's, so no key handler.
A name can come from a wrapping `<label>`, which props cannot see, so
`hasAccessibleName` reads the DOM once after mount and warns when nothing
names the control.
Three mutation checks, each restored:
- removing the `defaultPrevented` guard fails the veto test
- `checked !== true` → `!checked` fails the mixed→true assertion
- `hasAccessibleName` ignoring `closest("label")` fails the warning
test's label case
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The kit's text field is the native `<input>` and nothing more. A wrapper would have to reinterpret `type`, and with it goes the platform's date picker, `inputMode`'s soft keyboard and the autofill the native element gets for free. What a theme needs is only the box, which grid.css draws through `data-pretable-text-input`; `site` lands as `data-pretable-site` after the spread, and a site's own attribute still arrives through it. No wrapper, no clear button, no debounce: the three chrome fields that will use it each own their value semantics, and one owner for all three would be wrong for two. The name check is `PretableCheckbox`'s — a merged callback ref so the component can read its own node, and one post-mount `hasAccessibleName` call, because a `<label for>` is a fact about the tree and not the props. That branch is what the new test's label-for case covers; the checkbox suite only exercises the wrapping label. Mutation-checked: dropping the `label[for]` branch from `hasAccessibleName` makes the label-for case warn and the test fail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…r covered Every glyph in the set carries the same `data-pretable-icon` hook, so asserting its presence proved only that some icon rendered — the tick and the minus were interchangeable and the test would not have noticed. It now reads the drawn `<path d>` and anchors each state against the icon rendered on its own, plus asserts the two differ. Mutation-checked: swapping CheckIcon and MinusIcon in checkbox.tsx fails the test (expected 'M4 8h8' to be 'M3.5 8.5 6.5 11.5 12.5 4.5'); before this change it passed. Also adds a `<label for>` case to the warning test, the naming branch the wrapping-label case does not reach. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Mirrors SP2's Select slot (f10cbe0) for two more kit components: components.TextInput and components.Checkbox now resolve through the same context, with member JSDoc on PretableComponents and the two new Pretable*Component types. Two mutation checks confirm the four-part edit is complete: dropping Checkbox from useResolvedComponents' deps array made its rerender test fail (stale hit), and dropping TextInput from the identity comparison made its replacement test fail too. Both were reverted after confirming. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s fourteen controls The field box the three text fields each drew for themselves, and the 16px square four checkbox sites hand-kept a copy of, become two kit rules at the top of the layer — beside the push-button and select rules, ahead of every site rule so a site's size still wins at equal specificity. Both join the state section's ring and disabled list, and the checked checkbox joins the forced-colours block: its fill and its glyph colour are both author values the UA erases, which between them are the whole of what the control says. The old four-site checkbox rules stay for now. They come AFTER the kit rules in source order and declare the same values, so nothing moves visually until Task 8 puts [data-pretable-checkbox] on the sites and deletes them. The site guard grows a third and fourth kind — TEXT_INPUT_SITES (three, one of which is really three attributes: the dialog's value field and its between-range min/max twins) and CHECKBOX_SITES (seven) — each with its own forbidden list and its own OWN entry. `display` stays legal at bool-cell alone: `display: flex` there places a 16px control in a table cell rather than copying the kit's box. The bracket guard anchors on both new kit base rules against their own sites. Two intended failures, red until Task 8 migrates the sites: the site guard (the fields and checkboxes still declare the kit's box) and the new no-element-type-selector guard (twelve `input[data-pretable-…]` / `button[data-pretable-…]` selectors). Everything else green. Mutation-checked both new guards: deleting `border-radius: 3px` from the kit checkbox rule fails the kit checkbox guard (the surviving copy in the old four-site rule does not vouch for it); moving the kit checkbox base rule below the tool-search rule fails the bracket guard. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The three chrome text fields and the seven checkboxes now render
`PretableTextInput` / `PretableCheckbox`. Every site attribute is kept, so
nothing that identified one of these controls stops identifying it; what
goes is the second hand-kept copy of the box in grid.css.
The wrapper rename. The builder's set-shape checklist wrapper wore
`data-pretable-filter-row-value` — the FIELD's attribute — and was told
apart from the field only by `div[…]` vs `input[…]`. Both are kit controls
now, so the element type can no longer separate them and one shared
attribute would hand the wrapper the field's `block-size: 24px`. The
wrapper is `data-pretable-filter-row-set`.
Where the row-select writes live. In `onCheckedChange` for the plain
toggle, in `onClick` for the shift-click range — which calls
`preventDefault()` to veto the kit's toggle, because the range IS the
write and a toggle landing on top of it unselects the row just clicked.
The old handler called `preventDefault()` UNCONDITIONALLY; it dated from
the control's first commit with no stated reason, a `<button type=button>`
click has no default action to prevent, and nothing in the surface reads
`defaultPrevented` on a click that also stops propagating. So it is now
the shift branch's alone, and the writes stay where they read best rather
than moving wholesale into `onClick` behind a no-op `onCheckedChange`.
The kit-level checkbox ring offset. `:where([data-pretable-checkbox]
:focus-visible) { outline-offset: 1px }`, after the shared ring: a 16px
square paints to its own edge, so the shared `-2px` would land ON the box
and read as a recoloured border. The two checklists each argued this in a
rule of their own; every checkbox gets it now — including the four button
sites, which had the UA's ring before the kit existed.
Three guard fixes in css-cascade.test.ts.
- The element-type guard builds its pattern from the SITE LISTS instead of
a hand-kept alternation, and catches `…) input:focus-visible` as well as
`…] input`. `button[data-pretable-filter-join]` is deliberately excluded
rather than stripped as the task sketched: the join is no kit control,
and the SAME attribute rides a `<span>` (`Where`) that must not take the
button's border — the element type there is the selector doing its job.
- The checklist test pinned the old `…) input:focus-visible` rule. It now
asserts the wrapper still takes its own line under its new attribute,
that the ring comes from the kit's offset rule, and that no site rule
draws a ring for `filter-row-choice`.
- `DISPLAY_ALLOWED` was vacuous — bool-cell declares `display: flex` and
the forbidden pattern is `inline-flex`, so the carve-out never fired.
Deleted; the pattern already names only the kit's value.
Plus a new ordering guard: the checkbox offset rule must come AFTER the
shared ring in source. Both are :where()-flattened to (0,0,0), so order is
the whole of what makes it win.
The choice checkboxes carry `data-pretable-value`, not `data-value`: the
attribute-contract sweep caught the namespace escape, and the kit select
already established that name for "a button has no `.value`".
Mutation-checked, each restored: a `border` on the builder field rule
fails the site guard; the offset rule above the shared ring fails the new
ordering guard; dropping the shift-branch `preventDefault()` fails both
the new `row-selection-callback` range test and the existing 100k
`indexed-rendering` shift-span test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… data-pretable-option-value Six review findings from the fourteen-site migration (74609cf). 1. A disabled, checked checkbox lost its tick. The kit's shared `:disabled` rule set `color: var(--pretable-text-dim)`, and on a checkbox `color` is not label ink — it is the CHECK GLYPH, painted on `--pretable-checkbox-checked-bg` at about 1.9:1. A read-only boolean column renders `BooleanCellControl` disabled on EVERY row, so that was every tick in the column. The checkbox is split out of the shared rule: the four text-drawing controls keep the dim ink, the checkbox keeps only `cursor: default` and lets its own tokens (and the checked rule above it) say what disabled looks like. Guarded in `css-cascade.test.ts` — no rule matching `[data-pretable-checkbox]:disabled` outside the forced-colours block may declare `color`; mutation-checked by re-adding the declaration, which fails the guard, then reverting. 2. `data-pretable-value` on the two checklists' choices conflated identity with state: on a select trigger that attribute is the one COMMITTED value. Both checklists now write `data-pretable-option-value`, and `listbox.tsx`'s options, which wrote plain `data-value` outside the `data-pretable-` namespace, write the same name. Selectors updated in `select-helpers.ts`, `components-listbox`, `components-select`, `filter-builder` and `tool-panel-aggregates-picker`; `attribute-contract.test.tsx` pins the new attribute on both checklists. 3. `filter-min` / `filter-max` were unpinned in `components-override`. The fixture gains a number column and both tests open its funnel, pick `between` through `chooseOption`, and assert the twins are the kit `TextInput` at `site="filter-value"` — and that an override replaces them too. 4. The "no `onSelectionChange` emit here" argument had ended up inside the `onClick` shift-range branch while the plain toggle moved to `onCheckedChange`. Hoisted above the `<Checkbox>` and reworded for the pair; only the shift-veto note stays inline. 5. The `resolvedComponents` comment had the context relation inverted. It now says what is true: a hook call in this body would run above its own provider and read an ancestor's context, ignoring this surface's prop. 6. Five suites hand-rolled `getAttribute("aria-checked")` reads that `checkboxState` exists for; all folded onto the helper, which leaves `checkbox-helpers.ts` as the only reader of the attribute in the suite. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t types Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…bute is namespaced Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rivers follow the rename `chooseOption` was selecting `[data-pretable-option][data-value=...]`, which the listbox stopped writing when the option attribute was namespaced — every website e2e that picks a select option was failing on a locator that matched nothing. Verified by mutation: restoring the old selector fails the funnel dialog test at helpers.ts:371. The components fixture now replaces `TextInput` and `Checkbox` too, and gains an enum column and a row-selection column so the two sites only those reach — the filter checklist and the row-select cell — are actually rendered. The override test asserts all four new sites and that nothing the kit draws itself is left, at the end, once every site has rendered at least once. A new real-grid test covers the two checkbox claims jsdom can only approximate: Space activates a `button[role=checkbox]` through the native button's own activation behaviour, and a wrapping `<label>` forwards a click on its TEXT. Both are asserted against a visible consequence rather than the attribute alone — `/fixtures/grouping` hides its grouped columns by default, so the header cell for `sector` goes 0 → 1 → 0 as the switch is driven. `.check()` needs an `<input>`; the kit's checkbox is a button, so the three smoke.spec drivers move to a new `toggleCheckbox` helper — a click plus a wait on `aria-checked`. `toBeChecked()` is unaffected and stays. The radios in server-data.spec.ts are native and keep `.check()`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`content-metadata.test.ts` had been red since 7f0d2d5: the new description was 175 characters. Trimmed to 143; nothing else changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… claim, and two docs facts `toggleCheckbox`'s comment said Playwright's `.check()` / `.uncheck()` throw on a `button[role=checkbox]`. They do not — `getChecked` honours `aria-checked` for that role. The two real reasons to hand-roll the driver are that the same read collapses `aria-checked="mixed"` to `false` (so `.check()` on a mixed select-all throws "did not change its state" on a box that changed) and that `_setChecked` reads the final state once with no retry, racing the React commit that writes the attribute. The comment now says that. The helper also took a `page` it never used; dropped, with a clause on why `chooseOption` still needs one. The components spec's closing zero-kit sweep claimed to cover "every site above, the builder's own field and checklist included". Both dialogs are closed by then, so their sites are unmounted, and the new builder row defaults to a text column, so no checklist is ever drawn. The comment now says what the sweep sees, and the gap it left is closed at the source: a page-wide zero count for the kit's field while the name dialog is open, and for the kit's checkbox while the status one is — a stray built-in inside a portalled dialog is invisible to the count at the top of the test. Docs: `warnOnce` is not gated on a build flag, so "warns in development" was wrong in four places; and "seven grid fields" (instances) beside "the three chrome fields" (kinds) read as a contradiction on one page, so the second now names the kinds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
Vercel preview readyPreview: https://pretable-a8pb3y1mv-cacheplane.vercel.app Updated automatically by the |
…wo hook lists
The changeset is the published CHANGELOG, and it under-reported the one change
that breaks a selector a consumer could already have written against 0.17.0:
the listbox option's `data-value` is now `data-pretable-option-value`. It was
the tail of the Attributes paragraph, framed as naming consistency; it now has
its own bolded paragraph beside the "no longer `<input type=checkbox>`" one,
saying plainly that a `[data-value="x"]` selector matches nothing. This
branch's own e2e broke on exactly that.
The same file's "Look" paragraph named three sites as leaving the UA focus ring
and then said the four already-button checkboxes keep everything "exactly".
True of the box, but `row-select` and `bool-cell` match
`[data-pretable-checkbox]:focus-visible` too and also leave the UA's ring —
they were simply never measured, because `tabIndex={-1}` puts them out of a
Tab's reach. The sentence now says that rather than implying their ring is
unchanged.
The consumer-`onClick`-veto contract was stated in checkbox.tsx's module
comment, which does not reach the `.d.ts`, and in the docs — but in no exported
symbol's JSDoc. `onCheckedChange` now carries it.
`warnOnce` is explicitly not gated on a build flag, so four JSDoc blocks
claiming the missing-name check "warns in development" were wrong the way the
website's four were before 0df8e5d. They now say "warns once per page", the
phrasing that commit settled on.
`packages/react/react.api.md` is UNCHANGED, contrary to what the review
expected: `pnpm api` regenerated it byte-identically and `api:check` passes.
The report carries release tags and signatures, not JSDoc prose, and
`onCheckedChange` was already documented — so no `(undocumented)` marker moved
either.
Two docs reference lists were a revision behind: tool-panel.mdx's filter-row
hooks lacked `data-pretable-filter-row-set` and `-row-choice`, and
filtering.mdx's funnel-menu hooks lacked `data-pretable-filter-choice`; both
now note the per-box `data-pretable-option-value`.
grid.css's invalid boolean cell draws an `outline`, the same property the kit
focus ring at the end of the layer now claims for every
`[data-pretable-checkbox]:focus-visible`. A comment records that the collision
is latent (the control is `tabIndex={-1}`, so a click never gives it
`:focus-visible`) and that the precedence would be correct if it ever fired —
a focus ring should outrank an error outline while focused, and `aria-invalid`
with `aria-errormessage` carries the error to assistive tech regardless. No
declaration changed.
Cosmetic: a css-cascade guard's skip explained itself as a wait for "Task 8"
rather than describing what an empty site list means; a helper's doc said it
was read by two guards when one reads it; and a helpers.ts comment kept an
awkward line break left by the rename.
No behaviour changes anywhere, and nothing a browser test reads: helpers.ts's
edit is comment-only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Components SP3, the third slice of the kit: the grid's plain fields and its
checkboxes stop being ad-hoc markup and become two components a consumer can
replace, on the contract SP1 wrote and SP2 already follows.
What ships
PretableTextInput— an<input>carryingdata-pretable-text-input—and
PretableCheckbox— abutton[role="checkbox"]carryingdata-pretable-checkboxandaria-checked— with their props types, their…Componentaliases, andTextInput/Checkboxslots onPretableComponents.components={{ TextInput, Checkbox }}replaces themeverywhere, the same way
Button,IconButtonandSelectalready do.Fourteen controls migrate. Seven text fields: the filter dialog's value and
its two range bounds, the tool panel's three filter-row values, and the Columns
search box. Seven checkboxes: row select, header select-all, the boolean cell,
the column visibility toggle, "hide grouped columns", and the two enum
checklists. Three of those checkboxes were
<input type="checkbox">and are nowbuttons; four already were buttons and simply stopped hand-rolling the markup.
The site vocabulary is renamed.
PretableButtonSite→PretableSite,PretableBuiltInButtonSite→PretableBuiltInSite: it names where any kitcontrol sits, not where a button does. The union gains ten names —
filter-value,filter-row-value,tool-search,row-select,row-select-all,bool-cell,tool-column-toggle,hide-grouped,filter-choice,filter-row-choice. Ten names cover fourteen controls becausethe range twins share their sibling's site: the theme keys on the site, and a
from/topair is one thing to style.CSS collapses onto the kit contract.
grid.cssloses the per-site field andcheckbox rules and gains one
[data-pretable-text-input]block and one[data-pretable-checkbox]block, with the existing:where()and cascade-orderguards extended to cover them.
Docs. The Components page documents both, and the option-attribute rename
follows through the docs and the e2e drivers.
Measurement
A Playwright probe reads the computed style of all fourteen controls at rest,
checked, and focused, before and after, and diffs every property. Every
difference below is deliberate; the probe's "finding" class — a change nobody
asked for — came back empty.
filter-value/filter-min/filter-maxfilter-row-valuefilter-row-minfilter-row-maxtool-searchrow-selectbool-cellrow-select-alltool-column-togglefilter-choicefilter-row-choicehide-groupedThe six text fields report zero differing properties: same box, same type,
same spacing, same ring.
row-selectandbool-cellare byte-identical acrossall 65 measured properties —
bool-cellkeeps itsmargin: 0 autocentring,which resolves to 0px top/bottom and 15.5px left/right on both sides.
The three ex-native checkboxes go 13×13 → 16×16 on purpose: they take the kit's
16px square and the
--pretable-checkbox-*tokens in place of the browser's~13px default. Their wrapping label boxes are unchanged — 218×21 and 239×21
exactly, with
hide-groupedat 168×24 against 165×24, +3px wide and +0 tall.Focus rings. The six fields keep
rgb(37, 84, 207) solid 2px @ -2pxuntouched.
tool-searchmoves off the user agent'srgb(0, 95, 204) auto 1px @ 0pxonto that same product ring.row-select-allandtool-column-togglemoveoff the UA ring onto the product ring at
+1px; the three ex-native checkboxesend there too (
filter-choice's offset moves from-2px, and the other twoalready sat at
+1px). A 16px square paints to its own edge, so an inset ringwould draw on the border — the ring sits outside it instead.
Guards, each mutation-checked
kit element and carries its own
data-pretable-site— and that a consumeroverride removes the kit attribute from the whole tree, portal included.
four-part edit (comparison, defaults, literal, deps) SP1's review flagged.
input[type=checkbox]creeping back intogrid.css.Both are
:where()-flattened to (0,0,0), so order is the whole of whatmakes it win; above the shared rule it is silently undone and every checkbox
rings inside its own box again.
color. On a checkboxcoloristhe check glyph, not label ink:
--pretable-text-dimon the checked fill isabout 1.9:1, and a read-only boolean column renders disabled on every row — so
that would be the entire column's ticks.
Consumer-visible DOM
.checked,.check()andinput[type=checkbox]must move toaria-checkedonthe three ex-native checkboxes (
toBeChecked()still works — it reads ARIA).The two checklists gain
data-pretable-filter-choice/data-pretable-filter-row-choiceplusdata-pretable-option-valueper choice.The builder's checklist wrapper is now
data-pretable-filter-row-set; thefield beside it keeps
data-pretable-filter-row-value, which the wrapper usedto borrow. The listbox option's
data-valueis nowdata-pretable-option-value— which option an element is, as against theselect trigger's
data-pretable-value, which is what that picker has committed.Full detail in the changeset.
Verification
All from a clean tree on this branch:
format/lint/typecheck/build/api/api:check/typecheck:publicpnpm apigit statusclean — report already currentpnpm testpackages/react136 files / 1868 tests)pnpm bench:e2e--workers=1againstnext build+next startThe 4 skips are the dev-build-only StrictMode suite, which cannot run against a
production
next start.Review provenance
Every task in the plan ran spec review and quality review with fix rounds before
its commit; the fix rounds are visible as their own commits (the disabled-tick
contrast catch, the slot-ref docs correction, the option-identity rename). The
final whole-branch review's findings are addressed in
0df8e5d6.🤖 Generated with Claude Code