ecs-mobile-menu.css
215 lines
| 1 | /* ============================================================ |
| 2 | ECS Mobile Menu — WordPress Menu Responsive Layout Controls |
| 3 | ============================================================ |
| 4 | |
| 5 | HOW IT WORKS |
| 6 | ───────────────────────────────────────────────────────── |
| 7 | Elementor PHP does NOT add breakpoint prefixes (tablet-/mobile-) |
| 8 | to prefix_class values. All breakpoint values of the `layout` |
| 9 | responsive control are added as ecs-nav-layout-{value} without |
| 10 | any device prefix, meaning the same class appears multiple times |
| 11 | when multiple breakpoints share a value. |
| 12 | |
| 13 | To handle responsive behaviour correctly, we use CSS Custom |
| 14 | Properties (--ecs-nav-main-display, etc.) that are set by |
| 15 | Elementor's dynamic CSS engine via the `selectors` configuration |
| 16 | in upgrade_layout(). Elementor generates: |
| 17 | • @media max-width rules for frontend |
| 18 | • body.elementor-device-* rules for editor device preview |
| 19 | |
| 20 | The presence of any ecs-nav-layout-* class on the wrapper |
| 21 | indicates ECS layout is active for that widget, scoping our |
| 22 | CSS variable rules so they never affect other nav-menu widgets. |
| 23 | |
| 24 | ALIGNMENT: justify-content is set via direct Elementor selectors |
| 25 | (per-element scoped CSS) rather than a global CSS variable rule. |
| 26 | This avoids a flex-start fallback overriding the user's existing |
| 27 | alignment when the CSS cache is stale. |
| 28 | ============================================================ */ |
| 29 | |
| 30 | /* Apply CSS variables to nav-menu elements when ECS layout is active. |
| 31 | Fallbacks = default "horizontal" behaviour. */ |
| 32 | |
| 33 | .ecs-nav-layout-horizontal .elementor-nav-menu--main, |
| 34 | .ecs-nav-layout-vertical .elementor-nav-menu--main, |
| 35 | .ecs-nav-layout-dropdown .elementor-nav-menu--main { |
| 36 | display: var( --ecs-nav-main-display, flex ) !important; |
| 37 | } |
| 38 | |
| 39 | /* flex-direction must target the inner <ul>, not the <nav> wrapper */ |
| 40 | .ecs-nav-layout-horizontal .elementor-nav-menu--main .elementor-nav-menu, |
| 41 | .ecs-nav-layout-vertical .elementor-nav-menu--main .elementor-nav-menu, |
| 42 | .ecs-nav-layout-dropdown .elementor-nav-menu--main .elementor-nav-menu { |
| 43 | flex-direction: var( --ecs-nav-main-dir, row ) !important; |
| 44 | } |
| 45 | |
| 46 | .ecs-nav-layout-horizontal .elementor-menu-toggle, |
| 47 | .ecs-nav-layout-vertical .elementor-menu-toggle, |
| 48 | .ecs-nav-layout-dropdown .elementor-menu-toggle { |
| 49 | display: var( --ecs-nav-toggle-display, none ) !important; |
| 50 | } |
| 51 | |
| 52 | /* Pointer visibility — reads --ecs-nav-ptr-vis set by the ecs_pointer_hide responsive |
| 53 | control. visibility:hidden preserves layout space and does not block hover animations. */ |
| 54 | .ecs-nav-layout-horizontal .elementor-nav-menu--main .elementor-item::before, |
| 55 | .ecs-nav-layout-horizontal .elementor-nav-menu--main .elementor-item::after, |
| 56 | .ecs-nav-layout-vertical .elementor-nav-menu--main .elementor-item::before, |
| 57 | .ecs-nav-layout-vertical .elementor-nav-menu--main .elementor-item::after { |
| 58 | visibility: var( --ecs-nav-ptr-vis, visible ); |
| 59 | } |
| 60 | |
| 61 | /* Stretch/Justify alignment: grow each item to fill equal width. |
| 62 | --ecs-nav-li-grow is set to 1 by the align_items selectors_dictionary |
| 63 | when "Stretch" (space-between) is selected; defaults to 0 otherwise. */ |
| 64 | .ecs-nav-layout-horizontal .elementor-nav-menu--main .elementor-nav-menu > li, |
| 65 | .ecs-nav-layout-vertical .elementor-nav-menu--main .elementor-nav-menu > li { |
| 66 | flex-grow: var( --ecs-nav-li-grow, 0 ); |
| 67 | } |
| 68 | |
| 69 | /* ═══════════════════════════════════════════════════════════ |
| 70 | DROPDOWN ALIGNMENT — follows Toggle Button align |
| 71 | Uses prefix_class ecs-toggle-align-{left|center|right} set |
| 72 | by the Toggle Align CHOOSE control. |
| 73 | |
| 74 | NOTE: Elementor only sets position:absolute on the dropdown |
| 75 | when Full Width (stretch) is ON. For non-stretch dropdowns, |
| 76 | the panel is position:static, so left/right have no effect. |
| 77 | We add position:absolute here when any ecs-toggle-align-* |
| 78 | class is present. The widget wrapper is position:relative, |
| 79 | so absolute positioning is correct. |
| 80 | |
| 81 | Skipped when Full Width is ON (:not(.elementor-nav-menu--stretch)) |
| 82 | to avoid conflict with the stretch rules below. |
| 83 | |
| 84 | IMPORTANT: These rules require .ecs-nav-layout-dropdown on the |
| 85 | widget wrapper, which is only added when the ECS layout control |
| 86 | is explicitly set to 'dropdown' for at least one breakpoint. |
| 87 | This prevents overriding Smartmenus.js position calculations for |
| 88 | nav menus using the native Elementor Breakpoint control (those |
| 89 | have ecs-nav-layout-horizontal/vertical, not ecs-nav-layout-dropdown) |
| 90 | — Smartmenus sets left/top inline and CSS !important would win, |
| 91 | misplacing the dropdown panel. |
| 92 | ═══════════════════════════════════════════════════════════ */ |
| 93 | |
| 94 | /* Base: make dropdown absolutely positioned when ECS align is active. |
| 95 | top: 100% places it immediately below the widget (same as Full Width). */ |
| 96 | .ecs-nav-layout-dropdown.ecs-toggle-align-left:not(.elementor-nav-menu--stretch) |
| 97 | .elementor-nav-menu--dropdown.elementor-nav-menu__container, |
| 98 | .ecs-nav-layout-dropdown.ecs-toggle-align-center:not(.elementor-nav-menu--stretch) |
| 99 | .elementor-nav-menu--dropdown.elementor-nav-menu__container, |
| 100 | .ecs-nav-layout-dropdown.ecs-toggle-align-right:not(.elementor-nav-menu--stretch) |
| 101 | .elementor-nav-menu--dropdown.elementor-nav-menu__container { |
| 102 | position: absolute !important; |
| 103 | top: 100% !important; |
| 104 | width: auto !important; |
| 105 | min-width: max-content; |
| 106 | } |
| 107 | |
| 108 | .ecs-nav-layout-dropdown.ecs-toggle-align-left:not(.elementor-nav-menu--stretch) |
| 109 | .elementor-nav-menu--dropdown.elementor-nav-menu__container { |
| 110 | left: 0 !important; |
| 111 | right: auto !important; |
| 112 | } |
| 113 | |
| 114 | .ecs-nav-layout-dropdown.ecs-toggle-align-center:not(.elementor-nav-menu--stretch) |
| 115 | .elementor-nav-menu--dropdown.elementor-nav-menu__container { |
| 116 | left: 50% !important; |
| 117 | right: auto !important; |
| 118 | transform: translateX( -50% ) !important; |
| 119 | } |
| 120 | |
| 121 | .ecs-nav-layout-dropdown.ecs-toggle-align-right:not(.elementor-nav-menu--stretch) |
| 122 | .elementor-nav-menu--dropdown.elementor-nav-menu__container { |
| 123 | right: 0 !important; |
| 124 | left: auto !important; |
| 125 | } |
| 126 | |
| 127 | /* ═══════════════════════════════════════════════════════════ |
| 128 | FIX: Dropdown panel width (Fill Width ON) |
| 129 | ═══════════════════════════════════════════════════════════ */ |
| 130 | |
| 131 | /* Fill Width ON → full width, only when ECS dropdown layout is active. |
| 132 | Gated on ecs-nav-layout-dropdown so it never fires on menus using the |
| 133 | native Elementor Breakpoint (ecs-nav-layout-horizontal/vertical), where |
| 134 | Smartmenus.js owns positioning and width: 100% collapses the dropdown |
| 135 | to the toggle's narrow flex-item width instead of the viewport width. */ |
| 136 | .ecs-nav-layout-dropdown.elementor-nav-menu--stretch |
| 137 | .elementor-nav-menu--dropdown.elementor-nav-menu__container { |
| 138 | width: 100% !important; |
| 139 | left: 0 !important; |
| 140 | } |
| 141 | |
| 142 | /* ═══════════════════════════════════════════════════════════ |
| 143 | AUTO-RESPECT ELEMENTOR NATIVE BREAKPOINT |
| 144 | When no ECS layout is explicitly set for mobile/tablet, the |
| 145 | Elementor native Breakpoint control should still trigger |
| 146 | dropdown mode. These selectors have specificity 0,3,0 which |
| 147 | beats the base variable rules (0,2,0) even at equal !important, |
| 148 | so they override the inherited --ecs-nav-toggle-display:none. |
| 149 | Only targets horizontal/vertical — dropdown already works. |
| 150 | ═══════════════════════════════════════════════════════════ */ |
| 151 | |
| 152 | @media (max-width: 767px) { |
| 153 | .ecs-nav-layout-horizontal.elementor-nav-menu--dropdown-mobile .elementor-nav-menu--main, |
| 154 | .ecs-nav-layout-vertical.elementor-nav-menu--dropdown-mobile .elementor-nav-menu--main { |
| 155 | display: none !important; |
| 156 | } |
| 157 | .ecs-nav-layout-horizontal.elementor-nav-menu--dropdown-mobile .elementor-menu-toggle, |
| 158 | .ecs-nav-layout-vertical.elementor-nav-menu--dropdown-mobile .elementor-menu-toggle { |
| 159 | display: flex !important; |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | @media (max-width: 1024px) { |
| 164 | .ecs-nav-layout-horizontal.elementor-nav-menu--dropdown-tablet .elementor-nav-menu--main, |
| 165 | .ecs-nav-layout-vertical.elementor-nav-menu--dropdown-tablet .elementor-nav-menu--main { |
| 166 | display: none !important; |
| 167 | } |
| 168 | .ecs-nav-layout-horizontal.elementor-nav-menu--dropdown-tablet .elementor-menu-toggle, |
| 169 | .ecs-nav-layout-vertical.elementor-nav-menu--dropdown-tablet .elementor-menu-toggle { |
| 170 | display: flex !important; |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | /* ═══════════════════════════════════════════════════════════ |
| 175 | FORCE BREAKPOINT |
| 176 | When "Force Breakpoint" is ON, the menu switches to dropdown |
| 177 | mode at the Elementor native breakpoint regardless of the ECS |
| 178 | Layout setting. |
| 179 | |
| 180 | Combines .ecs-force-breakpoint (ECS prefix_class) with |
| 181 | .elementor-nav-menu--dropdown-{mobile|tablet} (Elementor's own |
| 182 | prefix_class for the Breakpoint control) to target exactly the |
| 183 | right viewport without needing JS. |
| 184 | |
| 185 | CSS custom properties use !important to override the inline |
| 186 | variables set by ecs-mobile-menu-editor.js in the editor. |
| 187 | ═══════════════════════════════════════════════════════════ */ |
| 188 | |
| 189 | /* Frontend: Mobile breakpoint (≤ 767px) */ |
| 190 | @media (max-width: 767px) { |
| 191 | .ecs-force-breakpoint.elementor-nav-menu--dropdown-mobile { |
| 192 | --ecs-nav-main-display: none !important; |
| 193 | --ecs-nav-main-dir: row !important; |
| 194 | --ecs-nav-toggle-display: flex !important; |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | /* Frontend: Tablet breakpoint (≤ 1024px, covers mobile too) */ |
| 199 | @media (max-width: 1024px) { |
| 200 | .ecs-force-breakpoint.elementor-nav-menu--dropdown-tablet { |
| 201 | --ecs-nav-main-display: none !important; |
| 202 | --ecs-nav-main-dir: row !important; |
| 203 | --ecs-nav-toggle-display: flex !important; |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | /* Editor device preview */ |
| 208 | body.elementor-device-mobile .ecs-force-breakpoint.elementor-nav-menu--dropdown-mobile, |
| 209 | body.elementor-device-mobile .ecs-force-breakpoint.elementor-nav-menu--dropdown-tablet, |
| 210 | body.elementor-device-tablet .ecs-force-breakpoint.elementor-nav-menu--dropdown-tablet { |
| 211 | --ecs-nav-main-display: none !important; |
| 212 | --ecs-nav-main-dir: row !important; |
| 213 | --ecs-nav-toggle-display: flex !important; |
| 214 | } |
| 215 |