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;
}
}