mj-context-menu
Version:
A generic context menu
209 lines • 7.09 kB
JavaScript
import * as MenuUtil from './menu_util.js';
import { HtmlClasses } from './html_classes.js';
import { AbstractMenu } from './abstract_menu.js';
import { Info } from './info.js';
export class SelectionMenu extends AbstractMenu {
static fromJson(factory, { title: title, values: values, variable: variable }, sb) {
const selection = new this(sb);
const tit = factory.get('label')(factory, { content: title || '', id: title || 'id' }, selection);
const rul = factory.get('rule')(factory, {}, selection);
const radios = values.map((x) => factory.get('radio')(factory, { content: x, variable: variable, id: x }, selection));
const items = [tit, rul].concat(radios);
selection.items = items;
return selection;
}
constructor(anchor) {
super();
this.anchor = anchor;
this.className = HtmlClasses['SELECTIONMENU'];
this.variablePool = this.anchor.menu.pool;
this.baseMenu = this.anchor.menu;
}
generateHtml() {
super.generateHtml();
this.items.forEach((item) => item.html.classList.add(HtmlClasses['SELECTIONITEM']));
}
display() { }
right(event) {
this.anchor.right(event);
}
left(event) {
this.anchor.left(event);
}
}
export class SelectionBox extends Info {
static fromJson(factory, { title: title, signature: signature, selections: selections, order: order, grid: grid }, ctxt) {
const sb = new this(title, signature, order, grid);
sb.attachMenu(ctxt);
const sels = selections.map((x) => factory.get('selectionMenu')(factory, x, sb));
sb.selections = sels;
return sb;
}
constructor(title, signature, style = "none", grid = "vertical") {
super(title, null, signature);
this.style = style;
this.grid = grid;
this._selections = [];
this.prefix = 'ctxt-selection';
this._balanced = true;
}
attachMenu(menu) {
this.menu = menu;
}
get selections() {
return this._selections;
}
set selections(selections) {
this._selections = [];
selections.forEach((x) => this.addSelection(x));
}
addSelection(selection) {
selection.anchor = this;
this._selections.push(selection);
}
rowDiv(sels) {
const div = document.createElement('div');
this.contentDiv.appendChild(div);
const rects = sels.map((sel) => {
div.appendChild(sel.html);
if (!sel.html.id) {
sel.html.id = this.prefix + MenuUtil.counter();
}
return sel.html.getBoundingClientRect();
});
const column = rects.map((x) => x.width);
const width = column.reduce((x, y) => x + y, 0);
const height = rects.reduce((x, y) => Math.max(x, y.height), 0);
div.classList.add(HtmlClasses['SELECTIONDIVIDER']);
div.setAttribute('style', 'height: ' + height + 'px;');
return [div, width, height, column];
}
display() {
super.display();
this.order();
if (!this.selections.length) {
return;
}
const outerDivs = [];
let maxWidth = 0;
let balancedColumn = [];
const chunks = this.getChunkSize(this.selections.length);
for (let i = 0; i < this.selections.length; i += chunks) {
const sels = this.selections.slice(i, i + chunks);
const [div, width, height, column] = this.rowDiv(sels);
outerDivs.push(div);
maxWidth = Math.max(maxWidth, width);
sels.forEach((sel) => (sel.html.style.height = height + 'px'));
balancedColumn = this.combineColumn(balancedColumn, column);
}
if (this._balanced) {
this.balanceColumn(outerDivs, balancedColumn);
maxWidth = balancedColumn.reduce((x, y) => x + y, 20);
}
outerDivs.forEach((div) => (div.style.width = maxWidth + 'px'));
}
getChunkSize(size) {
switch (this.grid) {
case "square":
return Math.floor(Math.sqrt(size));
case "horizontal":
return Math.floor(size / SelectionBox.chunkSize);
case "vertical":
default:
return SelectionBox.chunkSize;
}
}
balanceColumn(divs, column) {
divs.forEach((div) => {
const children = Array.from(div.children);
for (let i = 0, child; (child = children[i]); i++) {
child.style.width = column[i] + 'px';
}
});
}
combineColumn(col1, col2) {
let result = [];
let i = 0;
while (col1[i] || col2[i]) {
if (!col1[i]) {
result = result.concat(col2.slice(i));
break;
}
if (!col2[i]) {
result = result.concat(col1.slice(i));
break;
}
result.push(Math.max(col1[i], col2[i]));
i++;
}
return result;
}
left(event) {
this.move(event, (index) => (index === 0 ? this.selections.length : index) - 1);
}
right(event) {
this.move(event, (index) => index === this.selections.length - 1 ? 0 : index + 1);
}
generateHtml() {
super.generateHtml();
this.html.classList.add(HtmlClasses['SELECTION']);
}
generateContent() {
const div = super.generateContent();
div.classList.add(HtmlClasses['SELECTIONBOX']);
div.removeAttribute('tabindex');
return div;
}
findSelection(event) {
const target = event.target;
let selection = null;
if (target.id) {
selection = this.selections.find((x) => x.html.id === target.id);
}
if (!selection) {
const id = target.parentElement.id;
selection = this.selections.find((x) => x.html.id === id);
}
return selection;
}
move(event, isNext) {
const selection = this.findSelection(event);
if (selection.focused) {
selection.focused.unfocus();
}
const index = this.selections.indexOf(selection);
const next = isNext(index);
this.selections[next].focus();
}
order() {
this.selections.sort(SelectionBox.orderMethod.get(this.style));
}
toJson() {
return { type: '' };
}
}
SelectionBox.chunkSize = 4;
SelectionBox.orderMethod = new Map([
[
"alphabetical",
(x, y) => x.items[0].content.localeCompare(y.items[0].content)
],
["none", (_x, _y) => 1],
[
"decreasing",
(x, y) => {
const xl = x.items.length;
const yl = y.items.length;
return xl < yl ? 1 : yl < xl ? -1 : 0;
}
],
[
"increasing",
(x, y) => {
const xl = x.items.length;
const yl = y.items.length;
return xl < yl ? -1 : yl < xl ? 1 : 0;
}
]
]);
//# sourceMappingURL=selection_box.js.map