Repository navigation
fix: focus lists, rich-text sync, table header scroll + dragToScroll, panel floating actions, no styling prop reaches the DOM, descriptions take block content — 0.78.5 - #708
Conversation
…mounts — 0.77.9 A popover with handler='focus' listened for focusin only. A field focused as it mounts - an editor that opens with the cursor in its field, as a read-first form row does when it is clicked - was focused before the listener was attached, so no focusin came and its list stayed closed. The user then clicked in the field, which already had the focus, and still saw nothing until they left it and came back. In the Qorus IDE this hid every template a typed field offers (qorus#646: an added field of type Whole number showed no fields of the record to refer to). When the listener is attached, a focus popover whose field already has the focus opens. 0.77.9: 0.77.8 is taken by #696.
|
📦 Prerelease published for this PR: Install it in a consumer (updates yarn add @qoretechnologies/reqore@0.78.5-pr.708.g26327a4Published under the |
qlip visual regression🔍 7 changed, 0 new of 1191 snapshots. |
… committed, so a key typed after it is kept The editor compared a value it was given with its document in a passive effect, which the browser runs after painting - under load, after the next key. A value that matched the document when it was rendered was then compared with a document that had a key more, taken as a value from outside, and put in its place: the key was lost and the caret went to the end. reqraft's SmartEditor › With Markdown Docs typed "arr.for" and got "arr.fr" in CI; under 6x CPU throttling it lost a key in most runs. The comparison runs in a layout effect, before the browser handles another key. A value from outside still replaces the document. richTextEditorValueSync.test.tsx types a key between a value's commit and its passive effects: lost before, kept now.
|
Review: one regression to fix before merging The fix is right for a field focused as it mounts (qorus#646), but the new check runs every time the listener effect re-runs, not only when the listener is first attached. The effect depends on What needs to be done
|
…s a focus list again — 0.78.1 Foxhoundn's review of #708: the check for a field focused as it mounts ran on every re-run of the listener effect, and the effect re-runs whenever the list opens or closes (it depends on `open`, which depends on `isOpen`). Closing the list with the cursor still in the field - Escape, or a click on it with closeOnTargetClick - re-ran it, found the field focused, and reopened the list at once. The check is made once per trigger, remembered in a ref, as the listener is first attached. Test: a list closed with Escape stays closed while the field keeps the focus (fails without the change). 0.78.1: #696 goes in first as 0.78.0.
|
Thanks, you're right, and the reasoning checks out. The listener effect depends on Changed in 9e025c6:
|
…used # Conflicts: # package.json
…croll pulls it with the mouse — 0.78.3 From David's review of qorus#646: on a phone, a swipe on a wide table's header did not scroll it, while the same swipe on its rows did. The header is scrolled WITH the body (the body syncs it) but is not a scroller of its own (overflow hidden), so the browser has nothing to pan, and nothing forwarded the swipe; only the wheel was forwarded. - The header now takes sideways touch swipes (touch-action: pan-y; the page still pans up and down) and scrolls the body with them. - New `dragToScroll` (default false): a mouse or pen presses and pulls the table by its body or its header. A press that does not move is still a click (a header cell opens its options), the click that ends a real drag is not delivered, and a press on a text field is the field's. - The gesture is FadeScroller's dragToScroll, moved into a shared useDragToScroll hook that both use, so a fix to it reaches both. It rebinds when the element it scrolls appears after mount (a table renders no body until it has rows). Tests: table-drag-to-scroll (4 of 6 fail on develop), the FadeScroller drag tests unchanged and passing; story Collections/Table · Drag To Scroll; COMPONENTS.md.
…e-header-scrolls-the-table: stacked on #708 # Conflicts: # package.json
… pullable body shows a grab cursor The header forwarded only a wheel's deltaX to the body, so a plain wheel with Shift held did nothing over it. With dragToScroll the body shows a grab cursor, and grabbing with no text selection while pulled - what a consumer otherwise had to style through reqore's class names.
…e's styling props
ReqoreButton rendered `as={Link}` handed the router link every styling
prop it had: `fluid`, `compact`, `flat`, `animate`, `maxWidth`,
`alignSelf`, `tooltip`, the stack-position flags a ControlGroup clones
onto its children, ... and the link wrote them onto its `<a>`. A
resizable, transparent ReqorePanel handed re-resizable `transparent`,
which it spread onto its wrapper div.
Both come from the same gap: a styled component's `shouldForwardProp`
named a few props by hand (Button: fill/color/wrap; Panel: eight), and
a component target receives every prop not named. The hand lists went
stale as props were added.
The styling props of Button, Panel, Tag and Textarea are now listed by
listReqoreStyleProps from a record typed against the component's prop
interface minus what its element takes (TReqoreStylePropKeys), so a new
prop that is not added fails the build instead of leaking. Panel keeps
re-resizable's own props (`size`, `enable`, ...) forwarded.
The sweep of every `as`-capable component found the same leak in Tag
(asBadge, labelKey, size, ...), Paragraph (`_size`, `block`), the
TextEffect base (`block`, `inline`), the Tabs list item (`theme`,
`active`, `size`, ...), the Textarea given Slate's editable (`flat`,
`minimal`, `rounded`, `_size`), the Icon's animated wrapper (`size`,
control-group flags) and the Breadcrumbs item (`interactive`). The
tooltip component no longer hands `tooltip` to any component; the
Timeline item no longer writes `disabled` onto its `li` (it sets
`aria-disabled`), nor the Textarea wrapper `width`/`height` onto its
div.
ReqoreEntityRow, ReqoreSeverityRow, ReqoreCallout, ReqoreFeatureCard and ReqoreTier drew `description` (and ReqoreTestimonial its `quote`) in a ReqoreP, a `<p>`. Consumers pass paragraphs and control groups there, which a `<p>` cannot hold: React warned (validateDOMNesting) and the browser's parser closes the paragraph at the first block, moving the content out of it and its styling. The description is now a ReqoreP rendered as a `div`: the same styled classes, so a plain-text description looks exactly as before. The root of each of these components, the effect's `span`, is rendered as a `div` too, since a span may not hold blocks either. The prop docs say that a description takes block content.
…r render DeepScan REACT_INEFFICIENT_PURE_COMPONENT_PROP: the inline style handed to the memoized ReqoreP defeated its memoization. It was there before; the description's element moving to its own lines made DeepScan report it as new.
…t landed on losing its capture A touch pointer is captured by the element it lands on (implicit touch capture). Taking the capture for the header moved it away, the cell reported lostpointercapture, and the window listener ended the gesture: a real swipe on a phone moved the table 5px. Only the capture the gesture itself took ends it. Found driving the IDE's review on a phone with real touch events; the dispatched-event tests could not see it.
…description takes block content #696 added the panel's media props (media, mediaAlt, mediaPosition, mediaAspectRatio, mediaProps) and the modern Tier appearance. The panel's compiler-checked style list caught the props: they are now in it, so they do not reach a component the panel is rendered as. The modern tier drew its description in a ReqoreP <p>; it is a div like the classic tier's, with its effect memoized for the memoized paragraph.
… 0.78.4 From David's review of qorus#646: hovering an expression in reqraft's builder and clicking its floating Remove did nothing - the click switched the Visual / Text view underneath instead. The actions float above the panel's top-right corner, so a pointer heading for them leaves the panel through its top edge, usually left of the actions, on a diagonal. The hover ended as it left the panel, the actions went, and the click landed on what they had covered. While the pointer is in the strip between the panel's top edge and the actions (across the panel's width), the hover now stays; it ends when the pointer leaves the strip for anything that is neither the panel nor the actions. Decided by where the pointer is on each move, with no timer. Test: panelFloatingActionsReachable (the crossing fails without the change). Stacked on #710 (and #708).
…es only the elements it is an attribute of `size='normal'` (a Reqore size) was written as `size="normal"` onto every div and span it was handed to — the control group, panel title and content, tag content, table rows and cells, accordion, timeline, statistic, progress, ... A checkbox's `checked` reached its divs, a table cell's and collection item's `selected` and `disabled` theirs, a rating's and a checkbox's `readOnly` theirs, a dropdown button its `placeholder`. React says nothing about these: each is an HTML attribute name, so is-prop-valid, which styled-components filters a DOM tag's props with, lets it through — it only asks whether a name is an attribute of ANY element. The root cause is that filter being element-blind, so the fix is made there rather than in each of the ~60 styled components involved: - REQORE_AMBIGUOUS_PROP_ELEMENTS maps each Reqore prop name that is also an attribute name (size, width, height, wrap, color, fill, opacity, direction, offset, spacing, disabled, readOnly, checked, selected, placeholder, label, type, value) to the elements it is an attribute of, typed against React's intrinsic elements. omitStyleProps lets such a name through to a DOM tag only when the tag is one of them (SVG elements keep their presentation attributes). - src/helpers/styled.ts exports Reqore's `styled`: styled-components' own, with every styled component it makes filtered by omitStyleProps(). All of src imports it, and an ESLint rule forbids the default import of styled-components, so a new styled component cannot leak these again. A component's own withConfig filter is built with omitStyleProps and keeps the element check. Rendering all 1125 stories and reading what React handed each element now finds nothing but Slate's own `zindex` on its editable. The rich text editor no longer hands Slate `disabled` either (Slate is disabled through `readOnly`), which it wrote onto its div.
…re placed by when it resizes A panel's floating actions are fixed to the screen and were placed only as they were shown, when the actions changed and on a scroll. A window that resized, a panel that grew or shrank and actions whose own content widened left them where they had been: mid-screen once the window widened (reqraft's "Expression Can Be Wrapped", played at 1200px and captured at 1920px), or reaching past the panel's right edge once they widened. They are now placed again on a window resize and on a ResizeObserver report for the panel or for the actions, as well as on a scroll, at most once per frame; nothing is observed or listened to once they are hidden or the panel is gone. An open popover is placed again when its target changes size, at the next frame, as it already was when it changed size itself: Popper places it only on a scroll or a window resize, and the mutation observer on its target sees only changes inside it.
…pens no hover; only a pointer that hovers does A tap is followed by the mouse events a browser emulates - mouseenter, mouseover, mousemove - with nothing in them to tell them from a mouse's, so whatever opened on them opened at a tap: a hover's tooltip until the tap's click closed it (on a touch laptop, whose mouse is its primary pointer), a hoverStay tooltip for good, a panel's floating actions, a notification's pause, a rating's preview (over a value the tap had just cleared), a nav rail's reveal and labels, a truncated label's marquee - each until the next tap elsewhere. A browser sends an input's pointer events before the mouse events it makes of it, and the pointer event says what the pointer is. A hover gate (createHoverGate / useHoverGate / watchHoverGate) notes the pointer of the pointer events and the mouse event after them asks isHover(), which reads it once: a mouse, or a pen held above the screen (isHoveringPointer) is a hover, a finger is not. Decided per event, not per device, so a touch laptop's mouse still hovers. A mouse event no pointer event came before is a mouse's, so a test that fires mouseenter alone still opens a tooltip. Click- and focus-opened popovers, closing, and isHoverCapable are as they were. A notification's leave releases only a hold a hover made: a tap's leave restarted its running timer at the beat a hover leaves.
…d empties itself Two next() calls from one render at the page before the last - two auto-load observers answering one scroll - took the page past the last one: the page read as undefined, the collection showed no rows and still offered "Load more" and "Load all" (found by the Qorus IDE's story run, qorus#646: App Connections Manager, under load). - useReqorePaging keeps the page in its own state, always within the list: a step reads the page it is taken from (a functional update) and stops at either end, and setPage with a page outside the list opens the nearest one (it threw). react-use-pagination-hook, whose window index went past the end, is no longer used and is removed. - Paging's auto-load-more observer is disconnected when it is made again (every observer made stayed connected and answered every scroll), and calls the next() of the render it fires in, not the one it was made in. Tests that fail before the fix: useReqorePaging never goes past the last page however many steps one render takes, and takes a page outside the list as the nearest one; a Pagination with auto load more loads one page each time its end comes into view (it loaded two after the observer was made again).
… and last child useCloneThroughFragments kept its count of the children with the clone it memoizes, so a render that reused the clone - a control group's own state on a resize, or a context it reads - went on counting from where the last render stopped: a stacked group's first child was numbered the fourth and lost its rounded end, its last lost the other. The count starts from the first child on every call. (Found when the contrast work had a group read the theme, qorus#646.) Test that fails before the fix: the hook numbers the children from the first on every render, through fragments and past a null.
…wn on (qorus#646) David's review found black text on a dark gray background: the muted intent (#f2f2f230) picked its text from the near-white it is made of, but is drawn at 19% over a dark page. Text and icon colors are now picked against what is actually drawn, to WCAG AA: 4.5:1 for text, 3:1 for icons and large text. - getReadableColorFrom / getReadableColor choose the candidate with the higher contrast (not a luminance threshold), draw a translucent background over what is under it (the theme it was made from), and give up the dimmed #f2f2f2 / #0d0d0d for pure white / black where the dimmed one is under 4.5:1 (#f2f2f2 on the info blue is 4.2:1). - New: getDrawnColor, getContrastRatio (unrounded: polished rounds 4.495 up to 4.5), getReadableColorOn (one color for every color of a gradient), getReadableAlpha / getReadableFadedColor (fade only as far as it reads), getReadableAccentColor(On) (keep the hue, move toward the side that can read on every background), getReadableIntentColor, getReadableThemeColor, getReadableIconLook, getReadableTextOn, getReadableGroupOpacity (how far a group drawn as a whole can fade and every mark in it still read), getThemeSurface(s), fadeColor (a translucent color at a panel's opacity stays translucent; rgba() replaced its alpha). - A one-color gradient (buildGradientColorsFromString) keeps its text readable at both ends, and a step lighter as it is drawn hovered or pressed: info's light end was 3.25:1 with white. - ReqoreSurfaceProvider / useReqoreSurface (exported, with TReqoreSurface): what a component paints - one color, or each color of its gradient - is handed to what it holds, which reads against it rather than the page. An app can tell Reqore about a background of its own the same way. Tests that fail before: __tests__/helpers/readableColors.test.tsx (the helpers missing, black picked for the muted intent, #f2f2f2 kept on the info blue, info's gradient 3.86:1 a step lighter) and __tests__/surfaceContext.test.tsx.
…orus#646) Each component picks its text and icon colors against the background it actually draws (a minimal wash, a translucent intent, a gradient, a lightened header), hands that background to what it holds, and fades secondary text and icons only as far as they stay readable (4.5:1 text, 3:1 icons and large text; WCAG 1.4.3 / 1.4.11). - Button: text, description and icons against its drawn fill at rest, hovered and active; a minimal or transparent button keeps its color in a readable shade; badges read on the button, every color of it. - Tag: solid, minimal, soft and text tags, against every color they are drawn on; the key is darkened as far as its text needs (over a light color 30% black left it in the mid-tones, where nothing reads); icons and actions; label and key effects read on the tag. - Input / Textarea / RichTextEditor: text, placeholder, icons and the clear button against the field's own wash. - Span / Paragraph / Heading / Icon: an intent, or a theme color named by name, in a shade of it that reads where it is drawn; text drawn in a gradient is drawn in colors that read, hovered and pressed too; a text glow's letters sit in a halo of the glow and are drawn in the color that reads on it (white letters in a white halo inside a red glow were 3.85:1). - An effect with several gradients is read as the layers they are drawn as, each over the ones under it. - Panel (content, the title bar over the content's gradient, description, title icon, a title rendered as a link), Message, notifications, Callout, EntityRow, Statistic, Testimonial, FeatureCard, Accordion, Timeline, Table cells, NavRail, Breadcrumbs (the separator too), SegmentedControl, CollapsibleContent's reveal button, Rating, Tree, StackedBar, DescriptionList, Bubble timestamps: each reads against its own surface; one with a gradient hands every color of it to what it holds. A ControlGroup or a Menu drawn in a gradient does too, and a menu divider's label reads on the menu. - Checkbox: the box against every color under it; a switch's word on the knob reads on the knob and the other on the track (one color for both was white on the light info knob, 2.56:1), a gradient track and a knob in the effect's colors are read as drawn, and an unset knob is drawn faint without fading its icon, which still stands out by 3:1. - Notifications: the text, icon, close button and pending spinner read on the notification as drawn over every color under it; content of the caller's own (a tag) is no longer faded with the text under a title; an iconColor is drawn in a shade of it that stands out from its tile. - NavRail: an idle rail rests at idleOpacity, or as little less faded as its icons need to stand out by 3:1 - the marks' and the active mark's on its fill, faded into the page with the rail (it rested at 0.34: 2.3:1). - A translucent theme drawn at a panel's opacity stays translucent (the muted EmptyState was white on white). - Breadcrumbs no longer force the bar's text color onto a crumb with its own intent (dark text on the warning crumb). - StackedBar writes each segment in what reads on it (white on a light segment was 1.9:1). - Disabled components that are not form controls say so (aria-disabled): Tag, Callout, Panel, Accordion item, EntityRow, Statistic, CollapsibleContent, the Checkbox row, Progress, SeverityRow, StackedBar, Testimonial, the Textarea wrapper, a disabled row's table cells. WCAG exempts disabled controls from contrast; assistive technology is told. Tests that fail before: __tests__/componentSurfaces.test.tsx (a group or a menu drawn in a gradient hands its colors on), __tests__/tagColors.test.tsx (a tag's key darkened as far as it takes, a tag reads on every color it is drawn on). The story run's contrast gate (the last commit) failed 524 of 1186 story tests before these and the story colors, and none after.
…us#646) Some stories put text or icons on colors no text color reads on: a cyan-to-pink badge gradient, a purple-to-teal item, purple-to-pink groups and menus, a pink message, an orange badge under a darkened key, a light green backdrop blob, a magenta sphere, dark icon colors on a dark panel, a red spinner and a red bold leaf on dark gray, a muted wash on a mid-blue picker. Each keeps the same kind of color in a shade the text reads on, so the demos show readable choices. The notification stories' busy backdrop tells the notifications on it what it is drawn in (ReqoreSurfaceProvider), as an app would for a background of its own, and its row labels are a shade less faint.
…orus#646) After each story settles (its transitions and finite animations finished), in its theme: - text by axe-core's color-contrast rule (4.5:1, 3:1 when large); - text on a gradient, which axe cannot decide, against the gradient's colors where the text is drawn on it, and text drawn in a gradient (background-clip: text) in each of its colors; - icons (an svg in .reqore-icon) at 3:1 against what they are drawn on (WCAG 1.4.11), which axe does not check. What a mark is drawn on is composed as the browser draws it: each ancestor's paint over what is under it (a translucent color blended, a gradient as its colors along the part the mark covers, a fill over a ring counting alone), an element at an opacity drawn as a whole - its paint and everything in it - then faded into what is under it, and a floating layer (a popover, a modal) over the app layout's surface. Disabled content is exempt (WCAG), as is anything not drawn and decoration the page marks as such (data-decorative). Fail-first: the first gated run failed 524 of 1186 story tests in 62 of 75 story files (Button 22/25, Tag 18/22, Table 49/71, Panel 32/59, ...); with the previous commits every story passes. Unit tests: __tests__/contrastGate.test.ts (the gradient position, faded group and settling cases fail on the gate before them). axe-core 4.10.2 is a dev dependency. src/components/COMPONENTS.md has a section on how Reqore picks text and icon colors now: against what is drawn, the surface handed down (ReqoreSurfaceProvider), the floors on fades, what stays the caller's.
|
@Foxhoundn: this is the contrast pass for qorus#646. It comes from David's review: "black text on a dark grey background — extremely difficult to read". It changes how Reqore picks text and icon colours everywhere, so qlip will show broad changes. Below is what changed and why each kind of diff is expected. The work is in six commits:
1. Colours are picked against what is actually drawn, not against the colour a surface is made from (
2. Surface context (
3. Faded or secondary text and icons have a floor: 4.5:1 for text and 3:1 for icons (WCAG 1.4.3 / 1.4.11).
4. Intent-coloured text and icons use a readable shade of the intent
5. Behaviour changes worth a look
6. Bugs found along the way
7. The story run's contrast gate (
Some stories put colours together that no text colour can read on. Examples:
Those stories now use the same kind of colour in a shade the text reads on, so the demos model readable choices. |
…rus#646)
reqraft drew some of its text and icons in the intents themselves and
faded others to fixed opacities. On a dark page many did not read (the
Qorus IDE's contrast gate, qorus#646): the read-first summary's
"Incomplete" / "Ready" 4.0-4.1:1, a danger reason 2.7:1 on the
attention box, a code value's size note 4.1:1, the help mark 2.7:1, a
refused value's reason 3.1:1, a ticket's metadata 2.3:1, a log's
timestamps 2.6:1.
- The read-first summary ("Ready" / "Incomplete", "N need attention ->")
is drawn in a shade of its intent that reads on the form's background,
and a shade darker or lighter as the page or panel around it may be;
the "· 4/6 set" count is as soft as it still reads.
- A read-first row's reasons, its "Moves to ..." note and the required
asterisk are drawn in a shade of their intent that reads on every
surface a row is drawn on: the page, the attention and set boxes'
tints, the optional box, each hovered. Its size and line notes, a
label's short description, the type tag, an empty value's dash and the
help mark fade only as far as they still read.
- A refused value in a pick list: only its name is dimmed, as far as it
still reads; the reason under it is left as reqore draws a row's
description. Dimming the whole row through `effect` took the reason to
3.1:1, and reqore 0.77.2 fills a menu row that has an `effect`, so a
refused row with an intent turned solid.
- Colors reqraft hands reqore itself are picked to read where reqore
draws them: an invalid option's mark and a log's status dot (reqore
draws a panel's title icon at 70%), the expression builder's marks
beside an argument, the A/B test card's quiet buttons on a light theme
(on their own wash of the intent), a ticket message's links.
- Everywhere else reqraft fades its own text - a ticket's metadata
(faded twice: by the entity row and again by reqraft), number, notes
and empty state, a log's timestamps, the reference picker's "nothing
matches" note and its unmatched kinds, a structured value's field
names, the schema catalogue's empty cells and notes, the expression
builder's hints, an option's description - it fades only as far as the
text still reads (4.5:1; an icon 3:1).
- An image reference's name, shown while its thumbnail loads, was a
button's default black on the dark chip (1.1:1); it takes the page's
text color.
- reqraft builds on reqore 0.77.2, which has no readable-color helpers:
src/helpers/readableColors.ts, useReadableOpacity and
useReadableIntentShade pick these until reqraft moves to the reqore
release with qoretechnologies/reqore#708. The tracking UI ships on its
own and imports nothing of the rest of reqraft, so it has its own copy
of the one helper it needs (src/tracking/ui/readable.ts).
Tests that fail before: compactToolbarInks and readFirstInks (the
intents and the fixed opacities fail in every theme tried),
readableColors (a shade drawn faded), and the refused-value tests (the
name dimmed, the row not filled, the reason left alone). New tests for
the new helpers: unavailableRowOpacity, tracking/readable.
The story run's contrast gate (it comes with the reqore upgrade) found
795 findings in 31 story files before (373 of 717 story tests failed).
After, 208 are left in 28 files, all drawn by reqore 0.77.2 itself:
intent text and icons, input and tree icons, the switch's words, menu
descriptions, bubble timestamps, soft tags. reqore#708 fixes those.
isHoveringPointer let through only a mouse and a pen held above the screen, so a pointer event with pointerType '' - which the specification allows where the type cannot be told, and which user-event sends under some jsdom setups - was taken for a finger, and its mouseenter opened nothing: five of qorus-ide's unit tests that open a tooltip with userEvent.hover (MiniBarChart x2, IncompleteCasesStrip, valueControlAndChips x2) found none. Only what cannot hover is turned away now: a finger, and a pen pressed to the screen. A pointer of no named type, or no pointer type at all, hovers, as a mouseenter no pointer event came before does.
…city the button draws it at (qorus#646)
A button draws its icons at CONTROL_ICON_OPACITY and picks an icon's own
color (leftIconColor, iconColor) in the shade and at the opacity that
stand out by 3:1. A color given in the icon's own props instead -
leftIconProps={{ intent: 'warning' }}, as the Qorus IDE's readiness rows
give it - was spread over that look: the icon made it readable at full
opacity and the button drew it at 70%, a warning icon at 2.4:1 (814
findings in the IDE's contrast gate).
The button now takes the intent or color in leftIconProps /
rightIconProps as the icon's own color (they still win over leftIconColor,
as they did when spread last) and picks it as it does leftIconColor; the
rest of the props are passed on as before.
Test (fails before): an icon colored through its props is drawn exactly
like one colored through leftIconColor, left and right; that look stands
out by 3:1 at rest and hovered, on the dark theme and a light one; the
other icon props (its size) still reach it.
…ld holds (qorus#646) The contrast work (85f6099..98c46c3) computes each faded description, label and icon opacity in the render and hands it to a memo component (ReqoreIcon, ReqoreP, ReqoreSpan, ReqoreButton) as a new object every time, and DeepScan reported 21 REACT_INEFFICIENT_PURE_COMPONENT_PROP and one INSUFFICIENT_NULL_CHECK as new on #708 (FAILURE at 98c46c3, 6e203e6 and 5960623). Each such effect, style and handler is memoized on what it is made of: Button, Input and Tag's icon effects; Checkbox's knob and box icons; Callout, EntityRow and FeatureCard's descriptions (and EntityRow's metadata, Callout's close handler); RichTextEditor's placeholder and paragraph styles; SegmentedControl's item refs (one callback per item); Statistic's label; Timeline's timestamp and content styles. Panel reads the description effect's color and opacity null-safely, as it reads its textSize below.
…ks for it (qorus#646) A user who asks for reduced motion (WCAG 2.3.3) still got every spring Reqore runs - an input's clear button fading in, a drawer sliding, a reveal - and every CSS transition. A contrast check measured them on their way: the clear button, which rests at 4.8:1, at 1.4-2.6:1 in the IDE's gate (a react-spring fade, which no gate can wait on). - ReqoreUIProvider turns react-spring's skipAnimation on while the user prefers reduced motion (set as it renders, so the first render's springs settle too, and again when the preference changes; it turns off only what it turned on): every spring settles at once, at its end. - Its global style takes every CSS transition inside the layout and the portal to 0.01ms under the preference (not none: a transition of no time sends no transitionend, which some components wait for). Keyframe animations (a spinner, a pulse) are as they were. - The story run's contrast gate asks for reduced motion while it measures (a browser command, emulateReducedMotion), so it measures what stays on screen, never a frame of a fade. A story tagged `reduced-motion` runs under it; the others run with the motion a user sees, as their checks of a marquee, a count-up or a sheet expect. Tests (fail before): under reduced motion an input's clear button is at its settled opacity on first paint and Reqore's springs skip; the story Form/Input "Transitions Under Reduced Motion" finds the clear button's transition at 0.01ms (0.1s without the preference).
…put, a tag or a panel draws it faded (qorus#646) As with the button (5960623): an input's icons, a tag's and a panel's title icon are drawn at CONTROL_ICON_OPACITY, and the component picks an icon's own color and that opacity together so that it stands out by 3:1. A color given in the icon's props - leftIconProps={{ intent }}, iconProps={{ color }} - was spread over the pick: the icon made it readable at full opacity, and drawn at 70% it fell short (a collection's filter icon given leftIconProps={{ intent: 'muted' }} at 2.2:1, the IDE's gate). Each now takes it as the icon's own color (it still wins over the component's own, as it did when spread last) and passes the rest on. The split is one helper, splitIconColor (helpers/iconProps), the button uses it too. Test (fails before): an input's left and right icon, a tag's and a panel's title icon colored through their props are drawn exactly like ones colored through the component's own prop.
…#646) A link with an intent was written in the shade of it that reads on the theme's page, wherever it was: on a table cell tinted with the warning intent 3.5:1, over an entity row's gradient 2.8:1 (the IDE's gate). It now reads the surface a component around it hands down, as a span does. Test (fails before): an info link reads 4.5:1 on a warning-tinted cell and on every color of a gradient, in a shade of blue.
…646) An active button (a selected menu item, a sidebar section on the active path) is filled with its color, but its description, its badges and its icons were picked for the button at rest - its color's wash over the page: a light blue description 2.0:1 and badge 2.7:1 on the info blue of the IDE's account menu, a grey description 4.4:1 on a sidebar's section toggle. The description is now picked on the active fill, the button hands that fill to what it holds, and its icons are picked on it. The nav rail reads its active mark as the button now draws it (its icon on the fill, in the rail's activeEffect), so it still rests only as faded as that icon stands out by 3:1. Tests (fail before): an active button with a color writes its description and draws its badge on its fill; one without a color writes its description on its fill; at rest the description reads as before. The NavRail idle stories fail with the old rail and this button.
…at they hold (qorus#646) An entity row faded its description and metadata as a whole, so what they hold with a color of its own was faded a second time: a minimal warning tag in the metadata at 4.0:1, a link in the description at 2.8:1 (the IDE's gate). Their text is now written in the faded color (the readable floor as before; an opacity a caller gives is still that floor, a color a caller gives is the caller's), and a tag or a link in them is drawn as it is. Test (fails before): both slots are drawn whole in a faded color, a tag and a link in them are not faded, and a caller's opacity is kept.
…through it (qorus#646) A gradient effect paints its fill over a lighter ring of the same gradient (border-box). With an opaque fill the ring is hidden; with a translucent one - a glow of 'info:lighten:1:0.5' over the page - it shows through, but the colors a component read its text against left it out: a statistic's label faded for the blue over the page read 3.6:1 on what was painted (the IDE's support hero). The effect's own readable colors and getEffectBackgrounds now draw the fill over the ring (one fill layer stop by stop, as they share the gradient's geometry). Test (fails before): a statistic's label stands out by 4.5:1 from the corner of a translucent radial glow as it is painted, ring included.
|
Heads-up @davidnich: I'm fixing review findings on top of the current head. Please hold pushes until I comment that it's in; I'll rebase on anything that lands meanwhile. The fixes:
The version (these are changed defaults in 0.78.5) is left to you. |
…ws only its own changes The front-end lead asked, and David approved, that the qorus#646 contrast work - text and icons picked against what they are drawn on, the story run's contrast gate, and the reduced-motion support that came with it - be reviewed and released in a pull request of its own: inside this one it made the Qlip review enormous. This takes it out of this branch; the same changes are re-applied, as new commits, on the branch of their own PR (based on this one). Reverted, newest first: 0fba3d8, a7194b5, 2b84953, bd20c18, 2dcd352, de9b9cd, b5f7a66 (its DeepScan fixes are all to code the contrast commits added), 5960623, 98c46c3 (axe-core goes with the gate), 76de251, 3edf31d, 85f6099. Kept: everything else, among it the paging fix (03ddb5d), the control group fix (d11a941) and the hover fix (6e203e6), whose files are as they were. The tree is exactly the one before 85f6099 plus 6e203e6.
… the server's media answer Review of d45a15b ("a tap opens no hover"; Foxhoundn's review of #708): the hover gate turned away the mouseenter a browser emulates after a tap, and some popovers opened on nothing else. - The More menus of Tabs, SegmentedControl and Breadcrumbs (hoverStay) could not be opened on a phone, so the tabs, segments and crumbs they hide were unreachable. A hover popover that is a way in (the new opensOnTap, which ReqoreDropdown, the tabs' and the segmented control's More menus set) opens at the tap's click, at once, and closes as a click popover does. A plain tooltip, hoverStay or not, still opens at no tap (TapOpensNoHoverTooltip). - keepOpenOnHover popovers open at a tap too (their content is there to be reached), and one opened by a click or by focus listens to its own event as well as the hover: the IDE's `handler = isHoverCapable ? 'hover' : 'click'` with keepOpenOnHover opened nothing at a tap. - A panel's floatingActions float only where the device can hover; on one that cannot, its show: 'hover' actions stay in the bar, shown, so a tap reaches them (IDE collections, reqraft's form engine). 9ef15cc: useReqoreMedia answered matchMedia on the client's first render while a server drew the default (the docs SSR the provider): it now hydrates the server's answer and then the browser's (useSyncExternalStore), and still answers the browser's on a first render without a server. Tests (fail before): popoverOpensAtATap (hoverStay menus, keepOpenOnHover with hover, click and focus, a tab bar's More menu; a hoverStay tooltip still opens at no tap), panelFloatingActionsTouch, reqoreMediaHydration. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
1 new commit (7279d2e → 26327a4): the touch fixes for d45a15b and the hydration fix for 9ef15cc. My earlier heads-up listed colour fixes too; those now go to #717, where the contrast work lives.
|
Brings in develop 0.78.5 (#708: popover/panel fixes, helpers/styled prop filtering, the touch story project). Develop no longer carries the qorus#646 contrast work (7279d2e moved it to its own PR), so nothing of it reaches here. Conflicts, each resolved keeping both sides: - package.json: version stays 0.78.6 (this PR's single bump, above develop's 0.78.5). - Accordion, Callout, Checkbox, DataView, Link, Notifications/styles, Paragraph, Progress, Span, Tabs/list, Testimonial, Tier: imports only - develop's `styled` from helpers/styled (and its omitStyleProps / control-group props) together with this branch's typeScale helpers in place of the raw TEXT_FROM_SIZE / CONTROL_TEXT_FROM_SIZE imports. - Collection/item, Menu, NavRail, Table/cell: imports only - develop's helpers/styled and hoveringPointer imports with this branch's panel-surface colour helpers. - Panel: develop's REQORE_PANEL_STYLE_PROPS record and this branch's theme.panel header/border helpers, both kept. - Table/index: the wrapper class carries both the hover-reveal class (actionsRevealOn) and develop's draggable class (dragToScroll). - COMPONENTS.md (Panel, Statistic, Table, UIProvider rows): develop's 0.78.5 text followed by this branch's 0.78.6 text in each row. - Drawer, EntityRow, Statistic, Table stories: develop's new stories and this branch's themed-surface stories, both appended. Develop added no component size read, so the only TEXT_FROM_SIZE reads left in src/components are Spacer's default height.
…story taps them on a phone (qorus#646) A Qonsole panel on a phone folds its whole trail into the dropdown named for the current chat. In the Qorus IDE a tap opened nothing, so a finger reached neither the chat's full name nor "Back to chats". The IDE was on a #708 prerelease from before 26327a4. That commit made every ReqoreDropdown open at a tap (opensOnTap), including both of the breadcrumbs' fold menus, and it is in 0.78.5. Nothing tested it for the breadcrumbs. - Both fold menus (the current-page dropdown and the "…") now say what they are and whether they are open: aria-haspopup="menu" and aria-expanded, as the row actions menu does. - The "…" dropdown has a class of its own, reqore-breadcrumbs-overflow-menu. Story Navigation/Breadcrumbs › Folded Trail Opens At A Tap (touch, 390 px): - A finger taps the folded current crumb. Its menu names the chat in full, and "Back to chats" in it is reached by a tap. - A finger taps the "…" of folded ancestors. It opens, and "Back to chats" is reached there too. - Both buttons say they are menus, closed and then open. Before this change the aria checks fail. With the dropdown's opensOnTap taken out, the tap checks fail (the menu never opens), which was the IDE's case. Hover is unchanged: the menus are still hoverStay for a mouse.
All of our open reqore fixes in one PR, as 0.78.5. The PR carries what #709 (closed), #710 and #713 (consolidated here, closed unmerged) held. 0.78.0 is released; 0.78.1, 0.78.3 and 0.78.4 are claimed by the prereleases of #708, #710 and #713, so the one bump for this PR is 0.78.5 (7c9c127).
1. A list opened by focus opens for a field focused as it mounts (b293652)
A popover with
handler='focus'listened only forfocusin. A field focused as it mounts was focused before the listener was attached, so nofocusincame and its list stayed closed. This is what an editor that opens with the cursor in its field does, such as a read-first form row when it is clicked. Clicking the already-focused field still showed nothing until the user left it and came back.In the Qorus IDE, this hid every template a typed field offers: an added field of type Whole number showed none of the record's fields.
popoverFocusOpens.test.tsx2. A rich-text value is compared with the document as it is committed, so a key typed after it is kept (cdaba6c)
The editor compared an incoming value with its document in a passive effect. The browser runs those after painting, which under load can be after the next key. A value that matched the document when it was rendered was then compared with a document that had one more key, taken as a value from outside, and put in its place. The key was lost and the caret jumped to the end.
reqraft's SmartEditor › With Markdown Docs typed
arr.forand gotarr.frin CI (toolkit-react run 37601004195). With the CPU throttled 6x in a real browser, it lost a key in most runs.richTextEditorValueSync.test.tsxtypes a key between a value's commit and its passive effects: the key was lost before the fix and is kept now. A value from outside still replaces the document.3. No Reqore styling prop reaches a component a Reqore component is rendered
as(from #709, closed unmerged: 3d5c4a3, 0669d75, 9180338)<ReqoreControlGroup vertical stack><ReqoreButton as={Link} to='/x' fluid compact /></ReqoreControlGroup>handed the router link 36 props, which it wrote onto its<a>. Among them werefluid,compact,flat,animate,maxWidth,alignSelf,active,readOnly,tooltip,descriptionand the group's stack-position flags (isChild,isFirst,childId, …). Aresizable+transparentReqorePanelhanded re-resizabletransparent, which re-resizable spreads onto its div.Button/index.tsx:341kept back onlyfill/color/wrap, and the panel's list (Panel/index.tsx:587-596) was eight names written by hand. styled-components forwards everything else to a component target, and both lists had gone stale as props were added.TReqoreStylePropKeys<TProps, TTargetProps>+listReqoreStyleProps<…>({ … })(src/helpers/styled.ts). Each list is aRecordof every key of the component's prop interface that its element does not take, so a new prop left out of the list fails the build.REQORE_BUTTON_STYLE_PROPS,REQORE_PANEL_STYLE_PROPS,REQORE_TAG_STYLE_PROPS,REQORE_TEXTAREA_STYLE_PROPS.size/enable/minWidth, which a resizable panel still needs.media*props, which are now in the list.as-capable component: the same leak was in Tag, Paragraph, the TextEffect base (block/inline), the Tabs list item (theme,active,size, …), the Textarea rendered as Slate's editable, the Icon'sanimated.spanwrapper and the Breadcrumbs item (interactive).ReqoreTooltipComponentno longer handstooltipto any component, and the Timeline item no longer writesdisabledonto its<li>(it setsaria-disabled).4. A
descriptiontakes block content (from #709, and ae99313)ReqoreEntityRow,ReqoreSeverityRow,ReqoreCallout,ReqoreFeatureCard,ReqoreTier(classic and #696's modern appearance) andReqoreTestimonial(quote) drew the description in a<p>. Consumers passReqoreP/ReqoreControlGroupthere (qorus-ide: ~14 call sites), so React warned (validateDOMNesting) and the browser's parser moved the blocks out of the paragraph.<p>, exactly as before, so consumer CSS aimed atpstill matches. Anything else is aReqoreP as='div', which gets exactly the classes the<p>gets (getReqoreTextElement, ae99313). A callout'schildrenare drawn in adivtoo. The root of each of these components (the effect'sspan,display: flex/grid) is adiv, and it keeps back the control-group flags and its own styling props (disabled,radiusSize,inheritCustomTheme, …). The prop docs and COMPONENTS.md say a description takes block content.ReqoreButton's andReqoreCheckbox's descriptions stay inline content: they sit inside a<button>/<label>.5. A styling prop that is also an HTML attribute name reaches only the elements it is an attribute of (7f6bd6c)
size='normal'(a Reqore size) was written assize="normal"onto everydiv/spanit was handed: control groups, panel titles and contents, tag contents, table rows and cells, accordions, timelines, statistics, progress bars, …. Other props leaked the same way:checkedreached its divs;selected/disabledreached theirs;readOnlyreached its div;placeholder.React is silent about all of these.
REQORE_AMBIGUOUS_PROP_ELEMENTSmaps each such name (size,width,height,wrap,color,fill,opacity,direction,offset,spacing,disabled,readOnly,checked,selected,placeholder,label,type,value) to the elements it is an attribute of. The element names are typed against React's intrinsic elements, and SVG keeps its presentation attributes.omitStylePropslets such a name through to a tag only when it belongs there.src/helpers/styled.tsnow exports Reqore'sstyled: styled-components' ownstyled, with every styled component it makes filtered this way. All ofsrcimports it, and an ESLint rule forbids the default import ofstyled-components, so a new styled component cannot leak these props again.disabled(Slate is disabled throughreadOnly).size, 7.9kselected, 6.6kdisabled, 534checked, 95readOnly, 4placeholder. After: nothing, except Slate's own internalzindexon its editable.Tests for 3–5
__tests__/domPropLeaks.test.tsx: 16 new cases, and the detector now also flagssize/checked/selected/placeholderon elements that don't take them.__tests__/helpers-styled.test.tsx: the element table, case by case, and Reqore'sstyled(keepssizeoff adivwhile the styles still read it, keeps it on aninput, uses the element given asas, forwards everything to a component).__tests__/blockDescriptions.test.tsx: every component's description given block content (adiv, novalidateDOMNestingwarning), a callout's block children, and plain text still drawn as a<p>.__tests__/domPropLeaks.test.tsx(ae99313): a textarea in a filling stack group; the five surfaces in a filling stack group, and rendered as a router link with every surface prop set;<ReqorePanel as='fieldset' disabled>disables its fields,<ReqoreTag as='button' disabled>is a disabled button.6. A table is scrolled sideways from its header too;
dragToScrollpulls it with the mouse (from #710: eaff15b, c432b42, 30a6e81)From David's review of qorus#646.
Symptom. On a phone, a swipe on a wide table's header did not scroll the table, though the same swipe on its rows did. With the mouse, a table wider than its frame could only be scrolled by its scrollbar or Shift+wheel; the IDE had built its own drag-to-scroll around reqore tables, which could not reach the header either.
Root cause. The header is scrolled with the body (the body syncs
headerRef.scrollLeft), but it is not a scroller itself (overflow: hidden). The browser has nothing to pan under a finger there, and the header forwarded only the wheel (deltaX) to the body.Fix
touch-action: pan-y, so the page still pans up and down) and scrolls the body with them.dragToScrollprop (defaultfalse): a mouse or pen presses and pulls the table by its body or its header.onClick.ReqoreFadeScroller'sdragToScroll, moved into a shareduseDragToScrollhook that both components use. The hook rebinds when the element it scrolls appears after mount, since a table renders no body until it has rows.Also in this PR
deltaXwas forwarded.dragToScrollthe body shows agrabcursor, andgrabbingwith no text selection while it is pulled.lostpointercapture, and the gesture ended on it. Now only the capture the gesture itself took ends it. The dispatched-event tests could not see this; it was found with real touch events, and a test now covers it.Tests
__tests__/table-drag-to-scroll.test.tsx: 9 tests. 4 of the first 6 fail against develop's Table: the touch swipe on the header, the header'stouch-action, and mouse pulls by the header and by the body.yarn precheckpasses.Docs: COMPONENTS.md, ReqoreTable row.
7. Floating actions stay while the pointer crosses to them (from #713: c701745)
From David's review of qorus#646.
Symptom. In reqraft's expression builder, hovering an expression and clicking its floating Remove (the trash can) did nothing visible. The click switched the Visual / Text view underneath instead.
Root cause.
floatingActionsfloats a panel'sshow: 'hover'actions above its top-right corner. A pointer heading for them leaves the panel through its top edge, usually to the left of the actions, on a diagonal.handleMouseLeaveended the hover unless the pointer went straight into the actions, so they were gone before it reached them.Fix. While the pointer is in the strip between the panel's top edge and the actions, across the panel's width, the hover stays. It ends when the pointer leaves that strip for anything that is neither the panel nor the actions. This is decided by where the pointer is on each move, with no timer. The same applies when the pointer goes from the actions back towards the panel.
Tests.
__tests__/panelFloatingActionsReachable.test.tsx: crossing to the actions (fails without the change), leaving the strip elsewhere, and leaving the panel away from the actions (unchanged).yarn precheckpasses.Docs: COMPONENTS.md, ReqorePanel row.
8. Fixes found reviewing the Qorus IDE's value tabs (qorus#646: 94edea0, d877213, ba2c40f)
passPopoverDatahanded outisOpenas a closure over the render that passed it. A key typed straight after the click that opened a template list found the list "closed", so typing did not put it away. Escape was heard on the document's bubble phase, after an editor around the field (reqraft's form row, where Escape discards the edit), so one Escape for the list also threw the edit away. The open state is now kept as it changes. Escape is heard in the capture phase and, while a popover is open, closes it and stops there.ReqoreMenuItem'sscrollIntoViewscrolled every scrollable ancestor to centre the item, the page's scroll container and the document included. Arrow keys in an editor's completion list moved the page, and in a short view left it blank. Inside a.reqore-menu, the item is now centred in the first scrollable element up to and including the menu. Outside a menu it behaves as before.Tests. popoverControlsAnswerNow, menuItemScrollStaysInMenu, templateListOpensOnFocus and the updated menu.test all fail before their fixes. Stories: TextArea › Escape Closes The List Not The Editor Around It (fails before) and RichTextEditor › Typing Straight After The Click Dismisses The List (a guard). Locally: precheck passes (1470 unit tests) and all 1128 stories pass. Version stays 0.78.5: only
-prprereleases of it exist on npm.9. A drag on a column's resize handle resizes the column and leaves the table where it is (be112f0; found by Foxhoundn in #710's changes)
The header's drag-to-scroll gesture also started on the resize handle, so the table scrolled under the column edge being dragged: on every table with a finger, and with the mouse when
dragToScrollis on.useDragToScrollnow leaves out anything markedreqore-no-drag-scroll, and the header cell marks its resize handle with it. A consumer can mark its own grab handles the same way; COMPONENTS.md documents this in the ReqoreTable row. Test: table-drag-to-scroll, a drag on a column's resize handle (fails before).10. Text written without a key puts a field's template list away, as typing does (97f9adb, qorus#646). An on-screen keyboard sends no keydown for a character, only the input it makes. So on a touch device the template list stayed open over what was being written, and over the completion list that a typed
@or$opens beside it, which then could not be seen. The field's nativebeforeinputnow closes the list for input that writes or deletes text. A composition starting, which writes nothing yet, leaves it open. Tests:popoverControlsAnswerNow, text written without a key (fails before), and input that writes nothing.11. A click on a chip in a template field neither lists the templates nor scrolls the page (f9512c6, a regression from ba2c40f found in the Qorus IDE with 0.78.5-pr.708.g432b59a)
Symptom: with the cursor already in a rich-text field that offers templates, a click on a chip moved the page ~137px and showed nothing. On 0.78.0 the page stayed put.
Root cause:
Fix:
Textarea).closeOnTargetClick={false}).onBeforeOpen.WithCustomTagsrelies on that.Tests:
templateListOpensOnFocus, which fails at 432b59a for the first two:12. Merged with develop (0.78.1, #714), and two gesture fixes brought across (f74b521, 79d92e0)
The merge:
FadeScrollerconflicted. #710 here had moved the row's drag-to-scroll intouseDragToScroll, shared with the table. #714 had moved it ontousePointerDrag, the drag a sheet, a modal, a toast and a tier stack share.styledimport.useDragToScroll, because its scrollers appear after mount and its header scrolls another element.Gesture fixes: two fixes this branch made to the shared gesture had reached only
useDragToScroll; 79d92e0 brings them across.usePointerDragended a gesture on anylostpointercapturefor its pointer. A finger is captured by the element it lands on, and the element that drags takes that capture when the drag engages. So a swipe that started on a title or label inside a sheet, a toast or a tier stopped after a few pixels. This is the same bug fix(table): a table is scrolled sideways from its header too; dragToScroll pulls it with the mouse — 0.78.3 #710 found with real touch events on a table's header; now only the capture the gesture took ends it.reqore-no-drag-scrollgrab handles alone again. As documented, the row now takes the threshold and that list fromuseDragToScroll.pointerDragImplicitCapture(fails on develop's hook);fadeScroller-drag, a press on areqore-no-drag-scrollelement (fails on develop'sFadeScroller).Checks for 11–12:
zindex.13. A
responsiveLayoutdrawer opened on a phone is a sheet the screen's width from its first frame (9ef15cc, #714 code)Symptom: a
floatingdrawer that became a phone sheet keptright: 10px, so it was 380px wide on a 390px phone.Root cause:
useReqoreMediareturned react-use'suseMedia(query, false), which rendersfalsefirst and only asks the browser in an effect. So the drawer mounted as the caller's side panel for one render. The provider'sisMobile,isTabletandisHoverCapablewere also wrong for that first render.[position]: floating ? '10px' : '0px'ingetSpringConfig). That value was fixed at the render the drawer entered in, so a later switch to a sheet never removed it.Fix:
useReqoreMediais now Reqore's own hook. Where it can (CAN_QUERY_MEDIA) it asksmatchMediaas its state is created, and an effect keeps the answer current. Its other callers are the provider's three flags, which are now right from the first render.Tests:
drawerSheetOffsets: a floating drawer that becomes a sheet after it entered sits flush with the screen, and one that enters on a narrow screen does too. Fails before.touch, run in the phone project): the box is measured in the commit that mounts it and at each of the next 30 frames, and is a sheet exactly the screen's width with no offset each time. Fails before: the first frame is not a sheet.Visual:
:focus-visiblematching.14. DeepScan: the narrow-row stories pass their memoized rows stable props (34553ea)
The three issues DeepScan reported as new at 8faff91/47fde14d were all
REACT_INEFFICIENT_PURE_COMPONENT_PROPin stories:EntityRow.stories.tsx:729: an actions array built on every render;SeverityRow.stories.tsx:545and:663: a leading tag element built on every render.They are module constants now. DeepScan at 9ef15cc: 0 new issues in both projects.
Checks for 13–14: typecheck (test and prod) and lint pass; 133 test files, 1619 tests pass locally. The Drawer, Modal and Manager story files pass on the desktop project, and Drawer on the phone project.
15. Floating actions and popovers follow what they are placed by when it resizes (87239b9; found in reqraft #137's Qlip build)
Symptom:
Root cause:
position: fixed, so nothing moves them except a placement.useTargetWidth, the popover also kept the target's old width.Fix:
ResizeObserverreport for the panel or for the actions, as well as on a scroll.ResizeObserver, already one placement per frame for its own size, now also observes its target.Tests:
panelFloatingActionsPlacement, all seven new cases failing before the fix:ResizeObserver.popover-resize: a target that changes size is observed, and re-places the popover at the next frame. Fails before.Docs: COMPONENTS.md, the ReqorePanel and ReqorePopover rows.
Checks for 15:
16. A tap opens no hover; only a pointer that hovers does (d45a15b; the reqore side of reqraft #137's fix, qorus#646)
Symptom:
mouseenter,mouseover,mousemove), and nothing in them tells them from a mouse's.hoverStaytooltip, which stayed open over what the finger had pressed;Fix:
createHoverGate/useHoverGate/watchHoverGateinsrc/helpers/hoveringPointer.ts, exported) notes the pointer of those pointer events. The mouse event after them asksisHover(), which reads what was noted once. A mouse, or a pen held above the screen (isHoveringPointer), is a hover; a finger is not.mouseenteralone, reqore's and its consumers', still open their tooltips.hover,hoverStay,keepOpenOnHover, and so everytooltip, in tables, menus, dropdowns and collection items alike), PanelfloatingActions, NotificationpauseOnHover, Rating's preview, NavRailidleReveal/showLabels: 'hover', and the marquee.isHoverCapableare as they were.isHoveringPointer(Progress on Storybook improvements #137) and can use reqore's once a release ships it; Progress on Storybook improvements #137 is not pinned to this prerelease.Tests:
hoverIsAPointerThatHovers:isHoveringPointerand the gate (read once; no pointer counts as a mouse);mouseenter; it does not open at a finger's tap, forhover,hoverStayorkeepOpenOnHover, and opens for a mouse after a tap;notifications(also: a leave no hover came before restarts no timer),Rating,NavRailandmarqueeOnHover.mouseenterones pass both ways.hoverStayone's with a real finger (CDP touch). Each is pressed; no tooltip is ever added. It fails before the fix: thehoverStaytooltip opened, 2 of 2 runs.withMouse,src/stories/utils/mouse.ts), so it hovers nothing in later stories.Checks:
Docs: COMPONENTS.md, "A hover is a pointer that hovers".
17. Two fixes the IDE's story run found (03ddb5d, d11a941; pushed with the qorus#646 contrast work, but not part of it)
next()calls from one render at the page before the last leftcurrentPageundefined. The collection showed nothing while still offering "Load more".useReqorePagingkeeps its own page state, always within the list.react-use-pagination-hookis removed.next.useCloneThroughFragments: it kept counting children across renders that reused its memoized clone. A stacked ControlGroup that re-rendered on its own (for example on resize) lost its rounded ends.The contrast work moved to #717 (7279d2e)
At the front-end lead's request, approved by David, the qorus#646 contrast work (text and icons picked against what they are drawn on, the story run's contrast gate with axe-core, and the reduced-motion support that came with it) is reviewed and released in #717, which is based on this branch. 7279d2e takes it out of this one: it reverts 85f6099, 3edf31d, 76de251, 98c46c3, 5960623, b5f7a66, de9b9cd, 2dcd352, bd20c18, 2b84953, a7194b5 and 0fba3d8. The tree is exactly the one before 85f6099 plus 6e203e6 (the hover fix), so nothing else in this PR changed. b5f7a66 (the DeepScan fix) went with them: every line it changed was written by the contrast commits.
Checks at 7279d2e: typecheck and lint pass; 135 test files with 1658 unit tests pass; all 76 story files pass locally (1191 stories); CI is green and DeepScan reports 0 new issues.
Review (Foxhoundn, 2026-10-07)
focusCheckedForref)popoverFocusOpens.test.tsxmedia*props breaktscon the merged treeblockDescriptions.test.tsx)fill/sizein afillgroupsizestays off everydiv/span)spandivptodiv<p>; only other content is adivasanother tag drops its real attributedisabled,readOnly,checked,selected) reaches the element a component is renderedaswhen that element has it; COMPONENTS.md "Props and the DOM" lists what left the DOMfillgroup, callout block children, panel as a fieldset and tag as a button; 9180338: the modern tierChecks: typecheck (test and prod) and lint pass; 111 test files, 1458 tests pass locally. I rendered all 1126 stories in jsdom and read what React handed each element: nothing leaks except Slate's own internal
zindex.Visual. I captured all 75 story files (1111 snapshots) on develop (b1a555a) and on this branch, one file at a time, headless.
🤖 Generated with Claude Code