: 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 _set(type, content) {
const data = JSON.stringify({
await navigator.clipboard.writeText(data);
localStorage.setItem(this._key, data);//always save for local site,because we don't know on what site user will paste,otherwise on paste will ask the permission even if the site is the same
res=await navigator.clipboard.readText();
res=localStorage.getItem(this._key);
return res?.[type] || false;
if(fields?.locked===true){
this.fields = {...this.defaults(), ...fields};
_this.fields.element_id??=api.Helper.generateUniqueID();
_this.id = _this.fields.element_id;
_this.el = createElement();
if (_this.type !== 'module') {
_this.el.appendChild(doc.tfId('tmpl-builder_' + _this.type + '_item').content.cloneNode(true));
_this.setHtmlAttributes();
static getOptions(slug) {
async setData(data,dymmy) {
api.Helper.clearElementId([data],true);
const model=this.type==='module'?api.Module.initModule(data):new (api.Module.getModuleClassName(this.get('mod_name')))(data,this.isSubCol);
dymmy.replaceWith(model.el);
api.Utils.runJs(model.el);
mainId=type === 'module' ? 'mod_settings' : 'styling';
if (id === 'element_id') {
return type === 'module' ? fields[id] : type;
if (id === 'sizes' || id === 'cols' || id === 'modules' || id === 'mod_settings' || id === 'styling') {
return id === 'sizes' || id === 'cols'|| id === 'modules'?fields[id]:fields[mainId];
if(_this.defaults()[id]!==undefined){
if(type==='row' || type==='subrow' || (type==='column' && (id==='grid_class' || id==='grid_width'))){//backward
if(id==='grid_class' || id==='grid_width' || id === 'gutter' || id === 'column_alignment' || id === 'column_h' || id === 'desktop_dir' || id === 'tablet_landscape_dir' || id==='tablet_dir' || id==='mobile_dir' || id==='col_tablet_landscape' || id==='col_tablet' || id==='col_mobile'){
return fields[mainId][id];
mainId=_this.type === 'module' ? 'mod_settings' : 'styling',
if(id === 'cols' || id === 'modules' || id==='sizes' || id === 'mod_settings' || id === 'styling' || id === 'element_id'){
if(id!=='sizes' && id !== 'cols' && id !== 'modules'){
else if(_this.defaults()[id]!==undefined){
fields[mainId][id] = value;
if (id === 'mod_settings' || id === 'styling') {
id = this.type === 'module' ? 'mod_settings' : 'styling';
api.Registry.remove(this.id);
const attr = this.attributes();
attr['data-cid'] = this.id;
this.el.setAttribute(i, attr[i]);
//module can be tab or acc which they contains others modules, that is why we can't just copy the module settings,we need to call _getRowSettings
if(type==='column' || type==='module'){
const selectedRow = this.el.closest('.active_subrow,.module_row' ),
rowData = api.Utils.getRowSettings(selectedRow, (selectedRow.classList.contains('active_subrow') ? 'subrow' : 'row'));
main:for(let cols=rowData.cols,i=cols.length-1;i>-1;--i){
for(let j=col.modules.length-1;j>-1;--j){
if(col.modules[j].element_id===this.id){
else if(col.element_id===this.id){
data = api.Utils.getRowSettings(this.el, type);
if (api.isSafari === true && api.isVisual) {
const img = this.el.querySelectorAll('img[srcset]');
for(let i=img.length-1;i>-1;--i){// Fix Srcset in safari browser
img[i].outerHTML = img[i].outerHTML;
if(this.type!=='column'){
styling = _this.id === api.activeModel?.id && ThemifyConstructor.clicked === 'visibility' ? api.Forms.serialize('tb_options_visibility') : _this.get('mod_settings');
const el=_this.type==='subrow'?_this.el.tfClass('module_subrow')[0]:_this.el,
label = el.querySelector(':scope>.tb_visibility_hint'),
visibility_desktop: i18n.de,
visibility_mobile: i18n.mo,
visibility_tablet: i18n.ta,
visibility_tablet_landscape: i18n.table_landscape,
sticky_visibility: i18n.s_v
if ('hide_all' === styling.visibility_all) {
for (let i in visiblityVars) {
prefix = '' === txt ? '' : ', ';
txt += 'hide' === styling[i] ? prefix + visiblityVars[i] : '';
if(label.tfTag('svg')[0]===undefined){
label.appendChild(api.Helper.getIcon('ti-eye'));
let t = label.tfTag('span')[0];
label.appendChild(createElement('span','',txt));
label.classList.add('tb_has_visiblity');
label.classList.remove('tb_has_visiblity');
el=el.tfClass('tb_action_breadcrumb')[0];
if(this.el.isConnected && this.type!=='row'){
if(api.LightBox.el.contains(el)){
const id=e.target.dataset.id;
api.Registry.get(id)?.edit();
el.appendChild(this.getBreadCrumbs());
builder=api.Builder.get().el,
f = createDocumentFragment();
parent = parent.parentNode.closest('[data-cid]');
if(!parent || !builder.contains(parent)){
path.push(parent.dataset.cid);
for(let i=path.length-1;i>-1;--i){
let model = api.Registry.get(path[i]);
let type = model.get('mod_name'),
item = createElement('span','tb_bread tb_bread_' + type+' tf_inline_b tf_box tf_rel',model.isSubCol===true?'Sub-Column': type);
if (this.id === path[i]) {
item.className += ' tb_active_bc';
async duplicate(isMultiple) {
if(api.activeModel && this.el.contains(api.activeModel.el)){
await api.LightBox.save();
api.undoManager.start('duplicate',this.id);
const data=this.getData(),
await this.setData(data,dummy);
api.ModulePageBreak.countModules();
api.undoManager.end('duplicate');
async delete(isMultiple) {
const activeModel=api.activeModel,
isSame=activeModel===this;
if (!isSame && activeModel && this.el.contains(activeModel.el)) {
await api.LightBox.save();
if(isMultiple!==true && !activeModel?.is_new){
api.undoManager.start('delete',this.id);
if (this.type === 'column') {
await api.Drop.column(this.el);
else if(this.type==='subrow'){
const parent=this.el.parentNode,
parentCl=parent.classList;
if(parent.childElementCount===1 && (parentCl.contains('tab-content') || parentCl.contains('accordion-content') || parentCl.contains('tb_toggle_1') || parentCl.contains('tb_toggle_2') )){
const dummy=createElement();
api.Drop.row(dummy,'grid',1,false);
api.ModulePageBreak.countModules();
if(isMultiple!==true&& !activeModel?.is_new){
api.undoManager.end('delete');
if (api.activeModel && this.el.contains(api.activeModel.el)) {
await api.LightBox.save();
const data = this.getData();
// Attach used GS to data
if (Object.keys(api.GS.styles).length) {
const usedGS = api.GS.findUsedItems(data);
data.attached_gs = usedGS;
api.Helper.clearElementId([data]);
Clipboard._set(this.type, data);
paste(is_style,isMultiple) {
return new Promise(async(resolve,reject)=>{
await api.LightBox.save();
let component = this.get('mod_name'),
data = await Clipboard._get(this.type);
if (data === false || (is_style && this.type==='module' && component!==data.mod_name)) {
TF_Notification.showHide('error',i18n.text_alert_wrong_paste);
const stOptions = ThemifyStyles.getStyleOptions(component),
k = this.type === 'module' ? 'mod_settings' : 'styling',
let key = i.includes('_color') ? 'color' : (i.includes('_style') ? 'style' : false),
type=stOptions[key]?.type;
if (type === 'radio' || i.includes('breakpoint_') || i.includes('_apply_all') ) {
key = i.replace('_' + key, '_width');
else if (i.includes('_unit')) { //unit
key = i.replace(/_unit$/ig, '', '');
if (stOptions[key] !== undefined) {
else if (i.includes('_w') ) { //weight
key = i.replace(/_w$/ig, '', '');
if (type === 'font_select') {
if (stOptions[i] === undefined && !checkIsStyle(i)) {
if (stOptions[i] !== undefined) {
if (stOptions[i].isFontColor === true && data[k][stOptions[i].g + '-gradient'] !== undefined) {
res[k][stOptions[i].g + '-gradient'] = data[k][stOptions[i].g + '-gradient'];
if (stOptions[i].posId !== undefined && data[k][stOptions[i].posId] !== undefined) {
res[k][stOptions[i].posId] = data[k][stOptions[i].posId];
if (stOptions[i].repeatId !== undefined && data[k][stOptions[i].repeatId] !== undefined) {
res[k][stOptions[i].repeatId] = data[k][stOptions[i].repeatId];
if (data.used_gs !== undefined) {
res.used_gs = data.used_gs;
api.undoManager.start('paste',this.id);
this.setData(data,this.el);
api.ModulePageBreak.countModules();
api.undoManager.end('paste');
await api.LightBox.save();
box??= this.el.querySelector('.tb_' + this.type + '_action').getBoundingClientRect();
api.LightBox.el.classList.add('tb_save_module_lightbox');
help: 'Any changes made to a Layout Part are saved and reflected everywhere else they are being used (<a href="https://themify.me/docs/builder#layout-parts" target="_blank">learn more</a>)'
lb = await api.LightBox.setStandAlone(box.left, box.top).open(options),
saveAsLibraryItem = async e => {
if ('keydown' !== e.type) {
else if (e.code !== 'Enter') {
api.Spinner.showLoader('show');
settings = api.Utils.getRowSettings(this.el, this.type,true);
api.Helper.clearElementId([settings], true);
else if(type==='module'){
const data=api.Helper.cloneObject(this.get('mod_settings'));
this.constructor.builderSave(data);
mod_name: this.get('mod_name'),
element_id: api.Helper.generateUniqueID(),
const form = api.Forms.serialize(lb),
used_gs = api.GS.findUsedItems(settings),
is_layout = form.item_layout_save,
action: 'tb_save_custom_item',
item_title_field: form.item_title_field,
item: JSON.stringify(settings),
ajaxData.item_layout_save = 1;
ajaxData.usedGS = used_gs;
const data = await api.LocalFetch(ajaxData);
if (data.status === 'success') {
await api.Utils.saveCss([settings], '', data.id);
api.MainPanel.el.tfClass('panel_search')[0].value = '';
selected_layout_part: data.post_name
if (ThemifyConstructor.layoutPart.data.length > 0) {
ThemifyConstructor.layoutPart.data.push(data);
const row = new api.Row({
element_id: api.Helper.generateUniqueID(),