Skip to content

quasar-v2.28.0

Latest

Choose a tag to compare

@rstoenescu rstoenescu released this 28 Aug 17:08

Important

Several behavior changes worth checking before you upgrade:

  • QMenu and QTooltip lost the scroll-target prop. Popups now hear every scrolling container, nested ones included, through a single capture-phase document listener, so no container needs to be designated (and the scroll helper class is no longer part of the contract). The prop was only ever an escape hatch for containers the auto-detection missed; remove it from your templates.
  • QMenu/QTooltip freeze their placement decision per show. A popup no longer re-flips or pins itself on screen while you scroll: it stays glued to its anchor and scrolls off-screen together with it, like a natively anchor-positioned element. updatePosition() now means "re-run the placement decision"; a plain anchor move needs no call at all.
  • QTooltip tracks its anchor on container scroll instead of hiding.
  • fit/cover minimum sizing follows the real anchor box and is no longer inflated by the offset prop.
  • The Platform.is flags ie, edgeChromium, winphone, kindle and silk are gone (the first two were never set on any browser Quasar supports, the rest were dead platforms). is.edge now plainly means Chromium Edge.
  • ESC dismissal and modal focus containment are no longer desktop-only. A mobile device with a hardware keyboard (iPad with a Magic Keyboard, Android with a BT keyboard, DeX) now closes dialogs, menus, drawers and tooltips with ESC and keeps Tab inside a modal dialog.
  • Focus and hover feedback are keyed on input capability instead of the user agent. Focus paint moved to :focus-visible, so a desktop mouse click no longer leaves a lingering halo, and hover paint moved under @media (any-hover: hover), so hybrids gain hover feedback without phones gaining sticky hover.
  • Build targets moved up: browsers to the live Baseline Widely Available minima (Chrome/Edge 121, Firefox 123, Safari/iOS 17.2) and Node to 22.

Perf

  • perf(ui): zero idle cost for offscreen spinners -> spinners kept animating outside the viewport, burning main-thread style recalcs (a page of below-fold lazy QImg placeholders never went idle). Every spinner type now costs 0 recalcs and 0 layouts per second while offscreen, down from up to 173ms/s of style time, and visible spinners got 1.3-2x cheaper too. The SMIL paint animations were rewritten as CSS keyframes, preserving each spinner's timing, easing and phase relationships (#17151)
  • perf(QCircularProgress): zero offscreen cost and a ~2x cheaper visible indeterminate mode -> 100 offscreen instances used to burn 84ms/s of style time; the onscreen cost drops from 42-66ms/s to 25ms/s per 100. QLinearProgress and QSkeleton were measured too and need nothing (#17151)

New

  • feat(QMenu/QTooltip): native CSS anchor positioning on Chromium -> the anchor element gets a refcounted anchor name and the popup expresses its placement through position-anchor / anchor() insets, so the browser keeps it glued through any scroll, layout shift, resize or animation with zero listeners. Every other engine keeps the JS positioning engine, whose behavior was aligned with the native one so a flip in Chromium and a flip in Safari are the same flip by construction. Menus now follow ANY scrolling container on both paths, ending the long-standing "the menu only follows a container carrying the scroll class" limitation (the #8343 family)
  • feat(QSelect): open the options on hover -> new hover, hover-delay and hover-hide-delay props, the same contract QMenu, QBtnDropdown and QFab settled on. A hover-shown open never steals focus and never pretends to be focused, so a pointer merely passing over the select emits no focus/blur and cannot trip lazy validation; @filter selects still load their options on hover. A real click or Tab upgrades the show to a regular focused open (#11675)
  • feat(QSelect): close button for the options dialog -> behavior="dialog" finally has a visible dismiss affordance, a labeled button in the in-dialog control row where the dropdown arrow would sit, so the software keyboard can never cover it. Follows the color prop, styleable through q-select__dialog-close, opt out with the new hide-dialog-close prop (#6858)
  • feat(QSelect): no-chip-remove prop -> with use-chips, drops the chips' remove icon and disarms the chip-armed Backspace removal, so a select that must always hold a value is possible with chips. clearable's own affordances stay intact when explicitly enabled alongside it (#17937)
  • feat(QSelect): no-option-label prop -> a plain no-options message without having to reach for the no-option slot (#9463)
  • feat(QSelect): stable CSS class on the displayed value -> the selected-value span carries a dedicated q-select__selected-value class as an officially targetable hook, e.g. white-space: pre-line to render embedded newlines (#17973)
  • feat(QInput): support the v-model lazy modifier -> Vue implements it only on native elements and leaves the deferral to components, which QInput ignored. The emission now stays pending until the change event or blur, matching the modifier's native semantics; an external model change cancels it (#18023)
  • feat(Dialog plugin): report the dismissal reason to onCancel/onDismiss -> the callbacks receive 'cancel', 'backdrop', 'escape' or 'programmatic' (which includes an app route change), so the Cancel button can finally be told apart from a backdrop click, ESC or a hide() call. Custom components built on useDialogPluginComponent get it with no template changes. New exported TS type: DialogDismissReason (#17938)
  • feat(BottomSheet plugin): report the dismissal reason to onCancel/onDismiss -> the same argument, 'backdrop', 'escape' or 'programmatic'
  • feat(QParallax): react to scrolling in any container -> a parallax nested inside a second scrolling container silently stopped updating, since it only listened to the one auto-detected container. It now hears every scrolling container. The scroll-target prop stays with its other, narrower meaning: the box the scroll percentage is computed against
  • feat(ui): build for the current Baseline Widely Available browsers and Node 22 -> browser targets move off Vite's hard-coded snapshot to the live Baseline minima (Chrome/Edge 121, Firefox 123, Safari/iOS 17.2), to be refreshed on release trains, and both the Node build target and the long-stale engines field (node >= 10!) move to Node 22

Fixes

  • fix(QDialog): clip the viewport instead of pinning the body -> the scroll lock no longer moves the page to scroll 0 while a dialog, menu or drawer is open on non-iOS platforms, so nothing jumps on open or after dismissal (#18527, #18183)
  • fix(QDialog,QMenu,QTooltip): finish a pending transition on keep-alive deactivation -> a popup deactivated mid-transition inside a <KeepAlive> never received its transition-end, so it came back stuck half-faded (#18201, #18526)
  • fix(QDrawer): keep the drawer content alive across breakpoint crossings -> the close-swipe directive was attached only below the breakpoint and its condition is baked into the vnode key, so every mobile/desktop transition re-created the whole drawer content, and a containerized QLayout mounted it twice on every page load (#17099)
  • fix(QIntersection): don't remount the content right after SSR hydration -> with once + ssr-prerender, freshly hydrated content was destroyed and rebuilt on every page load (#17099)
  • fix(QTable): key slot content that can shift or swap so it never cross-patches -> cells rendered through body-cell-<name> / header-cell-<name> slots were dropped unkeyed into the row, so changing visible-columns made Vue diff one column's compiled slot output against another's, producing hybrid cells (one column's label with another's href) and worse after repeated toggles. Production-only, which is why it survived dev testing. Same treatment for top-row/bottom-row and top-selection (#16047)
  • fix(QTable): hover sort icon previews the descending-first sort order -> hovering an unsorted column configured with sortOrder: 'da' showed an up arrow even though the first click sorts descending (#18009)
  • fix(QVirtualScroll): shield the table padding cells from user sticky-column CSS -> the sticky-column recipe's td:first-child { position: sticky } also matched the hidden spacer cells, and mutating a sticky cell during a scrollbar thumb drag made Chromium ratchet the scroller to the end (#17107)
  • fix(useVirtualScroll): reposition with an instant scrollTo() -> a scroller with CSS scroll-behavior: smooth animated the internal repositioning (#18168)
  • fix(QLayout): put hide-scrollbar on the root element -> since Chrome/Edge 121 honor scrollbar-width, and it only reaches the viewport scrollbar from the root element, the anti-flicker resize logic had quietly stopped working (#17122)
  • fix(QImg): recheck an SVG's natural size after layout settles on WebKit -> Safari derives an SVG's naturalWidth/naturalHeight from the img's current laid-out box, so when the load event lost the race with layout the wrong ratio got locked in and object-fit: cover cropped the image (#15652)
  • fix(QInput): keep the masked display while a debounced emission is pending -> with unmasked-value, the first re-render after a keystroke rewrote the field to the raw text and it never recovered; the same happened after an IME composition
  • fix(QEditor): ignore holes in toolbar group definitions -> a stray comma in the toolbar prop rendered fine but crashed on entering source view (#16940)
  • fix(QEditor): render toolbar tooltips on every platform -> they were gated behind a desktop user agent, leaving an iPad with a trackpad or mouse without tooltips and without the CTRL shortcut hints that matter exactly when a hardware keyboard is attached. Touch users additionally get the platform-standard press-and-hold tooltip
  • fix(QForm): report a child whose validate() returns nullish instead of breaking (#17921)
  • fix(QDialog): forward the ESC keyup event through hide() -> the hide/before-hide events carried no payload on ESC dismissal, so listeners could not tell a keyboard dismissal from a backdrop one
  • fix(QSelect): let hardware keyboards navigate options on mobile too -> arrow keys, Home/End, PageUp/PageDown and typeahead could never focus an option on a non-desktop UA, and each arrow press still scrolled back to option 0. aria-activedescendant was never set there either, breaking the combobox pattern's active-descendant navigation (#16599)
  • fix(QSelect): keyboard accessibility for the options dialog close button -> the button was outside WebKit's Tab loop, its focus indicator was hidden, and dismissing the dialog dropped focus on body instead of returning it to the control (#6858)
  • fix(QSelect): raise the iOS keyboard when the options dialog opens -> the dialog input ended up DOM-focused but keyboardless, because nothing was focused inside the tap's user activation window (#16196)
  • fix(QSelect): open the options dialog when typing on the closed control -> with behavior="dialog" and use-input, typing into the focused but closed control was a dead end: the text piled up while the filter early-returned (#15976)
  • fix(QSelect): prefetch options to map a lazy loaded model value -> with @filter options and map-options, a preloaded model value displayed raw until a user interaction loaded the options. QSelect now fires that one filter call itself (#17983)
  • fix(QSelect): emit the triggering event with @popup-show/@popup-hide -> both events always received undefined (#14678)
  • fix(QSelect): shield disabled options from Backspace removal -> a disabled option's chip showed no remove icon yet still died to Backspace (#15645)
  • fix(QSelect): reset the typed input when focus leaves through the popup content -> clicking non-focusable popup content (such as the no-option slot) left the typed filter text behind in the input (#16135)
  • fix(QSelect): keep the page scroll position when the options dialog opens -> a leftover from the pinned-body era scrolled the page to top on dialog open, and it stayed there after dismissal
  • fix(QSelect): drive the option hover highlight per pointer, not per UA -> hybrids the UA calls mobile (iPad with a trackpad, hovering pens) never got the highlight; a touch contact's own moves are still ignored so taps and scroll-drags cannot drag it around
  • fix(QPopupProxy): declare the QMenu/QDialog pass-through props in its API -> all 31 forwarded props were missing from the API JSON, so the generated TS types rejected documented usage (e.g. cover with transition-show) for JSX/TSX consumers and the docs API card listed none of them. separate-close-popup is the one documented exception: the menu branch overrides it (#18530)
  • fix(ui): arm ESC dismissal and focus containment on mobile platforms too -> see the note at the top (WCAG 1.4.13)
  • fix(ui): key focus and hover feedback on input capability instead of UA -> see the note at the top. Covers QCheckbox, QRadio, QToggle, QSlider, QTable row hover, QChip and the q-focusable/q-hoverable helpers
  • fix(ui): paint the manual focused state on mobile platforms -> the whole focus-helper visual sat under body.desktop, so the keyboard navigation QSelect just gained on mobile moved a focused state nothing painted (#16599)

Other

  • API JSON now declares every pass-through surface it had been hiding: QPopupEdit gains the 15 QMenu props that already forwarded at runtime (target, context-menu, dark, the hover trio, dismissal and focus flags, transitions) plus the chained escape-key event, and QVirtualScroll's entirely undocumented QList / QMarkupTable attrs forwarding is now declared. Both reach the generated TS types and the docs API cards
  • docs(api): the error prop reserves the bottom space even when false -> the false vs null distinction (reserve the error line vs no external validation at all) was undocumented and kept getting reported as a bug (#17662)
  • A large internal cleanup pass: browser compat code obsolete under the new Baseline floor was removed (vendor-prefixed Fullscreen and Page Visibility, the passive-support probe, the RTL scrollLeft bug probe, QResizeObserver's <object> fallback, IE-era normalize rules), the hover engine shared by QMenu/QFab/QSelect moved into one composable, and the API builder gained mixin/extends features that let components pull pass-through definitions straight from their source component instead of hand-copying them

Donations

Quasar Framework is an open-source MIT-licensed project made possible due to the generous contributions by sponsors and backers. If Quasar is useful in your workflow and you want to support ongoing maintenance, please consider the following: