Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions blocks/canvas/ew-panel-extensions/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,29 @@ function createVersioningView() {
};
}

function extensionToPanelView(ext, section) {
export function extensionToPanelView(ext, section) {
// Block library opens its own dedicated modal (used by the slash menu and
// outline "+" button) rather than the generic inline panel or iframe dialog.
if (ext.name === 'blocks') {
return {
id: ext.name,
label: ext.title,
section,
firstParty: ext.ootb,
experience: 'modal',
icon: ext.icon,
openModal: async () => {
const { openBlockLibraryModal } = await import('../ew-block-library-modal/ew-block-library-modal.js');
openBlockLibraryModal({
onInsert: (dom) => {
const { view } = getExtensionsBridge();
if (view) insertBlock(view, dom);
},
});
},
};
}

const view = {
id: ext.name,
label: ext.title,
Expand Down Expand Up @@ -530,8 +552,7 @@ function extensionToPanelView(ext, section) {
*/
export async function getCanvasToolPanelViews({ org, site }) {
const extensions = await fetchExtensions(org, site);
const library = sortLibraryExtensions(extensions.filter(isLibraryExtension))
.filter((ext) => ext.name !== 'blocks');
const library = sortLibraryExtensions(extensions.filter(isLibraryExtension));
const thirdParty = extensions.filter((ext) => !isLibraryExtension(ext));

return [
Expand Down
7 changes: 6 additions & 1 deletion blocks/canvas/ew-tool-panel/tool-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ class EwToolPanel extends LitElement {
items.push({ section: v.section });
lastSection = v.section;
}
const opensExternally = v.experience === 'window' || v.experience === 'fullsize-dialog';
const opensExternally = v.experience === 'window' || v.experience === 'fullsize-dialog'
|| v.experience === 'modal';
items.push({
value: v.id,
label: v.label,
Expand Down Expand Up @@ -153,6 +154,10 @@ class EwToolPanel extends LitElement {
this._fullsizeDialogViewId = name;
return;
}
if (consumer.experience === 'modal') {
await consumer.openModal?.();
return;
}
if (!this._loaded[name]) {
this._loaded[name] = await consumer.load();
}
Expand Down
29 changes: 29 additions & 0 deletions test/unit/blocks/canvas/ew-panel-extensions/helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { setNx } from '../../../../../scripts/utils.js';
setNx('/test/fixtures/nx', { hostname: 'example.com' });

let getBlockVariants;
let extensionToPanelView;

before(async () => {
const mod = await import('../../../../../blocks/canvas/ew-panel-extensions/helpers.js');
getBlockVariants = mod.getBlockVariants;
extensionToPanelView = mod.extensionToPanelView;
});

describe('EW panel helpers transformBlock', () => {
Expand Down Expand Up @@ -179,3 +181,30 @@ describe('EW panel helpers transformBlock', () => {
expect(dom.querySelector('p')).to.not.be.null;
});
});

describe('extensionToPanelView', () => {
it('gives the "blocks" extension a dedicated modal experience', () => {
const ext = { name: 'blocks', title: 'Blocks', ootb: true, icon: '#icon-blocks' };
const view = extensionToPanelView(ext, 'Library');
expect(view.id).to.equal('blocks');
expect(view.label).to.equal('Blocks');
expect(view.section).to.equal('Library');
expect(view.firstParty).to.be.true;
expect(view.experience).to.equal('modal');
expect(view.icon).to.equal('#icon-blocks');
expect(view.openModal).to.be.a('function');
// The modal view opts out of the generic inline-panel loader.
expect(view.load).to.be.undefined;
});

it('leaves non-blocks extensions on the standard inline/load experience', () => {
const ext = {
name: 'templates', title: 'Templates', ootb: true, experience: 'inline', sources: ['/tpl'], icon: '',
};
const view = extensionToPanelView(ext, 'Library');
expect(view.id).to.equal('templates');
expect(view.experience).to.equal('inline');
expect(view.load).to.be.a('function');
expect(view.openModal).to.be.undefined;
});
});
64 changes: 64 additions & 0 deletions test/unit/blocks/canvas/ew-tool-panel/tool-panel.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable no-underscore-dangle */
import { expect } from '@esm-bundle/chai';
import { setNx } from '../../../../../scripts/utils.js';

setNx('/test/fixtures/nx', { hostname: 'example.com' });

before(async () => {
await import('../../../../../blocks/canvas/ew-tool-panel/tool-panel.js');
});

// The element is used detached (never appended), so Lit never runs an update
// cycle — we call the changed methods directly. The 'modal' branch of showPanel
// returns before touching the shadow root, so this stays safe without rendering.
function createPanel(views) {
const el = document.createElement('ew-tool-panel');
el.views = views;
return el;
}

describe('EwToolPanel — modal experience', () => {
describe('_pickerItemsFromViews', () => {
it('marks a modal view as an external-opening action item', () => {
const el = createPanel([
{ id: 'blocks', label: 'Blocks', section: 'Library', experience: 'modal' },
]);
const item = el._pickerItemsFromViews().find((i) => i.value === 'blocks');
expect(item.action).to.be.true;
expect(item.trailingIcon).to.exist;
expect(item.ariaLabel).to.equal('Blocks (opens in dialog)');
});

it('leaves a plain inline view as a non-action item', () => {
const el = createPanel([
{ id: 'files', label: 'Files', section: 'Editor', experience: 'inline' },
]);
const item = el._pickerItemsFromViews().find((i) => i.value === 'files');
expect(item.action).to.be.undefined;
expect(item.trailingIcon).to.be.undefined;
});
});

describe('showPanel', () => {
it('invokes openModal and does not activate the view for a modal experience', async () => {
let opened = 0;
const openModal = async () => { opened += 1; };
const el = createPanel([{ id: 'blocks', label: 'Blocks', experience: 'modal', openModal }]);
await el.showPanel('blocks');
expect(opened).to.equal(1);
expect(el.activeId).to.be.undefined;
});

it('does not throw when a modal view has no openModal handler', async () => {
const el = createPanel([{ id: 'blocks', label: 'Blocks', experience: 'modal' }]);
let threw = false;
try {
await el.showPanel('blocks');
} catch {
threw = true;
}
expect(threw).to.be.false;
expect(el.activeId).to.be.undefined;
});
});
});
Loading