DropletShelfService
Opens and closes Droppy's shelf, and reveals this droplet's widgets in it.
@MainActor protocol DropletShelfService : AnyObject
Requires the shelf-read capability to observe and shelf-write to act.
Properties
didChange
@MainActor var didChange: AnyPublisher<Void, Never> { get }
Fires when the shelf opens, closes, or swaps widgets.
holdsOpen
@MainActor var holdsOpen: Bool { get }
Whether this droplet currently asks the shelf to stay open.
holdsOpen
@MainActor var holdsOpen: Bool { get }
A host that does not implement holds never holds.
isExpanded
@MainActor var isExpanded: Bool { get }
Whether the shelf is currently expanded.
soloWidgetID
@MainActor var soloWidgetID: ShelfWidgetID? { get }
The widget currently shown alone on the shelf, if any. nil while the shelf is closed or showing a paired row.
Methods
close()
@MainActor func close()
Closes the shelf if this droplet opened it.
invalidateLayout(for:)
@MainActor func invalidateLayout(for widgetID: ShelfWidgetID)
Asks the host to re-measure a widget whose content changed size. Call this after your model changes the size your view reports, not on every keystroke.
The host re-reads this droplet's widgetDescriptors and re-lays the open shelf out when what they declare changed; a call that changed nothing moves nothing. It names a widget the droplet already declares: one it does not is refused. A droplet whose state broadcasts reach the host (see ShelfWidgetProviding) is re-read on those too, so this call is for a size change the host cannot otherwise hear about.
open(revealing:)
@discardableResult @MainActor func open(revealing widgetID: ShelfWidgetID?) -> Bool
Opens the shelf, optionally showing one of this droplet's widgets alone. Returns false when the host refused.
requestKeyboardFocus()
@discardableResult @MainActor func requestKeyboardFocus() -> Bool
Asks the host to make the shelf panel key so a text field in one of this droplet's widgets receives keystrokes.
Accepted only while one of this droplet's widgets is on the expanded shelf and that widget declared keyboardFocusable. Returns false when the shelf is closed, none of the droplet's widgets is showing, the widget is not keyboard-focusable, the capability is missing, or the panel could not become key.
Call it when the field appears, from the view's onAppear or the action that navigates to it. The host then keeps the panel eligible for the keyboard for as long as that widget stays on the shelf, even while the droplet's own page is open in Settings; there is nothing to release. A widget that opens alone is promoted by the host on its own, so a single field the user types into the instant the widget opens needs only the focus policy.
Requires shelf-write, and DroppyKit 1.10.0: declare minAPI 1.10.0 when a droplet calls this method.
requestKeyboardFocus()
@discardableResult @MainActor func requestKeyboardFocus() -> Bool
A host that predates explicit shelf focus refuses the request.
setHoldsOpen(_:)
@discardableResult @MainActor func setHoldsOpen(_ holdsOpen: Bool) -> Bool
Keeps the expanded shelf from closing on its own while one of this droplet's widgets is on it.
For a widget the user watches rather than touches: a teleprompter reading out, a timer counting down, a live transcript. The shelf normally closes a moment after the pointer leaves it and on any click outside it; while this is on, neither closes it, and it stays on screen over a full-screen app. The user can still close it: Droppy draws its floating close button below the shelf for as long as the hold counts, a swipe up and the shelf shortcut still work, and so does close().
The hold only counts while one of your widgets is actually showing on the expanded shelf, so a hold you forget to release cannot pin the user's other widgets open. Release it the moment the work that needed it ends; the host also releases it when the droplet deactivates.
Requires shelf-write, and DroppyKit 1.5.0: declare minAPI 1.5.0, because a droplet that calls it cannot load in an older host. Returns false when the host refused, which is also what a host that does not implement holds answers.
setHoldsOpen(_:)
@discardableResult @MainActor func setHoldsOpen(_ holdsOpen: Bool) -> Bool
A host that does not implement holds refuses every one.