: 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
async row(drag, type,slug,scrollTo) {
const rowDrop=async data=>{
const fragment = createDocumentFragment(),
isRow=drag.closest('.tb_holder')===null;
for (let i = 0; i < data.length; ++i) {
let row = isRow===true?(new api.Row(data[i])):(new api.Subrow(data[i]));
fragment.appendChild(row.el);
if (api.isVisual && type!=='grid') {
for (let items = row.el.querySelectorAll('[data-cid]'),j = items.length - 1; j > -1; --j) {
styles.push(items[j].dataset.cid);
row.el.style.visibility='hidden';
drag.replaceWith(fragment);
api.Builder.get().removeLayoutButton();
await api.bootstrap(styles);
await api.correctColumnPaddings();
for (let i = 0; i < rows.length; ++i) {
api.Utils.setColumnsCount(rows[i].el.tfClass('module_column'));
api.Utils.runJs(rows[i].el);
rows[i].el.style.visibility='';
api.ModulePageBreak.countModules();
if(type!=='grid' && type!=='pagebreak'){
api.Spinner.showLoader('done');
api.Utils.scrollTo(drag);
if (type === 'library' || type==='predesign') {
const data=type === 'library'?(await this.Library.row(slug)):(await api.preDesignedRows.get(slug));
else if (type==='pagebreak') {
await rowDrop(api.ModulePageBreak.cols());
else if (type==='grid') {
await rowDrop(api.Utils.grid(slug));
* if "to" doesn't from should be deleted
* if from===to => "to" should be added
async column(from, to, side) {
const from_inner = from.parentNode,
{activeBreakPoint:currentBp,breakpointsReverse:points,Registry}=api,
fromModel=isAdd===true?null:Registry.get(from_inner.closest('[data-cid]').dataset.cid),
to_inner = to ? to.parentNode : from_inner,
toModel=Registry.get(to_inner.closest('[data-cid]').dataset.cid),
isSame=fromModel?.isLightboxOpen() || false,
fromData=isSame===true?ThemifyConstructor.grid.get():(fromModel?.get('sizes') || {}),
next = side === 'left' || !to ? to : to.nextElementSibling,
fromGridArea = getComputedStyle(from).getPropertyValue('grid-area').split('/')[0].replace('"', '').trim();
if (from_inner !== to_inner) {
to_inner.insertBefore(from, next);
const is_sub_row = to_inner.classList.contains('module_subrow');
from.classList.toggle('sub_column', is_sub_row);
from.classList.toggle('tb-column', !is_sub_row);
fromModel.isSubCol=is_sub_row;
if (from_inner && !from_inner.tfClass('module_column')[0]) {
const col=new api.Column({grid_class: 'col-full'},from_inner.classList.contains('module_subrow'));
from_inner.appendChild(col.el);
else if (isDelete === true) {
const toCount = api.Utils.getColumns(to_inner).length,
fromCount = from_inner?api.Utils.getColumns(from_inner).length:0,
computed = getComputedStyle(to_inner),
toGridArea = isDelete === false && to ? getComputedStyle(to).getPropertyValue('grid-area').split('/')[0].replaceAll('"', '').trim() : null,
if (!col.includes(' ')) {
return computed.getPropertyValue('--c' + col);
col = col.replace(/\s\s+/g, ' ').trim();
if (col.includes('repeat')) {
if (!col.includes('auto-fit') && !col.includes('auto-fill')) {
repeat = col.replace(/\s\,\s|\s\,|\,\s/g, ',').replace(/\s\(\s|\s\(|\(\s/g, '(').replaceAll(' )', ')').trim().split(' ');
for (let i = 0; i < repeat.length; ++i) {
if (repeat[i].includes('repeat')) {
let item = repeat[i].split('(')[1].replace(')', '').split(','),
tmp += ' ' + (' ' + unit).repeat(count);
addColClasses = (grid, cols) => {//backward compatibility
const count = cols.length,
_COL_CLASSES=api.getColClass(),
_COL_CLASSES_VALUES=api.getColClassValues(),
colsClass = grid && !grid.includes(' ')&& _COL_CLASSES[grid] !== undefined ? _COL_CLASSES[grid] : _COL_CLASSES[count],
len = _COL_CLASSES_VALUES.length - 1;
for (let i = count - 1; i > -1; --i) {
for (let j = len; j > -1; --j) {
c.remove(_COL_CLASSES_VALUES[j]);
if (colsClass !== undefined && count < 7) {
c.remove('first','last');
cols[0].classList.add('first');
cols[count-1].classList.add('last');
/*we have 3 ways to drop column
* 1. in the same row in the desktop mode
* 2. in the same row in the responsive mode
* 3. to different rows/subrows in desktop mode
* dropping to different rows in responsive mode isn't allowed
if (from_inner === to_inner && isDelete === false && isAdd===false) {//if it's change in the same row/subrow(only for responsive mode), just change the area order,because order is responsive
if (currentBp === 'desktop') {//in desktop mode we need to move html and save the order in responsive mode
desktopArea = computed.getPropertyValue('--area').replaceAll('"', '').trim().split(' '),
childs =api.Utils.getColumns(from_inner),
from_inner.classList.remove('direction_rtl');
fromModel.setSizes({dir:''});
for (let i = len - 1; i > -1; --i) {//save old position
let cid = childs[i].dataset.cid;
oldcolsAreas[cid] = (i + 1);
let desktopSize = computed.getPropertyValue('--col');
from_inner.insertBefore(from, next);//move call
if (desktopSize && desktopSize !== 'unset' && desktopSize !== 'initial' && desktopSize !== 'none' && !desktopSize.includes('repeat')) {//moving sizes
const oldDesktopSizeIndex = desktopArea.indexOf(fromGridArea),
newFromGridArea = getComputedStyle(from).getPropertyValue('grid-area').split('/')[0].replaceAll('"', '').trim(), //get new position(col1,col2 ...) after dropping
newIndex = desktopArea.indexOf(newFromGridArea);
desktopSize = desktopSize.split(' ');
const value = desktopSize[newIndex];
desktopSize[newIndex] = desktopSize[oldDesktopSizeIndex];
desktopSize[oldDesktopSizeIndex] = value;
fromModel.setCols( {size: desktopSize.join(' ')}, currentBp);
for (let i = len - 1; i > -1; --i) {//save new position
let cid = childs[i].dataset.cid;
newColsAreas[cid] = (i + 1);
for (let i = points.length - 2; i > -1; --i) {
respArea = fromData[bp+'_area'];
if (!respArea.includes('"')) {//is css variable
respArea = computed.getPropertyValue('--area' + respArea).replace(/\s\s+/g, ' ').trim();
for (let cid in newColsAreas) {
if (oldcolsAreas[cid] !== newColsAreas[cid]) {
.replaceAll(oldcolsAreas[cid] + ' ', '#' + newColsAreas[cid] + '# ')
.replaceAll(oldcolsAreas[cid] + '"', '#' + newColsAreas[cid] + '#"');
fromModel.setCols( {area: respArea.replaceAll('#', '')}, bp);
addColClasses(desktopSize, childs);
let area = computed.getPropertyValue('--area').replace(/[\r\n]/gm, '').replace(/ +/g, ' ').trim(), //e.g "col1 col1 col1 col2 col2 col2" "col3 col3 col4 col4 col5 col5"
col = computed.getPropertyValue('--col'),
colIndex = side === 'right' ? 1 : 0,
colsSize = area.split('" "')[0].split(' ').length;//save original col size for above example it's 6
area = area.replaceAll('"', '').trim().split(' ');//convert the matrix to single array, e,g "col1 col1 col1" "col2 col2 col2" "col3 col3"=> "col1 col1 col1 col2 col2 col2 col3 col3"
let droppIndex = area.indexOf(toGridArea),
firstIndex=area.indexOf(fromGridArea),
draggedIndex = firstIndex,
oldArea=Themify.convert(area),
if (draggedIndex < droppIndex) {
colIndex = side === 'right' ? 0 : -1;
draggedIndex = area.lastIndexOf(fromGridArea);
toColArea = area[droppIndex];
for (let i = draggedIndex - 1; i >= droppIndex; --i) {
let currentCol = area[i],
replaceCol = area[i + 1];
if (currentCol !== replaceCol) {
for (let j = i; j < len; ++j) {
if (area[j] === replaceCol) {
area[j] = '_' + currentCol;
for (let i = draggedIndex + 1; i <= droppIndex; ++i) {
let currentCol = area[i],
replaceCol = area[i - 1];
if (currentCol !== replaceCol) {
for (let j = 0; j < i; ++j) {
if (area[j] === replaceCol) {
area[j] = '_' + currentCol;
for (let i = len - 1; i > -1; --i) {
if (area[i][0] === '_') {
area[i] = area[i].substring(1);
} else if (toColArea === area[i]) {
for (let i = 0,len2=(len/colsSize); i < len2; ++i) {
newArea.push('"' + area.slice(i*colsSize,(i+1)*colsSize).join(' ') + '"');
newArea=newArea.join(' ');
const update={area: newArea};
if (col && col !== 'unset' && col !== 'initial' && col !== 'none') {//move resized col value only if movement the same grid rows(e.g "col1 col2" "col3 col4" save size when col2 moved to col1,don't save when col2 moved to col3/col4)
const wasInRow=~~(firstIndex/colsSize),
indexAfter=newArea.replaceAll('"', '').trim().split(' ').indexOf(fromGridArea),
currentRow=~~(indexAfter/colsSize);
if(currentRow===wasInRow){//is the same
col = parseRepeat(col)?.split(' ');
newOrder=newArea.split('" "')[wasInRow].replaceAll('"', '').split(' ');
oldArea=oldArea.slice(wasInRow*colsSize,(wasInRow*colsSize)+colsSize);//cut the grid row where the column is
for(let i=0,len=newOrder.length;i<len;++i){
let index=oldArea.indexOf(newOrder[i]);
update.size=newSizes.join(' ');
toModel.setCols(update, currentBp);
fromSize = fromData.desktop_size,
fromColNumber = ~~fromGridArea.replace('col', '');
////in desktop mode the order is ALWAYS the same as document order,e.g col1 col2 col3 can't be col3 col1 col2
for (let j = 1; j <=(fromCount + 1); ++j) {
if(fromSize && fromSize!=='1' && fromSize!==1){
fromSize = parseRepeat(fromSize)?.split(' ');
//the maximum(css is using nth-child) column is removed=>make col4 to col3,col3 to col2 and etc.
for(let j=fromArea.length-1;j>-1;--j){
if(index===fromColNumber){
if(fromSize?.[j]!==undefined){
if(fr && fr!=='1fr'){//increase other columns sizes proportional
let frVal = parseFloat(fr),
if(fromSize && (frVal-1)>.1){//if the diff is very small we don't need to do anything
for (let j = fromSize.length - 1; j > -1; --j) {
let v = parseFloat(fromSize[j]);
if ((frVal > 1 && v < 1) || (frVal < 1 && v > 1)) {
let diff = parseFloat(frVal / count);
for (let j = fromSize.length - 1; j > -1; --j) {
let v = parseFloat(fromSize[j]);
if ((frVal > 1 && v < 1) || (frVal < 1 && v > 1)) {
fromSize[j] = (v + diff) + 'fr';
if(fromCount!==1 && fromSize){
//if there is grid with the same size use it instead of custom size(e.g "2.1fr 1fr" will be become to grid 2_1)
fromSize=ThemifyStyles.getColSize(fromSize.join(' '),false);
addColClasses(fromSize, api.Utils.getColumns(from_inner));
fromUpdate.gutter=fromCss['--area']=fromCss['--colg']='';
fromArea = '"' + fromArea.join(' ') + '"';
fromUpdate.area=fromArea;
fromUpdate.size=fromSize;
fromModel.setCols(fromUpdate);
//remove the last col css variable
fromModel.setGridCss(fromCss);
from_inner.classList.remove('tb_col_count_'+(fromCount+1));
from_inner.classList.add('tb_col_count_'+fromCount);
toSize =toModel.getSizes('size','desktop');
////in desktop mode the order is ALWAYS the same as document order,e.g col1 col2 col3 can't be col3 col1 col2
for (let j = 1; j <(toCount+1); ++j) {
if(!toSize && fr!=='1fr'){
toSize='1fr '.repeat(toCount).trim();
if (toSize) {//move resized size
toSize = parseRepeat(toSize);
toSize=toSize.split(' ');
let toColNumber=Themify.convert(api.Utils.getColumns(to_inner)).indexOf(from);//get new index in html,will be the same in area
if(toSize.length<toCount){
toSize.splice(toColNumber, 0, fr);
toSize = ThemifyStyles.getColSize(toSize.join(' '),false);//if there is a grid with the same the size use it instead of custom size(e.g "2.2fr 1fr" will be become to grid 2_1)
addColClasses(toSize, api.Utils.getColumns(to_inner));
//add the last col css variable
toArea = '"' + toArea.join(' ') + '"';
toModel.setCols({area: toArea, size: toSize});
toModel.setGridCss(toCss);
to_inner.classList.remove('tb_col_count_'+(toCount-1));
to_inner.classList.add('tb_col_count_'+toCount);
for (let i = points.length - 2; i > -1; --i) {
//reseting to auto, if breakpoint has auto value select it otherwise the parent value should be applied
let fromArea= fromModel.getGridCss({size:'auto'},bp);
if(fromArea['--area'] && !fromArea['--area'].includes(' ')){//apply css and update data,if there is no auto grid should be inherted from parent breakpoint
fromModel.setCols({size: 'auto'},bp);
fromModel.setGridCss({'--area':'','--col':''},bp);//reset css
fromModel.setSizes({size: 'auto'},bp);// update data
let toArea=toModel.getGridCss({size:'auto'},bp);
if(toArea['--area'] && !toArea['--area'].includes(' ')){//apply css and update data,if there is no auto grid should be inherted from parent breakpoint
toModel.setCols({size: 'auto'},bp);
toModel.setGridCss({'--area':'','--col':''},bp);//reset css
toModel.setSizes({size: 'auto'},bp);// update data
api.Utils.onResize(true);
async module(drag, type,slug,scrollTo,settings) {
api.Utils.scrollTo(drag);
if(type === 'part' || type === 'module'){
await this.Library.module(drag, type,slug);
const _default=settings || api.Module.getDefault(slug),
module = api.Module.initModule({mod_settings:_default,mod_name:slug});
drag.replaceWith(module.el);
if (api.isVisual && 'layout-part' !== slug && 'overlay-content' !== slug) {
await module.visualPreview(_default);
let row = await api.Library.get(id, 'row');
if (!Array.isArray(row)) {
// Attach used GS to data
const usedGS = api.GS.findUsedItems(row);