From e731de490ca6375eef7309cb3ef6bf1452dcf0e1 Mon Sep 17 00:00:00 2001 From: sight <26325820+Sight-wcg@users.noreply.github.com> Date: Sun, 7 Sep 2025 19:27:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(dropdown):=20=E6=94=AF=E6=8C=81=E5=90=8C?= =?UTF-8?q?=E6=97=B6=E6=89=93=E5=BC=80=E5=A4=9A=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/dropdown.js | 161 ++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 80 deletions(-) diff --git a/src/modules/dropdown.js b/src/modules/dropdown.js index 6cb5ccbb..db01703e 100644 --- a/src/modules/dropdown.js +++ b/src/modules/dropdown.js @@ -95,6 +95,8 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { var that = this; that.index = ++dropdown.index; that.config = $.extend({}, that.config, dropdown.config, options); + that.stopClickOutsideEvent = $.noop; + that.stopResizeEvent = $.noop; that.init(); }; @@ -307,18 +309,25 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { mainElem.attr('style', options.style); // 辞旧迎新 - that.remove(dropdown.thisId); + that.remove(options.id); options.target.append(mainElem); options.elem.data(MOD_INDEX_OPENED, true); // 面板已打开的标记 // 遮罩 var shade = options.shade ? ('
') : ''; - mainElem.before(shade); + var shadeElem = $(shade); + // 处理移动端点击穿透问题 + if(clickOrMousedown === 'touchstart'){ + shadeElem.on(clickOrMousedown, function(e){ + e.preventDefault(); + }) + } + mainElem.before(shadeElem); // 如果是鼠标移入事件,则鼠标移出时自动关闭 if(options.trigger === 'mouseenter'){ mainElem.on('mouseenter', function(){ - clearTimeout(thisModule.timer); + clearTimeout(that.timer); }).on('mouseleave', function(){ that.delayRemove(); }); @@ -326,7 +335,6 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { } that.position(); // 定位坐标 - dropdown.thisId = options.id; // 当前打开的面板 id // 阻止全局事件 mainElem.find('.layui-menu').on(clickOrMousedown, function(e){ @@ -369,6 +377,11 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { resizeObserver.observe(mainElem[0], that.position.bind(that)); } + that.stopClickOutsideEvent(); + that.stopResizeEvent(); + that.stopClickOutsideEvent = that.onClickOutside(); + that.stopResizeEvent = that.autoUpdatePosition(); + // 组件打开完毕的事件 typeof options.ready === 'function' && options.ready(mainElem, options.elem); }; @@ -410,6 +423,12 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { delete dropdown.thisId; typeof options.close === 'function' && options.close(options.elem); } + + // 关闭后移除全局事件 + that.stopResizeEvent(); + that.stopResizeEvent = $.noop; + that.stopClickOutsideEvent(); + that.stopClickOutsideEvent = $.noop; }; Class.prototype.normalizedDelay = function(){ @@ -427,9 +446,9 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { Class.prototype.delayRemove = function(){ var that = this; var options = that.config; - clearTimeout(thisModule.timer); + clearTimeout(that.timer); - thisModule.timer = setTimeout(function(){ + that.timer = setTimeout(function(){ that.remove(); }, that.normalizedDelay().hide); }; @@ -449,7 +468,7 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { // 触发元素事件 options.elem.off(trigger).on(trigger, function(e) { - clearTimeout(thisModule.timer); + clearTimeout(that.timer); that.e = e; // 主面板是否已打开 @@ -458,7 +477,7 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { // 若为鼠标移入事件,则延迟触发 if (isMouseEnter) { if (!opened) { - thisModule.timer = setTimeout(function(){ + that.timer = setTimeout(function(){ that.render(); }, that.normalizedDelay().show); } @@ -483,6 +502,60 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { } }; + /** + * 点击面板外部时的事件 + * @returns {() => void} 返回一个函数,用于取消事件 + */ + Class.prototype.onClickOutside = function () { + var that = this; + var options = that.config; + var isCtxMenu = options.trigger === 'contextmenu'; + var isTopElem = lay.isTopElem(options.elem[0]); + + return lay.onClickOutside( + that.mainElem[0], + function (e) { + // 点击面板外部时的事件 + if(typeof options.onClickOutside === 'function'){ + var shouldClose = options.onClickOutside(e); + if(shouldClose === false) return; + } + + that.remove(); + }, + { + ignore: (isCtxMenu || isTopElem) ? null : [options.elem[0]], + event: clickOrMousedown, + capture: false, + detectIframe: true + } + ); + }; + + /** + * 窗口大小变化时自动更新位置 + * @returns {() => void} 返回一个函数,用于取消事件 + */ + Class.prototype.autoUpdatePosition = function(){ + var that = this; + var options = that.config; + + var handleResize = function(){ + if(that.mainElem && (!that.mainElem[0] || !that.mainElem.is(':visible'))) return; + if(options.trigger === 'contextmenu'){ + that.remove(); + } else { + that.position(); + } + } + + $(window).on('resize.lay_dropdown_resize', handleResize) + + return function(){ + $(window).off('resize.lay_dropdown_resize') + } + } + // 记录所有实例 thisModule.that = {}; // 记录所有实例对象 @@ -535,78 +608,6 @@ layui.define(['i18n', 'jquery', 'laytpl', 'lay', 'util'], function(exports) { var _WIN = $(window); var _DOC = $(document); - // 自适应定位 - _WIN.on('resize', function(){ - if(!dropdown.thisId) return; - var that = thisModule.getThis(dropdown.thisId); - if(!that) return; - - if((that.mainElem && !that.mainElem[0]) || !$('.'+ STR_ELEM)[0]){ - return false; - } - - var options = that.config; - - if(options.trigger === 'contextmenu'){ - that.remove(); - } else { - that.position(); - } - }); - - - - // 点击任意处关闭 - lay(_DOC).on(clickOrMousedown, function(e){ - if(!dropdown.thisId) return; - var that = thisModule.getThis(dropdown.thisId) - if(!that) return; - - var options = that.config; - var isTopElem = lay.isTopElem(options.elem[0]); - var isCtxMenu = options.trigger === 'contextmenu'; - - // 若触发的是绑定的元素,或者属于绑定元素的子元素,则不关闭 - // 满足条件:当前绑定的元素是 body document,或者是鼠标右键事件时,忽略绑定元素 - var isTriggerTarget = !(isTopElem || isCtxMenu) && (options.elem[0] === e.target || options.elem.find(e.target)[0]); - var isPanelTarget = that.mainElem && (e.target === that.mainElem[0] || that.mainElem.find(e.target)[0]); - if(isTriggerTarget || isPanelTarget) return; - // 处理移动端点击穿透问题 - if(e.type === 'touchstart' && options.elem.data(MOD_INDEX_OPENED)){ - $(e.target).hasClass(STR_ELEM_SHADE) && e.preventDefault(); - } - - // 点击 dropdown 外部时的回调 - if(typeof options.onClickOutside === 'function'){ - var shouldClose = options.onClickOutside(e); - if(shouldClose === false) return; - } - - that.remove(); - }, lay.passiveSupported ? { passive: false} : false); - - // onClickOutside 检测 iframe - _WIN.on('blur', function(e){ - if(!dropdown.thisId) return; - var that = thisModule.getThis(dropdown.thisId) - if(!that) return; - if(!that.config.elem.data(MOD_INDEX_OPENED)) return; - - setTimeout(function(){ - if(document.activeElement && document.activeElement.tagName === 'IFRAME' - && that.mainElem && that.mainElem[0] - && that.mainElem[0].contains && !that.mainElem[0].contains(document.activeElement) - ){ - // 点击 dropdown 外部时的回调 - if(typeof that.config.onClickOutside === 'function'){ - var shouldClose = that.config.onClickOutside(e.originalEvent); - if(shouldClose === false) return; - } - that.remove(); - } - }, 0); - }) - // 基础菜单的静态元素事件 var ELEM_LI = '.layui-menu:not(.layui-dropdown-menu) li'; _DOC.on('click', ELEM_LI, function(e){