@@ -44,6 +44,9 @@ struct FieldEditState: Identifiable {
4444 /// Save never writes.
4545 var isServerOwned : Bool = false
4646
47+ /// False for a NOT NULL column. Nil from the schema reads as nullable, as the grid's menus do.
48+ var isNullable = true
49+
4750 /// The value already differs from the loaded schema because the edit is recorded elsewhere.
4851 var hasCommittedEdit : Bool = false
4952
@@ -130,6 +133,7 @@ final class MultiRowEditState: ObservableObject {
130133 @Published private( set) var absentCells : [ Set < Int > ] = [ ]
131134 @Published private( set) var columns : [ String ] = [ ]
132135 @Published private( set) var columnTypes : [ ColumnType ] = [ ]
136+ private var nonNullableColumns : Set < String > = [ ]
133137
134138 var hasEdits : Bool {
135139 fields. contains { $0. hasEdit }
@@ -146,6 +150,7 @@ final class MultiRowEditState: ObservableObject {
146150 primaryKeyColumns: Set < String > = [ ] ,
147151 foreignKeyColumns: Set < String > = [ ] ,
148152 serverOwnedColumns: Set < String > = [ ] ,
153+ nonNullableColumns: Set < String > = [ ] ,
149154 displayFormats: [ ValueDisplayFormat ? ] = [ ] ,
150155 binaryColumns: Set < Int > = [ ]
151156 ) {
@@ -159,6 +164,7 @@ final class MultiRowEditState: ObservableObject {
159164 self . absentCells = absentCells
160165 self . columns = columns
161166 self . columnTypes = columnTypes
167+ self . nonNullableColumns = nonNullableColumns
162168
163169 var newFields : [ FieldEditState ] = [ ]
164170
@@ -228,6 +234,7 @@ final class MultiRowEditState: ObservableObject {
228234 isPrimaryKey: primaryKeyColumns. contains ( columnName) ,
229235 isForeignKey: foreignKeyColumns. contains ( columnName) ,
230236 isServerOwned: serverOwnedColumns. contains ( columnName) ,
237+ isNullable: !nonNullableColumns. contains ( columnName) ,
231238 originalValue: originalValue,
232239 hasMultipleValues: hasMultipleValues,
233240 pendingValue: pendingValue,
@@ -337,13 +344,22 @@ final class MultiRowEditState: ObservableObject {
337344 guard index < fields. count else { return }
338345 let hadPendingEdit = fields [ index] . hasEdit
339346 let original = fields [ index] . originalValue
340- let pending = Self . resolvePendingValue ( value, original: original, isJson: fields [ index] . isJson)
347+ var pending = Self . resolvePendingValue ( value, original: original, isJson: fields [ index] . isJson)
348+ var staged = pending. map { PluginCellValue . entered ( $0, columnType: fields [ index] . columnTypeEnum) }
349+ /// A NOT NULL column whose type has no empty value holds nothing an emptied field could
350+ /// mean, so the field goes back to its stored value, as the grid's cell does.
351+ if staged == . null, !fields[ index] . isNullable {
352+ pending = nil
353+ staged = nil
354+ }
341355 fields [ index] . pendingValue = pending
342356 fields [ index] . isPendingNull = false
343357 fields [ index] . isPendingDefault = false
344358 fields [ index] . isPendingRemoval = false
345- if pending != nil {
346- onFieldChanged ? ( index, PluginCellValue . fromOptional ( pending) , . typing)
359+ /// The field keeps its empty text while the change it stages is NULL: swapping the editor
360+ /// for the pending NULL pill would take the field away in the middle of typing a new value.
361+ if let staged {
362+ onFieldChanged ? ( index, staged, . typing)
347363 } else if hadPendingEdit {
348364 /// `originalValue` is nil for three different situations, and only one of them is a
349365 /// value: a stored NULL, a selection whose rows do not agree, and a field the rows do
@@ -391,7 +407,9 @@ final class MultiRowEditState: ObservableObject {
391407 updateField ( at: columnIndex, value: value)
392408 return
393409 }
394- onDetachedFieldChanged ? ( columnIndex, PluginCellValue . fromOptional ( value) , rowIDs)
410+ let staged = PluginCellValue . entered ( value, columnType: columnTypes [ safe: columnIndex] )
411+ if staged. isNull, columns [ safe: columnIndex] . map ( nonNullableColumns. contains) == true { return }
412+ onDetachedFieldChanged ? ( columnIndex, staged, rowIDs)
395413 }
396414
397415 private static func resolvePendingValue( _ value: String ? , original: String ? , isJson: Bool ) -> String ? {
@@ -497,6 +515,7 @@ final class MultiRowEditState: ObservableObject {
497515 absentCells = [ ]
498516 columns = [ ]
499517 columnTypes = [ ]
518+ nonNullableColumns = [ ]
500519 }
501520
502521 /// Get all edited fields with their new values
0 commit comments