65 lines
2.5 KiB
Plaintext
65 lines
2.5 KiB
Plaintext
|
|
---
|
||
|
|
title: Lifecycle and route interception
|
||
|
|
description: Understand results, queues, Host lifetime, and router adapters.
|
||
|
|
order: 20
|
||
|
|
toc:
|
||
|
|
- id: result-contracts
|
||
|
|
title: Result contracts
|
||
|
|
- id: queue
|
||
|
|
title: Queues and Hosts
|
||
|
|
- id: dismissal
|
||
|
|
title: Dismissal
|
||
|
|
- id: navigation
|
||
|
|
title: Route interception
|
||
|
|
---
|
||
|
|
|
||
|
|
## Result contracts {#result-contracts}
|
||
|
|
|
||
|
|
`openConfirm` uses `false` for cancellation, `openPrompt` uses `null`, and custom Modals, Sheets, and Drawers use `undefined` when no result is supplied. `close(value)` is checked against the generic passed to `openModal<Result>()`.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { openModal } from "@workspace/overlays/modal"
|
||
|
|
|
||
|
|
const productId = await openModal<string>({
|
||
|
|
title: "Choose a product",
|
||
|
|
content: ({ close }) => (
|
||
|
|
<ProductPicker onSelect={(product) => close(product.id)} />
|
||
|
|
),
|
||
|
|
})
|
||
|
|
```
|
||
|
|
|
||
|
|
## Queues and Hosts {#queue}
|
||
|
|
|
||
|
|
Within one Scope, each feature has a lazily created channel and Base UI handle. Calls of the same kind enter a FIFO queue and the next request appears after the previous closing animation completes. Different features can remain visible together, so a Modal may open a Confirm.
|
||
|
|
|
||
|
|
Unmounting a Host settles its pending requests with their cancellation values. React Strict Mode's simulated unmount does not incorrectly cancel requests.
|
||
|
|
|
||
|
|
## Dismissal {#dismissal}
|
||
|
|
|
||
|
|
Custom content can call `dismiss()`. The close button, Escape key, and backdrop press enter the same cancellation path. Setting `dismissible: false` blocks external dismissal, while the content may still explicitly call `close(result)` or `dismiss()`.
|
||
|
|
|
||
|
|
## Route interception {#navigation}
|
||
|
|
|
||
|
|
`@workspace/overlays/navigation` depends only on Scope, not on a concrete overlay feature or router:
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import { OverlayNavigationGuard } from "@workspace/overlays/navigation"
|
||
|
|
|
||
|
|
const adapter: OverlayNavigationAdapter = {
|
||
|
|
subscribe(listener) {
|
||
|
|
return router.block((transition) => {
|
||
|
|
listener({
|
||
|
|
cancel: transition.reset,
|
||
|
|
proceed: transition.retry,
|
||
|
|
})
|
||
|
|
})
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
<OverlayNavigationGuard adapter={adapter} />
|
||
|
|
```
|
||
|
|
|
||
|
|
The default Guard examines the current and named local Scopes and finds the last-opened layer. It dismisses an allowed layer and cancels navigation. A layer with `dismissible: false` remains open and keeps blocking. It calls `proceed()` only when no layer is open.
|
||
|
|
|
||
|
|
The adapter's `proceed()` must continue the exact blocked attempt instead of starting a new navigation that triggers the blocker again. Pass a custom `interceptor` to replace the policy.
|