Commit in-flight edits before acting on the draft
Typing a label and clicking Save straight away saved the old value. Fields commit on editingFinished and spin boxes parse their typed text on focus loss, so a value that had been typed but not blurred was never in the draft that Save read -- and the typed text went with the delegate when the draft array was replaced. Committing on every keystroke is not the fix: the draft is a var array, so replacing it re-creates the row delegates and would throw away the field being typed into. Instead every action that reads or replaces the draft -- Save, add, remove, the timezone picker, the per-clock seconds toggle -- now moves focus first, letting each control commit through its own normal path. Focus lands on the panel's key scope, so Escape still closes the editor. Verified against the reported case: at the moment Save ran the draft still held the old label, and the file received the typed one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MhxtL7WzL5XmuTd8GHbcTY
This commit is contained in:
+34
-6
@@ -80,9 +80,26 @@ Item {
|
|||||||
function rowShowsSeconds(row) { return Model.showsSeconds(row, root.defaultSeconds) }
|
function rowShowsSeconds(row) { return Model.showsSeconds(row, root.defaultSeconds) }
|
||||||
|
|
||||||
// QML only re-renders a var array when the reference changes, so every
|
// QML only re-renders a var array when the reference changes, so every
|
||||||
// mutation rebuilds it rather than editing in place.
|
// mutation rebuilds it rather than editing in place. That rebuild is also
|
||||||
|
// why edits are committed on focus loss rather than on every keystroke:
|
||||||
|
// replacing the array re-creates the row delegates, which would throw away
|
||||||
|
// the field you are typing into.
|
||||||
function replaceDraft(next) { root.draft = next }
|
function replaceDraft(next) { root.draft = next }
|
||||||
|
|
||||||
|
// Run an action that reads or replaces the draft, but only after every
|
||||||
|
// control has committed what is currently in it.
|
||||||
|
//
|
||||||
|
// A text field commits on `editingFinished` and a spin box parses its typed
|
||||||
|
// text on focus loss, so a value typed and then acted on directly — type a
|
||||||
|
// label, click Save — was never in the draft the action read, and the typed
|
||||||
|
// text was lost with the delegate. Moving focus first makes each control
|
||||||
|
// commit through its own normal path; focus goes back to the panel's key
|
||||||
|
// scope so Escape keeps working.
|
||||||
|
function withPendingEdits(action) {
|
||||||
|
keyScope.forceActiveFocus()
|
||||||
|
Qt.callLater(action)
|
||||||
|
}
|
||||||
|
|
||||||
// Copies every key the row already had. Rebuilding it from a fixed pair of
|
// Copies every key the row already had. Rebuilding it from a fixed pair of
|
||||||
// fields silently dropped anything else on it — editing a label would have
|
// fields silently dropped anything else on it — editing a label would have
|
||||||
// thrown away that clock's own seconds setting.
|
// thrown away that clock's own seconds setting.
|
||||||
@@ -181,6 +198,7 @@ Item {
|
|||||||
MouseArea { anchors.fill: parent; onClicked: {} }
|
MouseArea { anchors.fill: parent; onClicked: {} }
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: keyScope
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: card.padding
|
anchors.margins: card.padding
|
||||||
focus: true
|
focus: true
|
||||||
@@ -250,7 +268,10 @@ Item {
|
|||||||
triggerLabel: row.modelData.timezone || "Choose a timezone"
|
triggerLabel: row.modelData.timezone || "Choose a timezone"
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
accent: root.accent
|
accent: root.accent
|
||||||
onChanged: function (value) { root.updateRow(row.index, "timezone", value) }
|
onChanged: function (value) {
|
||||||
|
var i = row.index
|
||||||
|
root.withPendingEdits(function () { root.updateRow(i, "timezone", value) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The live offset, so the effect of a choice is visible
|
// The live offset, so the effect of a choice is visible
|
||||||
@@ -276,7 +297,11 @@ Item {
|
|||||||
: "Show seconds on this clock"
|
: "Show seconds on this clock"
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
accent: root.accent
|
accent: root.accent
|
||||||
onClicked: root.updateRow(row.index, "showSeconds", !root.rowShowsSeconds(row.modelData))
|
onClicked: {
|
||||||
|
var i = row.index
|
||||||
|
var next = !root.rowShowsSeconds(row.modelData)
|
||||||
|
root.withPendingEdits(function () { root.updateRow(i, "showSeconds", next) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@@ -284,7 +309,10 @@ Item {
|
|||||||
tooltipText: "Remove this clock"
|
tooltipText: "Remove this clock"
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
accent: root.urgent
|
accent: root.urgent
|
||||||
onClicked: root.removeRow(row.index)
|
onClicked: {
|
||||||
|
var i = row.index
|
||||||
|
root.withPendingEdits(function () { root.removeRow(i) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,7 +334,7 @@ Item {
|
|||||||
bordered: true
|
bordered: true
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
accent: root.accent
|
accent: root.accent
|
||||||
onClicked: root.addRow()
|
onClicked: root.withPendingEdits(root.addRow)
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelSeparator { Layout.fillWidth: true }
|
PanelSeparator { Layout.fillWidth: true }
|
||||||
@@ -459,7 +487,7 @@ Item {
|
|||||||
selected: true
|
selected: true
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
accent: root.accent
|
accent: root.accent
|
||||||
onClicked: root.save()
|
onClicked: root.withPendingEdits(root.save)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user