Skip to content

Commit 2d5844c

Browse files
authored
feat: considerations for elements within Shadow DOM (#12)
* feat: considerations for elements within ShadowRoot * Updating documentation * Updating documentation again
1 parent 1111cc6 commit 2d5844c

File tree

4 files changed

+70
-6
lines changed

4 files changed

+70
-6
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This is particularly useful when tracking in custom variables in analytics:
1515
_gaq.push(['_trackEvent', 'Engagement', 'Click', selector]);
1616
}, false);
1717

18+
Selector uniqueness is determined based on the given element's root node. Elements rendered within Shadow DOM will derive a selector unique within the associated ShadowRoot context. Otherwise, a selector unique within an element's owning document will be derived.
1819

1920
Installation
2021
------------
@@ -74,6 +75,11 @@ Eric Clemmons : [@ericclemmons](https://twitter.com/ericclemmons)
7475

7576
Releases
7677
--------
78+
79+
- v2.1.0
80+
81+
-
82+
7783
- v0.1.0
7884

7985
- Big refactor/rewrite using es6

src/index.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ function getUniqueSelector( element, selectorTypes, attributesToIgnore, filter )
188188
}
189189

190190
/**
191-
* Generate unique CSS selector for given DOM element
191+
* Generate unique CSS selector for given DOM element. Selector uniqueness is determined based on the given element's root node.
192+
* Elements rendered within Shadow DOM will derive a selector that is unique within the associated ShadowRoot context.
193+
* Otherwise, a selector that is unique within the element's owning document will be derived.
192194
*
193195
* @param {Element} el
194196
* @param {Object} options (optional) Customize various behaviors of selector generation
@@ -200,7 +202,6 @@ function getUniqueSelector( element, selectorTypes, attributesToIgnore, filter )
200202
* @return {String}
201203
* @api private
202204
*/
203-
204205
export default function unique( el, options={} ) {
205206
const {
206207
selectorTypes=['id', 'name', 'class', 'tag', 'nth-child'],
@@ -248,7 +249,11 @@ export default function unique( el, options={} ) {
248249
if (isUniqueSelector) {
249250
return maybeUniqueSelector
250251
}
251-
currentElement = currentElement.parentNode
252+
253+
// Using parentElement here (rather than parentNode) to
254+
// filter out any document/document fragment nodes that may
255+
// be ancestors to elements within Shadow DOM trees.
256+
currentElement = currentElement.parentElement
252257
}
253258

254259
return null;

src/isUnique.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
/**
2-
* Checks if the selector is unique
2+
* Checks if the selector is unique. The selector is unique
3+
* if the elements root node (either its owner document, or a shadow root)
4+
* has exactly one element matching the selector.
5+
*
36
* @param { Object } element
47
* @param { String } selector
5-
* @return { Array }
8+
* @return { Boolean }
69
*/
710
export function isUnique( el, selector )
811
{
912
if( !Boolean( selector ) ) return false;
1013
try {
11-
var elems = el.ownerDocument.querySelectorAll(selector);
14+
// Using getRootNode here to scope checks to any parent
15+
// ShadowRoot. getRootNode will otherwise return the
16+
// document associated to the elements page/frame (like
17+
// the ownerDocument property would).
18+
var elems = el.getRootNode().querySelectorAll(selector);
1219
return elems.length === 1 && elems[0] === el;
1320
} catch (e) {
1421
return false

test/unique-selector.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,50 @@ describe( 'Unique Selector Tests', () =>
299299
expect( uniqueSelector ).to.equal( 'span' );
300300
})
301301
})
302+
303+
describe('shadow dom', () => {
304+
it( 'builds expected selector inside and outside shadow context', () => {
305+
$( 'body' ).append( '<div id="shadow-host" class="shadow-host-class"></div>' );
306+
307+
const hostNode = $( '#shadow-host' ).get( 0 );
308+
309+
const shadowRoot = hostNode.attachShadow({ mode: "open" })
310+
const shadowElement = hostNode.ownerDocument.createElement('div')
311+
shadowElement.innerHTML = `
312+
<div id="inner-shadow-container">
313+
<button id="shadow-button" class="shadow-button-class">Click Me</button>
314+
</div>
315+
`
316+
shadowRoot.appendChild(shadowElement);
317+
318+
const uniqueSelectorForHost = unique( hostNode );
319+
expect( uniqueSelectorForHost ).to.equal( '#shadow-host' );
320+
321+
const uniqueSelectorForShadowContent = unique ( shadowElement.querySelector('#shadow-button') )
322+
expect( uniqueSelectorForShadowContent ).to.equal( '#shadow-button' );
323+
})
324+
325+
it( 'builds unique selector scoped to shadow root', () => {
326+
$( 'body' ).append( '<div id="shadow-host" class="shadow-host-class"></div>' );
327+
$( 'body' ).append( '<button class="shadow-button-class">Click Me Third</button>' );
328+
329+
const hostNode = $( '#shadow-host' ).get( 0 );
330+
331+
const shadowRoot = hostNode.attachShadow({ mode: "open" })
332+
const shadowElement = hostNode.ownerDocument.createElement('div')
333+
shadowElement.innerHTML = `
334+
<div id="inner-shadow-container">
335+
<button class="shadow-button-class">Click Me First</button>
336+
<button class="shadow-button-class">Click Me Second</button>
337+
</div>
338+
`
339+
shadowRoot.appendChild(shadowElement);
340+
341+
const uniqueSelectorInRootDocument = unique( $( 'body' ).find( '.shadow-button-class' ).get( 0 ) );
342+
expect( uniqueSelectorInRootDocument ).to.equal( '.shadow-button-class' );
343+
344+
const uniqueSelectorForShadowContent = unique ( shadowElement.querySelectorAll('.shadow-button-class')[0] )
345+
expect( uniqueSelectorForShadowContent ).to.equal( '#inner-shadow-container > :nth-child(1)' );
346+
})
347+
})
302348
} );

0 commit comments

Comments
 (0)