: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
api.Row = class extends api.Base {
if(fields.sizes?.length!==undefined){
this.convertToGrid(fields);//convert old data to grid
if (!(this instanceof api.Subrow )) {//hack js isn't real OOP,doesn't recognize child class props/method in constructor
static getSettingsName(slug) {
img: 'row_fullwidth_content',
value: 'fullwidth-content',
const data = this.get('styling'),
class: 'module_row tb_active_row tb_' + this.id
if (data.custom_css_row !== undefined && data.custom_css_row !== '') {
attr.class += ' ' + data.custom_css_row;
if (data.row_width === 'fullwidth-content') {
attr.class += ' fullwidth';
if (data.custom_css_id !== undefined && data.custom_css_id !== '') {
attr.id = data.custom_css_id;
if(!Array.isArray(cols)){//very very old saved data can be object, not array.
cols=Object.values(cols);
const isSubRow=this.type === 'subrow',
container = isSubRow?this.el.tfClass('module_subrow')[0]:this.el.tfClass(this.type + '_inner')[0],
fr = createDocumentFragment(),
if (len > 1 && this.get('desktop_dir') === 'rtl') { //it's old version data. Should follow dom order in desktop mode
for (let i = 0; i < len; ++i) {
if (cols[i] !== undefined && cols[i] !== null) {
let c = new api.Column(cols[i], isSubRow);
if (not_empty === false) {
let m = c.get('modules');
not_empty = m?.length > 0;
if (len > 1 && (i === 0 || i === (len - 1))) {
c.el.classList.add((i === 0 ? 'first' : 'last'));
cl.push('tb_col_count_' + len);
const sizes = this.get('sizes');
const points = api.breakpointsReverse,
st = this.get('styling'),
data = {grid: Object.assign({count: len, model: this}, sizes)};
data.grid.desktop_size??= len;
for (let i = points.length - 1; i > -1; --i) {
let vals = ThemifyStyles.fields.grid.call(ThemifyStyles, 'grid', this.type, {}, data, this.id, st, points[i], true);
this.setGridCss(vals, points[i]);
let align = sizes.desktop_align;
if (align === undefined) {
align = api.isFullSection === true ? 'center' : 'start';
align = align === 'center' ? 'middle' : 'bottom';
cl.push('col_align_' + align);
if (sizes.desktop_dir === 'rtl') {
cl.push('direction_rtl');
if (sizes.desktop_gutter === 'narrow' || sizes.desktop_gutter === 'none') {//backward
cl.push('gutter-' + sizes.desktop_gutter);
if (sizes.desktop_auto_h === 1) {
cl.push('col_auto_height');
let col = new api.Column({}, isSubRow);
cl.push('tb_col_count_1');
const anchor = this.get('row_anchor'),
custom_css_id = this.get('custom_css_id');
if (anchor !== undefined && anchor !== '') {
this.el.tfClass('tb_row_anchor')[0].textContent = anchor || '';
if (custom_css_id !== undefined && custom_css_id !== '') {
this.el.tfClass('tb_row_id')[0].textContent = custom_css_id;
container.appendChild(fr);
cl.push('tb_' + this.id);
container.className += ' ' + cl.join(' ');
const points = api.breakpointsReverse,
bpLength = points.length,
count = cols?.length || 0;
let sizes = fields.sizes;
if (sizes === undefined) {//this key should always exist in the grid version,even if it's empty
let align = fields.column_alignment,
const gutter = fields.gutter;
if (gutter && gutter !== 'gutter' && gutter !== 'gutter-default') {
sizes.desktop_gutter = gutter.replace('gutter-', '');
const g = sizes.desktop_gutter || 'def',
let hasCustomWidth = false;
for (let i = 0; i < count; ++i) {
let custom_w = cols[i].grid_width;
gridWidth.push(custom_w);
delete cols[i].grid_width;
if (cols[i].grid_class) {
gridClass.push(cols[i].grid_class);
gridWidth.push(cols[i].grid_class);
useResizing = hasCustomWidth;
if (useResizing === false && g !== 'def' && gridClass.length > 0) {
desktop_size = ThemifyStyles.gridBackwardCompatibility(gridClass);
//in old version gutter narrow,none have been done wrong for sizes 1_2,2_1,1_1_2,1_2_1 and etc we need to convert them to custom sizes to save the same layout
useResizing = desktop_size.includes('_');
if (useResizing === true) {
const colSizes = ThemifyStyles.getOldColsSizes(g);
for (let i = gridWidth.length - 1; i > -1; --i) {
if (typeof gridWidth[i] === 'string' && gridWidth[i].includes('col')) {
let cl = gridWidth[i].split(' ')[0].replace(/tb_3col|tablet_landscape|tablet|mobile|column|first|last/ig, '').trim();
if (colSizes[cl] !== undefined) {
gridWidth[i] = colSizes[cl];
const min = Math.min.apply(null, gridWidth);
for (let i = gridWidth.length - 1; i > -1; --i) {
gridWidth[i] = min === gridWidth[i] ? '1fr' : (parseFloat((gridWidth[i] / min).toFixed(5)).toString() + 'fr');
desktop_size = gridWidth.join(' ');
} else if (!desktop_size && gridClass.length > 0) {
desktop_size = ThemifyStyles.gridBackwardCompatibility(gridClass);
desktop_size = ThemifyStyles.getColSize(desktop_size, false);
if (desktop_size !== '1' && desktop_size !== '2' && desktop_size !== '3' && desktop_size !== '4' && desktop_size !== '5' && desktop_size !== '6') {
if (fields.desktop_dir === 'rtl' && (useResizing === true || desktop_size.toString().includes('_'))) {
desktop_size = useResizing === true ? desktop_size.split(' ') : desktop_size.split('_');
desktop_size = desktop_size.reverse();
desktop_size = useResizing === true ? desktop_size.join(' ') : desktop_size.join('_');
sizes.desktop_size = desktop_size;
for (let i = bpLength - 1; i > -1; --i) {
dir = fields[bp + '_dir'] || 'ltr',
col = fields['col_' + bp];
sizes[bp + '_dir'] = dir === '1' || dir === 1 ? 'rtl' : dir;
//backward compatibility for themify-builder-style.css media-query(by default all cols in mobile should be fullwidth)
let c = (col && col !== 'auto' && col !== '-auto') ? ThemifyStyles.gridBackwardCompatibility(col) : 'auto';
if (hasCustomWidth === true) {
bpkey += bp.split('_')[1][0];
grid = ThemifyStyles.getAreaValue('--area' + bpkey + count + '_' + c) || ThemifyStyles.getAreaValue('--area' + count + '_' + c); //check first for area for breakpoint e.g --aream5_3
for (let j = i + 1; j < bpLength; ++j) {
grid = sizes[points[j] + '_size'];
if (grid && grid !== 'auto') {
grid = grid.includes('fr') ? grid : ThemifyStyles.gridBackwardCompatibility(grid);
c = !grid ? count.toString() : grid;
} else if (!c.toString().includes('_') && c > 0 && c < 6 && count < c) {
for (let j = i + 1; j < bpLength; ++j) {
if (sizes[points[j] + '_size'] !== undefined) {
c = sizes[points[j] + '_size'];
sizes[bp + '_size'] = c === '' ? '' : ThemifyStyles.getColSize(c, false);
delete fields[bp + '_dir'];
delete fields['col_' + bp];
if (align === 'col_align_top') {
align = align === 'col_align_middle' ? 'center' : 'end';
sizes.desktop_align = align;
sizes.desktop_auto_h = colh ? 1 : -1;
delete fields.column_alignment;
for (let i = 0; i < bpLength - 1; ++i) {
if (sizes[points[i] + '_size'] === undefined) {
for (let j = i + 1; j < bpLength - 1; ++j) {
if (sizes[points[j] + '_size'] !== undefined) {
sizes[points[i] + '_size'] = sizes[points[j] + '_size'];
this.fields.sizes = sizes;
return api.activeModel?.id===this.id && api.LightBox.el.querySelector('#tb_grid_lb_root')!==null;
let lb=api.LightBox.el.querySelector('#tb_grid_lb_root')?.shadowRoot.querySelector('#grid'),
item=lb.querySelector('[data-col="grid"] [data-grid="'+value+'"]');
else if(k==='gutterVal'){
item=lb.querySelector('#range');
const gutterVal = parseFloat(value);
lb.querySelector('#range_unit').value=value.toString().replace(gutterVal.toString(), '') || '%';
Themify.triggerEvent(item,'change');
item=lb.querySelector('[data-col="'+k+'"] [data-value="'+value+'"]');
if(item && !item.classList.contains('selected')){
Themify.triggerEvent(item,_CLICK_);
delete settings[ThemifyConstructor.grid.id];
const gridSetting=ThemifyConstructor.grid.get();
this.set('sizes', {...gridSetting});
bp??= api.activeBreakPoint;
let sizes =this.isLightboxOpen()?ThemifyConstructor.grid.get():this.get('sizes'),
gutter, aligment, auto_h, area, size;
if (sizes !== undefined) {
for (let points = api.breakpointsReverse,i = points.indexOf(bp); i < points.length; ++i) {
gutter??= sizes[_bp + '_gutter'];
aligment??= sizes[_bp + '_align'];
size??= sizes[_bp + '_size'];
auto_h??= sizes[_bp + '_auto_h'];
area??= sizes[_bp + '_area'];
if (gutter && aligment && size && auto_h && area) {
size = ThemifyStyles.getColSize(size, false);
const sel=this.type==='subrow'?'module_subrow':this.type + '_inner';
area = ThemifyStyles.getArea(area, false, bp, api.Utils.getColumns(this.el.tfClass(sel)[0]).length);
aligment??= api.isFullSection === true ? 'center' : 'start';
let res = {gutter: gutter, align: aligment, size: size, auto_h: auto_h, area: area};
bp??= api.activeBreakPoint;
const isSame=this.isLightboxOpen(),
sel=this.type==='subrow'?'module_subrow':this.type + '_inner',
inner = this.el.tfClass(sel)[0],
count = api.Utils.getColumns(inner).length,
sizes =isSame?ThemifyConstructor.grid.get():this.get('sizes');
delete sizes[bp + '_' + k];
else if (vals[k] !== undefined && vals[k] !== null) {
vals[k] = ThemifyStyles.getGutter(vals[k]);
else if (k === 'size' && vals[k].includes(' ')) {
//if there is a grid with the same the size use it instead of custom size(e.g "2.1fr 1fr" will be become to grid 2_1)
vals[k] =ThemifyStyles.getColSize(vals[k], false);
sizes[bp + '_' + k] =vals[k].toString().replace(/ +/g, ' ').trim();
ThemifyConstructor.grid.set(sizes);
this.set('sizes', sizes);
if (vals[k]?.includes(' ')) {
vals[k] = ThemifyStyles.getColSize(vals[k], false);
} else if (k === 'gutter') {
vals[k] = ThemifyStyles.getGutter(vals[k]);
if (bp !== k.split('_')[0]) {
data.grid[bp + '_' + k] = vals[k];
return ThemifyStyles.fields.grid.call(ThemifyStyles, 'grid', this.type, {}, data, this.id, null, bp, true);
setCols(vals, bp, update) {
bp??=api.activeBreakPoint;
if (vals.gutter !== undefined) {
vals.gutter = ThemifyStyles.getGutter(vals.gutter);
const res = this.getGridCss(vals, bp);
if(bp==='desktop' && vals.size?.toString()==='1'){
res['--area'] = res['--colg'] = res['--col']=res['--align_items']=res['--align_content']='';
if (res['--align_items'] === undefined && vals.auto_h === '-1') {
res['--align_items'] = '';
if (res['--colg'] === undefined && vals.gutter === 'gutter') {