33 result ,
44 merge ,
55 forIn ,
6- isObject ,
76 isEqual ,
87 isString ,
98 cloneDeep ,
@@ -29,6 +28,7 @@ import {
2928} from '../util/util.mjs' ;
3029import { Model } from '../mvc/Model.mjs' ;
3130import { cloneCells } from '../util/cloneCells.mjs' ;
31+ import { removeEmptyAttributes , removeAtTopLevelOnly } from '../util/removeEmptyAttributes.mjs' ;
3232import { attributes } from './attributes/index.mjs' ;
3333import * as g from '../g/index.mjs' ;
3434import { config } from '../config/index.mjs' ;
@@ -43,22 +43,6 @@ const attributesMerger = function(a, b) {
4343 }
4444} ;
4545
46- function removeEmptyAttributes ( obj ) {
47-
48- // Remove toplevel empty attributes
49- for ( const key in obj ) {
50-
51- const objValue = obj [ key ] ;
52- const isRealObject = isObject ( objValue ) && ! Array . isArray ( objValue ) ;
53-
54- if ( ! isRealObject ) continue ;
55-
56- if ( isEmpty ( objValue ) ) {
57- delete obj [ key ] ;
58- }
59- }
60- }
61-
6246export const Cell = Model . extend ( {
6347
6448 cidPrefix : 'c' ,
@@ -88,13 +72,27 @@ export const Cell = Model.extend({
8872 const { ignoreDefaults, ignoreEmptyAttributes = false } = opt || { } ;
8973 const defaults = result ( this . constructor . prototype , 'defaults' ) ;
9074
75+ // `ignoreEmptyAttributes`:
76+ // - `false` (default) — keep all empties.
77+ // - `true` — drops top-level empties only, for backwards compatibility.
78+ // TODO(next major): `true` should drop all empties (recursive). Pass
79+ // `removeAtTopLevelOnly` explicitly to preserve the legacy behavior.
80+ // - `(key, path) => boolean` — recursive predicate; truthy drops the key
81+ // bottom-up (so a parent emptied by child removal is itself a candidate).
82+ let removeEmptyPredicate = null ;
83+ if ( typeof ignoreEmptyAttributes === 'function' ) {
84+ removeEmptyPredicate = ignoreEmptyAttributes ;
85+ } else if ( ignoreEmptyAttributes ) {
86+ removeEmptyPredicate = removeAtTopLevelOnly ;
87+ }
88+
9189 if ( ignoreDefaults === false ) {
9290 // Return all attributes without omitting the defaults
9391 const finalAttributes = cloneDeep ( this . attributes ) ;
9492
95- if ( ! ignoreEmptyAttributes ) return finalAttributes ;
96-
97- removeEmptyAttributes ( finalAttributes ) ;
93+ if ( removeEmptyPredicate ) {
94+ removeEmptyAttributes ( finalAttributes , removeEmptyPredicate ) ;
95+ }
9896
9997 return finalAttributes ;
10098 }
@@ -117,8 +115,8 @@ export const Cell = Model.extend({
117115 // Omit `id` and `type` attribute from the defaults since it should be always present
118116 const finalAttributes = objectDifference ( attributes , omit ( defaultAttributes , 'id' , 'type' ) , { maxDepth : 4 } ) ;
119117
120- if ( ignoreEmptyAttributes ) {
121- removeEmptyAttributes ( finalAttributes ) ;
118+ if ( removeEmptyPredicate ) {
119+ removeEmptyAttributes ( finalAttributes , removeEmptyPredicate ) ;
122120 }
123121
124122 return finalAttributes ;
0 commit comments