Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@semantic-ui/query

Package Overview
Dependencies
Maintainers
0
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@semantic-ui/query - npm Package Compare versions

Comparing version 0.1.2 to 0.1.3

4

package.json

@@ -26,5 +26,5 @@ {

"dependencies": {
"@semantic-ui/utils": "^0.1.2"
"@semantic-ui/utils": "^0.1.3"
},
"version": "0.1.2"
"version": "0.1.3"
}

@@ -82,5 +82,4 @@ import { isPlainObject, isString, isArray, isDOM, isFunction, findIndex, inArray, isClient, isObject, each } from '@semantic-ui/utils';

/* Note this is a naive implementation for performance reasons
we will add all elements across shadow root boundaries but without
matching complex selectors that would match ACROSS shadow root boundaries
/* we will add all elements across shadow root boundaries while matching
intermediate selectors at shadow boundaries
*/

@@ -116,3 +115,3 @@ querySelectorAllDeep(root, selector, includeRoot = true) {

const addElements = (node) => {
const addElements = (node, selector) => {
if(domSelector && (node === selector || node.contains)) {

@@ -130,4 +129,18 @@ if(node.contains(selector)) {

const findElements = (node, query) => {
const getRemainingSelector = (el, selector) => {
const parts = selector.split(' ');
let partialSelector;
let remainingSelector;
each(parts, (part, index) => {
partialSelector = parts.slice(0, index + 1).join(' ');
if(el.matches(partialSelector)) {
remainingSelector = parts.slice(index + 1).join(' ');
return;
}
});
return remainingSelector || selector;
};
const findElements = (node, selector, query) => {
// if we are querying for a DOM element we can stop searching once we've found it

@@ -141,3 +154,3 @@ if(domFound) {

if(query === true) {
addElements(node);
addElements(node, selector);
queriedRoot = true;

@@ -148,14 +161,16 @@ }

if (node.nodeType === Node.ELEMENT_NODE && node.shadowRoot) {
addElements(node.shadowRoot);
findElements(node.shadowRoot, !queriedRoot);
selector = getRemainingSelector(node, selector);
addElements(node.shadowRoot, selector);
findElements(node.shadowRoot, selector, !queriedRoot);
}
if(node.assignedNodes) {
node.assignedNodes().forEach((node) => findElements(node, queriedRoot));
selector = getRemainingSelector(node, selector);
node.assignedNodes().forEach((node) => findElements(node, selector, queriedRoot));
}
if (node.childNodes.length) {
node.childNodes.forEach((node) => findElements(node, queriedRoot));
node.childNodes.forEach((node) => findElements(node, selector, queriedRoot));
}
};
findElements(root);
findElements(root, selector);
return [...new Set(elements)];

@@ -341,3 +356,3 @@ }

on(eventName, targetSelectorOrHandler, handlerOrOptions, options) {
on(eventNames, targetSelectorOrHandler, handlerOrOptions, options) {
const eventHandlers = [];

@@ -359,55 +374,58 @@

const abortController = options?.abortController || new AbortController();
const eventSettings = options?.eventSettings || {};
const signal = abortController.signal;
// Split event names by spaces and attach handlers for each
const events = eventNames.split(' ').filter(Boolean);
this.each((el) => {
let delegateHandler;
if (targetSelector) {
delegateHandler = (event) => {
let target;
// if this event is composed from a web component
// this is required to get the original path
if (event.composed && event.composedPath) {
events.forEach(eventName => {
const abortController = options?.abortController || new AbortController();
const eventSettings = options?.eventSettings || {};
const signal = abortController.signal;
// look through composed path bubbling into the attached element to see if any match target
let path = event.composedPath();
const elIndex = findIndex(path, thisEl => thisEl == el);
path = path.slice(0, elIndex);
target = path.find(el => el instanceof Element && el.matches && el.matches(targetSelector));
this.each((el) => {
let delegateHandler;
if (targetSelector) {
delegateHandler = (event) => {
let target;
// if this event is composed from a web component
// this is required to get the original path
if (event.composed && event.composedPath) {
// look through composed path bubbling into the attached element to see if any match target
let path = event.composedPath();
const elIndex = findIndex(path, thisEl => thisEl == el);
path = path.slice(0, elIndex);
target = path.find(el => el instanceof Element && el.matches && el.matches(targetSelector));
}
else if(targetSelector) {
// keep things simple for most basic uses
target = event.target.closest(targetSelector);
}
else {
// no target selector
target = event.target;
}
}
else if(targetSelector) {
// keep things simple for most basic uses
target = event.target.closest(targetSelector);
}
else {
// no target selector
target = event.target;
}
if (target) {
// If a matching target is found, call the handler with the correct context
handler.call(target, event);
}
};
}
const eventListener = delegateHandler || handler;
if (target) {
// If a matching target is found, call the handler with the correct context
handler.call(target, event);
}
// will cause illegal invocation if used from proxy object
const domEL = (this.isGlobal) ? globalThis : el;
if (domEL.addEventListener) {
domEL.addEventListener(eventName, eventListener, { signal, ...eventSettings });
}
const eventHandler = {
el,
eventName,
eventListener,
abortController,
delegated: targetSelector !== undefined,
handler,
abort: (reason) => abortController.abort(reason),
};
}
const eventListener = delegateHandler || handler;
// will cause illegal invocation if used from proxy object
const domEL = (this.isGlobal) ? globalThis : el;
if (domEL.addEventListener) {
domEL.addEventListener(eventName, eventListener, { signal, ...eventSettings });
}
const eventHandler = {
el,
eventName,
eventListener,
abortController,
delegated: targetSelector !== undefined,
handler,
abort: (reason) => abortController.abort(reason),
};
eventHandlers.push(eventHandler);
eventHandlers.push(eventHandler);
});
});

@@ -414,0 +432,0 @@

import { describe, vi, beforeAll, beforeEach, afterEach, afterAll, it, expect } from 'vitest';
import { $ } from '@semantic-ui/query';
import { $, $$ } from '@semantic-ui/query';

@@ -156,2 +156,156 @@ describe('query', () => {

});
describe('Shadow DOM Traversal', () => {
beforeAll(() => {
// Register custom elements once in this suite
// Open Shadow DOM component
class TestDOMInnerComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.textContent = 'Inside Nested Component';
shadow.appendChild(titleDiv);
}
connectedCallback() {
this.dispatchEvent(new CustomEvent('initialized', { bubbles: true }));
}
}
customElements.define('test-dom-inner', TestDOMInnerComponent);
// Nested component
class TestDOMComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const myComponent = document.createElement('test-dom-inner');
const titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.textContent = 'Inside Web Component';
shadow.appendChild(myComponent);
shadow.appendChild(titleDiv);
}
async connectedCallback() {
const el = this.shadowRoot.querySelector('test-dom-inner');
if(el) {
el.addEventListener('initialized', () => {
this.dispatchEvent(new CustomEvent('initialized', { bubbles: true }));
});
}
}
}
customElements.define('test-dom', TestDOMComponent);
});
afterEach(() => {
// Clean up after each test
document.body.innerHTML = '';
});
it('should return outer elements and nested shadow elements', async () => {
// Create an element with class 'title' outside
const outsideDiv = document.createElement('div');
outsideDiv.className = 'title';
outsideDiv.textContent = 'Outside Shadow DOM';
document.body.appendChild(outsideDiv);
const testComponent = document.createElement('test-dom');
// Create a Promise that resolves when the 'initialized' event is dispatched
const componentInit = new Promise((resolve) => {
testComponent.addEventListener('initialized', resolve);
});
document.body.appendChild(testComponent);
// Wait for the component to initialize
await componentInit;
// selects outer title, nested component title and inner component title
const $allElements = $$('.title');
expect($allElements.length).toBe(3);
});
it('should select nested items', async () => {
// Create an element with class 'title' outside
const outsideDiv = document.createElement('div');
outsideDiv.className = 'title';
outsideDiv.textContent = 'Outside Shadow DOM';
document.body.appendChild(outsideDiv);
const testComponent = document.createElement('test-dom');
// Create a Promise that resolves when the 'initialized' event is dispatched
const componentInit = new Promise((resolve) => {
testComponent.addEventListener('initialized', resolve);
});
document.body.appendChild(testComponent);
// Wait for the component to initialize
await componentInit;
// selects nested component title and inner component title
const $elements = $$('test-dom .title');
expect($elements.length).toBe(2);
});
it('should not match items not at shadow root', async () => {
// Create an element with class 'title' outside
const outsideDiv = document.createElement('div');
outsideDiv.className = 'title';
outsideDiv.textContent = 'Outside Shadow DOM';
document.body.appendChild(outsideDiv);
const testComponent = document.createElement('test-dom');
// Create a Promise that resolves when the 'initialized' event is dispatched
const componentInit = new Promise((resolve) => {
testComponent.addEventListener('initialized', resolve);
});
document.body.appendChild(testComponent);
// Wait for the component to initialize
await componentInit;
// selects nested component title
const $elements = $$('not-component .title');
expect($elements.length).toBe(0);
});
it('should select deeply nested items', async () => {
// Create an element with class 'title' outside
const outsideDiv = document.createElement('div');
outsideDiv.className = 'title';
outsideDiv.textContent = 'Outside Shadow DOM';
document.body.appendChild(outsideDiv);
const testComponent = document.createElement('test-dom');
// Create a Promise that resolves when the 'initialized' event is dispatched
const componentInit = new Promise((resolve) => {
testComponent.addEventListener('initialized', resolve);
});
document.body.appendChild(testComponent);
// Wait for the component to initialize
await componentInit;
// selects nested component title
const $innerElements = $$('test-dom test-dom-inner .title');
expect($innerElements.length).toBe(1);
});
});
});

@@ -1,2 +0,2 @@

import { describe, beforeEach, afterEach, expect, it, vi } from 'vitest';
import { describe, beforeEach, afterEach, beforeAll, expect, it, vi } from 'vitest';
import { $, $$, Query, exportGlobals, restoreGlobals, useAlias } from '@semantic-ui/query';

@@ -672,2 +672,111 @@

it('should attach multiple event handlers when passing space-separated events', () => {
const div = document.createElement('div');
document.body.appendChild(div);
const callback = vi.fn();
$('div').on('mouseup touchmove', callback);
div.dispatchEvent(new Event('mouseup'));
expect(callback).toHaveBeenCalledTimes(1);
div.dispatchEvent(new Event('touchmove'));
expect(callback).toHaveBeenCalledTimes(2);
});
it('should handle multiple events with delegation', () => {
const div = document.createElement('div');
const span = document.createElement('span');
div.appendChild(span);
document.body.appendChild(div);
const callback = vi.fn();
$('div').on('mouseup touchmove', 'span', callback);
span.dispatchEvent(new Event('mouseup', { bubbles: true }));
expect(callback).toHaveBeenCalledTimes(1);
span.dispatchEvent(new Event('touchmove', { bubbles: true }));
expect(callback).toHaveBeenCalledTimes(2);
});
it('should handle multiple events with options', () => {
const div = document.createElement('div');
document.body.appendChild(div);
const callback = vi.fn();
const abortController = new AbortController();
$('div').on('mouseup touchmove', callback, { abortController });
div.dispatchEvent(new Event('mouseup'));
div.dispatchEvent(new Event('touchmove'));
expect(callback).toHaveBeenCalledTimes(2);
abortController.abort();
div.dispatchEvent(new Event('mouseup'));
div.dispatchEvent(new Event('touchmove'));
expect(callback).toHaveBeenCalledTimes(2); // Count shouldn't increase
});
it('should handle multiple events with delegation and options', () => {
const div = document.createElement('div');
const span = document.createElement('span');
div.appendChild(span);
document.body.appendChild(div);
const callback = vi.fn();
const abortController = new AbortController();
$('div').on('mouseup touchmove', 'span', callback, { abortController });
span.dispatchEvent(new Event('mouseup', { bubbles: true }));
span.dispatchEvent(new Event('touchmove', { bubbles: true }));
expect(callback).toHaveBeenCalledTimes(2);
abortController.abort();
span.dispatchEvent(new Event('mouseup', { bubbles: true }));
span.dispatchEvent(new Event('touchmove', { bubbles: true }));
expect(callback).toHaveBeenCalledTimes(2); // Count shouldn't increase
});
it('should handle empty spaces in event string', () => {
const div = document.createElement('div');
document.body.appendChild(div);
const callback = vi.fn();
$('div').on(' mouseup touchmove ', callback);
div.dispatchEvent(new Event('mouseup'));
div.dispatchEvent(new Event('touchmove'));
expect(callback).toHaveBeenCalledTimes(2);
});
it('should return handlers for all events when returnHandler is true', () => {
const div = document.createElement('div');
document.body.appendChild(div);
const callback = vi.fn();
const handlers = $('div').on('mouseup touchmove', callback, { returnHandler: true });
expect(Array.isArray(handlers)).toBe(true);
expect(handlers.length).toBe(2);
expect(handlers[0].eventName).toBe('mouseup');
expect(handlers[1].eventName).toBe('touchmove');
});
it('should properly remove specific events using off()', () => {
const div = document.createElement('div');
document.body.appendChild(div);
const callback = vi.fn();
$('div').on('mouseup touchmove', callback);
div.dispatchEvent(new Event('mouseup'));
div.dispatchEvent(new Event('touchmove'));
expect(callback).toHaveBeenCalledTimes(2);
$('div').off('mouseup');
div.dispatchEvent(new Event('mouseup'));
div.dispatchEvent(new Event('touchmove'));
expect(callback).toHaveBeenCalledTimes(3); // Only touchmove should trigger
});
});

@@ -674,0 +783,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc