Skip to content

Commit

Permalink
Add copy from selector engine
Browse files Browse the repository at this point in the history
Copy the selector from the open window with the element hovering over
the element.
  • Loading branch information
ankur22 committed Dec 4, 2024
1 parent 6e1454d commit 4906504
Showing 1 changed file with 106 additions and 77 deletions.
183 changes: 106 additions & 77 deletions common/js/selector_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,70 +29,69 @@

// 5. Fallback to XPath
return generateXPath(element);
}

// Helper function to compute the role (explicit or implicit)
function getRole(element) {
// Check for explicit role
if (element.hasAttribute('role')) {
return element.getAttribute('role');
}

// Implicit role mapping
const implicitRoles = {
button: ['button', "input[type='button']", "input[type='submit']", "input[type='reset']"],
link: ['a[href]'],
checkbox: ["input[type='checkbox']"],
heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
dialog: ['dialog'],
img: ['img[alt]'],
textbox: ["input[type='text']", 'textarea'],
radio: ["input[type='radio']"],
// Add more implicit roles if needed
};
}

for (const [role, selectors] of Object.entries(implicitRoles)) {
for (const selector of selectors) {
if (element.matches(selector)) {
return role;
}
}
}
// Helper function to compute the role (explicit or implicit)
function getRole(element) {
// Check for explicit role
if (element.hasAttribute('role')) {
return element.getAttribute('role');
}

return null;
}
// Implicit role mapping
const implicitRoles = {
button: ['button', "input[type='button']", "input[type='submit']", "input[type='reset']"],
link: ['a[href]'],
checkbox: ["input[type='checkbox']"],
heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
dialog: ['dialog'],
img: ['img[alt]'],
textbox: ["input[type='text']", 'textarea'],
radio: ["input[type='radio']"],
// Add more implicit roles if needed
};

for (const [role, selectors] of Object.entries(implicitRoles)) {
for (const selector of selectors) {
if (element.matches(selector)) {
return role;
}
}
}

// Helper function to compute the accessible name of an element
function getAccessibleName(element) {
// Prefer aria-label or aria-labelledby
if (element.hasAttribute('aria-label')) {
return element.getAttribute('aria-label');
}
if (element.hasAttribute('aria-labelledby')) {
const labelId = element.getAttribute('aria-labelledby');
const labelElement = element.ownerDocument.getElementById(labelId);
return labelElement ? labelElement.textContent.trim() : '';
}
// Use text content as a fallback
return element.textContent.trim();
}
return null;
}

// Helper function to generate XPath as a fallback
function generateXPath(element) {
if (element.id) {
return `//*[@id="${element.id}"]`;
}
const siblings = Array.from(element.parentNode.children).filter(
(el) => el.nodeName === element.nodeName
);
const index = siblings.indexOf(element) + 1;
const tagName = element.nodeName.toLowerCase();
if (element.parentNode === document) {
return `/${tagName}[${index}]`;
}
return `${generateXPath(element.parentNode)}/${tagName}[${index}]`;
}
// Helper function to compute the accessible name of an element
function getAccessibleName(element) {
// Prefer aria-label or aria-labelledby
if (element.hasAttribute('aria-label')) {
return element.getAttribute('aria-label');
}
if (element.hasAttribute('aria-labelledby')) {
const labelId = element.getAttribute('aria-labelledby');
const labelElement = element.ownerDocument.getElementById(labelId);
return labelElement ? labelElement.textContent.trim() : '';
}
// Use text content as a fallback
return element.textContent.trim();
}

// Helper function to generate XPath as a fallback
function generateXPath(element) {
if (element.id) {
return `//*[@id="${element.id}"]`;
}
const siblings = Array.from(element.parentNode.children).filter(
(el) => el.nodeName === element.nodeName
);
const index = siblings.indexOf(element) + 1;
const tagName = element.nodeName.toLowerCase();
if (element.parentNode === document) {
return `/${tagName}[${index}]`;
}
return `${generateXPath(element.parentNode)}/${tagName}[${index}]`;
}

// Highlight and Selector Display
let lastHighlightedElement = null;
Expand All @@ -107,29 +106,59 @@ function generateXPath(element) {
selectorOverlay.style.zIndex = '9999';
document.body.appendChild(selectorOverlay);

// Helper to copy text to clipboard
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
console.log(`Copied to clipboard: ${text}`);
showTemporaryMessage('Copied!', selectorOverlay);
}).catch((err) => {
console.error('Failed to copy text: ', err);
showTemporaryMessage('Failed to copy', selectorOverlay);
});
}

// Show a temporary message in the overlay
function showTemporaryMessage(message, overlay) {
const originalText = overlay.textContent;
overlay.textContent = message;
setTimeout(() => {
overlay.textContent = originalText;
}, 1000); // Reset after 1 second
}

// Highlight the element and show selector
function highlightElement(event) {
if (lastHighlightedElement) {
lastHighlightedElement.style.outline = '';
}
const element = event.target;
element.style.outline = '2px solid #FF671D';
lastHighlightedElement = element;

const selector = findBestSelector(element);
const rect = element.getBoundingClientRect();
selectorOverlay.textContent = selector;
selectorOverlay.style.top = `${rect.top + window.scrollY}px`;
selectorOverlay.style.left = `${rect.left + window.scrollX}px`;
if (lastHighlightedElement) {
lastHighlightedElement.style.outline = '';
}
const element = event.target;
element.style.outline = '2px solid #FF671D';
lastHighlightedElement = element;

const selector = findBestSelector(element);
const rect = element.getBoundingClientRect();
selectorOverlay.textContent = selector;
selectorOverlay.style.top = `${rect.top + window.scrollY}px`;
selectorOverlay.style.left = `${rect.left + window.scrollX}px`;

// Copy to clipboard on Command + C
document.onkeydown = (e) => {
if (e.metaKey && e.key === 'c') { // Press Command + C
e.preventDefault();
copyToClipboard(selector);
}
};
}

function removeHighlight() {
if (lastHighlightedElement) {
lastHighlightedElement.style.outline = '';
lastHighlightedElement = null;
}
selectorOverlay.textContent = '';
if (lastHighlightedElement) {
lastHighlightedElement.style.outline = '';
lastHighlightedElement = null;
}
selectorOverlay.textContent = '';
document.onkeydown = null; // Remove keydown listener
}

document.addEventListener('mouseover', highlightElement);
document.addEventListener('mouseout', removeHighlight);
})();
})();

0 comments on commit 4906504

Please sign in to comment.