@g9tm/lynxcontrols-progress-overlay
One shared overlay for long operations (MAUI G9MAUIControls.ProgressOverlay): leases, staged progress, cancel,
minimize to a draggable bubble, success and failure terminals with retry. Depends on @g9tm/lynxcontrols-foundation and
@g9tm/lynxcontrols only. Migration table: docs/migration/ProgressOverlay.md.
import { g9ProgressReport, useG9ProgressOverlay } from '@g9tm/lynxcontrols-progress-overlay';
const progress = useG9ProgressOverlay(); // or g9ProgressOverlay() outside components (active host)
const lease = await progress.show('Uploading samples', 'bottom');
try {
progress.report(0.048, 'Uploading', '12 of 250');
progress.report(g9ProgressReport.indeterminate('Connecting'));
progress.reportQueued(3); // "+3 waiting"
await upload();
await progress.tryShowCurrentSuccess('250 samples uploaded');
} catch (error) {
await progress.tryShowCurrentFailure('Server unreachable', 'Retry', () => upload());
} finally {
await lease.dispose(); // the last lease waits until the terminal state was seen
}
const off = progress.onCancelRequested(() => uploader.cancel());
// The host decides where an overlay shown without a position sits (MAUI 1.3.0), and moves a live one on a screen change.
progress.placementProvider = () => (mapTabShown ? { position: 'top', offset: mapTopBarHeight } : null);
onTabChanged(() => void progress.refreshPlacement());
Behaviour
- Leases. Concurrent operations each hold a lease on one visual; the oldest is the running one (its context text shows), the rest are queued. Releasing the running lease hands the view to the next; the overlay closes when the last lease is released after its terminal state was seen.
- Reports are coalesced to ≤ 10 Hz and drive only the Running state; indeterminate reports do not move the bar.
- Cancel (a 44 × 44 target) switches instantly to Canceling and broadcasts to
onCancelRequestedlisteners. A cancellation is never an error: when the run stops, the overlay closes and a neutral "Cancelled" toast shows; if the step cannot be interrupted, a notice appears after 5 s. - Terminals always force the full card. Success dwells 2.2 s (countdown bar). Failure waits for the user: Retry
resets to Running and runs the action once (a second tap joins it); Close acknowledges.
showStandaloneFailureshows a failure with nothing running. - Minimize. Tapping the running card collapses it to a 72 × 72 bubble (lynx-ui
Draggable); tapping the bubble restores it. - Placement. Bottom (default) or top, above the host's bottom inset (
bottomWithTabBar) or below its top inset, 14 px in; toasts lift above a bottom overlay. CallrefreshBottomInset()after the tab bar hides or returns (it glides 220 ms). - Host placement (MAUI 1.3.0). The right spot depends on the screen, not on who started the work: set the
service's
placementProvider(it receives the host and returns{ position, offset }, an edge plus extra distance from that edge's inset, ornullfor the defaultg9ProgressOverlayPlacement.default). An overlay shown without a position follows it: the provider is read when the overlay mounts, andrefreshPlacement()moves a live overlay when the screen under it changes (a tab switch, a full-screen sheet). An offset change on the same edge glides; an edge change fades out at the old edge and in at the new one (a glide would sweep the card over the whole screen);refreshPlacement(false)jumps. An overlay shown with a position keeps it for its life, and a minimized bubble is never moved. A throwing provider falls back to the default (with a console warning).
Bubble drag on desktop is a device check (ledger N-047); the placement motions are N-060.