/// <summary>
        /// Saves the current edit.
        /// </summary>
        public async Task CommitEdit()
        {
            if (SelectedNode?.IsEditing == true)
            {
                if (string.IsNullOrWhiteSpace(SelectedNode.EditText) || SelectedNode.HasSiblingWithText(SelectedNode.EditText))
                {
                    SelectedNode.CancelEdit();
                }
                else
                {
                    // notify and allow cancel
                    var afterEditArgs = new TreeNodeAfterEditEventArgs <TItem>(SelectedNode, SelectedNode.Text, SelectedNode.EditText);
                    await AfterEdit.InvokeAsync(afterEditArgs).ConfigureAwait(true);

                    if (afterEditArgs.Cancel)
                    {
                        SelectedNode.CancelEdit();
                    }
                    else
                    {
                        SelectedNode.EditText = afterEditArgs.NewValue;                         // application my of altered
                        SelectedNode.CommitEdit();
                        SelectedNode?.ParentNode?.Nodes?.Sort(NodeSort);                        // re-sort parent
                        await JSRuntime.InvokeVoidAsync("panoramicData.focus", Id).ConfigureAwait(true);
                    }
                }
            }
        }
        /// <summary>
        /// Commits the current edit.
        /// </summary>
        public async Task CommitEditAsync()
        {
            if (IsEditing && EditItem != null)
            {
                // build dictionary of edit values
                var args = new TableAfterEditEventArgs <TItem>(EditItem);
                foreach (var column in ActualColumnsToDisplay)
                {
                    if (_editValues.ContainsKey(column.Id) && IsColumnInEditMode(column, EditItem))
                    {
                        args.NewValues.Add(column.Id, _editValues[column.Id]);
                    }
                }

                // notify and allow cancel
                await AfterEdit.InvokeAsync(args).ConfigureAwait(true);

                if (!args.Cancel)
                {
                    // apply edit value to each column
                    foreach (var column in ActualColumnsToDisplay)
                    {
                        if (args.NewValues.ContainsKey(column.Id))
                        {
                            // get new value (possible updated by host app) and attempt to set on edit item
                            var newValue = args.NewValues[column.Id];
                            try
                            {
                                column.SetValue(EditItem, newValue);
                            }
                            catch (Exception ex)
                            {
                                throw new PDTableException($"Failed to assign value {newValue} to column {column.Id}", ex);
                            }
                        }
                    }
                }

                // save changes if configured to do so and data provider is given
                if (SaveChanges && DataProvider != null)
                {
                    var delta = new Dictionary <string, object>();
                    foreach (var kvp in _editValues)
                    {
                        if (kvp.Value != null)
                        {
                            var col = Columns.Find(x => x.Id == kvp.Key);
                            if (col != null && col.Field?.GetPropertyMemberInfo() is MemberInfo mi)
                            {
                                delta.Add(mi.Name, kvp.Value);
                            }
                        }
                    }
                    await DataProvider.UpdateAsync(EditItem, delta, default).ConfigureAwait(true);
                }

                EditItem  = null;
                IsEditing = false;
                await JSRuntime.InvokeVoidAsync("panoramicData.focus", Id).ConfigureAwait(true);

                StateHasChanged();
            }
        }