The safety layer: two presses for anything you cannot take back
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
// The two-stage destructive confirm, as one component instead of twenty
|
||||
// hand-rolled copies. The first press arms ("Verb…", normal tone -- danger
|
||||
// never initiates, per SettingsButton's own contract); armed, the row shows
|
||||
// Cancel ("Keep") beside the confirming press ("Verb it", danger tone).
|
||||
//
|
||||
// One armed confirm exists app-wide: the token lives on ShellState, so arming
|
||||
// this one disarms whichever other row was armed, on any page. Consumers give
|
||||
// a unique actionId, the verb pair, and handle onConfirmed; the component owns
|
||||
// the state, the copy shape, and the keyboard path (via SettingsButton).
|
||||
//
|
||||
// ConfirmAction {
|
||||
// actionId: "forget-network:" + ssid
|
||||
// armText: "Forget…" // default: verb + ellipsis
|
||||
// confirmText: "Forget it"
|
||||
// cancelText: "Keep" // the house cancel word
|
||||
// enabled: !Service.busy
|
||||
// onConfirmed: Service.forget(ssid)
|
||||
// }
|
||||
//
|
||||
// The armed state is readable (`armed`) so a row can swap its detail line for
|
||||
// the consequence-naming sentence while armed -- naming what is lost is the
|
||||
// caller's half of the contract; this component only guarantees the two
|
||||
// presses happen and the wrong button cannot be the easy one.
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
property string actionId: ""
|
||||
property string armText: "Remove…"
|
||||
property string confirmText: "Remove it"
|
||||
property string cancelText: "Keep"
|
||||
property bool enabled: true
|
||||
signal confirmed
|
||||
signal armedChanged2
|
||||
|
||||
readonly property bool armed: root.actionId !== ""
|
||||
&& ShellState.armedConfirm === root.actionId
|
||||
|
||||
spacing: 7
|
||||
|
||||
function disarm(): void {
|
||||
if (root.armed)
|
||||
ShellState.armedConfirm = "";
|
||||
}
|
||||
|
||||
// Leaving the page, or the row vanishing under the armed state, must not
|
||||
// leave a stale token claiming some other future row's identity. Rows more
|
||||
// often hide than unload (an override pill, a conditional card), so the
|
||||
// visibility guard matters as much as the destruction one.
|
||||
Component.onDestruction: root.disarm()
|
||||
onVisibleChanged: if (!root.visible) root.disarm()
|
||||
|
||||
SettingsButton {
|
||||
visible: !root.armed
|
||||
enabled: root.enabled
|
||||
text: root.armText
|
||||
onClicked: ShellState.armedConfirm = root.actionId
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
visible: root.armed
|
||||
enabled: root.enabled
|
||||
text: root.cancelText
|
||||
onClicked: root.disarm()
|
||||
}
|
||||
|
||||
SettingsButton {
|
||||
visible: root.armed
|
||||
enabled: root.enabled
|
||||
tone: "danger"
|
||||
text: root.confirmText
|
||||
onClicked: {
|
||||
root.disarm();
|
||||
root.confirmed();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user