Skip to content

feat: add single-corner radius modifiers and inset dock modifier - #244

Merged
tenphi merged 2 commits into
mainfrom
feat-radius-corners-and-inset-stretch
Jul 29, 2026
Merged

feat: add single-corner radius modifiers and inset dock modifier#244
tenphi merged 2 commits into
mainfrom
feat-radius-corners-and-inset-stretch

Conversation

@tenphi

@tenphi tenphi commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Two shapes that currently have no expressible form in the DSL, which forces authors into raw 4-value box syntax. Found while auditing cube-ui-kiteslint-plugin-tasty wanted to rewrite that raw syntax into directional shorthand and couldn't, because these two properties had no lossless target.

radius: single-corner modifiers

A directional modifier addresses the corner pair along an edge — radius: 'top' rounds top-left and top-right — so a single corner had no representation.

Worse, an unrecognized corner name was silently dropped and the value fell through to every corner:

radius: '4px top-left'   // before: border-radius: 4px   ← all four corners

Added top-left, top-right, bottom-right, bottom-left:

radius: 'top-right'         // border-radius: 0 var(--radius) 0 0
radius: '4px top-left'      // border-radius: 4px 0 0 0
radius: 'top bottom-right'  // border-radius: var(--radius) var(--radius) var(--radius) 0

The value is optional and defaults to var(--radius) (1r), matching how edge modifiers already behave. Corner modifiers combine with edge modifiers, work with longhand, and accept CSS-wide keywords (radius: 'inherit top-right').

The edge and corner loops are now one applyCornerMods helper shared by the value path and the keyword path — previously the keyword path had its own copy of the corner-pair logic and would have needed the fix duplicated.

inset: dock modifier

Pins the named edge and spans its full length, applying the value to the two perpendicular sides:

inset: 'bottom dock'     // inset: auto 0 0 0    (bottom-anchored, full width)
inset: 'right dock'      // inset: 0 0 0 auto    (right-anchored, full height)
inset: '2x bottom dock'  // inset: auto 16px 16px 16px
inset: 'dock'            // inset: 0

The name follows the established UI-toolkit meaning — WPF/WinForms Dock=Bottom, Qt dock widgets: attach to an edge and fill its length.

Properties opt in via a new spanModifiers field on DirectionalConfig. Only inset sets it, so padding, margin and scrollMargin treat dock as an unknown modifier and are untouched (padding: '1x bottom dock' === padding: '1x bottom').

On the name

An earlier draft of this PR used stretch. That was the wrong choice: stretch is in the parser's global VALUE_KEYWORDS (it's meaningful for align-items, width, …), so it gets classified as a value, not a modifier — inset: 'bottom stretch' parses to values: ['stretch'] and emits inset: auto auto stretch auto. Supporting it meant an opt-in flag plus lifting the token back out of the value list.

dock is parsed as a modifier natively, so that machinery is gone and DirectionalConfig just gains a list of recognized span modifiers. I checked the alternatives through the parser — stretch was the only candidate of ~16 that buckets as a value; fixed (already a dimension.ts modifier), fill (a tasty style name) and span (a real CSS grid keyword) were ruled out for collisions.

Note this means inset: 'bottom stretch' still emits the invalid inset: auto auto stretch auto. That's pre-existing and untouched here — happy to reject or ignore stretch for inset in a separate PR if you want it hardened.

Reviewer notes

  • No behaviour change for any previously valid input. Edge modifiers, shapes (round, ellipse, leaf, backleaf), longhand, multi-group syntax and individual direction props all emit exactly what they did before. 1746 tests pass (+10 new), tsc, eslint, prettier clean.
  • One semantic decision worth confirming: for inset: '2x bottom dock' I applied the value to every side the modifier touches → auto 16px 16px 16px. The alternative reading is "pin 2x from the bottom, but span flush to the edges" → auto 0 16px 0. Both collapse to the same thing for the zero-value case, so the motivating example doesn't disambiguate. I picked the uniform reading for consistency with how values apply to named sides elsewhere — easy to flip.
  • docs/styles.md now distinguishes edge from corner modifiers for radius (the old wording, "rounds only the specified corners", was what made the pair behaviour surprising), and documents dock. Prop-type JSDoc updated for both, and 'dock' added to the inset union.

Follow-up

Once this lands, eslint-plugin-tasty can stop excluding radius and inset from prefer-directional-shorthand (excluded in #26 precisely because no lossless rewrite existed) and start suggesting these forms instead.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

📦 Snapshot release

Published 0.0.0-snapshot.341030c.

pnpm add @tenphi/tasty@0.0.0-snapshot.341030c

@tenphi
tenphi force-pushed the feat-radius-corners-and-inset-stretch branch from 3a5e4ed to a103255 Compare July 29, 2026 11:19
@tenphi tenphi changed the title feat: add single-corner radius modifiers and inset stretch modifier feat: add single-corner radius modifiers and inset dock modifier Jul 29, 2026
tenphi and others added 2 commits July 29, 2026 13:24
Both cases previously had no expressible form, which forced authors into raw
4-value box syntax that other tooling then could not safely rewrite.

radius: a directional modifier addresses the corner *pair* along an edge
(`radius: 'top'` rounds top-left and top-right), so a single corner could not
be expressed. An unrecognized corner name was silently dropped and the value
applied to every corner — `radius: '4px top-left'` emitted
`border-radius: 4px`. Add `top-left`, `top-right`, `bottom-right` and
`bottom-left`, which set one corner, default to `var(--radius)` when no value
is given, combine with edge modifiers, and work with `longhand` and CSS-wide
keywords.

inset: add a `dock` modifier that pins the named edge and spans its full
length by also applying the value to the two perpendicular sides, so
`inset: 'bottom dock'` gives `inset: auto 0 0 0`. The name follows the
established UI-toolkit meaning (WPF/WinForms `Dock=Bottom`): attach to an edge
and fill it. Properties opt in via a new `spanModifiers` field on
DirectionalConfig; only inset sets it, so padding/margin/scrollMargin treat
`dock` as an unknown modifier and are untouched.

`dock` is parsed as a modifier natively, so no parser changes are needed. An
earlier draft used `stretch`, which the parser classifies as a value keyword
(it is meaningful for align-items and width) and which therefore required
lifting the token back out of the value list.

No behaviour change for previously valid input: 1746 tests pass, including the
existing radius and inset suites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The feature adds ~110 B to the static bundle, which had only 30 B of headroom
against its 17 kB limit. Deduplicating the edge/corner logic into a single
cornerIndices helper and deriving perpendicular sides by index arithmetic
instead of a lookup object recovered most of it — main actually lands at
52.92 kB, and every other entry keeps its headroom — but static still needs
17.25 kB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tenphi
tenphi force-pushed the feat-radius-corners-and-inset-stretch branch from a103255 to 789bd1e Compare July 29, 2026 11:26
@tenphi
tenphi merged commit e0c6bd0 into main Jul 29, 2026
6 of 7 checks passed
@tenphi
tenphi deleted the feat-radius-corners-and-inset-stretch branch July 29, 2026 11:28
@github-actions github-actions Bot mentioned this pull request Jul 29, 2026
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