Skip to content

Commit

Permalink
wip: menu. Added commands to change the variant of a symbol
Browse files Browse the repository at this point in the history
  • Loading branch information
arnog committed Nov 19, 2023
1 parent 37ba47e commit 4452e2a
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 20 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
Right-clicking on a mathfield or clicking the menu glyph next to the
virtual keyboard glyph will bring up a contextual menu.

The menu allows toggling the virtual keyboard, inserting text,
copying LaTeX, MathML or MathASCII and inserting and editing matrixes.
The menu includes commands to:
- toggle the virtual keyboard
- insert text
- copying LaTeX, MathML or MathASCII to the clipboard
- insert and edit matrixes
- change the variant of a symbol (blackboard, bold, fraktur, etc...)

The content of the menu may change in future versions.

Expand Down
41 changes: 41 additions & 0 deletions src/editor/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DEFAULT_KEYBINDINGS } from './keybindings-definitions';
import { VirtualKeyboard } from '../virtual-keyboard/global';
import { MenuItem } from 'ui/menu/types';
import { _Mathfield } from './mathfield';
import { convertLatexToMarkup } from 'public/mathlive-ssr';

/** @internal */
export type _MathfieldOptions = MathfieldOptions & {
Expand Down Expand Up @@ -175,6 +176,42 @@ export function effectiveMode(options: MathfieldOptions): 'math' | 'text' {
return options.defaultMode;
}

function getSelection(mf: _Mathfield): string {
const model = mf.model;
return model.getValue(model.selection, 'latex');
}

function getVariantSubmenu(mf: _Mathfield): MenuItem[] {
return [
{
label: () => convertLatexToMarkup(`\\mathbb{${getSelection(mf)}}`),
visible: () => getSelection(mf).length === 1,
onMenuSelect: () => mf.insert('\\mathbb{#@}', { selectionMode: 'item' }),
},
{
label: () => convertLatexToMarkup(`\\mathfrak{${getSelection(mf)}}`),
visible: () => getSelection(mf).length === 1,
onMenuSelect: () =>
mf.insert('\\mathfrak{#@}', { selectionMode: 'item' }),
},
{
label: () => convertLatexToMarkup(`\\mathcal{${getSelection(mf)}}`),
visible: () => getSelection(mf).length === 1,
onMenuSelect: () => mf.insert('\\mathcal{#@}', { selectionMode: 'item' }),
},
{
label: () => convertLatexToMarkup(`\\mathrm{${getSelection(mf)}}`),
visible: () => getSelection(mf).length === 1,
onMenuSelect: () => mf.insert('\\mathrm{#@}', { selectionMode: 'item' }),
},
{
label: () => convertLatexToMarkup(`\\mathbf{${getSelection(mf)}}`),
visible: () => getSelection(mf).length === 1,
onMenuSelect: () => mf.insert('\\mathbf{#@}', { selectionMode: 'item' }),
},
];
}

export function getDefaultMenuItems(mf: _Mathfield): MenuItem[] {
return [
// // {
Expand Down Expand Up @@ -273,6 +310,10 @@ export function getDefaultMenuItems(mf: _Mathfield): MenuItem[] {
],
},

{
type: 'divider',
},
{ label: 'Variant', submenu: getVariantSubmenu(mf) },
{
type: 'divider',
},
Expand Down
18 changes: 13 additions & 5 deletions src/ui/menu/menu-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const BLINK_SPEED = 80;
export class _MenuItem<T> implements MenuItemInterface {
parentMenu: MenuInterface;
/** If this menu _type is 'submenu' */
submenu?: MenuInterface;
submenu?: MenuList;

_type: MenuItemType;
_label?: string;
Expand Down Expand Up @@ -72,9 +72,8 @@ export class _MenuItem<T> implements MenuItemInterface {

if (Array.isArray(template.submenu)) {
this._type = 'submenu';
this.submenu = new MenuList(template.submenu, {
parentMenu,
});
this.submenu = new MenuList(template.submenu, { parentMenu });
this.submenu.updateMenu(modifiers);
} else if (template.type === undefined && template.checked !== undefined)
this._type = 'checkbox';
else this._type = template.type ?? 'command';
Expand All @@ -90,11 +89,20 @@ export class _MenuItem<T> implements MenuItemInterface {
get visible(): boolean {
return this._visible;
}
set visible(value: boolean) {
this._visible = value;
if (this.element) this.element.hidden = !value;
}

get enabled(): boolean {
return this._enabled;
}

get items(): MenuItemInterface[] | undefined {
if (this.type === 'submenu' && this.submenu) return this.submenu.items;
return undefined;
}

private render(): HTMLElement | null {
if (!this.visible) return null;

Expand Down Expand Up @@ -287,7 +295,7 @@ export class _MenuItem<T> implements MenuItemInterface {

const bounds = this.element.getBoundingClientRect();
this.submenu.show({
container: this.parentMenu.rootMenu.element?.parentNode ?? null,
container: this.parentMenu.rootMenu.element!.parentNode!,
location: { x: bounds.right, y: bounds.top - 4 },
alternateLocation: { x: bounds.left, y: bounds.top - 4 },
modifiers: modifiers,
Expand Down
36 changes: 24 additions & 12 deletions src/ui/menu/menu-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class MenuList implements MenuInterface {
hasRadio = false; // If true, has at least one radio menu item

private _element: HTMLElement | null = null;
private _menuItems: MenuItemInterface[] = [];
_menuItems: MenuItemInterface[] = [];
private _activeMenuItem: MenuItemInterface | null = null;

/*
Expand Down Expand Up @@ -92,6 +92,14 @@ export class MenuList implements MenuInterface {
(x) => new _MenuItem(x, this, modifiers)
);

// Make any item list (submenus, etc..) empty, invisible
for (const item of this._menuItems) {
if (
item.items &&
item.items.reduce((acc, x) => (x.visible ? acc + 1 : acc), 0) === 0
)
item.visible = false;
}
this.hasCheckbox = this._menuItems.some((x) => x.type === 'checkbox');
this.hasRadio = this._menuItems.some((x) => x.type === 'radio');

Expand Down Expand Up @@ -125,6 +133,10 @@ export class MenuList implements MenuInterface {
if (this._menuItems.filter((x) => x.visible).length === 0) this.hide();
}

get items(): undefined | MenuItemInterface[] {
return this._menuItems;
}

/** First activable menu item */
get firstMenuItem(): MenuItemInterface | null {
let result = 0;
Expand Down Expand Up @@ -270,20 +282,20 @@ export class MenuList implements MenuInterface {
// Remove all items
ul.textContent = '';

// Add back all necessary items (after they've been updated if applicable)
// Remove consecutive dividers
let wasDivider = false;
for (const { element, type } of this._menuItems) {
if (element) {
// Avoid consecutive dividers
if (type === 'divider') {
if (wasDivider) continue;
wasDivider = true;
}
wasDivider = false;
ul.append(element);
}
for (const item of this._menuItems) {
// Avoid consecutive dividers
if (item.type === 'divider') {
if (wasDivider) item.visible = false;
wasDivider = true;
} else if (item.visible) wasDivider = false;
}

// Add back all necessary items (after they've been updated if applicable)
for (const { element, visible } of this._menuItems)
if (element && visible) ul.append(element);

ul.querySelector('li:first-of-type')?.setAttribute('tabindex', '0');

return ul;
Expand Down
3 changes: 2 additions & 1 deletion src/ui/menu/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ export interface MenuItemInterface<T = any> {
readonly type: MenuItemType;
readonly label: string;
readonly enabled: boolean;
readonly visible: boolean;
visible: boolean;
readonly checked: boolean;
readonly submenu?: MenuInterface;
readonly items?: MenuItemInterface[]; // if a list of items
readonly id?: string;
readonly data?: T;
readonly ariaLabel?: string;
Expand Down

0 comments on commit 4452e2a

Please sign in to comment.