Skip to content

Commit

Permalink
fix(styling): better support of auto width on drop menu
Browse files Browse the repository at this point in the history
- now every menu control/plugin will have much better support of `width: auto` (resized by its content), the side effect is that we no longer have a fixed width provided and user must pay attention to final width  and in that use case we also added `maxWidth`
  • Loading branch information
ghiscoding committed Nov 26, 2021
1 parent d15022b commit 8a48dd2
Show file tree
Hide file tree
Showing 27 changed files with 260 additions and 228 deletions.
Expand Up @@ -353,7 +353,6 @@ export class Example12 {
formatter: () => `<div class="button-style margin-auto" style="width: 35px; margin-top: -1px;"><span class="mdi mdi-chevron-down mdi-22px color-primary"></span></div>`,
cellMenu: {
hideCloseButton: false,
width: 175,
commandTitle: 'Commands',
commandItems: [
{
Expand Down
Expand Up @@ -313,7 +313,6 @@ export class Example14 {
formatter: () => `<div class="button-style margin-auto" style="width: 35px; margin-top: -1px;"><span class="mdi mdi-chevron-down mdi-22px color-primary"></span></div>`,
cellMenu: {
hideCloseButton: false,
width: 175,
commandTitle: 'Commands',
commandItems: [
{
Expand Down
Expand Up @@ -29,6 +29,7 @@ const gridOptionsMock = {
autoAlignSideOffset: 0,
hideMenuOnScroll: true,
maxHeight: 'none',
maxWidth: 'none',
width: 175,
onExtensionRegistered: jest.fn(),
onCommand: () => { },
Expand Down Expand Up @@ -156,7 +157,6 @@ describe('CellMenu Plugin', () => {
autoAdjustDropOffset: 0,
autoAlignSideOffset: 0,
hideMenuOnScroll: true,
maxHeight: 'none',
width: 'auto',
});
});
Expand Down Expand Up @@ -273,11 +273,11 @@ describe('CellMenu Plugin', () => {
expect(cellMenuElm.classList.contains('dropright'));
expect(commandListElm.querySelectorAll('.slick-cell-menu-item').length).toBe(5);
expect(removeExtraSpaces(document.body.innerHTML)).toBe(removeExtraSpaces(
`<div style="display: block; width: auto; max-height: none; top: 0px; left: 0px;" class="slick-cell-menu slickgrid12345 dropdown dropright" aria-expanded="true">
<button class="close" type="button" data-dismiss="slick-cell-menu" aria-label="Close">
<span class="close" aria-hidden="true">×</span>
</button>
`<div class="slick-cell-menu slickgrid12345 dropdown dropright" style="display: block; width: auto; top: 0px; left: 0px;" aria-expanded="true">
<div class="slick-cell-menu-command-list">
<div class="command-header with-close no-title">
<button class="close" type="button" data-dismiss="slick-cell-menu" aria-label="Close">×</button>
</div>
<li class="slick-cell-menu-item orange" data-command="command1">
<div class="slick-cell-menu-icon"></div>
<span class="slick-cell-menu-content">Command 1</span>
Expand Down Expand Up @@ -328,7 +328,7 @@ describe('CellMenu Plugin', () => {

it('should expect a Cell Menu to be created when cell is clicked with a list of commands defined but without "Command 1" when "itemVisibilityOverride" and "itemUsabilityOverride" return false', () => {
plugin.dispose();
plugin.init();
plugin.init({ maxHeight: 290 });
(columnsMock[3].cellMenu.commandItems[1] as MenuCommandItem).itemVisibilityOverride = () => false;
(columnsMock[3].cellMenu.commandItems[1] as MenuCommandItem).itemUsabilityOverride = () => false;
gridStub.onClick.notify({ cell: 3, row: 1, grid: gridStub }, eventData, gridStub);
Expand All @@ -341,6 +341,7 @@ describe('CellMenu Plugin', () => {
const commandIconElm1 = commandItemElm1.querySelector('.slick-cell-menu-icon') as HTMLDivElement;

expect(closeBtnElm).toBeTruthy();
expect(cellMenuElm.style.maxHeight).toBe('290px');
expect(commandListElm.querySelectorAll('.slick-cell-menu-item').length).toBe(4);
expect(commandItemElm1.classList.contains('orange')).toBeTruthy();
expect(commandIconElm1.className).toBe('slick-cell-menu-icon');
Expand All @@ -350,7 +351,7 @@ describe('CellMenu Plugin', () => {

it('should create a Cell Menu and a 2nd button item usability callback returns false and expect button to be disabled', () => {
plugin.dispose();
plugin.init();
plugin.init({ maxWidth: 310 });
(columnsMock[3].cellMenu.commandItems[1] as MenuCommandItem).itemVisibilityOverride = () => true;
(columnsMock[3].cellMenu.commandItems[1] as MenuCommandItem).itemUsabilityOverride = () => false;
gridStub.onClick.notify({ cell: 3, row: 1, grid: gridStub }, eventData, gridStub);
Expand All @@ -363,6 +364,7 @@ describe('CellMenu Plugin', () => {
const commandIconElm1 = commandItemElm1.querySelector('.slick-cell-menu-icon') as HTMLDivElement;

expect(closeBtnElm).toBeTruthy();
expect(cellMenuElm.style.maxWidth).toBe('310px');
expect(commandListElm.querySelectorAll('.slick-cell-menu-item').length).toBe(5);
expect(commandItemElm1.classList.contains('orange')).toBeTruthy();
expect(commandIconElm1.className).toBe('slick-cell-menu-icon');
Expand Down Expand Up @@ -662,11 +664,11 @@ describe('CellMenu Plugin', () => {

expect(optionListElm.querySelectorAll('.slick-cell-menu-item').length).toBe(5);
expect(removeExtraSpaces(document.body.innerHTML)).toBe(removeExtraSpaces(
`<div style="display: block; width: auto; max-height: none; top: 0px; left: 0px;" class="slick-cell-menu slickgrid12345 dropdown dropright" aria-expanded="true">
<button class="close" type="button" data-dismiss="slick-cell-menu" aria-label="Close">
<span class="close" aria-hidden="true">×</span>
</button>
`<div class="slick-cell-menu slickgrid12345 dropdown dropright" style="display: block; width: auto; top: 0px; left: 0px;" aria-expanded="true">
<div class="slick-cell-menu-option-list">
<div class="option-header with-close no-title">
<button class="close" type="button" data-dismiss="slick-cell-menu" aria-label="Close">×</button>
</div>
<li class="slick-cell-menu-item purple" data-option="option1">
<div class="slick-cell-menu-icon"></div>
<span class="slick-cell-menu-content">Option 1</span>
Expand Down
23 changes: 12 additions & 11 deletions packages/common/src/extensions/__tests__/slickContextMenu.spec.ts
Expand Up @@ -180,7 +180,6 @@ describe('ContextMenu Plugin', () => {
autoAlignSideOffset: 0,
commandItems: [],
hideMenuOnScroll: false,
maxHeight: 'none',
width: 'auto',
optionShownOverColumnIds: [],
commandShownOverColumnIds: [],
Expand Down Expand Up @@ -304,11 +303,11 @@ describe('ContextMenu Plugin', () => {
expect(contextMenuElm.classList.contains('dropright'));
expect(commandListElm.querySelectorAll('.slick-context-menu-item').length).toBe(5);
expect(removeExtraSpaces(document.body.innerHTML)).toBe(removeExtraSpaces(
`<div style="display: block; width: auto; max-height: none; top: 0px; left: 0px;" class="slick-context-menu slickgrid12345 dropdown dropright" aria-expanded="true">
<button class="close" type="button" data-dismiss="slick-context-menu" aria-label="Close">
<span class="close" aria-hidden="true">×</span>
</button>
`<div class="slick-context-menu slickgrid12345 dropdown dropright" style="display: block; width: auto; top: 0px; left: 0px;" aria-expanded="true">
<div class="slick-context-menu-command-list">
<div class="command-header with-close no-title">
<button class="close" type="button" data-dismiss="slick-context-menu" aria-label="Close">×</button>
</div>
<li class="slick-context-menu-item orange" data-command="command1">
<div class="slick-context-menu-icon"></div>
<span class="slick-context-menu-content">Command 1</span>
Expand Down Expand Up @@ -415,7 +414,7 @@ describe('ContextMenu Plugin', () => {

it('should create a Context Menu and a 2nd item is "disabled" and expect button to be disabled', () => {
plugin.dispose();
plugin.init({ commandItems: deepCopy(commandItemsMock) });
plugin.init({ commandItems: deepCopy(commandItemsMock), maxHeight: 290 });
(gridOptionsMock.contextMenu.commandItems[1] as MenuCommandItem).disabled = true;
gridStub.onContextMenu.notify({ grid: gridStub }, eventData, gridStub);

Expand All @@ -424,14 +423,15 @@ describe('ContextMenu Plugin', () => {
const commandItemElm2 = commandListElm.querySelector('[data-command="command2"]') as HTMLDivElement;
const commandContentElm2 = commandItemElm2.querySelector('.slick-context-menu-content') as HTMLDivElement;

expect(contextMenuElm.style.maxHeight).toBe('290px');
expect(commandListElm.querySelectorAll('.slick-context-menu-item').length).toBe(5);
expect(commandContentElm2.textContent).toBe('Command 2');
expect(commandItemElm2.classList.contains('slick-context-menu-item-disabled')).toBeTruthy();
});

it('should create a Context Menu and expect button to be disabled when command property is hidden', () => {
plugin.dispose();
plugin.init({ commandItems: deepCopy(commandItemsMock) });
plugin.init({ commandItems: deepCopy(commandItemsMock), maxWidth: 310 });
(gridOptionsMock.contextMenu.commandItems[1] as MenuCommandItem).hidden = true;
gridStub.onContextMenu.notify({ grid: gridStub }, eventData, gridStub);

Expand All @@ -440,6 +440,7 @@ describe('ContextMenu Plugin', () => {
const commandItemElm2 = commandListElm.querySelector('[data-command="command2"]') as HTMLDivElement;
const commandContentElm2 = commandItemElm2.querySelector('.slick-context-menu-content') as HTMLDivElement;

expect(contextMenuElm.style.maxWidth).toBe('310px');
expect(commandListElm.querySelectorAll('.slick-context-menu-item').length).toBe(5);
expect(commandContentElm2.textContent).toBe('Command 2');
expect(commandItemElm2.classList.contains('slick-context-menu-item-hidden')).toBeTruthy();
Expand Down Expand Up @@ -1226,11 +1227,11 @@ describe('ContextMenu Plugin', () => {

expect(optionListElm.querySelectorAll('.slick-context-menu-item').length).toBe(5);
expect(removeExtraSpaces(document.body.innerHTML)).toBe(removeExtraSpaces(
`<div style="display: block; width: auto; max-height: none; top: 0px; left: 0px;" class="slick-context-menu slickgrid12345 dropdown dropright" aria-expanded="true">
<button class="close" type="button" data-dismiss="slick-context-menu" aria-label="Close">
<span class="close" aria-hidden="true">×</span>
</button>
`<div class="slick-context-menu slickgrid12345 dropdown dropright" style="display: block; width: auto; top: 0px; left: 0px;" aria-expanded="true">
<div class="slick-context-menu-option-list">
<div class="option-header with-close no-title">
<button class="close" type="button" data-dismiss="slick-context-menu" aria-label="Close">×</button>
</div>
<li class="slick-context-menu-item purple" data-option="option1">
<div class="slick-context-menu-icon"></div>
<span class="slick-context-menu-content">Option 1</span>
Expand Down
5 changes: 1 addition & 4 deletions packages/common/src/extensions/extensionCommonUtils.ts
Expand Up @@ -10,13 +10,10 @@ export function addCloseButtomElement(this: SlickColumnPicker | SlickGridMenu, m
const context: any = this;
const closePickerButtonElm = createDomElement('button', {
type: 'button', className: 'close',
innerHTML: '&times;',
dataset: { dismiss: context instanceof SlickColumnPicker ? 'slick-columnpicker' : 'slick-grid-menu' }
});
closePickerButtonElm.setAttribute('aria-label', 'Close');

const closeSpanElm = createDomElement('span', { className: 'close', innerHTML: '&times;' });
closeSpanElm.setAttribute('aria-hidden', 'true');
closePickerButtonElm.appendChild(closeSpanElm);
menuElm.appendChild(closePickerButtonElm);
}

Expand Down
18 changes: 10 additions & 8 deletions packages/common/src/extensions/menuBaseClass.ts
Expand Up @@ -38,12 +38,12 @@ export class MenuBaseClass<M extends CellMenu | ContextMenu | GridMenu | HeaderM
protected _addonOptions: M = {} as unknown as M;
protected _bindEventService: BindingEventService;
protected _camelPluginName = '';
protected _commandTitleElm?: HTMLDivElement;
protected _commandTitleElm?: HTMLSpanElement;
protected _eventHandler: SlickEventHandler;
protected _gridUid = '';
protected _menuElm?: HTMLDivElement | null;
protected _menuCssPrefix = '';
protected _optionTitleElm?: HTMLDivElement;
protected _optionTitleElm?: HTMLSpanElement;

/** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */
constructor(
Expand Down Expand Up @@ -119,15 +119,17 @@ export class MenuBaseClass<M extends CellMenu | ContextMenu | GridMenu | HeaderM
itemClickCallback: (event: DOMMouseEvent<HTMLDivElement>, type: MenuType, item: ExtractMenuType<ExtendableItemTypes, MenuType>, columnDef?: Column) => void
) {
if (args && commandOrOptionItems && menuOptions) {
const menuHeaderElm = this._menuElm?.querySelector(`.${itemType}-header`) ?? createDomElement('div', { className: `${itemType}-header` });
// user could pass a title on top of the Commands/Options section
const titleProp = itemType === 'command' ? 'commandTitle' : 'optionTitle';
const titleProp: 'commandTitle' | 'optionTitle' = `${itemType}Title`;
if ((menuOptions as CellMenu | ContextMenu)?.[titleProp]) {
this[`_${itemType}TitleElm`] = createDomElement('div', {
className: 'title',
textContent: (menuOptions as never)[titleProp],
});
commandOrOptionMenuElm.appendChild(this[`_${itemType}TitleElm`]!);
this[`_${itemType}TitleElm`] = createDomElement('span', { className: 'title', textContent: (menuOptions as never)[titleProp] });
menuHeaderElm.appendChild(this[`_${itemType}TitleElm`]!);
menuHeaderElm.classList.add('with-title');
} else {
menuHeaderElm.classList.add('no-title');
}
commandOrOptionMenuElm.appendChild(menuHeaderElm);
for (const item of commandOrOptionItems) {
this.populateSingleCommandOrOptionItem(itemType, menuOptions, commandOrOptionMenuElm, item, args, itemClickCallback);
}
Expand Down
28 changes: 17 additions & 11 deletions packages/common/src/extensions/menuFromCellBaseClass.ts
Expand Up @@ -80,35 +80,38 @@ export class MenuFromCellBaseClass<M extends CellMenu | ContextMenu> extends Men
}
}

const maxHeight = isNaN(this.addonOptions.maxHeight as any) ? this.addonOptions.maxHeight : `${this.addonOptions.maxHeight ?? 0}px`;

// create a new Menu
this._menuElm = createDomElement('div', {
className: `${this._menuCssPrefix} ${this.gridUid}`,
style: {
display: 'none',
left: `${event.pageX}px`, top: `${event.pageY + 5}px`,
width: findWidthOrDefault(this.addonOptions?.width),
}
});
this._menuElm.classList.add(this._menuCssPrefix);
this._menuElm.classList.add(this.gridUid);

const maxHeight = isNaN(this.addonOptions.maxHeight as any) ? this.addonOptions.maxHeight : `${this.addonOptions.maxHeight ?? 0}px`;
const maxWidth = isNaN(this.addonOptions.maxWidth as any) ? this.addonOptions.maxWidth : `${this.addonOptions.maxWidth ?? 0}px`;

if (maxHeight) {
this._menuElm.style.maxHeight = maxHeight as string;
}
if (maxWidth) {
this._menuElm.style.maxWidth = maxWidth as string;
}

const closeButtonElm = createDomElement('button', { className: 'close', type: 'button', dataset: { dismiss: this._menuCssPrefix } });
const closeButtonElm = createDomElement('button', { className: 'close', type: 'button', innerHTML: '&times;', dataset: { dismiss: this._menuCssPrefix } });
closeButtonElm.setAttribute('aria-label', 'Close');

const closeSpanElm = createDomElement('span', { className: 'close', innerHTML: '&times;' });
closeSpanElm.setAttribute('aria-hidden', 'true');
closeButtonElm.appendChild(closeSpanElm);

// -- Option List section
if (!(this.addonOptions as CellMenu | ContextMenu).hideOptionSection && isColumnOptionAllowed && optionItems.length > 0) {
const optionMenuElm = createDomElement('div', { className: `${this._menuCssPrefix}-option-list` });
if (!this.addonOptions.hideCloseButton) {
this._bindEventService.bind(closeButtonElm, 'click', ((e: DOMMouseEvent<HTMLDivElement>) => this.handleCloseButtonClicked(e)) as EventListener);
this._menuElm.appendChild(closeButtonElm);
const optionMenuHeaderElm = createDomElement('div', { className: 'option-header' });
optionMenuHeaderElm?.appendChild(closeButtonElm);
optionMenuElm.appendChild(optionMenuHeaderElm);
optionMenuHeaderElm.classList.add('with-close');
}
this._menuElm.appendChild(optionMenuElm);
this.populateCommandOrOptionItems(
Expand All @@ -126,7 +129,10 @@ export class MenuFromCellBaseClass<M extends CellMenu | ContextMenu> extends Men
const commandMenuElm = createDomElement('div', { className: `${this._menuCssPrefix}-command-list` });
if (!this.addonOptions.hideCloseButton && (!isColumnOptionAllowed || optionItems.length === 0 || (this.addonOptions as CellMenu | ContextMenu).hideOptionSection)) {
this._bindEventService.bind(closeButtonElm, 'click', ((e: DOMMouseEvent<HTMLDivElement>) => this.handleCloseButtonClicked(e)) as EventListener);
this._menuElm.appendChild(closeButtonElm);
const commandMenuHeaderElm = createDomElement('div', { className: 'command-header' });
commandMenuHeaderElm?.appendChild(closeButtonElm);
commandMenuElm.appendChild(commandMenuHeaderElm);
commandMenuHeaderElm.classList.add('with-close');
}
this._menuElm.appendChild(commandMenuElm);
this.populateCommandOrOptionItems(
Expand Down
1 change: 0 additions & 1 deletion packages/common/src/extensions/slickCellMenu.ts
Expand Up @@ -37,7 +37,6 @@ export class SlickCellMenu extends MenuFromCellBaseClass<CellMenu> {
autoAdjustDropOffset: 0,
autoAlignSideOffset: 0,
hideMenuOnScroll: true,
maxHeight: 'none',
width: 'auto',
} as unknown as CellMenuOption;
pluginName: 'CellMenu' = 'CellMenu';
Expand Down
1 change: 0 additions & 1 deletion packages/common/src/extensions/slickContextMenu.ts
Expand Up @@ -41,7 +41,6 @@ export class SlickContextMenu extends MenuFromCellBaseClass<ContextMenu> {
autoAdjustDropOffset: 0,
autoAlignSideOffset: 0,
hideMenuOnScroll: false,
maxHeight: 'none',
width: 'auto',
optionShownOverColumnIds: [],
commandShownOverColumnIds: [],
Expand Down

0 comments on commit 8a48dd2

Please sign in to comment.