: 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,ToolBar,_CLICK_,body,topBody,topBodyCl,topWindow,topWindowDoc) => {
const isFrontend=api.isFrontend;
static #storageKey ='tb_module_panel';
static async initialize() {
root = doc.tfId('tb_main_panel_root'),
fr = root.firstElementChild,
fragment=createDocumentFragment();
fragment.append(ToolBar.getBaseCss(), ToolBar.el.getRootNode().querySelector('#module_combine_style').cloneNode(true));
if (fr) { // shadowrootmode="open" isn't support
mode: fr.getAttribute('shadowrootmode')
}).appendChild(fr.content);
root.shadowRoot.prepend(fragment);
this.el = root.shadowRoot.tfId('main_panel');
arr=Object.keys(modules).sort(),
if ( typeof favs === 'object' && favs !== null && ! Array.isArray( favs )) {
favs = Object.values( favs );
await api.jsModuleLoaded();
Themify.on('tb_toolbar_loaded',()=>{
for (let i=0;i<arr.length;++i) {
let module = createElement('','module module_' + slug),
favorite = createElement('span','favorite tb_disable_sorting'),
name = createElement('span','module_name',modules[slug].name),
add = createElement('button',{class:'tf_plus_icon add_module_btn tb_disable_sorting tf_rel',type:'button',title:i18n.add_module}),
icon = modules[slug].icon,
isFavorited=favs?.includes(slug),
obj=api.Module.getModuleClassName(slug);
dataset.categories = cat;
if (isFavorited===true) {
module.className += ' favorited';
add.dataset.type = 'module';
favorite.title=i18n.add_fv;
favorite.appendChild(api.Helper.getIcon('ti-star'));
module.appendChild(api.Helper.getIcon('ti-' + icon));
module.append(favorite, name, add);
let categories = isFavorited===true ? ['favorite'] : cat;
for (let k = 0; k < categories.length; ++k) {
containers[categories[k]]??= createDocumentFragment();
containers[categories[k]].appendChild(module.cloneNode(true));
oldAddons.set(slug,modules[slug].name || slug);
let categories = this.el.tfClass('panel_category');
for (let i = categories.length - 1; i > -1; --i) {
let c = categories[i].dataset.category;
if (undefined !== containers[c]) {
categories[i].appendChild(containers[c]);
categories[i].parentNode.style.display = 'none';
this._updateAddonsMessage(oldAddons);
topBody.appendChild(root);
if(!CSS.supports('container-type:inline-size') || !CSS.supports('selector(:has(*))')){
this._updateBrowserMessage();
},true,ToolBar?.isLoaded===true);
api.Dock.setDocked(false);
api.jsModuleLoaded().then(()=>{
this.el.getRootNode().host.classList.remove('tf_hide');
const target = elm.dataset.target;
const parent = elm.closest('.panel'),
hideTabs = parent.tfClass(elm.dataset.hide),
showTabs = parent.tfClass(target),
notFound = parent.tfClass('tb_no_content')[0],
search = parent.tfClass('panel_search')[0],
current = elm.closest('li'),
nav = current.parentNode,
dropdownLabel = nav.parentNode.querySelector(':scope>.dropdown_label');
for (let i = hideTabs.length - 1; i > -1; --i) {
hideTabs[i].style.display = 'none';
for (let i = showTabs.length - 1; i > -1; --i) {
showTabs[i].style.display = '';
showTabs[i].classList.remove('tf_hide');
notFound?.classList.toggle('tf_hide', showTabs.length > 0);
for (let i = menu.length - 1; i > -1; --i) {
menu[i].classList.toggle('current', menu[i] === current);
dropdownLabel.textContent = elm.textContent;
Themify.triggerEvent(this.el, 'tb_panel_tab_' + target);
Themify.trigger('tb_panel_tab_' + target, parent);
this.el.tfOn(_CLICK_, e => {
'.add_module_btn': 'addComponent',
'.panel_close': 'closeFloat',
'.favorite': 'toggleFavoriteModule',
'.panel_title': 'toggleAccordion'
for (let sel in events) {
if (e.target.closest(sel)) {
this[events[sel]](e.target);
.tfOn('tb_panel_tab_panel_rows', () => {
.tfOn('tb_panel_tab_panel_library', () => {
.getRootNode().querySelector('.docked_min')?.tfOn(_CLICK_,e=>{
static async rowPanel() {
const link = this.el.getRootNode().querySelectorAll('style');
await Promise.all([Themify.loadJs(api.componentsURL + 'predesigned-rows',!!api.preDesignedRows), Themify.loadCss(Themify.builder_url + 'css/editor/components/predesigned-rows', null,null, link[link.length - 3].nextElementSibling)]);
new api.preDesignedRows(this.el.tfClass('predesigned_container')[0]);
static async libraryPanel() {
const link = this.el.getRootNode().querySelectorAll('style');
await Promise.all([Themify.loadJs(api.componentsURL + 'library',!!api.Library), Themify.loadCss(Themify.builder_url + 'css/editor/components/library', null,null, link[link.length - 3].nextElementSibling)]);
new api.Library(this.el.tfClass('library_container')[0]);
static toggleAccordion(item) {
item.closest('.panel_acc').classList.toggle('tb_collapsed');
static toggleFavoriteModule(el) {
const module = el.closest('.module'),
slug = module.dataset.slug,
this.classList.toggle('favorited');
const categories = this.dataset.categories.split(','),
parent = this.closest('.panel_modules_wrap'),
fav = parent.querySelector('[data-category="favorite"]');
if (this.classList.contains('favorited')) {
for (let i = categories.length - 1; i > -1; --i) {
let cat = parent.querySelector('[data-category="' + categories[i] + '"]');
let items=cat.tfClass('module_' + slug);
for(let j=items.length-1;j>-1;--j){
if (cat.childElementCount===0) {
cat.parentNode.style.display='none';
if (cat.childElementCount===0) {
cat.parentNode.style.display='none';
requestAnimationFrame(()=>{
requestAnimationFrame(()=>{
fav.parentNode.style.display=this.style.transform=this.style.opacity='';
for (let i = categories.length - 1; i > -1; --i) {
let cat = parent.querySelector('[data-category="' + categories[i] + '"]'),
clone=this.cloneNode(true),
p = ~~clone.dataset.index,
place = cat.querySelector('[data-index="' + p + '"]');
place?.after(clone) || cat.prepend(clone);
cat.parentNode.style.display='';
requestAnimationFrame(()=>{
requestAnimationFrame(()=>{
clone.style.transform=clone.style.opacity='';
if (fav.tfClass('module').length===0) {
fav.parentNode.style.display='none';
module.tfOn('transitionend',trEnd, {
module.style.transform = 'scale(.5)';
action:'tb_module_favorite',
module_state:module.classList.contains('favorited')?0:1
const workspace = topWindowDoc.tfClass('tb_workspace_container')[0],
items.push(api.LightBox.el);
workspace.tfOn('transitionend', function() {
this.style.transition ='';
Themify.trigger('tb_resize_lightbox');
api.Utils.onResize(true);
},{passive:true,once:true})
.style.transition = 'width .3s';
for(let i=items.length-1;i>-1;--i){
items[i].classList.toggle('tb_dock_minimized');
if (cl.contains('is_minimized')) {
const storage = this._getStorage();
el.style.height = storage.height ? (storage.height + 'px') : '';
cl.toggle('is_minimized');
classes=[ToolBar.el.classList,el.classList,bodyCl];
for(let i=classes.length-1;i>-1;--i){
classes[i].remove('tb_panel_closed');
requestAnimationFrame(()=>{
el.tfClass('panel_search')[0].focus();
classes=[ToolBar.el.classList,el.classList,bodyCl];
el.style.display = 'none';
for(let i=classes.length-1;i>-1;--i){
classes[i].add('tb_panel_closed');
static addComponent(target) {
const type = target.dataset.type,
slug=target.closest('[data-slug]')?.dataset.slug || '',
scrollTo=_this.el!==api.SmallPanel.el;
_this.newModule(slug,scrollTo);
else if ('page_break' === type) {
_this.newPageBreak(scrollTo);
else if ('row' === type) {
_this.newGrid(slug,scrollTo);
else if ('predesigned' === type) {
_this.newPredesign(slug,scrollTo);
static newModule(slug,scrollTo,settings) {
const builder=api.Builder.get(),
holder=builder.el.querySelector('.tb_column_btn_plus.clicked');
const subHolder=holder.parentNode;
if(subHolder.classList.contains('module_column')){
subHolder.tfClass('tb_holder')[0].appendChild(dummy);
subHolder.parentNode.after(dummy);
builder.newRowAvailable(true).el.tfClass('tb_holder')[0].appendChild(dummy);
return api.Drop.module(dummy, false,slug,scrollTo,settings);
static newPageBreak(scrollTo) {
api.undoManager.start('move');
let builder=api.Builder.get(),
holder=builder.el.querySelector('.tb_column_btn_plus.clicked');
holder=holder.closest('.module_row');
const rows=builder.el.tfClass('module_row');
holder=rows[rows.length-1];
api.Drop.row(dummy,'pagebreak',null,scrollTo).then(()=>{
api.undoManager.end('move');
static newGrid(slug,scrollTo) {
api.undoManager.start('move');
let builder=api.Builder.get(),
holder=builder.el.querySelector('.tb_column_btn_plus.clicked');
const subHolder=holder.parentNode;
if(subHolder.classList.contains('module_column')){
subHolder.tfClass('tb_holder')[0].appendChild(dummy);
subHolder.parentNode.after(dummy);
if(builder.hasRows!==false){
for(let rows=builder.el.children,i=rows.length-1;i>-1;--i){
if(rows[i].classList.contains('module_row')){
builder.el.prepend( dummy );
builder.el.tfClass('tb_holder')[0].appendChild(dummy);
api.Drop.row(dummy,'grid',slug,scrollTo).then(()=>{
api.undoManager.end('move');
static newPredesign(slug,scrollTo){
api.undoManager.start('move');
let builder=api.Builder.get(),
holder=builder.el.querySelector('.tb_column_btn_plus.clicked')?.closest('.module_row');
const rows=builder.el.tfClass('module_row');
holder=rows[rows.length-1];
api.Drop.row(dummy,'predesign',slug,scrollTo).then(()=>{
api.undoManager.end('move');
static setResponsiveTabs(cl) {
cl??= this.getPanelClass(this._getStorage().width);
this.el.classList.add(cl);
static getPanelClass(w) {
let cl = 'tb_float_large';
handle = this.el.tfClass('drag_handle')[0];
this.setResponsiveTabs();
handle.tfOn('pointerdown', function(e) {
e.stopImmediatePropagation();
owner = this.ownerDocument;
el.style.willChange='transform';
box = el.getBoundingClientRect(),
draggableCallback = e => {
e.stopImmediatePropagation();
timer = requestAnimationFrame(() => {
const {clientX:x,clientY:y} = e,
el.style.transform = 'translate(' + clientX + 'px,' + clientY + 'px)';
Themify.trigger('tb_panel_drag', [clientX, width]);
e.stopImmediatePropagation();
owner.body.classList.add('tb_start_animate');
ToolBar.el.classList.add('tb_start_animate');
el.classList.add('tb_start_animate');
Themify.trigger('tb_panel_drag_start');
e.stopImmediatePropagation();
cancelAnimationFrame(timer);
this.tfOff('pointermove', startDrag, {passive: true,once: true})
.tfOff('pointermove', draggableCallback, {passive: true})
.tfOff('lostpointercapture pointerup', up, {passive: true,once: true});
Themify.trigger('tb_panel_drag_end');
owner.body.classList.remove('tb_start_animate');
ToolBar.el.classList.remove('tb_start_animate');