Skip to content

fix(deps): update dependency quasar to v2.30.0 - #708

Merged
renovate[bot] merged 1 commit into
mainfrom
renovate/quasar-2.x
Sep 4, 2026
Merged

renovate[bot] merged 1 commit into
mainfrom
renovate/quasar-2.x

Conversation

@renovate

@renovate renovate Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
quasar (source) 2.22.0 → 2.30.0 age confidence

Release Notes

quasarframework/quasar (quasar)

v2.30.0

Compare Source

[!IMPORTANT]
Behavior changes worth checking before you upgrade:

  • QInfiniteScroll no longer watches the scroll position; it watches the end of its content. offset is now measured from the end of the content instead of the end of the scroll target (identical whenever the component is the last thing in its scroller), nothing loads once you scrolled past the component, debounce counts from the moment the end comes within the offset rather than from the last scroll event, the mount-time load lands one frame later instead of synchronously, and a scroll-target outside the component's ancestry no longer clips it.
  • v-scroll-fire fires as soon as any part of the element is visible, where it used to wait for the element's bottom edge to enter the container (pass the new :1 argument for the old timing; elements taller than the viewport never reach it). It also fires when the element becomes visible without a scroll, and the 25ms debounce is gone.
  • v-intersection dispatches every entry of a callback batch (previously only the first per callback, which could swallow a fast in-then-out), no longer passes the observer as an undocumented second argument, and ignores entries queued by an element's former observer.
  • Every QFab animates its Fab Actions individually rather than as a single block, cascading out of the trigger.
  • extractDate() returns an Invalid Date for a week mask that cannot resolve (week 0, week 54, week 53 of a 52-week year, or no year at all) where it used to return a date in 1900; masks containing dd now match instead of never matching.
  • v-touch-hold ignores a press while another one is tracked, so a two-finger hold fires the handler once instead of twice.
  • QTooltip's cursor-position and QMenu's touch-position/context-menu re-decide their placement from the intended side after a resize or an updatePosition(), instead of building on the correction of the first paint, and a point-mode offset now carries the sign of the direction the popup grows.
  • A disabled QEditor dims its toolbars and content instead of its whole root, so its border and background are no longer dimmed and its plain toolbar buttons now dim like its dropdowns already did.

New

  • feat(ui): useIntersection() composable -> observe any element's visibility from setup() with a plain object, a ref or a getter (target, root, rootMargin, threshold, once, disabled, onIntersect), getting back a reactive isIntersecting and a stop(). The observer pool behind it is now shared by the composable, v-intersection and QIntersection (rebuilt on top of the composable), so a page mixing the three with the same options runs a single IntersectionObserver
  • feat(QPullToRefresh): side prop, pull from any edge of the content to refresh -> the pull can start from the top (the default, unchanged), bottom, left or right of the content, as an inward pan from that edge while the scroll target sits at it. bottom is the messenger case, left/right serve horizontally scrolling content (#​7599)
  • feat(QTooltip): cursor-position prop, position the tooltip at the pointer -> on a large trigger a tooltip placed against the anchor's box ends up far from what the user is pointing at, which is why the native title attribute opens at the cursor. The pointer becomes the anchor point (anchor no longer applies, self and offset keep working), after waiting for the pointer to settle so a tooltip opening mid-sweep can't freeze at a point already left behind; a touch or stylus press opens at the contact point without waiting, and a keyboard focus or a bare model toggle keeps the regular anchor-relative placement (#​6019)
  • feat(QTabs): let the tab strip scroll natively along its axis -> the strip was overflow: hidden on desktop, so the only way to reach offscreen tabs was the arrow icons and a horizontal trackpad swipe or a wheel tilt did nothing. It now scrolls on its own axis on every platform, the cross axis staying hidden, the scrollbar staying invisible and the arrows still tracking the position. Vertical wheel over horizontal tabs is deliberately not remapped, so a tab bar in a page never captures the page scroll (#​7404)
  • feat(QFab): animate Fab Actions individually via the new stagger prop -> each action fades and scales on its own, cascading outwards from the trigger when opening and back towards it when closing, which is the Material speed dial behavior. stagger sets the milliseconds between consecutive actions (default 40; 0 animates them all at once); the delays are derived in CSS with sibling-index(), so no number of actions is special-cased and browsers predating those functions simply animate the actions together (#​6413)
  • feat(QPagination): ellipsis slot to replace the "..." buttons -> the ellipsis buttons were hard-wired to jump to the first hidden page on their side and there was no slot to attach anything else. The slot replaces the button entirely and hands over { side, page, btnProps, onClick } (plus to when to-fn is set); btnProps replicates the default button but carries neither the click handler nor the router target, so spreading it onto a QBtn hosting a QPopupEdit does not also trigger the jump. Without the slot the rendered output is unchanged (#​11341)
  • feat(QEditor): open toolbar dropdowns on hover -> the new dropdown-hover, dropdown-hover-delay and dropdown-hover-hide-delay props are forwarded to the QBtnDropdown of every toolbar dropdown. Click, tap and keyboard interactions keep toggling as before, and touch devices fall back to them
  • feat(date): extractDate() parses ISO week dates -> the week (w/wo/ww), ISO week year (GG/GGGG) and day of week (d/do/E/ddd/dddd) tokens were emitted by the parser but never mapped, so a GGGG-[W]ww-E mask resolved to Jan 1st 1900 instead of the day it described and twelve formatting tokens could not survive a formatDate() round trip. A mask carrying a week number now resolves as an ISO week date, an explicit calendar day still winning. See the note at the top (#​7732)
  • feat(ScrollFire): watch through the shared IntersectionObserver, new threshold argument -> the directive registered a debounced scroll listener per element on the scroll target and only ever checked at mount and on scroll, so an element that became visible through a layout change, a resize or an already scrolled container never fired. It now subscribes to the pooled observer shared with v-intersection, QIntersection and useIntersection, clipping through every scrollable ancestor up to the viewport, and takes the fraction of the element that must be visible as an argument (v-scroll-fire:0.5, default 0). See the note at the top
  • feat(TouchHold/TouchPan/TouchRepeat/TouchSwipe): the argument and every modifier follow runtime changes -> the four docs pages carried a "Note on HMR" since 2019 saying not all modifiers were reactive. Now every directive re-derives its listener options and settings whenever the modifiers object changes, swapping only the listeners whose options changed, re-parsing the argument when its string differs and always creating its context, so a directive that starts inert (no .mouse on a device without touch) can be armed later. The notes are gone from the four pages
  • feat(ui): the scroll-target prop accepts a Vue component reference, standing for its root element -> QInfiniteScroll, QScrollObserver, QVirtualScroll, QParallax, QPullToRefresh, QTree and QTable share the prop, and the resolver behind it has always unwrapped a component instance to its root element, but the prop type rejected it: in production it worked, in development Vue's prop validation refused it and building the warning threw ("Cannot convert object to primitive value"), aborting the update. A value without a root element now falls back to auto detection with a warning naming the cause
  • feat(ui/app-vite): the quasar.config spinner of Loading and Notify is typed by name -> framework.config.loading.spinner and framework.config.notify.spinner take a spinner's name ('QSpinnerGears'), which the CLI imports for you, but the typing dropped both keys altogether, so naming a spinner there was a type error. quasar now also exports the QuasarSpinners union of the spinner component names

Fixes

  • fix(ui): intersection observing works in cross-origin iframes (Codepen and the like) -> Chromium and WebKit withhold rootBounds from a cross-origin iframe for every entry of the viewport root, and a 2020 guard read that null as the element having been caught out of the DOM by a Vue transition, re-observing forever. So inside Codepen, jsFiddle and StackBlitz previews no v-intersection or QIntersection handler ever ran, since 2020 (the docs advised the root prop as a workaround and Codepen was disabled on the examples). Firefox was unaffected. The guard is dropped, the docs' workaround paragraph with it, and the Intersection examples get their Codepen link back
  • fix(QMenu): close on a press whose compatibility mousedown got suppressed -> the outside-click handler listened for mousedown and touchstart only, so a page cancelling pointerdown (Cesium does it on every mouse press with a registered action) suppressed the compatibility mousedown and the popup stayed open, in every engine. QSelect's dropdown got stuck along with it. A mouse or pen pointerdown now arms a 0ms fallback that the mousedown of a normal press cancels in the same task, so every existing path is unchanged (#​12575)
  • fix(QMenu/QTooltip): a growing popup grows away from its anchor -> the JS fallback engine expressed every placement as a pixel top/left, so a popup with a bottom or right self origin kept its top edge frozen: content growing after the decision extended it over its own anchor until the next pass re-derived the position and it jumped. The engine now writes its inset from the edge the self origin anchors, like the native one
  • fix(QMenu/QTooltip): keep a flipped popup capped so growth can't escape the viewport, without losing the bound it already has -> a flipped popup is now capped at its side's space whether or not it fits there, and that cap is the smaller of the space and the popup's own computed max size, which resolves the max-height/max-width props, the stylesheet ceilings ($menu-max-height, ...) and any app CSS at once. Before the amendment a flip towards a roomier side could widen the popup past those bounds (#​18536, #​18537)
  • fix(position-engine): correct point-mode self origins and offsets -> a popup positioned from a coordinate got two things wrong. An axis whose self origin is center/middle had no viewport correction at all, and now shifts back inside the viewport, frozen at decision time. The offset was applied with a fixed positive sign whatever the self origin, so a popup growing up or left opened onto the coordinate instead of clearing it; it now carries the sign of the direction the popup grows. Visible today for a QMenu opened at a coordinate. See the note at the top
  • fix(QEditor): honor disable/readonly in the toolbar, keep the fullscreen root opaque -> getBtn() overwrote the editor-wide disabled state with the per-button definition, so every plain toolbar button of a disabled or readonly editor stayed actionable (only the dropdowns behaved), which is how a disabled fullscreen editor's fullscreen button could be clicked. readonly keeps the read-only commands (fullscreen, print, viewsource), disable takes the whole toolbar out of play. The global .disabled rule also sat on the root, which in fullscreen is a viewport-sized fixed element, so the page composited straight through it. See the note at the top (#​15486)
  • fix(QKnob/QTabPanels/QStepper/QCarousel/QDrawer/QSplitter): don't remount the content when a gesture prop is toggled -> all of them baked a prop-derived condition into the vnode key, so flipping it destroyed and rebuilt the whole subtree: QKnob re-created its QCircularProgress and default slot on a disable/readonly toggle, QTabPanels/QStepper/QCarousel destroyed every panel (KeepAlive state included) on a swipeable toggle, QDrawer re-created its content on a no-swipe-close toggle and QSplitter its separator slot on disable. The directive now stays attached and is disarmed in place. QColor's spectrum, QSlider/QRange's track and QDrawer's backdrop got the same treatment internally (#​12668, #​18535)
  • fix(TouchPan): deliver the final payload when the value is unset mid-pan -> the mouse variant defers the final handler call by 50ms and read the handler at call time, so a value that turned undefined during an active pan threw and the isFinal payload never arrived: QSplitter kept its --active class and dropped the update:modelValue, QPullToRefresh stayed in its pulling state, QDrawer's close pan lost its end on a breakpoint crossing
  • fix(v-intersection/v-mutation): disable in place on a non-function value -> v-intersection accepted Boolean false only and threw on undefined, and v-mutation tolerated a non-function value only by accident, keeping the observer connected and rebuilding it on every handler change (an inline arrow function on each render). Both now disarm cleanly, v-mutation dropping its observer and only swapping the callback target on a handler change (#​18531)
  • fix(QFabAction): restore the fallback state when used outside a QFab -> the inject() fallback factory was never flagged as one, so the function itself landed in $fab and a QFabAction rendered outside a QFab crashed instead of showing itself as an enabled, standalone button (#​18538)
  • fix(Morph): an object value no longer resets the directive modifiers it does not name -> the modifier pass also ran on the object form, so v-morph.tween="{ name, group, model }" lost the tween. The directive modifiers are the base now and the object overrides only the keys it defines
  • fix(Ripple): a keyboard ripple within 300ms of re-enabling is no longer swallowed -> the throttle window opened on any key press, even while the element was disabled; it now only advances when a ripple actually fires
  • fix(utils/is): deepEqual() compares binary data by content -> ArrayBuffers, DataViews and typed arrays were compared through the generic object path, and the typed-array branch was duck-typed on a .buffer property, so a plain { buffer: ArrayBuffer } (and DataView) spun forever. Binary views are recognised through ArrayBuffer.isView(), which also covers views over a SharedArrayBuffer, and both sides' byteLength is compared before anything is allocated (#​18539)
  • fix(ui/api): a Vue reference in an event or method payload is a ComponentInstance, not a Component -> QFile, QForm, QPopupProxy, QScrollArea, QSelect, QTable, QTree, QUploader, QVirtualScroll and useVirtualScroll hand over mounted component instances that the generated typings declared with Vue's definition type. Component stays where a definition really is passed: the plugins' spinner options and Dialog's component

Other

  • perf(ui): a sweep over the directives and the scroll-driven components -> v-ripple, v-scroll, v-close-popup, v-intersection, v-morph and the four touch directives now share module-level listeners instead of allocating a set of closures per element, v-intersection shares one IntersectionObserver per distinct config (per-scroll main-thread time on 3000 observed elements 4.1ms -> 2.5ms) and v-scroll one listener per target (2000 elements on window: mount 8.9ms -> 2.0ms, unmount 8.3ms -> 0.8ms, one scroll event 1.74ms -> 0.08ms). QInfiniteScroll and QPullToRefresh watch the content through the intersection pool rather than a scroll listener and render their indicator in a sub-component, so a load or a pull no longer re-renders the slot content, and QPullToRefresh arms TouchPan only while the pulled edge is on screen, keeping JS out of the touch path of every other scroll gesture. Every template ref in the ui package is a shallowRef()
  • refactor(QMenu/QTooltip): share the positioning lifecycle through a single internal composable -> the two components each carried their own copy of both positioning engines and the show-time glue, which had drifted apart. Two side effects on QTooltip: the scroll offset of content that fits is restored after a boundary pass like QMenu's, and the JS fallback re-tracks the anchor after a useFullscreen() relocation (#​18513)

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:

v2.29.0

Compare Source

[!IMPORTANT]
One behavior change worth checking before you upgrade:

  • A @click listener on QItem or QChip now implies clickable. The clickable prop became tri-state (default null): when you do not set it, the presence of a click listener decides. An explicit value still wins, so :clickable="cond" and clickable="false" are untouched. Markup that carried a previously dead click listener now gains the hover styling, focusability, the derived button/menuitem role and starts firing its handler.

New

  • feat(QItem/QChip): a click listener implies clickable when the prop is not set -> both components only emitted click from the handler they bind while clickable, so a @click on an item or chip without that prop was dead code. See the note at the top (#​16731)
  • feat(Lang): getClosestIsoName() maps a locale to the nearest language pack -> language packs carry a region subtag only when a language ships more than one translation, so a browser locale like es-MX or sr-Cyrl-RS has no pack of its own and every app hand-maintained a locale-to-pack map. Lang.getClosestIsoName(locale, isoNames) (also on $q.lang) picks the closest of the packs your app ships: exact match, then the RFC 4647 lookup chain with the script outranking the region (sr-Cyrl-RS reads sr-Cyrl, zh-Hant-TW reads zh-TW), then the CLDR likely subtags (zh reads zh-CN, pt reads pt-BR, de-AT reads de-DE over de-CH). Malformed entries are skipped, so a lang/*.js glob's deprecated files do no harm, and getLocale() can be passed straight in. The docs' dynamic boot-file recipe now uses it (#​12174)

Fixes

  • fix(QMenu/QTooltip): keep the natural width when flipped away from the far viewport edge -> since v2.28.0 a popup whose anchor sits near the right viewport edge collapsed to its min-content width (one or two words per line) while flipping to the roomier side, because both engines measured it with the intended placement's inset already live and then froze that squeezed width as the cap. Firefox and Safari were affected too, contrary to the report. A popup that fits its mirrored side is also no longer capped at its own rounded-down width, which used to wrap the last word after a flip (#​18533)
  • fix(QMenu/QTooltip): keep the popup's scroll position when the placement is re-checked -> the boundary pass lifts the popup's max-height/max-width to measure its natural size, which makes it briefly non-scrollable and lets the browser clamp its scroll offset to 0, with nothing putting it back. QSelect re-checks the placement on every re-render, so a flipped and capped options list jumped back to the top on pointer move. A v2.28.0 regression, on both engines (#​18534)
  • fix(QMenu/QTooltip): flip towards the side with more room, and keep the requested side on a tie -> the boundary pass compared the requested placement's anchor line against the viewport middle, so a popup taller than the available space could flip to the SMALLER side, and a trigger centered on the screen always flipped away from the requested placement. Each of the axis' two placements is now measured from its own anchor line; both axes and both engines are covered (#​16443)
  • fix(QField): keep the field focused while a menu or dialog opened from inside it is showing -> a QMenu or QDialog rendered inside a field's control (the QDate/QTime/QColor in a QPopupProxy pattern, an autocomplete menu, ...) blurred the active element on open, so the field dropped its focused state, emitted blur and ran lazy validation (an empty date input went red just for opening the picker). QMenu and QDialog dispatch bubbling popup-show/popup-hide DOM events again, the way Quasar v1 did, and QField holds its focused state, styling and lazy validation until the popup hides. Hover-shown menus and tooltips take no focus and stay silent (#​9779)
  • fix(QDialog): keep the dialog inside the visual viewport while the iOS soft keyboard is open -> iOS never shrinks the layout viewport a position: fixed dialog is attached to, and it reveals the focused field only for a user-initiated focus, so a field focused programmatically (autofocus or a .focus() call) opened the keyboard with a bottom-anchored dialog left entirely underneath it. QDialog now tracks the visual viewport while shown on iOS and insets its inner element to it. Verified on the iPhone 17 Pro simulator (#​17594)
  • fix(QDialog): size dialogs by the dynamic viewport instead of guessing the mobile toolbar height -> a flat 108px was subtracted from the card's max-height on iOS and web Android to keep it inside the toolbar-visible viewport, wasting 60px whenever the toolbar was already collapsed and always in native iOS apps, which have no toolbar at all. The card sizes itself by 100dvh now and the platform override is gone
  • fix(QDialog): let top/bottom anchored dialogs reach the screen edge under safe-area padding -> in native mode the safe-area rule padded the dialog on both ends and beat the position modifier, so a bottom (or top) anchored dialog, QBottomSheet included, stopped one inset short of the edge with the backdrop showing through the gap; the same appeared in edge-to-edge Android apps. The inset moves onto the dialog's child, so its background runs under the home indicator while the content stays clear of it (#​17977)
  • fix(QTabs): keep the justify stretch when fractional tabs fill the container -> the tabs were summed through offsetWidth, which rounds each one up, so three justified tabs in a 440px container summed to 441, scrollable flipped on, the tabs collapsed to their natural width and nothing re-measured. It repeated at every third pixel of width, which is the on/off alternation of the report. Tabs are now measured with getBoundingClientRect() and an overflow of a pixel or less is never treated as scrollable (#​18532)
  • fix(QVirtualScroll): measure the items without rounding so fractional sizes don't shift the content -> a dense QTable row lays out at 28.5px but was measured through offsetHeight as 29, so the paddings standing in for the items outside the slice put the content half a pixel per item off on every re-slice and the rows visibly jittered while scrolling, worse the deeper into the list. Both directions measure through getBoundingClientRect() now (#​15754)
  • fix(QDrawer): measure a standard layout by the window width so the breakpoint ignores the page scrollbar -> QLayout's total width added the native scrollbar width unconditionally, which is right only while a page scrollbar is showing, so on Windows and Linux desktops the drawer switched to mobile mode at 1006px instead of 1023px and disagreed with $q.screen and the CSS breakpoints. A standard layout now reports $q.screen.width; containerized layouts are unchanged (#​15506)
  • fix(QDrawer): release the body scroll lock when a mobile-opened drawer turns desktop -> a drawer opened below the breakpoint and then pushed above it rendered as a standard desktop drawer while the page stayed locked (#​16651)
  • fix(QChip/QPullToRefresh/QIntersection): don't remount the content when disable is toggled -> all three baked !disable into the root vnode key, so flipping the prop destroyed and rebuilt the whole subtree instead of leaving the content as it was. Disabling now travels through the directive value. One related change: with QIntersection's once, an observer that has already fired is no longer re-armed by toggling disable off and on, which was a side effect of the remount and contradicted once (#​12668, #​18531)
  • fix(ui): stop WebKit from scrolling the panel wrapper when a field is focused mid-transition -> an autofocused QInput inside an animated QStepper/QTabPanels/QCarousel panel made WebKit reveal the caret and scroll the panel wrapper, so both panels slid displaced and snapped back when the transform ended. The wrapper uses overflow: clip, which is not a scroll container; layout is unchanged (#​17464)
  • fix(ui): throw an actionable error when the SSR server build is installed without an ssrContext -> Node (and Vitest) resolve quasar through the node export condition to the SSR build, so a test setup that does not go through @​quasar/vite-plugin installed it and crashed with "Cannot convert undefined or null to object". The error now explains what happened and how to fix it, and the Vite plugin docs gained a "Testing with Vitest" section (#​15469)

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:

v2.28.0

Compare Source

[!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:

v2.27.0

Compare Source

[!IMPORTANT]
Two behavior changes worth checking before you upgrade:

  • A click on the target no longer closes a hover-opened QMenu/QBtnDropdown while it is still animating into view. A single move-and-click gesture can no longer dismiss what it just opened; once the menu is fully shown, clicking toggles it closed exactly as before.
  • Three language packs now report a standards-compliant isoName: kur-CKB reports ckb, mm reports my and sr-CYR reports sr-Cyrl. The old import paths keep working as deprecated aliases for the whole of Quasar v2, but code comparing $q.lang.isoName against the old values needs updating.

New

  • feat(QFab): open on hover -> new hover, hover-delay and hover-hide-delay props, the same contract QMenu and QBtnDropdown got in v2.26. The pointer can cross the gap between the main button and the actions without closing them (150ms grace period by default), touch devices keep tap-toggling, keyboard interaction is untouched, and focus is never moved
  • feat(QTd/QTh): new col-name prop -> a QTd or QTh wrapped in your own component inside QTable's #body slot can finally say which column it belongs to. The cells used to resolve their column from the vnode key, which Vue never forwards through $attrs, so a wrapped cell rendered nothing. Existing key="..." usage keeps working; col-name takes precedence (#​17830)
  • feat(ui/lang): canonical names for three language packs -> import quasar/lang/ckb, quasar/lang/my and quasar/lang/sr-Cyrl for Central Kurdish, Burmese and Serbian Cyrillic. kur-CKB, mm and sr-CYR remain available as deprecated aliases

Fixes

  • fix(QMenu): ignore a click on the target while hover is animating the menu into view -> moving the pointer onto a hover menu's target and clicking it in one gesture closed the menu the hover had just opened. Also applies to QBtnDropdown (#​18524)
  • fix(QSelect): keep long selected values inside the parent -> a nowrap value made the field grow past its container instead of truncating (#​18015). Based on PR #​18525 by Timur Arbaev
  • fix(QTable): honor the key set on the grid item slot content -> the mapped rows reached Vue as keyless Fragments, so removing a row recreated every card after it instead of moving the surviving instances (#​18045)
  • fix(QScrollArea): put the vertical thumb on the left side in RTL -> the bar moved to the left edge while the thumb stayed glued to the right one, because its inline right inset could not be flipped by the RTL stylesheet (#​17943)
  • fix(ui): make overlay content clickable over .q-electron-drag regions in Electron -> menus, dialogs, notifications and tooltips overlapping a draggable title bar lost the overlapping area to window dragging. An explicit drag class on the content itself still wins, and dialogs keep letting you drag the window (#​17285)
  • fix(use-mask): don't read a fill char that satisfies its own token as data -> with fill-mask="0" and reverse filling, the unmasked value grew by one fill char per keystroke ("0.01" became "00.01", "000.10", ...), which is the docs' own "Price with 2 decimals" example (#​18523)
  • fix(use-mask): keep the fill padding out of the unmasked value -> with unmasked-value and forward filling, a typed "1" over ###-## with fill-mask="0" was reported as "10000"; with mask AA-AA and fill-mask="A", "X" became "XAAA" (#​18523)
  • fix(use-mask): follow the data, not the render, when placing the caret -> when the fill char is itself a valid data char, "06" arrived as "60" and, on masks with a leading literal, "09" arrived as "90" (#​18523)
  • fix(use-mask): unmask masks that mix token types -> under reverse-fill-mask, every mask mixing letters and digits silently rendered nothing for ordinary values ("ab12" over AA-##), including values the control had just rendered itself (#​18523)
  • fix(use-mask): drop a right-aligned separator nothing filled -> "057" over AA-## rendered "-57", which unmasked back to "57", so what the field displayed was not a value it could read again (#​18523)
  • fix(ui/lang): repair strings across 51 language packs -> stray quotes and characters, leading/trailing/doubled spaces, broken interpolation, Cyrillic text inside the Latin sr pack, Simplified characters inside zh-TW, untranslated English left in translated packs, and year-range plural/agreement errors in some 40 languages (#​18522). Ukrainian was corrected first in PR #​18522 by Ihor Diachenko

Other

  • docs(popup offset): the offset prop of the position engine does not translate the popup, it inflates the anchor box, so middle/center origins are offset-invariant by construction. That is why QTooltip's horizontal offset reads as broken on its default anchor="bottom middle". Documented on the API of QMenu, QTooltip, QBtnDropdown, QPopupEdit and QSelect, with the direction each offset component expands in, plus the note that QMenu's cover drops the offset entirely (#​18212)
  • The language-pack build validator now checks string values, not only pack shape: validateStringArray was declared with four parameters and called with three, so its loop never ran. Every string, array entry and formatter result is now checked for padding, doubled spaces and control characters, and formatters are probed across the counts that select their plural branches

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:

v2.26.0

Compare Source

[!IMPORTANT]
A few behavior changes worth checking before you upgrade:

  • QEditor no longer emits a placeholder attribute (it was not valid on a div). Custom CSS targeting .q-editor__content[placeholder] must switch to [aria-placeholder].
  • QSelect renders its fall-through attributes only on the focus target, no longer also on the .q-field__native wrapper. Selectors or tests that matched the wrapper copy (or relied on the attribute matching two elements) need updating.
  • The touch/hover behavior of QTooltip, context menus, the touch directives and the slider family is now decided per interaction instead of by the mobile UA. Hybrid devices change the most: touchscreen laptops and tablets with a mouse gain hover tooltips and right-click context menus, mobile UAs gain keyboard support on QSlider/QRange/QKnob, and QRange stops emitting UA-dependent SSR markup.
  • QDialog dismisses its backdrop on mousedown instead of click, so a press on the backdrop with a menu open closes only the menu on desktop too, matching what touch already did.
  • QSlider/QRange/QKnob de-duplicate their emissions: change fires only when an interaction actually moved the value, and a parent that does not sync the model prop back hears each value once per interaction rather than once per press/release/click.
  • Firefox now matches QField's autofill styling: the stylesheet moved from :-webkit-autofill to the standard :autofill, so the q-autofill keyframe and $input-autofill-color apply there for the first time.

New

  • feat(QMenu): open on hover -> new hover, hover-delay and hover-hide-delay props, also forwarded by QBtnDropdown (in split mode hovering either button opens it). A hover-open never steals focus from where you are typing, ESC still dismisses, submenus keep the chain open while the pointer travels between them, and touch devices keep tap-toggling. No effect when context-menu is set (#​6776)
  • feat(QRange): new min-range and max-range props -> constrain the width of the selection. Out-of-limits model values are coerced like inner-min/inner-max already do, dragging and the keyboard clamp each thumb to what the other thumb allows, and a minimum width blocks thumb crossover (#​18182, #​2100). Based on PR #​18182 by Teodor Atroshenko (thexeos)
  • feat(QTable): new footer slot -> renders a real <tfoot> element, so a totals/summary row can be made sticky through CSS. Works in default and virtual-scroll mo

✂ Note

PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot enabled auto-merge (squash) September 4, 2026 18:00
@renovate
renovate Bot merged commit aa51204 into main Sep 4, 2026
0 of 3 checks passed
@renovate
renovate Bot deleted the renovate/quasar-2.x branch September 4, 2026 22:00
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.

0 participants