# AtomUI 6.0.8 API Change Examples
This document supplements the main `6.0.8` release notes with the most important API changes and migration examples from `6.0.7`.
For the authoritative release entries, see the repository root `CHANGELOG.md` and `CHANGELOG.zh-CN.md`.
Chinese version: [6.0.8 API 变更示例](6.0.8-api-changes.zh-CN.md).
## ImagePreviewer Image Source Migration
`6.0.8` unifies ImagePreviewer image sources around `IImagePreviewSource`:
- `SourceUri` -> `Source`
- `SourceUris` -> `Sources`
- `FallbackSourceUri` -> `FallbackSource`
- Use `UriImagePreviewSource` for URI, local file, and Avalonia resource images.
- Use `StreamImagePreviewSource` for database, authenticated network, encrypted file, memory, and other lazy stream sources.
### 6.0.7
```xml
```
### 6.0.8
```csharp
using AtomUI.Desktop.Controls;
public IImagePreviewSource PreviewSource { get; } =
new UriImagePreviewSource("https://example.com/photo.jpg");
public IImagePreviewSource PreviewFallbackSource { get; } =
new UriImagePreviewSource("avares://Demo/Assets/fallback.png");
```
```xml
```
### Multiple Images, Cover, and Preloading
`CoverIndex` only controls which image is shown as the closed-state cover.
It does not change `CurrentIndex`.
```csharp
public IList PreviewImages { get; } =
[
new UriImagePreviewSource("https://example.com/photo-1.jpg"),
new UriImagePreviewSource("https://example.com/photo-2.jpg"),
new UriImagePreviewSource("https://example.com/photo-3.jpg")
];
public int CurrentPreviewIndex { get; set; }
```
```xml
```
### Lazy Stream Sources
`OpenReadAsync` should return a new readable stream each time it is called.
ImagePreviewer owns and disposes the returned stream.
```csharp
public IImagePreviewSource SecurePreviewSource { get; } =
new StreamImagePreviewSource(
openReadAsync: cancellationToken =>
{
Stream stream = File.OpenRead("/secure/photos/invoice.png");
return new ValueTask(stream);
},
displayName: "invoice.png",
contentType: "image/png",
identity: "/secure/photos/invoice.png");
```
## Upload Composable Trigger Migration
In `6.0.8`, the root Upload control owns file state, the queue, Form values, and lifecycle.
File selection, directory selection, and drop input are handled by composable controls.
### 6.0.7
```xml
```
### 6.0.8
```csharp
using AtomUI.Desktop.Controls;
using System.Collections.ObjectModel;
public ObservableCollection Attachments { get; } = [];
public IFileUploadTransport UploadTransport { get; } = new MyUploadTransport();
```
```xml
```
### Drag-and-Drop Upload
```xml
```
### Form Value Projection
`FileValueMode` controls the value Upload writes into Form:
```xml
```
## New OtpLineEdit Control
`OtpLineEdit` uses one `Text` value for the complete verification code.
Cells, separators, and masking only affect visuals.
```xml
```
```xml
```
```csharp
public string? Code { get; set; }
```
## Form and DataValidationErrors
In `6.0.8`, errors produced by Form validators are written to Avalonia `DataValidationErrors`.
If a ViewModel validator, binding validator, or application validation layer already provides a native validation error, AtomUI controls display that native error with highest priority.
The default trigger is `OnChanged`.
Configure `OnBlur` explicitly when validation should run after focus leaves the field:
```xml
```
## Default Two-Way Bindings
Several selection and value properties now use `BindingMode.TwoWay` by default.
Common scenarios no longer need explicit `Mode=TwoWay`:
```xml
```
If application logic needs a read-only projection, set `Mode=OneWay` explicitly on the binding.
## DataGrid Filter Model
Column filters now live directly on column APIs, and both the filter item source and selected values are bindable:
```xml
```
Tree filters use `FilterChildrenMemberPath`:
```xml
```
## Select, ComboBox, and Cascader Overflow Tips
Enable overflow tooltips when selected content cannot be fully displayed:
```xml
```
## TabControl and TabStrip Reordering
```xml
Content 1
Content 2
Content 3
```
`TabActivationTrigger="PointerReleased"` is the default and fits tab reordering scenarios.
Use `PointerPressed` when tabs should activate as soon as the pointer is pressed.
## Dialog Async Close Validation
```csharp
await Dialog.ShowDialogAsync(
new TextBlock { Text = "Close without saving?" },
options: new DialogOptions
{
Title = "Unsaved changes",
BeforeCloseAsync = async context =>
{
if (context.Reason == DialogCloseReason.Rejected)
{
return true;
}
return await ConfirmSaveAsync();
}
});
```