Skip to content

fix: button icon size contract + context menu touch selection - #696

Merged
nhobes merged 7 commits into
mainfrom
fix/icon-size-contract-and-touch-select
Aug 25, 2026
Merged

fix: button icon size contract + context menu touch selection#696
nhobes merged 7 commits into
mainfrom
fix/icon-size-contract-and-touch-select

Conversation

@nhobes

@nhobes nhobes commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Two fixes, both CSS-only, both riding the unpublished 4.15.2.

Button icons render oversized in standard consumer apps. Nic spotted the loading/trailing icon on petal.build's buttons page sitting 24px wide vs the playground's 20px. Root cause is a cascade-layer fight, not specificity: consumer heroicons plugins (petal.build's tailwind_heroicons.js, and the stock Phoenix setup) emit the mask's own width: 1.5rem into @layer utilities, which outranks this library's @layer components rules no matter how specific the selector. (The playground only looked right because its heroicons land in base.) The spinner/icon sizing rules now use the same doubled-selector + !important icon contract as the pagination chevrons, combobox icons, etc.

iOS long-press on a context menu region starts text selection. The trigger already killed the touch callout but not selection itself, so holding a card selected its text while the menu opened. user-select: none is now applied under @media (pointer: coarse) only - the hold reads purely as the menu gesture on touch, and mouse users keep normal text selection inside the region.

Verified live on the playground: arrow icon computes 20x20 (unchanged), trigger reports user-select: none under mobile emulation and auto on desktop. 1813 tests, 0 failures.

Button spinner/icon sizing moves to the doubled-selector + !important
icon contract. The parent-scoped rules sat in the components layer, so
consumers whose heroicons plugin emits into the utilities layer (the
standard Phoenix setup, including petal.build) saw the mask's own w-6
width beat them regardless of specificity - md button icons rendered
24px wide instead of 20px.

Context menu triggers suppress user-select under (pointer: coarse) so
an iOS long press reads as the menu gesture instead of also starting
text selection. Desktop selection inside the region is untouched.
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown

Greptile Summary

The PR updates button icon sizing to withstand consumer Heroicons cascade layers, suppresses context-menu text selection during touch long-press, stabilizes dropdown attribute order, and releases version 4.15.3.

  • Enforces button spinner dimensions with important sizing declarations.
  • Adds touch-oriented selection suppression to context-menu triggers.
  • Replaces dropdown attribute maps with ordered keyword lists.
  • Updates package version and changelog entries.

Confidence Score: 4/5

The PR should not merge until context-menu triggers preserve text selection when touch-primary hybrid devices are operated with a mouse or trackpad.

The current primary-pointer media query still applies user-select: none after a user switches to an attached fine pointer, leaving the previously reported hybrid-device behavior unresolved.

Files Needing Attention: assets/default.css

Important Files Changed

Filename Overview
assets/default.css Button icon sizing now follows the established cascade-safe contract, but the context-menu media query still blocks selection on touch-primary hybrid devices using a mouse or trackpad.
lib/petal_components/dropdown.ex Attribute collections now use keyword lists to preserve deterministic rendering order without changing their values.
mix.exs Advances the package version from 4.15.2 to 4.15.3.
CHANGELOG.md Documents the 4.15.3 release and its component fixes.

Reviews (8): Last reviewed commit: "docs: changelog bullet for two_months in..." | Re-trigger Greptile

Comment thread assets/default.css Outdated
Comment on lines +9616 to +9621
@media (pointer: coarse) {
.pc-context-menu__trigger {
-webkit-user-select: none;
user-select: none;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Coarse query breaks mouse selection

When a touch-primary hybrid device is operated with a mouse or trackpad, (pointer: coarse) still applies user-select: none to the entire context-menu trigger, preventing users from selecting or copying its text despite the mouse-selection contract.

nhobes added 3 commits August 25, 2026 12:16
Greptile's catch: bare pointer:coarse also matches touch-primary
hybrids currently driving a mouse. hover:none + pointer:coarse is the
same true-touch guard the data table's mobile font sizing already
uses.
4.15.2 published without this branch's two CSS fixes, so they move to
their own 4.15.3 section and the version bumps to match. The avatar
showcase refresh that did make 4.15.2 gets its changelog line.
Comment thread assets/default.css
mid-hold otherwise. Guarded the same way as the data table's mobile
font sizing: hover:none + pointer:coarse means a true touch device,
so desktops (touchscreen laptops included) keep normal selection. */
@media (hover: none) and (pointer: coarse) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Hybrid pointers still block selection

When a touch-primary device is operated with an attached mouse or trackpad, (hover: none) and (pointer: coarse) still matches the primary touchscreen and applies user-select: none, preventing users from selecting or copying text inside the context-menu trigger.

The panel's dynamic attributes came from a map, and Erlang's
small-map iteration order is a compile-time property of the literal
pool - a clean rebuild reorders them on its own, exactly as the
old comment warned. CI's fresh build rendered phx-hook before style
and the pinned legacy-default test went red on an untouched tree
(the failure predates this branch: main went red at cc86b35, a
version-bump-only commit).

Keyword lists render in the order written, so the panel's HTML is
now byte-stable across builds and the pin holds by construction.
@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.42%. Comparing base (cc86b35) to head (779ff56).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #696   +/-   ##
=======================================
  Coverage   95.41%   95.42%           
=======================================
  Files         163      163           
  Lines        8078     8079    +1     
=======================================
+ Hits         7708     7709    +1     
  Misses        370      370           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@nhobes

nhobes commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review - two commits since your pass: the coarse-pointer guard you flagged is now (hover: none) and (pointer: coarse) per the data-table precedent, plus 4.15.3 staging and a dropdown attr-order determinism fix (the CI flake on main).

@nhobes

nhobes commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@nhobes
nhobes merged commit 78a5dc1 into main Aug 25, 2026
5 checks passed
nhobes added a commit that referenced this pull request Sep 1, 2026
The sweep #696's dropdown fix called for. Erlang's small-map iteration
order is a compile-time property of the literal pool, so a clean
rebuild of an untouched tree can reorder the attributes a map-backed
HEEx splat renders - which is how CI went red on a pinned dropdown
test. Every remaining helper whose result is splatted into a tag now
returns a keyword list, rendered in the order written:

- accordion's js_attributes carried the same multi-key hazard on its
  button (phx-click / aria-controls / aria-expanded) and content panel
  (id / role / aria-labelledby / style)
- separator's aria helpers on the semantic vertical (role /
  aria-orientation) and labelled (role / aria-label) branches
- menu's js_attributes clauses are single-attribute today; lists keep
  them order-stable if they ever grow

Two helpers take maps from the caller rather than authoring one, so
there is no written order to preserve - they sort instead, making the
rendered order deterministic across builds:

- toggle_group's slot passthrough (Map.drop on the slot entry)
- pagination's phx-value-* set (Enum.map over event_values)

Left alone deliberately: dropdown.ex (fixed on #696's branch),
framework-built {@rest} globals, splats into component calls (assigns,
never rendered order), and accordion's JS.dispatch detail payload
(JSON read by key, not an attribute list).

1813 tests, 0 failures on a clean build before and after.
nhobes added a commit that referenced this pull request Sep 1, 2026
The sweep #696's dropdown fix called for. Erlang's small-map iteration
order is a compile-time property of the literal pool, so a clean
rebuild of an untouched tree can reorder the attributes a map-backed
HEEx splat renders - which is how CI went red on a pinned dropdown
test. Every remaining helper whose result is splatted into a tag now
returns a keyword list, rendered in the order written:

- accordion's js_attributes carried the same multi-key hazard on its
  button (phx-click / aria-controls / aria-expanded) and content panel
  (id / role / aria-labelledby / style)
- separator's aria helpers on the semantic vertical (role /
  aria-orientation) and labelled (role / aria-label) branches
- menu's js_attributes clauses are single-attribute today; lists keep
  them order-stable if they ever grow

Two helpers take maps from the caller rather than authoring one, so
there is no written order to preserve - they sort instead, making the
rendered order deterministic across builds:

- toggle_group's slot passthrough (Map.drop on the slot entry)
- pagination's phx-value-* set (Enum.map over event_values)

Left alone deliberately: dropdown.ex (fixed on #696's branch),
framework-built {@rest} globals, splats into component calls (assigns,
never rendered order), and accordion's JS.dispatch detail payload
(JSON read by key, not an attribute list).

1813 tests, 0 failures on a clean build before and after.
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