fix(bots-widgets): default data-input to mouse and drop dead danger:hover rose override so hover works from frame zero on hybrid devices
This commit is contained in:
parent
1209654347
commit
061608558a
6 changed files with 59 additions and 50 deletions
|
|
@ -6,16 +6,15 @@ import { WidgetApi, buildCapabilities } from './widget-api';
|
|||
import './styles.css';
|
||||
|
||||
// Input-mode detector — see apps/widget-telegram/src/main.tsx for the
|
||||
// full rationale. Capacitor Android WebView mis-reports `hover: hover`
|
||||
// on touch devices, so we drive `:hover` styling off the actual
|
||||
// `pointerdown.pointerType` instead of media queries. The initial guess
|
||||
// based on `(any-pointer: coarse)` covers the pre-first-pointerdown
|
||||
// frame so a touch device doesn't briefly show mouse-mode hover
|
||||
// affordances if the user immediately taps a card.
|
||||
// full rationale. Default to 'mouse'; the capture-phase pointerdown
|
||||
// listener flips to 'touch' on the first non-mouse pointerType.
|
||||
// matchMedia guessing was dropped — every variant
|
||||
// (`any-pointer: coarse|fine`, `hover: hover`, `pointer: fine|coarse`)
|
||||
// is mis-reported on at least one shipping device.
|
||||
const setInputMode = (mode: 'touch' | 'mouse'): void => {
|
||||
document.documentElement.dataset.input = mode;
|
||||
};
|
||||
setInputMode(window.matchMedia('(any-pointer: coarse)').matches ? 'touch' : 'mouse');
|
||||
setInputMode('mouse');
|
||||
window.addEventListener(
|
||||
'pointerdown',
|
||||
(event) => {
|
||||
|
|
|
|||
|
|
@ -376,13 +376,16 @@ body {
|
|||
}
|
||||
|
||||
/* Destructive card — red name marks logout as destructive vs the primary
|
||||
* login card. */
|
||||
* login card. The hover border stays on the generic
|
||||
* `:root[data-input='mouse'] .command-card:hover` rule (hairline) so the
|
||||
* accent is consistent across touch and mouse modes — a previous
|
||||
* `.command-card.danger:hover { border-color: var(--rose) }` override
|
||||
* was dead in mouse mode (lower specificity than the input-gated rule)
|
||||
* and only fired in the pre-first-pointerdown touch stub, leaking a
|
||||
* rose tint that looked amber on the dark surface. */
|
||||
.command-card.danger .command-card-name {
|
||||
color: var(--rose);
|
||||
}
|
||||
.command-card.danger:hover:not(:disabled) {
|
||||
border-color: var(--rose);
|
||||
}
|
||||
|
||||
/* Inline confirm-in-place body for the destructive logout card. */
|
||||
.command-card-confirm {
|
||||
|
|
|
|||
|
|
@ -5,25 +5,37 @@ import { createT } from './i18n';
|
|||
import { WidgetApi, buildCapabilities } from './widget-api';
|
||||
import './styles.css';
|
||||
|
||||
// Input-mode detector. Capacitor's Android Chromium WebView reports
|
||||
// `(hover: hover)` and `(any-pointer: fine)` as TRUE on a pure-touch
|
||||
// device — verified via on-device console.log of `matchMedia(...).matches`.
|
||||
// That makes media-query gating of `:hover` styles unreliable: the rule
|
||||
// fires on touch and then sticks (Chromium WebView synthesises `:hover` on
|
||||
// the focused element after a tap and never clears it until the next
|
||||
// interaction elsewhere). The visible symptom is a card that «greys out
|
||||
// after tap and only un-greys when you tap a different button».
|
||||
// Input-mode detector for hover styling. CSS gates `:hover` and
|
||||
// `:focus-visible` rules on `:root[data-input="mouse"]` because Capacitor's
|
||||
// Android Chromium WebView synthesises `:hover` on the focused element
|
||||
// after a tap and never clears it until the next interaction elsewhere —
|
||||
// without the gate, every tap leaves a sticky hover state on the tapped
|
||||
// card («card greys out after tap and only un-greys when you tap a
|
||||
// different button»).
|
||||
//
|
||||
// Real input is determined from the actual `pointerdown.pointerType` at
|
||||
// runtime. The first pointerdown after load is authoritative; CSS gates
|
||||
// hover styling via `:root[data-input="mouse"]`. The initial guess based
|
||||
// on `(any-pointer: coarse)` covers the pre-first-pointerdown frame so
|
||||
// the first paint on a touch device doesn't briefly show the mouse-mode
|
||||
// hover affordances if the user immediately taps a card.
|
||||
// Truth comes from `pointerdown.pointerType`. The capture-phase listener
|
||||
// runs in the same task as any post-tap `:hover` synthesis, so a touch
|
||||
// tap on Android lands in 'touch' mode in the same render frame as the
|
||||
// synthesised hover would paint.
|
||||
//
|
||||
// Initial mode is plain 'mouse'. matchMedia-based guessing was tried
|
||||
// here and dropped — every interaction-media query is mis-reported on at
|
||||
// least one shipping device: Capacitor Android WebView falsely matches
|
||||
// `hover: hover` and `any-pointer: fine` on pure-touch phones;
|
||||
// Samsung / OnePlus / Moto Androids expose a virtual-mouse HID and
|
||||
// falsely match `pointer: fine`; older Firefox-on-Windows desktops
|
||||
// reported `pointer: coarse` despite a real mouse. Defaulting to 'mouse'
|
||||
// is strictly no worse than any of those queries on any device: a
|
||||
// desktop / hybrid user gets hover affordances from frame zero, and a
|
||||
// touch user cannot trigger `:hover` before tapping because there is no
|
||||
// pointer hovering anything — by the time the first tap fires
|
||||
// `:hover` (synthesised), our listener has already moved the attribute
|
||||
// to 'touch'. Pen / stylus also lands in 'touch' (pointerType is `pen`,
|
||||
// matched by the `!== 'mouse'` branch).
|
||||
const setInputMode = (mode: 'touch' | 'mouse'): void => {
|
||||
document.documentElement.dataset.input = mode;
|
||||
};
|
||||
setInputMode(window.matchMedia('(any-pointer: coarse)').matches ? 'touch' : 'mouse');
|
||||
setInputMode('mouse');
|
||||
window.addEventListener(
|
||||
'pointerdown',
|
||||
(event) => {
|
||||
|
|
|
|||
|
|
@ -438,13 +438,15 @@ body {
|
|||
}
|
||||
|
||||
/* Destructive card — keeps the red name to mark «Выйти из Telegram» as a
|
||||
* destructive action, distinguishing it from the primary login card. */
|
||||
* destructive action, distinguishing it from the primary login card.
|
||||
* Hover border stays on the generic input-gated rule (hairline) so the
|
||||
* accent is consistent across touch and mouse modes. A previous
|
||||
* `.command-card.danger:hover { border-color: var(--rose) }` override
|
||||
* was dead in mouse mode (lower specificity than the input-gated rule)
|
||||
* and only fired in the pre-first-pointerdown touch stub. */
|
||||
.command-card.danger .command-card-name {
|
||||
color: var(--rose);
|
||||
}
|
||||
.command-card.danger:hover:not(:disabled) {
|
||||
border-color: var(--rose);
|
||||
}
|
||||
|
||||
/* Inline confirm-in-place body for the destructive logout card. The button
|
||||
* group lives inside the same card frame — no modal, no layout shift. */
|
||||
|
|
|
|||
|
|
@ -5,25 +5,16 @@ import { createT } from './i18n';
|
|||
import { WidgetApi, buildCapabilities } from './widget-api';
|
||||
import './styles.css';
|
||||
|
||||
// Input-mode detector. Capacitor's Android Chromium WebView reports
|
||||
// `(hover: hover)` and `(any-pointer: fine)` as TRUE on a pure-touch
|
||||
// device — verified via on-device console.log of `matchMedia(...).matches`.
|
||||
// That makes media-query gating of `:hover` styles unreliable: the rule
|
||||
// fires on touch and then sticks (Chromium WebView synthesises `:hover` on
|
||||
// the focused element after a tap and never clears it until the next
|
||||
// interaction elsewhere). The visible symptom is a card that «greys out
|
||||
// after tap and only un-greys when you tap a different button».
|
||||
//
|
||||
// Real input is determined from the actual `pointerdown.pointerType` at
|
||||
// runtime. The first pointerdown after load is authoritative; CSS gates
|
||||
// hover styling via `:root[data-input="mouse"]`. The initial guess based
|
||||
// on `(any-pointer: coarse)` covers the pre-first-pointerdown frame so
|
||||
// the first paint on a touch device doesn't briefly show the mouse-mode
|
||||
// hover affordances if the user immediately taps a card.
|
||||
// Input-mode detector — see apps/widget-telegram/src/main.tsx for the
|
||||
// full rationale. Default to 'mouse'; the capture-phase pointerdown
|
||||
// listener flips to 'touch' on the first non-mouse pointerType.
|
||||
// matchMedia guessing was dropped — every variant
|
||||
// (`any-pointer: coarse|fine`, `hover: hover`, `pointer: fine|coarse`)
|
||||
// is mis-reported on at least one shipping device.
|
||||
const setInputMode = (mode: 'touch' | 'mouse'): void => {
|
||||
document.documentElement.dataset.input = mode;
|
||||
};
|
||||
setInputMode(window.matchMedia('(any-pointer: coarse)').matches ? 'touch' : 'mouse');
|
||||
setInputMode('mouse');
|
||||
window.addEventListener(
|
||||
'pointerdown',
|
||||
(event) => {
|
||||
|
|
|
|||
|
|
@ -419,13 +419,15 @@ body {
|
|||
}
|
||||
|
||||
/* Destructive card — keeps the red name to mark «Выйти из WhatsApp» as a
|
||||
* destructive action, distinguishing it from the primary login card. */
|
||||
* destructive action, distinguishing it from the primary login card.
|
||||
* Hover border stays on the generic input-gated rule (hairline) so the
|
||||
* accent is consistent across touch and mouse modes. A previous
|
||||
* `.command-card.danger:hover { border-color: var(--rose) }` override
|
||||
* was dead in mouse mode (lower specificity than the input-gated rule)
|
||||
* and only fired in the pre-first-pointerdown touch stub. */
|
||||
.command-card.danger .command-card-name {
|
||||
color: var(--rose);
|
||||
}
|
||||
.command-card.danger:hover:not(:disabled) {
|
||||
border-color: var(--rose);
|
||||
}
|
||||
|
||||
/* Generic leading-icon slot — every command-card carries one as a
|
||||
* left-side semantic glyph (mirror of the right-side chevron). The
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue