* feat: add ToolTip.ShouldUseOverlayLayer attached property
Add ShouldUseOverlayLayer attached property to ToolTip, which is bound
to Popup.ShouldUseOverlayLayer when the tooltip popup is created. This
allows per-control control over whether the tooltip popup uses the
overlay layer.
* docs: add proper XML docs for GetShouldUseOverlayLayer/SetShouldUseOverlayLayer
Replace <inheritdoc/> pointing to Popup.ShouldUseOverlayLayer (a property)
with proper <summary>, <param>, and <remarks> documentation for the
getter and setter methods.
Previously {Binding ^} was not parsed because ParseStart did not
recognize the ^ stream operator. Also, {Binding .^} failed because
ParseDot returned State.End immediately, preventing subsequent
operators.
Changes:
- ParseStart: recognize ^, emit EmptyExpressionNode + StreamNode
- ParseDot: return State.AfterMember instead of State.End, so .^ works
- Add grammar tests for ^ and .^ patterns
* Add failing test: LineBreakEnumerator infinite loop on empty string
* Return false immediately for empty string in LineBreakEnumerator
Empty text has no content and should not produce any line break
opportunities. Short-circuit in MoveNext before entering the
read/rule loop, while keeping the PeekAt EndOfText fix as a
defensive safeguard.
* test: cover platform input detachment on close
Closed top levels retain the presentation source as the platform input callback. A late platform input event can therefore target an already disposed visual tree.\n\nThe regression test requires top-level closure to clear ITopLevelImpl.Input.
* fix: detach platform input when presentation source closes
Win32 can deliver WM_CAPTURECHANGED while destroying a popup that owns mouse capture. The platform then calls the input handler retained by the disposed presentation source, which logs a warning and can route input into a detached visual tree.\n\nClear the platform input callback at the start of presentation source disposal, alongside the existing scaling event unsubscription.
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* test(headless): reproduce cleanup exception dispatch hang
A dispatcher job that throws during work-item cleanup leaves the dispatch task incomplete and prevents later work from running. The session contract requires every queued dispatch to reach a terminal state.
Add behavioral coverage that posts a throwing cleanup job, expects its exception from Dispatch, and verifies the assembly session can process the next dispatch. The shared NUnit test runs under both PerTest and PerAssembly projects.
* fix(headless): report dispatch cleanup exceptions
An exception during application cleanup escapes the work item before its completion source is settled, faults the private consumer task, and leaves current and future Dispatch calls waiting forever.
Capture cleanup failures as the work item's exception so the consumer can continue. Restore synchronization context, locator scope, and dispatcher state in finally blocks before processing later work.
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* Fix off-thread accessibility hit-test crash on macOS
GetAutomationPeer() returns null when called off the UI thread, but
-[AvnWindow automationPeer] called SetNode on it without checking, which
segfaults. Guard against the null peer so it returns nil instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Removed comment
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Added failing test for #21746.
* fix(binding): reapply cached source values during UpdateTarget
UpdateTarget did not restore an uncommitted explicit two-way target value when the source still matched the binding expression's cached value. Equal-value suppression then prevented ValueStore from receiving the refresh.\n\nTrack the synchronous UpdateTarget rebuild and force publication only for that path, so the source value is reapplied without changing normal binding notification deduplication. The preceding regression test covers the observable behavior in both reflection and compiled binding paths.
---------
Co-authored-by: grokys <grokys@users.noreply.github.com>
- Added a check for 'target.IsEffectivelyVisible' inside 'HotkeyCommandWrapper.CanExecute'.
- Prevents HotKey from globally triggering and swallowing keyboard input when the host control or its parent panels are invisible (IsVisible="False").
- Added a corresponding unit test to verify input routing for hidden clickable controls.
Closes#21708
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* fix(ScrollBar): use large scroll events for ScrollToHome and ScrollToEnd
* Fix failing test
---------
Co-authored-by: Tim <47110241+timunie@users.noreply.github.com>
#21740 - SplitView must not steal BackRequested when its pane is closed
When the SplitView is in Overlay or CompactOverlay mode it handled the
TopLevel.BackRequested event even if its pane was not open, which broke
system back navigation. Now it only closes the pane and marks the event
handled when the pane is actually open; otherwise the event bubbles on.
Adds unit tests covering both overlay modes.
@
Co-authored-by: alexander.marek <alexander.marek@opti-q.com>
* Add AutomationPeer.ToScreen so each peer converts its own bounding rectangle to screen coordinates, fixing empty UIA bounds for root providers that are not ControlAutomationPeers.
* Update AutomationPeer.cs
* Update AutomationPeer.cs
* Only include XAML/AXAML AvaloniaResource in the AdditionalFiles
* Expose AvaloniaResourcePaths for the source generators and Roslyn
* Typo
* Update Generators.Sandbox, include some test assets
* Fix working area changed on windows platform won't trigger screens changed
* Fix ScreensPage on IntegrationTestApp won't remove Screens.Changed listener
* Add SystemParametersInfo.SPI_SETWORKAREA to Win32 UnmanagedMethods class
* Move notify screen working area changed to Win32Platform.WndProc instead of WindowImpl.AppWndProc
* Notify working area changed to screens without check PlatformSettings is Win32PlatformSettings
* Fix SelectableTextBlock selection offset with InlineUIContainer (#15880)
EmbeddedControlRun inherits TextRun.DefaultTextSourceLength = 1, so each
InlineUIContainer occupies one position in TextLayout. However,
InlineUIContainer.AppendText was a no-op, causing Inlines.Text to have
zero characters for each embedded control. This made TextLayout.HitTestPoint
return positions that were ahead of Inlines.Text indices by one per
InlineUIContainer, breaking SelectedText for any text after an embedded
control.
Fix: append the Unicode Object Replacement Character (U+FFFC) — the
standard placeholder for embedded objects — in AppendText so that
Inlines.Text character offsets stay in sync with the TextLayout positions.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Expand InlineUIContainer selection tests with fencepost cases
Replace single assertion with a [Theory] covering all boundary positions
around the embedded control in 'foo\uFFFCbar':
- entirely before the container
- exactly the container character
- up to and including the container
- starting exactly after the container
- container through end
- one character either side of the container
- entire content
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix inter-word justification distribution and safety
TextAlignment.Justify was incorrect or a no-op in several common cases.
This corrects glyph targeting, buffer safety, run partitioning, the fill
target, and which lines get justified.
- Target the glyph ending each break boundary; step zero-width breaks
(CJK/Korean ideograph boundaries, hyphens) back one so the widened gap
sits on the break and the last visible glyph is never stretched.
- Copy-on-write the shaped buffer before adjusting advances and swap in a
fresh run, so justification never mutates glyph storage shared with a
TextRunCache entry, a Split sibling or a WithBidiLevel alias. Adds
ShapedBuffer.CloneWritable and TextLineImpl.ReplaceTextRun, which also
repoints the bidi-reordered runs used by draw and hit-test.
- Consume break opportunities per run instead of draining the whole queue
against the first run.
- Fill the visible Width to the paragraph width rather than the width
including trailing whitespace, so wrapped lines with trailing whitespace
justify instead of being left un-justified.
- Target MaxWidth for wrapped text rather than the widest produced line.
- Skip the last line, newline-terminated lines and hard-break lines.
Adds justification test coverage: CJK, mixed Latin+CJK, Arabic buffer
integrity, Latin wrapping, trailing whitespace and multi-run lines.
* Add failing test for trailing-whitespace overcount across runs
- TextLineImpl.CreateLineMetrics walks a line's runs backward to
compute trailing whitespace, stopping only when a run has none of
its own - not when a run is only partly whitespace.
- On a multi-run line where an interior run also ends in a space
(common at a script boundary, since each script run tends to end at
the following space), that space gets wrongly counted as trailing
too, even though visible content follows it later in the line.
- This shrinks Width, which InterWordJustification uses to decide how
much space a line still needs to reach the target width.
* Fix trailing-whitespace overcount across runs
- Only continue accumulating trailing whitespace from an earlier run
when the current run is entirely whitespace; otherwise the current
run's own visible content is the line's true end, and an earlier
run's trailing space is interior, not trailing.
- Width now correctly includes interior, non-trailing whitespace, so
wrapped multi-run lines (e.g. mixed-script text) no longer get
stretched past the target width when justified.
* Ignore CLAUDE.md
* Generate a CycloneDX SBOM per published NuGet package
GitHub's dependency-graph SBOM export reports unresolved version ranges
for most packages and can't see which source projects get merged into
which final package by Numerge. Add a CreateSbom Nuke target that runs
CycloneDX against each project's already-restored assets, reassembles
the Numerge merge groups from numerge.json, and publishes one accurate
SBOM per shipped package as a pipeline artifact.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Fix SBOM dependency graph merge and cover bundled npm packages
The per-package SBOM merge only kept the dependency-graph (dependsOn)
edges from the first-scanned constituent project, so the flagship
Avalonia package listed 38 components but wired only 3 into its graph.
Union dependsOn edges by ref (each constituent's -sn/-sv override makes
its root collapse onto the shared final-package ref) so all edges are
retained - the Avalonia root now reports 17 direct dependencies.
cyclonedx-dotnet only walks the NuGet graph, so JS that ships inside a
package via a Bun/npm-built webapp was invisible: Avalonia.Browser's
staticwebassets and Avalonia.DesignerSupport's embedded previewer (which
merges into Avalonia). Scan each constituent's webapp/package.json
production dependencies, resolving versions from the installed
node_modules (git-pinned deps become pkg:github purls), so those shipped
components appear in the SBOM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Enrich npm SBOM components to match NuGet component fidelity
Previously the bundled-webapp npm packages were added as bare inventory
entries: no bom-ref, no license, no hashes, direct-only, and orphaned
from the dependency graph - second-class next to the fully-formed NuGet
components cyclonedx-dotnet emits.
Walk the installed node_modules transitively so npm depth matches the
NuGet side (e.g. react-dom now pulls in scheduler, loose-envify,
object-assign; loose-envify pulls in js-tokens), assign each component a
bom-ref, resolve its license from the installed package.json (SPDX id or
expression, with legacy license/licenses object fallback), and wire the
whole tree into the CycloneDX dependency graph - the shipped package now
declares its direct npm deps and every transitive edge below them.
Hashes are intentionally omitted for npm: the verifiable integrity
hashes live in bun's binary lockfile, not the installed tree, and a hash
of the unpacked directory couldn't be checked against a registry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Enrich SBOM root component and verify package contents
Two gaps remained after the npm work. First, cyclonedx-dotnet emits only
type/name/version for the root metadata.component - no manufacturer,
license, or provenance, which a CRA-facing SBOM is expected to carry.
Read the shipped .nuspec and fill in purl, publisher/supplier (also set
as the document-level manufacturer), SPDX license, description,
copyright, and website/vcs external references.
Second, nothing checked what the package actually ships against the
components derived from the dependency graph. Scan the final .nupkg's
binaries: Avalonia's own Numerge-merged modules (e.g. Avalonia.Base,
Avalonia.Controls folded into Avalonia; Avalonia.Win32.Automation into
Avalonia.Win32) are recorded as manufacturer-supplied components with a
SHA-512 of the shipped bytes, and any third-party binary that no
restored dependency accounts for is added and flagged - a regression
guard so bundled binaries can't silently escape the SBOM. Reference
assemblies under ref/ and the package's own primary assembly are skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Exclude type-only @types/* packages from the SBOM
The DesignerSupport previewer's package.json lists @types/react and
@types/react-dom under "dependencies" rather than "devDependencies", so
they were being scanned in. TypeScript declaration packages are stripped
by esbuild and never appear in the shipped bytes, so they fall outside
the SBOM's scope of delivery. Skip @types/* at both the direct and
transitive level (which also drops csstype, only reachable via
@types/react), leaving the SBOM listing exactly the npm packages that
are actually bundled and delivered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add SBOM coverage rules to Copilot review instructions
Instruct code review to flag two cases that would otherwise let a
package ship without accurate SBOM coverage (EU CRA): a newly published
NuGet package not covered by CreateSbom, and a change to what
dependencies are delivered with a component - notably adding npm/JS to a
previously .NET-only component, bundling third-party binaries directly,
or new Numerge merge groups - without a corresponding update to
nukebuild/SbomGenerator.cs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Link package-content binaries into the dependency graph
The binary:* components from the .nupkg content scan were added to
components but had no dependency node or root edge, leaving them
unreachable for SBOM consumers that traverse the graph from the root -
so the shipped merged modules (and any flagged third-party binary)
didn't appear in the delivered dependency tree. Give each its own leaf
node and a dependsOn edge from the root component.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Prune dangling dependency edges from generated SBOMs
cyclonedx-dotnet leaves dependsOn edges pointing at packages it excluded
as dev dependencies (e.g. analyzers stripped by -ed), so after exclusion
the graph references components that aren't in the SBOM. Drop any
dependsOn target that doesn't resolve to a present component, keeping the
dependency graph internally consistent - consistent with treating those
build-only dev dependencies as out of scope of delivery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Reference upstream issue for dangling-edge prune
Cite CycloneDX/cyclonedx-dotnet#761 in the PruneDanglingDependencyEdges
comment so a future reader knows the dangling dependsOn edges are an
upstream bug (still reproducing in 6.2.0), not something introduced here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Resolve CycloneDX via NuGetPackage instead of a global tool install
`dotnet tool update --global CycloneDX` mutated the developer's (and CI
agent's) global tool set as a side effect of running the build. Follow the
same pattern as the other build tools (ApiCompat, ApiDiff, ilrepack): pin
the package via PackageDownload in _build.csproj and resolve it through a
[NuGetPackage] Tool, passed into SbomGenerator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Reuse existing nuspec/numerge parsing helpers in the SBOM generator
Drop the hand-rolled Numerge* POCOs in favour of Numerge.MergeConfiguration.
LoadFile (already used by CreateNugetPackages), and have BuildToNuGetCache
call SbomGenerator.ReadPackageId instead of duplicating the nuspec-id
extraction inline, so the two can't drift.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Resolve each package's nupkg from a prebuilt id->path map
GenerateForPackage's fallback re-globbed and re-parsed every nupkg in the
output directory once per final package whenever the versioned-filename glob
missed - O(n^2) archive reads. Build the id->path map once in Generate
(reading each nupkg's id a single time) and look each package up in it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Degrade unrecognised git/URL npm specifiers instead of throwing
ResolveNpmComponent routed any git/URL dependency range to a GitHub-only
parser that threw NotSupportedException on anything else (GitLab/Bitbucket,
a raw tarball URL, ...). Because CreateSbom is a hard release dependency,
one such spec would fail the whole build. Fall back to a generic component
(preferring the installed on-disk version) with a warning instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Guard against an npm package.json with no version field
A package.json without a "version" (valid for private packages) made the
null-forgiving ["version"]! throw an NRE, aborting the build. Fall back to
the declared range with a warning instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Don't emit a file-license path as an SPDX id
A nuspec <license type="file"> (from PackageLicenseFile) stores a file path,
not an SPDX id. It was being fed to SpdxToLicenses and emitted as
{license:{id:"licenses/LICENSE"}}, an invalid SPDX id that can fail license
validation. Map type="file" to a license name instead. (Avalonia's own
packages all use PackageLicenseExpression=MIT, so this is a latent-correctness
fix rather than a live bug.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Generate and publish SBOMs from the macOS CI job too
CreateSbom was only wired into CiAzureWindows, so the macOS job's NuGetOSX
artifact - which carries the real macOS-native binaries the Windows build
can't produce - shipped without matching SBOMs. Wire CreateSbom into
CiAzureOSX and publish its output as a SBOMOSX artifact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Embed each package's CycloneDX SBOM inside its .nupkg
The SBOM previously existed only as a detached build artifact. Also embed
it at _manifest/cyclonedx/bom.cdx.json inside the shipped .nupkg so it
travels with the package, mirroring the _manifest/ layout Microsoft.Sbom.
Targets uses for SPDX. A .nupkg is an OPC package, so [Content_Types].xml
gains a json default or strict OPC readers (including NuGet signature
verification) would reject the modified package. Skips already-signed
packages to avoid invalidating their signature.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address Copilot review: SBOM zip ordering, nuspec read, component key
- Order ZipFiles after CreateSbom so zipped NuGet artifacts include the
embedded SBOM.
- Read the .nuspec entry with an ordinal EndsWith and dispose its stream.
- Key components by purl/bom-ref, falling back to name+version so distinct
versions sharing a name aren't de-duplicated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Model bundled assemblies as a composition, not dependencies
The numerge-folded first-party assemblies (Avalonia.Base, Avalonia.Controls,
etc.) are constituents of the package, not dependencies of it. Represent them
as a CycloneDX "assembly" via a top-level compositions entry with
aggregate=complete instead of drawing dependsOn edges from the root - contains
and depends-on are distinct relationships. They stay flat top-level components
so their per-assembly hashes remain visible to scanners that ignore nested
components, and the dependency graph is left holding only genuine dependencies.
Also carry the package's licence onto these first-party components, which
previously came out blank (cyclonedx-dotnet only sets it on the root).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Fix: TalkBack should highlight controls instead of their ancestors
* Fix: make parent controls navigable for specific types
---------
Co-authored-by: Julien Lebosquain <julien@lebosquain.net>
* Add tests around skipping controls for FocusManager #21620
* Return false by default in CanHaveFocusableChildren #21620
Importantly, this will return false for controls that are not focusable (e.g. TextBlock) or that have no focusable childen (e.g. an empty StackPanel)
* Add skip tests for FindFirstFocusableElement and FindLastFocusableElement #21620
* win32: let GraphicsAdapterSelectionCallback see unfiltered adapters on ARM64
The ARM64 Adreno blocklist in AngleWin32EglDisplay removed every Adreno
adapter from the list before it was handed to GraphicsAdapterSelectionCallback,
so an application had no way to opt back into hardware acceleration and no
indication that WARP (the Microsoft Basic Render Driver) was being forced.
Stop mutating the adapter list. The blocklist now only moves the *default*
selection to the first non-Adreno adapter when no callback is supplied, and
logs a warning about the CPU-usage impact and the available workarounds. When
a callback is present it receives the full, unfiltered adapter list and its
choice is used as-is.
As a side effect this also removes a latent IndexOutOfRange: the old code
could empty the list when every enumerated adapter was Adreno.
Refs #21689
* Update src/Windows/Avalonia.Win32/OpenGl/Angle/AngleWin32EglDisplay.cs
Co-authored-by: Max Katz <maxkatz6@outlook.com>
---------
Co-authored-by: nickna <nick@nassiri.xyz>
Co-authored-by: Max Katz <maxkatz6@outlook.com>