diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 89337e7c9e..e81dfa1c37 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -674,14 +674,58 @@ RED.palette = (function() { RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette"); sidebarControls.on("click", function() { + // On mobile, don't hide the button after clicking + if (!RED.utils.isMobileDevice()) { + sidebarControls.hide(); + } RED.menu.toggleSelected("menu-item-palette"); }) - $("#red-ui-palette").on("mouseenter", function() { - sidebarControls.toggle("slide", { direction: "left" }, 200); - }) - $("#red-ui-palette").on("mouseleave", function() { - sidebarControls.stop(false,true); - sidebarControls.hide(); + + // Check if mobile device + var isMobile = RED.utils.isMobileDevice(); + + if (isMobile) { + // On mobile devices, always show the toggle button + sidebarControls.show(); + // Update the icon based on palette state + if (RED.menu.isSelected("menu-item-palette")) { + sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); + } else { + sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); + } + } else { + // On desktop, use hover behavior + $("#red-ui-palette").on("mouseenter", function() { + sidebarControls.toggle("slide", { direction: "left" }, 200); + }) + $("#red-ui-palette").on("mouseleave", function() { + sidebarControls.stop(false,true); + sidebarControls.hide(); + }) + } + + // Handle window resize to update mobile state + $(window).on("resize", function() { + var wasMobile = isMobile; + isMobile = RED.utils.isMobileDevice(); + + if (wasMobile !== isMobile) { + if (isMobile) { + // Switching to mobile - show button and remove hover events + $("#red-ui-palette").off("mouseenter mouseleave"); + sidebarControls.show(); + } else { + // Switching to desktop - hide button and add hover events + sidebarControls.hide(); + $("#red-ui-palette").on("mouseenter", function() { + sidebarControls.toggle("slide", { direction: "left" }, 200); + }) + $("#red-ui-palette").on("mouseleave", function() { + sidebarControls.stop(false,true); + sidebarControls.hide(); + }) + } + } }) var userCategories = []; if (RED.settings.paletteCategories) { @@ -754,12 +798,21 @@ RED.palette = (function() { function togglePalette(state) { if (!state) { $("#red-ui-main-container").addClass("red-ui-palette-closed"); - sidebarControls.hide(); + // Only hide on desktop + if (!RED.utils.isMobileDevice()) { + sidebarControls.hide(); + } sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); } else { $("#red-ui-main-container").removeClass("red-ui-palette-closed"); sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); } + + // On mobile, always keep the button visible + if (RED.utils.isMobileDevice()) { + sidebarControls.show(); + } + setTimeout(function() { $(window).trigger("resize"); } ,200); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index eb10fe043d..23300f26d3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -172,23 +172,75 @@ RED.sidebar = (function() { var sidebarControls = $('
').appendTo($("#red-ui-sidebar-separator")); sidebarControls.on("click", function() { - sidebarControls.hide(); + // On mobile, don't hide the button after clicking + if (!RED.utils.isMobileDevice()) { + sidebarControls.hide(); + } RED.menu.toggleSelected("menu-item-sidebar"); }) - $("#red-ui-sidebar-separator").on("mouseenter", function() { - if (!sidebarSeparator.dragging) { - if (RED.menu.isSelected("menu-item-sidebar")) { - sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); + + // Check if mobile device + var isMobile = RED.utils.isMobileDevice(); + + if (isMobile) { + // On mobile devices, always show the toggle button + sidebarControls.show(); + // Update the icon based on sidebar state + if (RED.menu.isSelected("menu-item-sidebar")) { + sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); + } else { + sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); + } + } else { + // On desktop, use hover behavior + $("#red-ui-sidebar-separator").on("mouseenter", function() { + if (!sidebarSeparator.dragging) { + if (RED.menu.isSelected("menu-item-sidebar")) { + sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); + } else { + sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); + } + sidebarControls.toggle("slide", { direction: "right" }, 200); + } + }) + $("#red-ui-sidebar-separator").on("mouseleave", function() { + if (!sidebarSeparator.dragging) { + sidebarControls.stop(false,true); + sidebarControls.hide(); + } + }) + } + + // Handle window resize to update mobile state + $(window).on("resize", function() { + var wasMobile = isMobile; + isMobile = RED.utils.isMobileDevice(); + + if (wasMobile !== isMobile) { + if (isMobile) { + // Switching to mobile - show button and remove hover events + $("#red-ui-sidebar-separator").off("mouseenter mouseleave"); + sidebarControls.show(); } else { - sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); + // Switching to desktop - hide button and add hover events + sidebarControls.hide(); + $("#red-ui-sidebar-separator").on("mouseenter", function() { + if (!sidebarSeparator.dragging) { + if (RED.menu.isSelected("menu-item-sidebar")) { + sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); + } else { + sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); + } + sidebarControls.toggle("slide", { direction: "right" }, 200); + } + }) + $("#red-ui-sidebar-separator").on("mouseleave", function() { + if (!sidebarSeparator.dragging) { + sidebarControls.stop(false,true); + sidebarControls.hide(); + } + }) } - sidebarControls.toggle("slide", { direction: "right" }, 200); - } - }) - $("#red-ui-sidebar-separator").on("mouseleave", function() { - if (!sidebarSeparator.dragging) { - sidebarControls.stop(false,true); - sidebarControls.hide(); } }); } @@ -197,9 +249,27 @@ RED.sidebar = (function() { if (!state) { $("#red-ui-main-container").addClass("red-ui-sidebar-closed"); } else { + // On mobile, close palette if opening sidebar + if (RED.utils.isMobileDevice()) { + $("#red-ui-main-container").addClass("red-ui-palette-closed"); + if (RED.menu && RED.menu.isSelected("menu-item-palette")) { + RED.menu.setSelected("menu-item-palette", false); + } + } $("#red-ui-main-container").removeClass("red-ui-sidebar-closed"); sidebar_tabs.resize(); } + + // Update toggle button icon on mobile devices + if (RED.utils.isMobileDevice()) { + var sidebarControls = $(".red-ui-sidebar-control-right"); + if (state) { + sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); + } else { + sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); + } + } + RED.events.emit("sidebar:resize"); } @@ -207,6 +277,15 @@ RED.sidebar = (function() { if (id === ":first") { id = lastSessionSelectedTab || RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0] } + + // On mobile, close palette if opening sidebar + if (RED.utils.isMobileDevice() && !skipShowSidebar) { + $("#red-ui-main-container").addClass("red-ui-palette-closed"); + if (RED.menu && RED.menu.isSelected("menu-item-palette")) { + RED.menu.setSelected("menu-item-palette", false); + } + } + if (id) { if (!containsTab(id) && knownTabs[id]) { sidebar_tabs.addTab(knownTabs[id]); @@ -224,6 +303,15 @@ RED.sidebar = (function() { function init () { setupSidebarSeparator(); + + // Check if mobile and collapse by default + if (RED.utils.isMobileDevice()) { + $("#red-ui-sidebar").addClass("closed"); + $("#red-ui-main-container").addClass("red-ui-sidebar-closed"); + sidebarSeparator.opening = false; + sidebarSeparator.width = 0; + } + sidebar_tabs = RED.tabs.create({ element: $('').appendTo("#red-ui-sidebar"), onchange:function(tab) { @@ -272,8 +360,10 @@ RED.sidebar = (function() { RED.sidebar.help.init(); RED.sidebar.config.init(); RED.sidebar.context.init(); - // hide info bar at start if screen rather narrow... - if ($("#red-ui-editor").width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); } + // hide info bar at start if screen rather narrow or on mobile device + if ($("#red-ui-editor").width() < 600 || RED.utils.isMobileDevice()) { + RED.menu.setSelected("menu-item-sidebar",false); + } } return { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index 667fdbfbc0..5657fd22f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -1513,6 +1513,17 @@ RED.utils = (function() { return r; } + function isMobileDevice() { + // Check for mobile devices using both viewport width and touch capability + var viewportWidth = window.innerWidth || document.documentElement.clientWidth; + var browserInfo = getBrowserInfo(); + + // Consider device as mobile if: + // 1. Viewport width is less than 768px OR + // 2. Device has touch capability AND (is identified as mobile OR tablet) + return viewportWidth < 768 || (browserInfo.touch && (browserInfo.mobile || browserInfo.tablet)); + } + return { createObjectElement: createObjectElement, getMessageProperty: getMessageProperty, @@ -1538,6 +1549,7 @@ RED.utils = (function() { parseModuleList: parseModuleList, checkModuleAllowed: checkModuleAllowed, getBrowserInfo: getBrowserInfo, + isMobileDevice: isMobileDevice, validateTypedProperty: validateTypedProperty } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index 47c8dbc131..34612bcd1a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -160,3 +160,38 @@ button.red-ui-sidebar-header-button-toggle { border-top-right-radius: 5px; border-bottom-right-radius: 5px; } + +// Mobile-specific styles for sidebar toggles +@media (max-width: 768px) { + .red-ui-sidebar-control-right, + .red-ui-sidebar-control-left { + display: block !important; // Always visible on mobile + opacity: 0.8; // Slightly transparent to be less obtrusive + // Use same visual size as desktop by keeping desktop padding + // Touch target will still be maintained via transparent borders/margins + padding: 15px 8px; // Same padding as desktop + } + + .red-ui-sidebar-control-right:active, + .red-ui-sidebar-control-left:active { + opacity: 1; + background: var(--red-ui-secondary-background); + } +} + +// For touch devices regardless of viewport width +@media (hover: none) and (pointer: coarse) { + .red-ui-sidebar-control-right, + .red-ui-sidebar-control-left { + display: block !important; // Always visible on touch devices + opacity: 0.8; + // Use same visual size as desktop by keeping desktop padding + padding: 15px 8px; // Same padding as desktop + } + + .red-ui-sidebar-control-right:active, + .red-ui-sidebar-control-left:active { + opacity: 1; + background: var(--red-ui-secondary-background); + } +}