Handle RTL layout in menus
parent
3c7ae6ce44
commit
d14135fdc4
|
@ -5,7 +5,40 @@ function E(t){
|
|||
return document.createElement(t);
|
||||
}
|
||||
|
||||
// @TODO: make menus not take focus so we can support copy/pasting text in the text tool textarea from the menus
|
||||
// @TODO: DRY hotkey helpers with jspaint (export them?)
|
||||
|
||||
// & defines accelerators (hotkeys) in menus and buttons and things, which get underlined in the UI.
|
||||
// & can be escaped by doubling it, e.g. "&Taskbar && Start Menu"
|
||||
function index_of_hotkey(text) {
|
||||
// Returns the index of the ampersand that defines a hotkey, or -1 if not present.
|
||||
|
||||
// return english_text.search(/(?<!&)&(?!&|\s)/); // not enough browser support for negative lookbehind assertions
|
||||
|
||||
// The space here handles beginning-of-string matching and counteracts the offset for the [^&] so it acts like a negative lookbehind
|
||||
return ` ${text}`.search(/[^&]&[^&\s]/);
|
||||
}
|
||||
// function has_hotkey(text) {
|
||||
// return index_of_hotkey(text) !== -1;
|
||||
// }
|
||||
// function remove_hotkey(text) {
|
||||
// return text.replace(/\s?\(&.\)/, "").replace(/([^&]|^)&([^&\s])/, "$1$2");
|
||||
// }
|
||||
function display_hotkey(text) {
|
||||
// TODO: use a more general term like .hotkey or .accelerator?
|
||||
return text.replace(/([^&]|^)&([^&\s])/, "$1<span class='menu-hotkey'>$2</span>").replace(/&&/g, "&");
|
||||
}
|
||||
function get_hotkey(text) {
|
||||
return text[index_of_hotkey(text) + 1].toUpperCase();
|
||||
}
|
||||
|
||||
// returns writing/layout direction, "ltr" or "rtl"
|
||||
function get_direction() {
|
||||
return window.get_direction ? window.get_direction() : getComputedStyle(document.body).direction;
|
||||
}
|
||||
|
||||
// TODO: support copy/pasting text in the text tool textarea from the menus
|
||||
// probably by recording document.activeElement on pointer down,
|
||||
// and restoring focus before executing menu item actions.
|
||||
|
||||
const MENU_DIVIDER = "MENU_DIVIDER";
|
||||
|
||||
|
@ -19,9 +52,6 @@ function $MenuBar(menus){
|
|||
$menus.attr("touch-action", "none");
|
||||
let selecting_menus = false;
|
||||
|
||||
const _html = menus_key => menus_key.replace(/&(.)/, m => `<span class='menu-hotkey'>${m[1]}</span>`);
|
||||
const _hotkey = menus_key => menus_key[menus_key.indexOf("&")+1].toUpperCase();
|
||||
|
||||
const close_menus = () => {
|
||||
$menus.find(".menu-button").trigger("release");
|
||||
// Close any rogue floating submenus
|
||||
|
@ -38,7 +68,7 @@ function $MenuBar(menus){
|
|||
}
|
||||
};
|
||||
|
||||
// @TODO: API for context menus (i.e. floating menu popups)
|
||||
// TODO: API for context menus (i.e. floating menu popups)
|
||||
function $MenuPopup(menu_items){
|
||||
const $menu_popup = $(E("div")).addClass("menu-popup");
|
||||
const $menu_popup_table = $(E("table")).addClass("menu-popup-table").appendTo($menu_popup);
|
||||
|
@ -59,7 +89,7 @@ function $MenuBar(menus){
|
|||
|
||||
$item.attr("tabIndex", -1);
|
||||
|
||||
$label.html(_html(item.item));
|
||||
$label.html(display_hotkey(item.item));
|
||||
$shortcut.text(item.shortcut);
|
||||
|
||||
$menu_popup.on("update", () => {
|
||||
|
@ -79,7 +109,10 @@ function $MenuBar(menus){
|
|||
|
||||
if(item.submenu){
|
||||
$submenu_area.html('<svg xmlns="http://www.w3.org/2000/svg" width="10" height="11" viewBox="0 0 10 11" style="fill:currentColor;display:inline-block;vertical-align:middle"><path d="M7.5 4.33L0 8.66L0 0z"/></svg>');
|
||||
|
||||
if (get_direction() === "rtl") {
|
||||
$submenu_area.find("svg").css("transform", "scaleX(-1)");
|
||||
}
|
||||
|
||||
const $submenu_popup = $MenuPopup(item.submenu).appendTo("body");
|
||||
$submenu_popup.hide();
|
||||
|
||||
|
@ -87,21 +120,40 @@ function $MenuBar(menus){
|
|||
$submenu_popup.show();
|
||||
$submenu_popup.triggerHandler("update");
|
||||
const rect = $item[0].getBoundingClientRect();
|
||||
let submenu_popup_rect = $submenu_popup[0].getBoundingClientRect();
|
||||
$submenu_popup.css({
|
||||
position: "absolute",
|
||||
left: rect.right + window.scrollX,
|
||||
right: "unset", // needed for RTL layout
|
||||
left: (get_direction() === "rtl" ? rect.left - submenu_popup_rect.width : rect.right) + window.scrollX,
|
||||
top: rect.top + window.scrollY,
|
||||
});
|
||||
let submenu_popup_rect = $submenu_popup[0].getBoundingClientRect();
|
||||
if (submenu_popup_rect.right > innerWidth) {
|
||||
$submenu_popup.css({
|
||||
left: rect.left - submenu_popup_rect.width,
|
||||
});
|
||||
submenu_popup_rect = $submenu_popup[0].getBoundingClientRect();
|
||||
submenu_popup_rect = $submenu_popup[0].getBoundingClientRect();
|
||||
// This is surely not the cleanest way of doing this,
|
||||
// and the logic is not very robust in the first place,
|
||||
// but I want to get RTL support done and so I'm mirroring this in the simplest way possible.
|
||||
if (get_direction() === "rtl") {
|
||||
if (submenu_popup_rect.left < 0) {
|
||||
$submenu_popup.css({
|
||||
left: 0,
|
||||
left: rect.right,
|
||||
});
|
||||
submenu_popup_rect = $submenu_popup[0].getBoundingClientRect();
|
||||
if (submenu_popup_rect.right > innerWidth) {
|
||||
$submenu_popup.css({
|
||||
left: innerWidth - submenu_popup_rect.width,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (submenu_popup_rect.right > innerWidth) {
|
||||
$submenu_popup.css({
|
||||
left: rect.left - submenu_popup_rect.width,
|
||||
});
|
||||
submenu_popup_rect = $submenu_popup[0].getBoundingClientRect();
|
||||
if (submenu_popup_rect.left < 0) {
|
||||
$submenu_popup.css({
|
||||
left: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -175,7 +227,7 @@ function $MenuBar(menus){
|
|||
if(e.ctrlKey || e.shiftKey || e.altKey || e.metaKey){
|
||||
return;
|
||||
}
|
||||
if(String.fromCharCode(e.keyCode) === _hotkey(item.item)){
|
||||
if(String.fromCharCode(e.keyCode) === get_hotkey(item.item)){
|
||||
e.preventDefault();
|
||||
$item.trogger("click");
|
||||
}
|
||||
|
@ -193,11 +245,16 @@ function $MenuBar(menus){
|
|||
const $menu_popup = $MenuPopup(menu_items).appendTo($menu_container);
|
||||
|
||||
const update_position_from_containing_bounds = ()=> {
|
||||
$menu_popup.css("left", "");
|
||||
$menu_popup.css("left", "unset");
|
||||
$menu_popup.css("right", "unset"); // needed for RTL layout
|
||||
const uncorrected_rect = $menu_popup[0].getBoundingClientRect();
|
||||
if(uncorrected_rect.right > innerWidth) {
|
||||
// rounding down is needed for RTL layout for the rightmost menu
|
||||
if(Math.floor(uncorrected_rect.right) > innerWidth) {
|
||||
$menu_popup.css("left", innerWidth - uncorrected_rect.width - uncorrected_rect.left);
|
||||
}
|
||||
if(Math.ceil(uncorrected_rect.left) < 0) {
|
||||
$menu_popup.css("left", 0);
|
||||
}
|
||||
};
|
||||
$G.on("resize", update_position_from_containing_bounds);
|
||||
$menu_popup.on("update", update_position_from_containing_bounds);
|
||||
|
@ -207,7 +264,7 @@ function $MenuBar(menus){
|
|||
$menu_button.addClass(`${menu_id}-menu-button`);
|
||||
|
||||
$menu_popup.hide();
|
||||
$menu_button.html(_html(menus_key));
|
||||
$menu_button.html(display_hotkey(menus_key));
|
||||
$menu_button.attr("tabIndex", -1)
|
||||
$menu_container.on("keydown", e => {
|
||||
const $focused_item = $menu_popup.find(".menu-item:focus");
|
||||
|
@ -258,7 +315,7 @@ function $MenuBar(menus){
|
|||
return;
|
||||
}
|
||||
if(e.altKey){
|
||||
if(String.fromCharCode(e.keyCode) === _hotkey(menus_key)){
|
||||
if(String.fromCharCode(e.keyCode) === get_hotkey(menus_key)){
|
||||
e.preventDefault();
|
||||
$menu_button.trigger("pointerdown");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue