const LEnhaancerVer = "301"; const PP_LIST_MENU_ENHANCER_DEFAULTS = { logPrefix: "[PowerPagesListMenuEnhancer]", triggerSelector: "[data-automation-key='ppNativeListContextualMenu']", debug: true, triggerIcon: "hamburger", triggerIconColor: null, manageBaseClick: false, baseClickHandlerName: "BaseClick", baseClickActionId: null }; const ppListMenuEnhancerVer = "2.1.6"; if (typeof globalThis !== "undefined") { globalThis.PowerPagesListMenuEnhancerVersion = ppListMenuEnhancerVer; } class PowerPagesListMenuEnhancer { constructor(options) { var rawOptions = options || {}; options = Object.assign({}, PP_LIST_MENU_ENHANCER_DEFAULTS, rawOptions); this.version = ppListMenuEnhancerVer; this.logPrefix = options.logPrefix || PP_LIST_MENU_ENHANCER_DEFAULTS.logPrefix; this.logPrefixWithVersion = this.logPrefix + " v" + this.version; this.triggerSelector = options.triggerSelector || PP_LIST_MENU_ENHANCER_DEFAULTS.triggerSelector; this.listKey = options.listKey || null; this.listSelector = options.listSelector || (this.listKey ? "pages-grid[list-id='" + this.escapeCssAttrValue(this.listKey) + "']" : null); this.guidSelector = options.guidSelector || null; this.instanceKey = options.instanceKey || (this.logPrefix + (this.listSelector || "")).replace(/[^a-zA-Z0-9_-]/g, "_"); this.pendingRowContext = null; this.menuObserver = null; this.actions = []; this.isAwaitingMenuForClick = false; this.debug = typeof options.debug === "boolean" ? options.debug : !!PP_LIST_MENU_ENHANCER_DEFAULTS.debug; this.nativeActionsConfig = options.nativeActionsConfig || {}; this.nativeActionLabelsToHide = Array.isArray(this.nativeActionsConfig.labelsToHide) ? this.nativeActionsConfig.labelsToHide : []; var hasManageBaseClick = Object.prototype.hasOwnProperty.call(rawOptions, "manageBaseClick"); var hasManageBaseClickPascal = Object.prototype.hasOwnProperty.call(rawOptions, "ManageBaseClick"); var hasBaseClickHandlerName = Object.prototype.hasOwnProperty.call(rawOptions, "baseClickHandlerName"); var hasBaseClickHandlerNamePascal = Object.prototype.hasOwnProperty.call(rawOptions, "BaseClickHandlerName"); var hasBaseClickActionId = Object.prototype.hasOwnProperty.call(rawOptions, "baseClickActionId"); var hasBaseClickActionIdPascal = Object.prototype.hasOwnProperty.call(rawOptions, "BaseClickActionId"); this.manageBaseClick = hasManageBaseClick ? !!rawOptions.manageBaseClick : (hasManageBaseClickPascal ? !!rawOptions.ManageBaseClick : !!PP_LIST_MENU_ENHANCER_DEFAULTS.manageBaseClick); this.baseClickHandlerName = hasBaseClickHandlerName ? rawOptions.baseClickHandlerName : (hasBaseClickHandlerNamePascal ? rawOptions.BaseClickHandlerName : PP_LIST_MENU_ENHANCER_DEFAULTS.baseClickHandlerName); this.baseClickActionId = hasBaseClickActionId ? rawOptions.baseClickActionId : (hasBaseClickActionIdPascal ? rawOptions.BaseClickActionId : PP_LIST_MENU_ENHANCER_DEFAULTS.baseClickActionId); this.triggerIcon = options.triggerIcon == null || options.triggerIcon === "" ? PP_LIST_MENU_ENHANCER_DEFAULTS.triggerIcon : options.triggerIcon; this.triggerIconColor = options.triggerIconColor || null; this.icons = { hamburger: '', gear: '' + '', pencil: '', threeDotsVertical: '' }; this.ensureStylesInjected(); this.log("Initialized", { version: this.version, triggerSelector: this.triggerSelector, triggerIcon: this.triggerIcon, manageBaseClick: this.manageBaseClick, baseClickHandlerName: this.baseClickHandlerName, baseClickActionId: this.baseClickActionId, listKey: this.listKey, listSelector: this.listSelector, debug: this.debug }); } log() { var args = Array.prototype.slice.call(arguments); args.unshift(this.logPrefixWithVersion || this.logPrefix); console.log.apply(console, args); } debugLog() { if (!this.debug) return; var args = Array.prototype.slice.call(arguments); args.unshift(this.logPrefixWithVersion || this.logPrefix); args.unshift("[debug]"); console.log.apply(console, args); } normalizeText(value) { return String(value || "").replace(/\s+/g, " ").trim().toLowerCase(); } decodeUriSafe(value) { if (value == null) return ""; var text = String(value); try { return decodeURIComponent(text); } catch (e) { return text; } } formatCompactGuid(compact) { var normalized = String(compact || "").replace(/[^0-9a-fA-F]/g, ""); if (normalized.length !== 32) return null; return ( normalized.slice(0, 8) + "-" + normalized.slice(8, 12) + "-" + normalized.slice(12, 16) + "-" + normalized.slice(16, 20) + "-" + normalized.slice(20) ).toLowerCase(); } extractGuid(value) { var text = String(value || ""); var candidates = [text, this.decodeUriSafe(text)]; for (var i = 0; i < candidates.length; i++) { var candidate = candidates[i]; if (!candidate) continue; var hyphenated = candidate.match(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/); if (hyphenated) return hyphenated[0].toLowerCase(); var compact = candidate.match(/\b[0-9a-fA-F]{32}\b/); if (compact) return this.formatCompactGuid(compact[0]); } return null; } extractGuidFromElement(element) { if (!element) return null; var attributesToCheck = ["data-guid", "data-id", "data-record-id", "data-entityid", "data-row-id", "value", "id", "href", "onclick", "aria-label", "name"]; for (var i = 0; i < attributesToCheck.length; i++) { var attrValue = element.getAttribute(attributesToCheck[i]); var guidFromAttr = this.extractGuid(attrValue); if (guidFromAttr) return guidFromAttr; } if (element.attributes && element.attributes.length) { for (var j = 0; j < element.attributes.length; j++) { var attr = element.attributes[j]; if (!attr) continue; var guidFromAnyAttr = this.extractGuid(attr.value); if (guidFromAnyAttr) return guidFromAnyAttr; } } if (typeof element.value === "string") { var guidFromValue = this.extractGuid(element.value); if (guidFromValue) return guidFromValue; } var guidFromText = this.extractGuid(element.textContent); if (guidFromText) return guidFromText; return this.extractGuid(element.innerHTML); } getGuidFromRow(row) { if (!row) return null; var guid = null; if (this.guidSelector) { var selectedElement = null; if (typeof row.matches === "function" && row.matches(this.guidSelector)) { selectedElement = row; } else { selectedElement = row.querySelector(this.guidSelector); } guid = this.extractGuidFromElement(selectedElement); if (guid) return guid; } guid = this.extractGuidFromElement(row); if (guid) return guid; var hiddenInputs = row.querySelectorAll("input[type='hidden']"); for (var i = 0; i < hiddenInputs.length; i++) { guid = this.extractGuidFromElement(hiddenInputs[i]); if (guid) return guid; } var cells = row.querySelectorAll("[role='gridcell'], td"); for (var j = 0; j < cells.length; j++) { guid = this.extractGuidFromElement(cells[j]); if (guid) return guid; } return null; } getGuidFromTrigger(trigger) { if (!trigger) return null; var node = trigger; while (node && node !== document.body) { var guidFromNode = this.extractGuidFromElement(node); if (guidFromNode) return guidFromNode; node = node.parentElement; } var guidFromTriggerHtml = this.extractGuid(trigger.outerHTML); if (guidFromTriggerHtml) return guidFromTriggerHtml; return null; } getGuidFromMenuRoot(menuRoot) { if (!menuRoot) return null; var candidates = menuRoot.querySelectorAll("*"); var guidFromRoot = this.extractGuidFromElement(menuRoot); if (guidFromRoot) return guidFromRoot; for (var i = 0; i < candidates.length; i++) { var candidate = candidates[i]; var guidFromElement = this.extractGuidFromElement(candidate); if (guidFromElement) return guidFromElement; var hrefGuid = this.extractGuid(candidate.getAttribute("href")); if (hrefGuid) return hrefGuid; var onClickGuid = this.extractGuid(candidate.getAttribute("onclick")); if (onClickGuid) return onClickGuid; } return this.extractGuid(menuRoot.outerHTML); } resolveContextGuid(context, menuRoot) { var baseContext = context || {}; if (baseContext.guid) return baseContext; var resolvedGuid = null; var resolvedSource = null; if (baseContext.row) { resolvedGuid = this.getGuidFromRow(baseContext.row); if (resolvedGuid) resolvedSource = "row"; } if (!resolvedGuid && baseContext.trigger) { resolvedGuid = this.getGuidFromTrigger(baseContext.trigger); if (resolvedGuid) resolvedSource = "trigger"; } if (!resolvedGuid) { resolvedGuid = this.getGuidFromMenuRoot(menuRoot); if (resolvedGuid) resolvedSource = "native-menu"; } if (!resolvedGuid) return baseContext; this.log("Resolved GUID from", resolvedSource + ":", resolvedGuid); return Object.assign({}, baseContext, { guid: resolvedGuid, guidSource: resolvedSource }); } escapeCssAttrValue(value) { return String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'"); } ensureStylesInjected() { var styleId = "pp-custom-actions-styles"; if (document.getElementById(styleId)) return; var customActionStyles = document.createElement("style"); customActionStyles.id = styleId; customActionStyles.textContent = ".custom-menu-action { justify-content: flex-start; }" + ".custom-menu-action:hover, .custom-menu-action:focus-visible {" + " background-color: var(--bs-dropdown-link-hover-bg, rgba(0, 0, 0, 0.05));" + " color: var(--bs-dropdown-link-hover-color, inherit);" + "}"; document.head.appendChild(customActionStyles); } createSvgIcon(svgPaths, color) { var icon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); icon.setAttribute("xmlns", "http://www.w3.org/2000/svg"); icon.setAttribute("width", "16"); icon.setAttribute("height", "16"); icon.setAttribute("fill", "currentColor"); icon.setAttribute("viewBox", "0 0 16 16"); icon.setAttribute("aria-hidden", "true"); icon.setAttribute("focusable", "false"); icon.style.pointerEvents = "none"; icon.innerHTML = svgPaths; if (color) { icon.style.color = color; } return icon; } createIcon(name, color) { var svgPaths = this.icons[name]; if (!svgPaths) { this.log("Unknown triggerIcon name:", name, "Available icons:", Object.keys(this.icons)); return null; } return this.createSvgIcon(svgPaths, color); } resolveTriggerIconNode() { if (!this.triggerIcon) return null; if (typeof this.triggerIcon === "string") { var namedIcon = this.createIcon(this.triggerIcon, this.triggerIconColor); if (!namedIcon) { this.log("Failed to resolve trigger icon from string:", this.triggerIcon); } return namedIcon; } if (typeof this.triggerIcon === "function") { var generatedIcon = this.triggerIcon(); if (!generatedIcon) { this.log("triggerIcon function returned no node."); } return generatedIcon && typeof generatedIcon.cloneNode === "function" ? generatedIcon : null; } return this.triggerIcon && typeof this.triggerIcon.cloneNode === "function" ? this.triggerIcon : null; } describeElement(element) { if (!element || element.nodeType !== 1) return "(none)"; var tag = (element.tagName || "").toLowerCase(); var id = element.id ? ("#" + element.id) : ""; var className = typeof element.className === "string" && element.className.trim() ? ("." + element.className.trim().split(/\s+/).slice(0, 3).join(".")) : ""; var automationKey = element.getAttribute("data-automation-key"); var keyPart = automationKey ? ("[data-automation-key='" + automationKey + "']") : ""; return tag + id + className + keyPart; } diagnoseTriggerIcons() { var triggers = this.collectMatchesDeep(document, this.triggerSelector); var inScope = 0; var alreadyApplied = 0; for (var i = 0; i < triggers.length; i++) { var trigger = triggers[i]; if (this.isTriggerInScope(trigger)) { inScope++; } if (trigger.getAttribute("data-pp-trigger-icon-applied") === "true") { alreadyApplied++; } } var result = { version: this.version, triggerSelector: this.triggerSelector, triggerIcon: this.triggerIcon, found: triggers.length, inScope: inScope, alreadyApplied: alreadyApplied }; this.log("Trigger icon diagnostics:", result); return result; } applyTriggerIcon(trigger) { if (!trigger || !this.triggerIcon) return; var iconNode = this.resolveTriggerIconNode(); if (!iconNode) { this.log("No icon node resolved; skipping trigger:", this.describeElement(trigger)); return; } var target = trigger.querySelector("svg, i, [class*='icon'], [class*='Icon']") || trigger; this.debugLog("Applying trigger icon", { trigger: this.describeElement(trigger), replaceTarget: this.describeElement(target), triggerIcon: this.triggerIcon }); var replacement = iconNode.cloneNode(true); var targetTag = target && target.tagName ? target.tagName.toLowerCase() : ""; if (target !== trigger) { if (targetTag === "svg") { target.setAttribute("width", replacement.getAttribute("width") || "16"); target.setAttribute("height", replacement.getAttribute("height") || "16"); target.setAttribute("fill", replacement.getAttribute("fill") || "currentColor"); target.setAttribute("viewBox", replacement.getAttribute("viewBox") || "0 0 16 16"); target.setAttribute("aria-hidden", "true"); target.setAttribute("focusable", "false"); target.style.pointerEvents = "none"; if (this.triggerIconColor) { target.style.color = this.triggerIconColor; } target.innerHTML = replacement.innerHTML; } else { target.textContent = ""; target.appendChild(replacement); } } else { trigger.textContent = ""; trigger.appendChild(replacement); } trigger.setAttribute("data-pp-trigger-icon-applied", "true"); this.debugLog("Trigger icon updated."); } applyTriggerIconsInScope() { if (!this.triggerIcon) return; var triggers = this.collectMatchesDeep(document, this.triggerSelector); if (!triggers.length) { this.log("No trigger nodes found for selector:", this.triggerSelector); return; } var appliedCount = 0; var outOfScopeCount = 0; var alreadyAppliedCount = 0; for (var i = 0; i < triggers.length; i++) { var trigger = triggers[i]; if (!this.isTriggerInScope(trigger)) { outOfScopeCount++; continue; } if (trigger.getAttribute("data-pp-trigger-icon-applied") === "true") { alreadyAppliedCount++; continue; } this.applyTriggerIcon(trigger); appliedCount++; } this.debugLog("Trigger icon sweep", { found: triggers.length, applied: appliedCount, alreadyApplied: alreadyAppliedCount, outOfScope: outOfScopeCount }); } collectMatchesDeep(root, selector) { var results = []; if (!root || !selector) return results; function visit(node) { if (!node) return; if (node.nodeType === 1 && typeof node.matches === "function" && node.matches(selector)) { results.push(node); } var children = node.children || []; for (var i = 0; i < children.length; i++) { visit(children[i]); } if (node.shadowRoot) { visit(node.shadowRoot); } } visit(root); return results; } findFirstDeep(root, selector) { var matches = this.collectMatchesDeep(root, selector); return matches.length ? matches[0] : null; } findScopedHostFromTrigger(trigger) { if (!trigger) return null; if (this.listSelector) { var selectorMatch = trigger.closest(this.listSelector); if (selectorMatch) return selectorMatch; } if (this.listKey) { var keySelector = "pages-grid[list-id='" + this.escapeCssAttrValue(this.listKey) + "']"; var containers = document.querySelectorAll("pages-native-container"); for (var i = 0; i < containers.length; i++) { var container = containers[i]; if (container.querySelector(keySelector) && container.contains(trigger)) { return container; } } } return null; } getRowContextFromTrigger(trigger) { var row = trigger.closest("[role='row'], tr"); if (!row) return null; var firstCell = row.querySelector("[role='gridcell'], td"); var label = firstCell ? (firstCell.innerText || "").trim() : "Row"; var listRoot = this.findScopedHostFromTrigger(trigger); var guid = this.getGuidFromRow(row); return { row: row, label: label, guid: guid, trigger: trigger, listRoot: listRoot, listKey: this.listKey || null }; } getRowContextFromRow(row, listRoot) { if (!row) return null; var firstCell = row.querySelector("[role='gridcell'], td"); var label = firstCell ? (firstCell.innerText || "").trim() : "Row"; var guid = this.getGuidFromRow(row); return { row: row, label: label, guid: guid, listRoot: listRoot || null, listKey: this.listKey || null }; } isTriggerInScope(trigger) { if (!this.listSelector && !this.listKey) return true; return !!this.findScopedHostFromTrigger(trigger); } findOpenMenuRoot() { var menus = Array.from(document.querySelectorAll("[role='menu']")); if (!menus.length) return null; for (var i = menus.length - 1; i >= 0; i--) { var menu = menus[i]; var style = window.getComputedStyle(menu); if (style.display !== "none" && style.visibility !== "hidden") return menu; } return null; } getScopedListRoots() { if (this.listSelector) { return this.collectMatchesDeep(document, this.listSelector); } if (this.listKey) { var keySelector = "pages-grid[list-id='" + this.escapeCssAttrValue(this.listKey) + "']"; return this.collectMatchesDeep(document, keySelector); } return this.collectMatchesDeep(document, "pages-grid"); } listHasNativeActions(listRoot) { if (!listRoot) return false; return !!this.findFirstDeep(listRoot, this.triggerSelector); } addAction(config) { if (!config || !config.id || !config.label || typeof config.onClick !== "function") { throw new Error("Action must include: id, label, onClick(context)"); } this.actions.push({ id: config.id, label: config.label, ariaLabel: config.ariaLabel || config.label, icon: config.icon || null, onClick: config.onClick }); return this; } addActions(configs) { if (!Array.isArray(configs)) { throw new Error("addActions expects an array of action configs"); } for (var i = 0; i < configs.length; i++) { this.addAction(configs[i]); } return this; } shouldRenderActionInMenu(action) { if (!action || !action.id) return false; if (this.baseClickActionId && action.id === this.baseClickActionId) { return false; } return true; } createMenuItem(action, context) { var item = document.createElement("button"); item.type = "button"; item.setAttribute("role", "menuitem"); item.className = "dropdown-item custom-menu-action custom-menu-action-" + action.id; item.setAttribute("data-custom-action-id", this.instanceKey + "__" + action.id); item.setAttribute("aria-label", action.ariaLabel); item.style.display = "flex"; item.style.alignItems = "center"; item.style.gap = "8px"; item.style.width = "100%"; item.style.textAlign = "left"; item.style.padding = "8px 12px"; item.style.cursor = "pointer"; if (action.icon) { item.appendChild(action.icon.cloneNode(true)); } var text = document.createElement("span"); text.textContent = action.label; item.appendChild(text); item.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); action.onClick(context || {}); }); return item; } injectIntoMenu(menuRoot, context) { if (!menuRoot) return false; var actionsToRender = []; for (var a = 0; a < this.actions.length; a++) { if (this.shouldRenderActionInMenu(this.actions[a])) { actionsToRender.push(this.actions[a]); } } if (!actionsToRender.length && !this.nativeActionLabelsToHide.length) { this.debugLog("No custom menu actions to render for this menu."); return true; } var resolvedContext = this.resolveContextGuid(context, menuRoot); this.applyNativeActionVisibility(menuRoot); var existingMenuItems = menuRoot.querySelectorAll("[role='menuitem']"); var container = existingMenuItems.length ? (existingMenuItems[existingMenuItems.length - 1].parentElement || menuRoot) : menuRoot; var injectedCount = 0; for (var i = 0; i < actionsToRender.length; i++) { var action = actionsToRender[i]; var actionDomId = this.instanceKey + "__" + action.id; if (menuRoot.querySelector("[data-custom-action-id='" + actionDomId + "']")) { continue; } container.appendChild(this.createMenuItem(action, resolvedContext)); injectedCount++; } if (injectedCount > 0) { this.log("Injected", injectedCount, "custom action(s) into dropdown menu."); } return injectedCount > 0; } isCustomMenuItem(menuItem) { if (!menuItem) return false; var customActionId = menuItem.getAttribute("data-custom-action-id") || ""; return customActionId.indexOf(this.instanceKey + "__") === 0; } matchesNativeActionLabel(menuItem) { if (!menuItem || !this.nativeActionLabelsToHide.length) return false; var candidates = [ this.normalizeText(menuItem.textContent), this.normalizeText(menuItem.getAttribute("aria-label")), this.normalizeText(menuItem.getAttribute("title")), this.normalizeText(menuItem.getAttribute("data-original-title")) ]; var hasCandidate = false; for (var c = 0; c < candidates.length; c++) { if (candidates[c]) { hasCandidate = true; break; } } if (!hasCandidate) return false; for (var i = 0; i < this.nativeActionLabelsToHide.length; i++) { var label = this.nativeActionLabelsToHide[i]; if (!label || typeof label !== "string") continue; var normalizedLabel = this.normalizeText(label); for (var j = 0; j < candidates.length; j++) { var candidate = candidates[j]; if (!candidate) continue; if (candidate === normalizedLabel || candidate.indexOf(normalizedLabel) === 0) { return true; } } } return false; } shouldHideNativeAction(menuItem) { return this.matchesNativeActionLabel(menuItem); } applyNativeActionVisibility(menuRoot) { if (!menuRoot) return; if (!this.nativeActionLabelsToHide.length) return; var menuItems = menuRoot.querySelectorAll("[role='menuitem']"); var affected = 0; this.debugLog("Scanning menu items:", menuItems.length); for (var i = 0; i < menuItems.length; i++) { var menuItem = menuItems[i]; if (this.isCustomMenuItem(menuItem)) continue; var matched = this.shouldHideNativeAction(menuItem); this.debugLog( "Native menu item", i, { text: this.normalizeText(menuItem.textContent), ariaLabel: this.normalizeText(menuItem.getAttribute("aria-label")), title: this.normalizeText(menuItem.getAttribute("title")), matched: matched } ); if (!matched) continue; menuItem.style.display = "none"; menuItem.setAttribute("aria-hidden", "true"); affected++; } if (affected > 0) { this.log("Native action(s) hidden:", affected); } else { this.debugLog("No native actions matched hide rules."); } } baseClickDecision(shouldHandle, reason, details) { this.debugLog("Base click decision:", { shouldHandle: shouldHandle, reason: reason, details: details || null }); return shouldHandle; } shouldHandleAsBaseClick(target) { if (!this.manageBaseClick || !target) { return this.baseClickDecision(false, "manageBaseClick disabled or target missing", { manageBaseClick: this.manageBaseClick, hasTarget: !!target }); } if (target.closest("[role='menu']")) { return this.baseClickDecision(false, "click is inside menu", { target: this.describeElement(target) }); } if (target.closest(this.triggerSelector)) { return this.baseClickDecision(false, "click is on menu trigger", { target: this.describeElement(target), triggerSelector: this.triggerSelector }); } var row = target.closest("[role='row'], tr"); if (!row) { return this.baseClickDecision(false, "no row ancestor", { target: this.describeElement(target) }); } var scopedHost = this.findScopedHostFromTrigger(target); if (!scopedHost && (this.listSelector || this.listKey)) { return this.baseClickDecision(false, "row not in configured scope", { target: this.describeElement(target), listSelector: this.listSelector, listKey: this.listKey }); } var clickTarget = target.closest("a, button, [role='button']"); if (clickTarget) { var ariaHasPopup = (clickTarget.getAttribute("aria-haspopup") || "").toLowerCase(); if (ariaHasPopup === "menu") { return this.baseClickDecision(false, "click target opens a menu", { clickTarget: this.describeElement(clickTarget) }); } var automationKey = clickTarget.getAttribute("data-automation-key") || ""; if (this.normalizeText(automationKey) === this.normalizeText("ppNativeListContextualMenu")) { return this.baseClickDecision(false, "native contextual menu trigger", { clickTarget: this.describeElement(clickTarget), automationKey: automationKey }); } } return this.baseClickDecision(true, "eligible for base click interception", { target: this.describeElement(target), clickTarget: this.describeElement(clickTarget), row: this.describeElement(row) }); } resolveBaseClickAction() { if (!this.actions.length) return null; if (this.baseClickActionId) { for (var i = 0; i < this.actions.length; i++) { if (this.actions[i].id === this.baseClickActionId) { return this.actions[i]; } } } return this.actions[0]; } BaseClick(context, event) { var baseContext = context || {}; if (this.baseClickHandlerName && typeof globalThis !== "undefined") { var globalHandler = globalThis[this.baseClickHandlerName]; if (typeof globalHandler === "function") { this.log("BaseClick routed to global handler:", this.baseClickHandlerName); globalHandler(baseContext, event); return true; } } var targetAction = this.resolveBaseClickAction(); if (targetAction && typeof targetAction.onClick === "function") { this.log("BaseClick routed to action:", targetAction.id); targetAction.onClick(baseContext); return true; } this.log("BaseClick handler not found. Configure baseClickHandlerName or add actions/baseClickActionId."); return false; } handleBaseClickEvent(e, sourceEventName) { var target = e && e.target; if (target && target.nodeType === 3) { target = target.parentElement; } if (!target || target.nodeType !== 1) { this.debugLog("Base click event had no valid element target.", { sourceEventName: sourceEventName }); return; } this.debugLog("Base click candidate event", { sourceEventName: sourceEventName, target: this.describeElement(target), defaultPrevented: !!(e && e.defaultPrevented) }); if (!this.shouldHandleAsBaseClick(target)) return; var row = target.closest("[role='row'], tr"); var listRoot = this.findScopedHostFromTrigger(target); var rowContext = this.getRowContextFromRow(row, listRoot); rowContext = this.resolveContextGuid(Object.assign({}, rowContext, { trigger: target }), null); if (e && typeof e.preventDefault === "function") e.preventDefault(); if (e && typeof e.stopPropagation === "function") e.stopPropagation(); if (e && typeof e.stopImmediatePropagation === "function") e.stopImmediatePropagation(); this.debugLog("Base click detected; routing via BaseClick", { sourceEventName: sourceEventName, label: rowContext && rowContext.label, guid: rowContext && rowContext.guid }); this.BaseClick(rowContext, e); } startBaseClickObserver() { if (!this.manageBaseClick) { this.debugLog("Base click observer not started because manageBaseClick is false."); return; } if (this.baseClickListenerAttached) { this.debugLog("Base click observer already attached; skipping duplicate registration."); return; } var self = this; document.addEventListener("pointerdown", function (e) { self.handleBaseClickEvent(e, "pointerdown"); }, true); document.addEventListener("mousedown", function (e) { self.handleBaseClickEvent(e, "mousedown"); }, true); document.addEventListener("click", function (e) { self.handleBaseClickEvent(e, "click"); }, true); this.baseClickListenerAttached = true; this.log("Base click management enabled.", { events: ["pointerdown", "mousedown", "click"] }); } tryInjectAfterOpen() { if (!this.isAwaitingMenuForClick) return; var attempts = 0; var maxAttempts = 20; var self = this; var timer = setInterval(function () { attempts++; var menu = self.findOpenMenuRoot(); if (menu && self.injectIntoMenu(menu, self.pendingRowContext)) { clearInterval(timer); self.isAwaitingMenuForClick = false; } else if (attempts >= maxAttempts) { clearInterval(timer); self.isAwaitingMenuForClick = false; self.log("No open menu detected for injection."); } }, 100); } startMenuObserver() { if (this.menuObserver) return; var self = this; this.menuObserver = new MutationObserver(function () { self.applyTriggerIconsInScope(); if (!self.isAwaitingMenuForClick) return; var menu = self.findOpenMenuRoot(); if (menu) { var injected = self.injectIntoMenu(menu, self.pendingRowContext); if (injected) { self.isAwaitingMenuForClick = false; } } }); this.menuObserver.observe(document.body, { childList: true, subtree: true }); this.log("Menu observer started."); } init() { var self = this; self.log("init() start", { triggerSelector: self.triggerSelector, triggerIcon: self.triggerIcon, manageBaseClick: self.manageBaseClick, baseClickHandlerName: self.baseClickHandlerName, baseClickActionId: self.baseClickActionId, listSelector: self.listSelector, listKey: self.listKey }); self.applyTriggerIconsInScope(); self.diagnoseTriggerIcons(); document.addEventListener("click", function (e) { var trigger = e.target.closest(self.triggerSelector); if (!trigger) return; if (!self.isTriggerInScope(trigger)) return; self.applyTriggerIcon(trigger); self.pendingRowContext = self.getRowContextFromTrigger(trigger); self.isAwaitingMenuForClick = true; self.log("Context menu trigger clicked for row:", self.pendingRowContext ? self.pendingRowContext.label : "(unknown)"); self.tryInjectAfterOpen(); }); self.startMenuObserver(); self.startBaseClickObserver(); return self; } }