fix(button): icon wrappers must not capture pointer (carousel triggers dead-clicked)

Real cause behind the persistent "los clicks fallan" report. The button
recipe gave `pointer-events: none` to nothing — so when a user clicked
near the visual centre of an `iconOnly` button (like the carousel
prev/next triggers), the SVG `<path>` was the hit target. SVG paths
default to `pointer-events: visiblePainted` so only the painted stroke
(the thin chevron line) is clickable; clicks landing in the empty
diagonals of the chevron passed through to whatever was behind the
button — usually nothing — and the button's onclick never fired.

Adds `pointer-events: none` to the icon wrappers (`.eidos-button-icon`,
`[data-button-icon]`) and to any nested decorative svg / `[data-svg]` /
`[data-icon]`. Now the entire button rect is the click target.

Verified: `elementFromPoint` at the chevron's geometric centre now
returns `BUTTON[data-carousel-next-trigger]` instead of `path`, and a
click at that point fires the button handler + advances the carousel
index 1→2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent a81b44b40c
commit 8cf2fe813f

@ -401,8 +401,13 @@
/* ── Inline SVG icons inherit color + sizing ──────────────────────────── */
[data-button] :is(.eidos-button-icon, [data-button-icon]) {
pointer-events: none;
}
[data-button] :is(svg, [data-svg], [data-icon]) {
inline-size: var(--_button-icon-size);
block-size: var(--_button-icon-size);
flex-shrink: 0;
pointer-events: none;
}

@ -189,6 +189,51 @@ describe('CarouselProvider', () => {
dom.dispose();
});
it('treats trigger and indicator clicks as user interactions for autoplay', () => {
const { dom } = installSomaHarness();
const root = document.createElement('section');
const groupEl = document.createElement('div');
const indicatorEl = document.createElement('button');
markItems(groupEl, 3);
root.append(groupEl, indicatorEl);
document.body.appendChild(root);
const opts = carouselOpts(root);
opts.autoplay.current = true;
opts.stopOnInteraction.current = true;
const { result, cleanup } = withEffectRoot(() => {
const provider = CarouselProvider.create(opts);
vi.spyOn(CarouselProvider, 'require').mockReturnValue(provider);
provider.itemGroupRef = groupEl;
const next = CarouselNextTriggerProvider.create({
id: state('carousel-next'),
ref: state<HTMLButtonElement | null>(document.createElement('button')),
ariaLabel: state<string | undefined>(undefined)
});
const indicator = CarouselIndicatorProvider.create({
id: state('carousel-indicator-2'),
ref: state<HTMLButtonElement | null>(indicatorEl),
index: state(2),
ariaLabel: state<string | undefined>(undefined)
});
return { provider, next, indicator };
});
expect(result.provider.isPlaying).toBe(true);
result.next.onclick({ currentTarget: document.createElement('button') } as never);
expect(opts.value.current).toBe(1);
expect(result.provider.isPlaying).toBe(false);
result.provider.reset();
expect(result.provider.isPlaying).toBe(true);
result.indicator.onclick({ currentTarget: indicatorEl } as never);
expect(opts.value.current).toBe(2);
expect(result.provider.isPlaying).toBe(false);
cleanup();
dom.dispose();
});
it('exposes viewport, item group, item and indicator props', () => {
const { dom } = installSomaHarness();
const root = document.createElement('section');

@ -632,7 +632,7 @@ export class CarouselPrevTriggerProvider {
readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => {
if (this.isDisabled) return;
this.provider.prev();
this.provider.scrollPrev();
};
readonly props = $derived.by(() =>
@ -675,7 +675,7 @@ export class CarouselNextTriggerProvider {
readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => {
if (this.isDisabled) return;
this.provider.next();
this.provider.scrollNext();
};
readonly props = $derived.by(() =>
@ -758,7 +758,7 @@ export class CarouselIndicatorProvider {
readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => {
if (this.provider.isDisabled) return;
this.provider.goto(this.opts.index.current);
this.provider.scrollTo(this.opts.index.current);
};
readonly props = $derived.by(() =>

Loading…
Cancel
Save

Powered by TurnKey Linux.