Socket
Socket
Sign inDemoInstall

skatejs-named-slots

Package Overview
Dependencies
Maintainers
2
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

skatejs-named-slots - npm Package Compare versions

Comparing version 0.0.3 to 0.1.0

lib/util/can-patch-native-accessors.js

729

lib/index.js
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports', 'module', './polyfill', './polyfilled', './render', './slot', './version'], factory);
} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
factory(exports, module, require('./polyfill'), require('./polyfilled'), require('./render'), require('./slot'), require('./version'));
if (typeof define === "function" && define.amd) {
define(['module', 'exports', './util/each', './util/can-patch-native-accessors', 'debounce', './version', './util/weak-map'], factory);
} else if (typeof exports !== "undefined") {
factory(module, exports, require('./util/each'), require('./util/can-patch-native-accessors'), require('debounce'), require('./version'), require('./util/weak-map'));
} else {

@@ -10,27 +10,720 @@ var mod = {

};
factory(mod.exports, mod, global.polyfill, global.polyfilled, global.render, global.slot, global.version);
factory(mod, mod.exports, global.each, global.canPatchNativeAccessors, global.debounce, global.version, global.weakMap);
global.index = mod.exports;
}
})(this, function (exports, module, _polyfill, _polyfilled, _render, _slot, _version) {
})(this, function (module, exports, _each, _canPatchNativeAccessors, _debounce, _version, _weakMap) {
'use strict';
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
Object.defineProperty(exports, "__esModule", {
value: true
});
var _polyfill2 = _interopRequireDefault(_polyfill);
var _canPatchNativeAccessors2 = _interopRequireDefault(_canPatchNativeAccessors);
var _polyfilled2 = _interopRequireDefault(_polyfilled);
var _debounce2 = _interopRequireDefault(_debounce);
var _render2 = _interopRequireDefault(_render);
var _version2 = _interopRequireDefault(_version);
var _slot2 = _interopRequireDefault(_slot);
var _weakMap2 = _interopRequireDefault(_weakMap);
var _version2 = _interopRequireDefault(_version);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
module.exports = {
polyfill: _polyfill2['default'],
polyfilled: _polyfilled2['default'],
render: _render2['default'],
slot: _slot2['default'],
version: _version2['default']
var defaultShadowRootTagName = '_shadow_root_';
var defaultShadowRootTagNameUc = defaultShadowRootTagName.toUpperCase();
var polyfilAtRuntime = ['childNodes', 'parentNode'];
var protos = ['Node', 'Element', 'EventTarget'];
var assignedToSlotMap = new _weakMap2.default();
var hostToModeMap = new _weakMap2.default();
var hostToRootMap = new _weakMap2.default();
var nodeToChildNodesMap = new _weakMap2.default();
var nodeToParentNodeMap = new _weakMap2.default();
var nodeToSlotMap = new _weakMap2.default();
var rootToHostMap = new _weakMap2.default();
var rootToSlotMap = new _weakMap2.default();
var slotToModeMap = new _weakMap2.default();
var parser = new DOMParser();
function convertXmlToHtml(node) {
var nodeType = node.nodeType;
if (nodeType === 1) {
var copy = document.createElement(node.tagName);
for (var a = 0; a < node.attributes.length; a++) {
var attr = node.attributes[a];
copy.setAttribute(attr.name, attr.value);
}
for (var a = 0; a < node.childNodes.length; a++) {
var childNode = node.childNodes[a];
copy.appendChild(convertXmlToHtml(childNode));
}
return copy;
}
return node;
}
function parse(html) {
var tree = document.createElement('div');
var parsed = parser.parseFromString(html, 'text/xml');
while (parsed.hasChildNodes()) {
var firstChild = parsed.firstChild;
parsed.removeChild(firstChild);
tree.appendChild(convertXmlToHtml(firstChild));
}
return tree;
}
function staticProp(obj, name, value) {
Object.defineProperty(obj, name, {
configurable: true,
get: function get() {
return value;
}
});
}
function arrayItem(idx) {
return this[idx];
}
function makeLikeNodeList(arr) {
arr.item = arrayItem;
return arr;
}
function getNodeType(node) {
if (isHostNode(node)) {
return 'host';
}
if (isSlotNode(node)) {
return 'slot';
}
if (isRootNode(node)) {
return 'root';
}
return 'node';
}
function isHostNode(node) {
return !!hostToRootMap.get(node);
}
function isSlotNode(node) {
return node.tagName === 'SLOT';
}
function isRootNode(node) {
return node.tagName === defaultShadowRootTagNameUc;
}
function findClosest(node, func) {
while (node) {
if (node === document) {
break;
}
if (func(node)) {
return node;
}
node = node.parentNode;
}
}
function getSlotNameFromSlot(node) {
return node.getAttribute && node.getAttribute('name') || 'default';
}
function getSlotNameFromNode(node) {
return node.getAttribute && node.getAttribute('slot') || 'default';
}
function slotNodeIntoSlot(slot, node, insertBefore) {
var assignedNodes = slot.getAssignedNodes();
var slotInsertBeforeIndex = assignedNodes.indexOf(insertBefore);
nodeToSlotMap.set(node, slot);
if (!assignedNodes.length) {
slotToModeMap.set(slot, false);
[].slice.call(slot.childNodes).forEach(function (fallbackNode) {
return slot.__removeChild(fallbackNode);
});
}
var shouldAffectSlot = !slotToModeMap.get(slot);
if (slotInsertBeforeIndex > -1) {
if (shouldAffectSlot) {
slot.__insertBefore(node, insertBefore);
}
assignedNodes.splice(slotInsertBeforeIndex, 0, node);
} else {
if (shouldAffectSlot) {
slot.__appendChild(node);
}
assignedNodes.push(node);
}
slot.____triggerSlotChangeEvent();
}
function slotNodeFromSlot(node) {
var slot = node.assignedSlot;
if (slot) {
var assignedNodes = slot.getAssignedNodes();
var index = assignedNodes.indexOf(node);
if (index > -1) {
assignedNodes.splice(index, 1);
nodeToSlotMap.set(node, null);
var shouldAffectSlot = !slotToModeMap.get(slot);
if (shouldAffectSlot) {
slot.__removeChild(node);
}
if (!assignedNodes.length) {
slotToModeMap.set(slot, true);
(0, _each.eachChildNode)(slot, function (node) {
slot.__appendChild(node);
});
}
slot.____triggerSlotChangeEvent();
}
}
}
function indexOfNode(host, node) {
var chs = host.childNodes;
var chsLen = chs.length;
for (var a = 0; a < chsLen; a++) {
if (chs[a] === node) {
return a;
}
}
return -1;
}
function registerNode(host, node, insertBefore, func) {
var index = indexOfNode(host, insertBefore);
(0, _each.eachNodeOrFragmentNodes)(node, function (eachNode, eachIndex) {
func(eachNode, eachIndex);
if (_canPatchNativeAccessors2.default) {
nodeToParentNodeMap.set(eachNode, host);
} else {
staticProp(eachNode, 'parentNode', host);
}
if (index > -1) {
host.childNodes.splice(index + eachIndex, 0, eachNode);
} else {
host.childNodes.push(eachNode);
}
});
}
function unregisterNode(host, node, func) {
var index = indexOfNode(host, node);
if (index > -1) {
func(node, 0);
if (_canPatchNativeAccessors2.default) {
nodeToParentNodeMap.set(node, null);
} else {
staticProp(node, 'parentNode', null);
}
host.childNodes.splice(index, 1);
}
}
function addNodeToNode(host, node, insertBefore) {
registerNode(host, node, insertBefore, function (eachNode) {
host.__insertBefore(eachNode, insertBefore);
});
}
function addNodeToHost(host, node, insertBefore) {
registerNode(host, node, insertBefore, function (eachNode) {
var rootNode = hostToRootMap.get(host);
var slotNodes = rootToSlotMap.get(rootNode);
var slotNode = slotNodes[getSlotNameFromNode(eachNode)];
if (slotNode) {
slotNodeIntoSlot(slotNode, eachNode, insertBefore);
}
});
}
function addNodeToRoot(root, node, insertBefore) {
(0, _each.eachNodeOrFragmentNodes)(node, function (node) {
if (isSlotNode(node)) {
addSlotToRoot(root, node);
} else {
var slotNodes = node.querySelectorAll && node.querySelectorAll('slot');
var slotNodesLen = slotNodes.length;
for (var a = 0; a < slotNodesLen; a++) {
addSlotToRoot(root, slotNodes[a]);
}
}
});
addNodeToNode(root, node, insertBefore);
}
function addSlotToRoot(root, node) {
var slotName = getSlotNameFromSlot(node);
slotToModeMap.set(node, true);
rootToSlotMap.get(root)[slotName] = node;
(0, _each.eachChildNode)(rootToHostMap.get(root), function (eachNode) {
if (!eachNode.assignedSlot && slotName === getSlotNameFromNode(eachNode)) {
slotNodeIntoSlot(node, eachNode);
}
});
}
function removeNodeFromNode(host, node) {
unregisterNode(host, node, function () {
host.__removeChild(node);
});
}
function removeNodeFromHost(host, node) {
unregisterNode(host, node, function () {
slotNodeFromSlot(node);
});
}
function removeNodeFromRoot(root, node) {
unregisterNode(root, node, function () {
if (isSlotNode(node)) {
removeSlotFromRoot(root, node);
} else {
var nodes = node.querySelectorAll && node.querySelectorAll('slot');
for (var a = 0; a < nodes.length; a++) {
removeSlotFromRoot(root, nodes[a]);
}
}
});
}
function removeSlotFromRoot(root, node) {
node.getAssignedNodes().forEach(slotNodeFromSlot);
delete rootToSlotMap.get(root)[getSlotNameFromSlot(node)];
}
function appendChildOrInsertBefore(host, newNode, refNode) {
var nodeType = getNodeType(host);
var parentNode = newNode.parentNode;
if (!_canPatchNativeAccessors2.default && !host.childNodes.push) {
staticProp(host, 'childNodes', []);
}
if (parentNode && getNodeType(parentNode) === 'host') {
if (_canPatchNativeAccessors2.default) {
nodeToParentNodeMap.set(newNode, null);
} else {
staticProp(newNode, 'parentNode', null);
}
}
if (nodeType === 'node') {
if (_canPatchNativeAccessors2.default) {
return host.__insertBefore(newNode, refNode);
} else {
return addNodeToNode(host, newNode, refNode);
}
}
if (nodeType === 'slot') {
return addNodeToNode(host, newNode, refNode);
}
if (nodeType === 'host') {
return addNodeToHost(host, newNode, refNode);
}
if (nodeType === 'root') {
return addNodeToRoot(host, newNode, refNode);
}
}
var members = {
____assignedNodes: {
get: function get() {
return this.______assignedNodes || (this.______assignedNodes = []);
}
},
____isInFallbackMode: {
get: function get() {
return slotToModeMap.get(this);
}
},
____slotChangeListeners: {
get: function get() {
if (typeof this.______slotChangeListeners === 'undefined') {
this.______slotChangeListeners = 0;
}
return this.______slotChangeListeners;
},
set: function set(value) {
this.______slotChangeListeners = value;
}
},
____triggerSlotChangeEvent: {
value: (0, _debounce2.default)(function () {
if (this.____slotChangeListeners) {
this.dispatchEvent(new CustomEvent('slotchange', {
bubbles: false,
cancelable: false
}));
}
})
},
addEventListener: {
value: function value(name, func, opts) {
if (name === 'slotchange' && isSlotNode(this)) {
this.____slotChangeListeners++;
}
return this.__addEventListener(name, func, opts);
}
},
appendChild: {
value: function value(newNode) {
return appendChildOrInsertBefore(this, newNode);
}
},
assignedSlot: {
get: function get() {
return nodeToSlotMap.get(this) || null;
}
},
attachShadow: {
value: function value(opts) {
var _this = this;
var mode = opts && opts.mode;
if (mode !== 'closed' && mode !== 'open') {
throw new Error('You must specify { mode } as "open" or "closed" to attachShadow().');
}
var existingShadowRoot = hostToRootMap.get(this);
if (existingShadowRoot) {
return existingShadowRoot;
}
var lightNodes = makeLikeNodeList([].slice.call(this.childNodes));
var shadowRoot = document.createElement(opts.polyfillShadowRootTagName || defaultShadowRootTagName);
hostToModeMap.set(this, mode);
hostToRootMap.set(this, shadowRoot);
rootToHostMap.set(shadowRoot, this);
rootToSlotMap.set(shadowRoot, {});
if (_canPatchNativeAccessors2.default) {
nodeToChildNodesMap.set(this, lightNodes);
} else {
staticProp(this, 'childNodes', lightNodes);
}
(0, _each.eachChildNode)(this, function (node) {
return _this.__removeChild(node);
});
return this.__appendChild(shadowRoot);
}
},
childElementCount: {
get: function get() {
return this.children.length;
}
},
childNodes: {
get: function get() {
if (_canPatchNativeAccessors2.default && getNodeType(this) === 'node') {
return this.__childNodes;
}
var childNodes = nodeToChildNodesMap.get(this);
childNodes || nodeToChildNodesMap.set(this, childNodes = makeLikeNodeList([]));
return childNodes;
}
},
children: {
get: function get() {
var chs = [];
(0, _each.eachChildNode)(this, function (node) {
if (node.nodeType === 1) {
chs.push(node);
}
});
return makeLikeNodeList(chs);
}
},
firstChild: {
get: function get() {
return this.childNodes[0] || null;
}
},
firstElementChild: {
get: function get() {
return this.children[0] || null;
}
},
getAssignedNodes: {
value: function value() {
if (isSlotNode(this)) {
var assigned = assignedToSlotMap.get(this);
assigned || assignedToSlotMap.set(this, assigned = []);
return assigned;
}
}
},
hasChildNodes: {
value: function value() {
return this.childNodes.length > 0;
}
},
innerHTML: {
get: function get() {
var innerHTML = '';
(0, _each.eachChildNode)(this, function (node) {
innerHTML += node.nodeType === 1 ? node.outerHTML : node.textContent;
});
return innerHTML;
},
set: function set(innerHTML) {
var parsed = parse(innerHTML);
while (this.hasChildNodes()) {
this.removeChild(this.firstChild);
}
while (parsed.hasChildNodes()) {
var firstChild = parsed.firstChild;
parsed.removeChild(firstChild);
this.appendChild(firstChild);
}
}
},
insertBefore: {
value: function value(newNode, refNode) {
return appendChildOrInsertBefore(this, newNode, refNode);
}
},
lastChild: {
get: function get() {
var ch = this.childNodes;
return ch[ch.length - 1] || null;
}
},
lastElementChild: {
get: function get() {
var ch = this.children;
return ch[ch.length - 1] || null;
}
},
name: {
get: function get() {
return this.getAttribute('name');
},
set: function set(name) {
return this.setAttribute('name', name);
}
},
nextSibling: {
get: function get() {
var host = this;
return (0, _each.eachChildNode)(this.parentNode, function (child, index, nodes) {
if (host === child) {
return nodes[index + 1] || null;
}
});
}
},
nextElementSibling: {
get: function get() {
var host = this;
var found = undefined;
return (0, _each.eachChildNode)(this.parentNode, function (child) {
if (found && child.nodeType === 1) {
return child;
}
if (host === child) {
found = true;
}
});
}
},
outerHTML: {
get: function get() {
var name = this.tagName.toLowerCase();
var attributes = Array.prototype.slice.call(this.attributes).map(function (attr) {
return ' ' + attr.name + (attr.value ? '="' + attr.value + '"' : '');
}).join('');
return '<' + name + attributes + '>' + this.innerHTML + '</' + name + '>';
}
},
parentElement: {
get: function get() {
return findClosest(this.parentNode, function (node) {
return node.nodeType === 1;
});
}
},
parentNode: {
get: function get() {
return nodeToParentNodeMap.get(this) || this.__parentNode || null;
}
},
previousSibling: {
get: function get() {
var host = this;
return (0, _each.eachChildNode)(this.parentNode, function (child, index, nodes) {
if (host === child) {
return nodes[index - 1] || null;
}
});
}
},
previousElementSibling: {
get: function get() {
var host = this;
var found = undefined;
return (0, _each.eachChildNode)(this.parentNode, function (child) {
if (found && host === child) {
return found;
}
if (child.nodeType === 1) {
found = child;
}
});
}
},
removeChild: {
value: function value(refNode) {
var nodeType = getNodeType(this);
if (nodeType === 'node') {
if (_canPatchNativeAccessors2.default) {
return this.__removeChild(refNode);
} else {
return removeNodeFromNode(this, refNode);
}
}
if (nodeType === 'slot') {
return removeNodeFromNode(this, refNode);
}
if (nodeType === 'host') {
return removeNodeFromHost(this, refNode);
}
if (nodeType === 'root') {
return removeNodeFromRoot(this, refNode);
}
}
},
removeEventListener: {
value: function value(name, func, opts) {
if (name === 'slotchange' && this.____slotChangeListeners && isSlotNode(this)) {
this.____slotChangeListeners--;
}
return this.__removeEventListener(name, func, opts);
}
},
replaceChild: {
value: function value(newNode, refNode) {
this.insertBefore(newNode, refNode);
return this.removeChild(refNode);
}
},
shadowRoot: {
get: function get() {
return hostToModeMap.get(this) === 'open' ? hostToRootMap.get(this) : null;
}
},
textContent: {
get: function get() {
var textContent = '';
(0, _each.eachChildNode)(this, function (node) {
textContent += node.textContent;
});
return textContent;
},
set: function set(textContent) {
while (this.hasChildNodes()) {
this.removeChild(this.firstChild);
}
this.appendChild(document.createTextNode(textContent));
}
}
};
function findDescriptorFor(name) {
for (var a = 0; a < protos.length; a++) {
var ctor = window[protos[a]];
if (!ctor) {
continue;
}
var proto = ctor.prototype;
if (proto.hasOwnProperty(name)) {
return Object.getOwnPropertyDescriptor(proto, name);
}
}
}
if (!('attachShadow' in document.createElement('div'))) {
(function () {
var elementProto = HTMLElement.prototype;
Object.keys(members).forEach(function (memberName) {
var memberProperty = members[memberName];
memberProperty.configurable = true;
if (_canPatchNativeAccessors2.default || polyfilAtRuntime.indexOf(memberName) === -1) {
var nativeDescriptor = findDescriptorFor(memberName);
Object.defineProperty(elementProto, memberName, memberProperty);
if (nativeDescriptor && nativeDescriptor.configurable) {
Object.defineProperty(elementProto, '__' + memberName, nativeDescriptor);
}
}
});
})();
}
exports.default = _version2.default;
module.exports = exports['default'];
});

18

lib/version.js
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports', 'module'], factory);
} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
factory(exports, module);
if (typeof define === "function" && define.amd) {
define(['module', 'exports'], factory);
} else if (typeof exports !== "undefined") {
factory(module, exports);
} else {

@@ -10,9 +10,13 @@ var mod = {

};
factory(mod.exports, mod);
factory(mod, mod.exports);
global.version = mod.exports;
}
})(this, function (exports, module) {
})(this, function (module, exports) {
'use strict';
module.exports = '0.0.1';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = '0.0.1';
module.exports = exports['default'];
});
{
"name": "skatejs-named-slots",
"version": "0.0.3",
"version": "0.1.0",
"description": "A polygap (partial polyfill) for the Shadow DOM Named Slot API.",

@@ -32,4 +32,7 @@ "main": "lib/index.js",

"gulp": "gulpjs/gulp#73eced0",
"skatejs-build": "skatejs/build#feda154"
"skatejs-build": "skatejs/build#268a84a"
},
"dependencies": {
"debounce": "^1.0.0"
}
}
# named-slots
A polygap (partial polyfill) for the Shadow DOM Named Slot API using SkateJS.
A polygap (partial polyfill) for the Shadow DOM Named Slot API.
Working example: http://jsbin.com/weboki/31/edit?js,output
## Why

@@ -16,2 +14,3 @@

- You want interopaberability with React, jQuery and other libraries that don't care about your implementation details
- You want something that is performant

@@ -22,22 +21,30 @@

What this polyfill does is override native methods so that it can check added / removed elements for a `slot` attribute, or use a default. It uses this slot as a property name and sets that property with information regarding the change. From there, you do the work.
Instead of polyfilling everything, we polyfill only the bare minimum that is required to supply the consumers of your custom elements with an API where they can distribute content to / from your element.
On top of this, it needs to report only the nodes that are contained in slots from native accessors such as `chilNodes`, `children`, etc.
Your consumers may use it like so:
For example, let's assume we have some initial content. This is what your consumer would write.
```html
<my-component id="example">
<p slot="content">paragraph 1</p>
<p slot="content">paragraph 2</p>
<p>paragraph 1</p>
<p>paragraph 2</p>
</my-component>
```
And you want it to result in:
Your shadow root may be templated out like:
```html
<div class="wrapper">
<slot />
</div>
```
Which would result in:
```html
<my-component id="example">
<div class="wrapper">
<p slot="content">paragraph 1</p>
<p slot="content">paragraph 2</p>
<slot>
<p>paragraph 1</p>
<p>paragraph 2</p>
</slot>
</div>

@@ -47,46 +54,40 @@ </my-component>

However, you don't want your consumers to worry, or know about `.wrapper`.
## Usage
### Skate (vanilla)
This polyfill is used in the same way as specified in [the spec](http://w3c.github.io/webcomponents/spec/shadow/).
There are some helpers that make using with [Skate](https://github.com/skatejs/skatejs) a lot simpler.
```js
import slots from 'skatejs-named-slots';
skate('my-component', {
properties: {
content: slots.property({
set (elem, data) {
const wrapper = elem.querySelector('.wrapper');
wrapper[data.type].apply(wrapper, data.args);
}
})
},
render: slots.render(function (elem) {
elem.innerHTML = '<div class="wrapper"></div>';
})
});
const host = document.createElement('div');
const root = host.attachShadow({ mode: 'closed' });
root.innerHTML = '<h1><slot name="title"></slot></h1><slot></slot>';
host.innerHTML = '<span slot="title">title</span><p>content</p>';
```
If the browser you run that code in does not support native Shadow DOM then it would render:
```html
<div>
<_shadow_root_>
<h1>
<slot name="title">title</slot>
</h1>
<slot>
<p>content</p>
</slot>
</_shadow_root_>
</div>
```
### Skate (functional + virtual DOM)
The `attachShadow()` method accepts an options dictionary as per the spec and requires that you specify a `mode` that is either `open` or `closed`. For the polyfill, you may also specify an option for using a different name for the shadow root.
And if you like writing in a more functional approach, you can use [skatejs-dom-diff](https://github.com/skatejs/dom-diff).
```js
import diff from 'skatejs-dom-diff';
import slots from 'skatejs-named-slots';
const root = host.attachShadow({ mode: 'open', polyfillShadowRootTagName: 'custom-shadow-root-name' });
```
skate('my-component', {
properties: {
content: slots.property({ set: skate.render }
},
render: slots.render(diff.render(function (elem) {
return diff.vdom.element('div', { 'class': 'wrapper' }, elem.content.nodes);
}))
});
Which would then render a shadow root as:
```html
<custom-shadow-root-name>
```

@@ -98,45 +99,43 @@

The following lists are an exhaustive representation of what this polygap supports and why for the following interfaces:
The following describe what is polyfilled, what is not polyfilled, and why. All members which are not standardised or are listed as experimental are not included in these lists.
- [ChildNode](https://developer.mozilla.org/en/docs/Web/API/ChildNode)
- [Element](https://developer.mozilla.org/en/docs/Web/API/Element)
- [EventTarget](https://developer.mozilla.org/en/docs/Web/API/EventTarget)
- [Node](https://developer.mozilla.org/en/docs/Web/API/Node)
- [NonDocumentTypeChildNode](https://developer.mozilla.org/en/docs/Web/API/NonDocumentTypeChildNode)
- [ParentNode](https://developer.mozilla.org/en/docs/Web/API/ParentNode)
Note:
- `HTMLElement` and any prototypes more specific are not polyfilled for simplicity.
- All members which are not standardised or are listed as experimental are not included in these lists.
- Members are only polyfilled on the specific web component unless otherwise noted.
- Members must be overridden on the instance rather than prototype because WebKit has a bug that prevents correct descritpors from being returned using [`Object.getOwnPropertyDescriptor()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) See https://bugs.webkit.org/show_bug.cgi?id=49739 for more info. We need to be able to bypass the overrides if modifying the prototypes. This does have performance implications but since only web component elements get these overrides, these are minimised. We'll be gathering some performance data on this soon.
### Overview
- JavaScript API encapsulation for *most* things.
- Finders like `document.getElementById()` and `element.querySelectorAll()` are *not* polyfilled for performance reasons.
- All getters and setters that provide encapsulation are polyfilled.
- CSS encapsulation and selectors are *not* polyfilled.
## Polyfilled
These are members which are already polyfilled.
### Polyfilled
### Properties
These are members which are already polyfilled along with notes about their implementation details.
#### Element
#### Properties
- `Element.assignedSlot` - Available on every node at time of creation. Available in WebKit after being added to a shadow root.
- `Element.childElementCount`
- `Element.children` - Same as `Node.childNodes` except that it only contains element nodes.
- `Element.firstElementChild`
- `Element.innerHTML`
#### Node
- `Node.childNodes`
- `Element.lastElementChild`
- `Element.nextElementSibling`
- `Element.outerHTML`
- `Element.previousElementSibling`
- `Node.childNodes` - Returns an array instead of a `NodeList`, however, it applies an `item()` function so things expecting it to behave like a `NodeList` don't break.
- `Node.firstChild`
- `Node.lastChild`
- `Node.nextSibling`
- `Node.parentElement`
- `Node.parentNode`
- `Node.previousSibling`
- `Node.textContent`
#### ParentNode
#### Methods
- `ParentNode.children`
### Methods
#### Node
- `Element.attachShadow()`
- `HTMLSlotElement.getAssignedNodes()` - Only available after being added to a shadow root.
- `Node.appendChild()`

@@ -150,65 +149,33 @@ - `Node.hasChildNodes()`

## Probably
### Maybe
These are members which are not yet polyfilled but are planned to be.
These are members which are not yet polyfilled for a few reasons:
### Properties
- They'd probably have to be polyfilled for all elements, not just the host.
- They may not behave as expected causing confusion.
- If only part of the finding methods are polyfilled, not polyfilling some may cause confusion.
#### ParentNode
#### Properties
- `ParentNode.childElementCount`
- `ParentNode.firstElementChild`
- `ParentNode.lastElementChild`
## Maybe
These are members which are not yet polyfilled but are up for discussion.
### Properties
#### Element
- `Element.id`
#### NonDocumentTypeChildNode
#### Methods
- `NonDocumentTypeChildNode.nextElementSibling`
- `NonDocumentTypeChildNode.previousElementSibling`
#### Node
- `Node.nodeValue`
- `Node.nextSibling`
- `Node.nodeValue`
- `Node.parentNode`
- `Node.parentElement`
- `Node.previousSibling`
### Methods
#### Element
- `Document.getElementById()`
- `Element.getElementsByClassName()`
- `Element.getElementsByTagName()`
- `Element.getElementsByTagNameNS()`
#### Node
- `Node.cloneNode()`
- `Element.querySelector()`
- `Element.querySelectorAll()`
- `Node.compareDocumentPosition()`
- `Node.contains()`
- `Node.normalize()`
## Unlikely
### Unlikely
These are members which are not polyfilled and probably never will be because it's likely not necessary.
These are members which are not polyfilled because it's likely not necessary.
### Properties
#### Properties
#### Element
- `Element.accessKey`

@@ -220,14 +187,10 @@ - `Element.attributes`

- `Element.tagName`
#### Node
- `Node.baseURI`
- `Node.nodeName`
- `Node.nodeType`
- `Node.nodeValue` - doesn't need polyfilling because it returns `null` on element nodes in native anyways.
- `Node.ownerDocument`
### Methods
#### Methods
#### Element
- `Element.getAttribute()`

@@ -240,4 +203,2 @@ - `Element.getAttributeNS()`

- `Element.hasAttributes()`
- `Element.querySelector()`
- `Element.querySelectorAll()`
- `Element.releasePointerCapture()`

@@ -249,14 +210,24 @@ - `Element.removeAttribute()`

- `Element.setPointerCapture()`
- `Node.addEventListener()`
- `Node.cloneNode()`
- `Node.dispatchEvent()`
- `Node.isDefaultNamespace()`
- `Node.isEqualNode()`
- `Node.lookupNamespaceURI()`
- `Node.lookupPrefix()`
- `Node.normalize()`
- `Node.removeEventListener()`
#### EventTarget
- `EventTarget.addEventListener()`
- `EventTarget.dispatchEvent()`
- `EventTarget.removeEventListener()`
#### Node
## Performance
- `Node.isDefaultNamespace()`
- `Node.isEqualNode()`
- `Node.lookupNamespaceURI()`
- `Node.lookupPrefix()`
Obviously, performance is a concern when polyfilling anything and the past has shown Shadow DOM polyfills to be slow. Since we're not polyfilling everything, and don't ever aim to, we strive to keep an acceptable level of performance.
We've written some simple perf tests to show overhead against native. These vary depending on the browser you run them, so if you're concerned about performance, it's best to run these yourself. You can do so by:
1. Clone the repo
2. `npm install`
3. `gulp perf`
For most purposes, the performance should be acceptable. That said, we're always looking at ways to imporove.

@@ -1,3 +0,13 @@

import namedSlots from './index';
window.skateNamedSlots = namedSlots;
export default namedSlots ;
import main, * as api from '../src/index.js';
const previousGlobal = window.skatejsNamedSlots;
main.noConflict = function noConflict () {
window.skatejsNamedSlots = previousGlobal;
return this;
};
window.skatejsNamedSlots = main;
for (let name in api) {
main[name] = api[name];
}
main.version = 0.1.0
export default main;

@@ -1,13 +0,727 @@

import polyfill from './polyfill';
import polyfilled from './polyfilled';
import render from './render';
import slot from './slot';
import { eachChildNode, eachNodeOrFragmentNodes } from './util/each';
import canPatchNativeAccessors from './util/can-patch-native-accessors';
import debounce from 'debounce';
import version from './version';
import WeakMap from './util/weak-map';
export default {
polyfill,
polyfilled,
render,
slot,
version
// We use a real DOM node for a shadow root. This is because the host node
// basically becomes a virtual entry point for your element leaving the shadow
// root the only thing that can receive instructions on how the host should
// render to the browser.
const defaultShadowRootTagName = '_shadow_root_';
const defaultShadowRootTagNameUc = defaultShadowRootTagName.toUpperCase();
// * WebKit only *
//
// These members we need cannot override as we require native access to their
// original values at some point.
const polyfilAtRuntime = ['childNodes', 'parentNode'];
// These are the protos that we need to search for native descriptors on.
const protos = ['Node', 'Element', 'EventTarget'];
// Private data stores.
const assignedToSlotMap = new WeakMap();
const hostToModeMap = new WeakMap();
const hostToRootMap = new WeakMap();
const nodeToChildNodesMap = new WeakMap();
const nodeToParentNodeMap = new WeakMap();
const nodeToSlotMap = new WeakMap();
const rootToHostMap = new WeakMap();
const rootToSlotMap = new WeakMap();
const slotToModeMap = new WeakMap();
// * WebKit only *
//
// We require some way to parse HTML natively because we can't use the native
// accessors. To do this we parse as XML and conver each node in the tree to
// HTML nodes.
//
// This works because we polyfill at the HTMLElement level and XML nodes are
// considered Element nodes and we don't polyfill at that level.
const parser = new DOMParser();
function convertXmlToHtml (node) {
const { nodeType } = node;
if (nodeType === 1) {
const copy = document.createElement(node.tagName);
for (let a = 0; a < node.attributes.length; a++) {
const attr = node.attributes[a];
copy.setAttribute(attr.name, attr.value);
}
for (let a = 0; a < node.childNodes.length; a++) {
const childNode = node.childNodes[a];
copy.appendChild(convertXmlToHtml(childNode));
}
return copy;
}
return node;
}
function parse (html) {
const tree = document.createElement('div');
const parsed = parser.parseFromString(html, 'text/xml');
while (parsed.hasChildNodes()) {
const firstChild = parsed.firstChild;
parsed.removeChild(firstChild);
tree.appendChild(convertXmlToHtml(firstChild));
}
return tree;
}
function staticProp (obj, name, value) {
Object.defineProperty(obj, name, {
configurable: true,
get () { return value; }
});
}
// Slotting helpers.
function arrayItem (idx) {
return this[idx];
}
function makeLikeNodeList (arr) {
arr.item = arrayItem;
return arr;
}
function getNodeType (node) {
if (isHostNode(node)) {
return 'host';
}
if (isSlotNode(node)) {
return 'slot';
}
if (isRootNode(node)) {
return 'root';
}
return 'node';
}
function isHostNode (node) {
return !!hostToRootMap.get(node);
}
function isSlotNode (node) {
return node.tagName === 'SLOT';
}
function isRootNode (node) {
return node.tagName === defaultShadowRootTagNameUc;
}
function findClosest (node, func) {
while (node) {
if (node === document) {
break;
}
if (func(node)) {
return node;
}
node = node.parentNode;
}
}
function getSlotNameFromSlot (node) {
return node.getAttribute && node.getAttribute('name') || 'default';
}
function getSlotNameFromNode (node) {
return node.getAttribute && node.getAttribute('slot') || 'default';
}
function slotNodeIntoSlot (slot, node, insertBefore) {
const assignedNodes = slot.getAssignedNodes();
const slotInsertBeforeIndex = assignedNodes.indexOf(insertBefore);
nodeToSlotMap.set(node, slot);
// If there's currently no assigned nodes, there will be, so remove all fallback content.
if (!assignedNodes.length) {
slotToModeMap.set(slot, false);
[].slice.call(slot.childNodes).forEach(fallbackNode => slot.__removeChild(fallbackNode));
}
const shouldAffectSlot = !slotToModeMap.get(slot);
if (slotInsertBeforeIndex > -1) {
if (shouldAffectSlot) {
slot.__insertBefore(node, insertBefore);
}
assignedNodes.splice(slotInsertBeforeIndex, 0, node);
} else {
if (shouldAffectSlot) {
slot.__appendChild(node);
}
assignedNodes.push(node);
}
slot.____triggerSlotChangeEvent();
}
function slotNodeFromSlot (node) {
const slot = node.assignedSlot;
if (slot) {
const assignedNodes = slot.getAssignedNodes();
const index = assignedNodes.indexOf(node);
if (index > -1) {
assignedNodes.splice(index, 1);
nodeToSlotMap.set(node, null);
const shouldAffectSlot = !slotToModeMap.get(slot);
// We only update the actual DOM representation if we're displaying
// slotted nodes.
if (shouldAffectSlot) {
slot.__removeChild(node);
}
// If this was the last slotted node, then insert fallback content.
if (!assignedNodes.length) {
slotToModeMap.set(slot, true);
eachChildNode(slot, function (node) {
slot.__appendChild(node);
});
}
slot.____triggerSlotChangeEvent();
}
}
}
function indexOfNode (host, node) {
const chs = host.childNodes;
const chsLen = chs.length;
for (let a = 0; a < chsLen; a++) {
if (chs[a] === node) {
return a;
}
}
return -1;
}
// Adds the node to the list of childNodes on the host and fakes any necessary
// information such as parentNode.
function registerNode (host, node, insertBefore, func) {
const index = indexOfNode(host, insertBefore);
eachNodeOrFragmentNodes(node, function (eachNode, eachIndex) {
func(eachNode, eachIndex);
if (canPatchNativeAccessors) {
nodeToParentNodeMap.set(eachNode, host);
} else {
staticProp(eachNode, 'parentNode', host);
}
if (index > -1) {
host.childNodes.splice(index + eachIndex, 0, eachNode);
} else {
host.childNodes.push(eachNode);
}
});
}
// Cleans up registerNode().
function unregisterNode (host, node, func) {
const index = indexOfNode(host, node);
if (index > -1) {
func(node, 0);
if (canPatchNativeAccessors) {
nodeToParentNodeMap.set(node, null);
} else {
staticProp(node, 'parentNode', null);
}
host.childNodes.splice(index, 1);
}
}
function addNodeToNode (host, node, insertBefore) {
registerNode(host, node, insertBefore, function (eachNode) {
host.__insertBefore(eachNode, insertBefore);
});
}
function addNodeToHost (host, node, insertBefore) {
registerNode(host, node, insertBefore, function (eachNode) {
const rootNode = hostToRootMap.get(host);
const slotNodes = rootToSlotMap.get(rootNode);
const slotNode = slotNodes[getSlotNameFromNode(eachNode)];
if (slotNode) {
slotNodeIntoSlot(slotNode, eachNode, insertBefore);
}
});
}
function addNodeToRoot (root, node, insertBefore) {
eachNodeOrFragmentNodes(node, function (node) {
if (isSlotNode(node)) {
addSlotToRoot(root, node);
} else {
const slotNodes = node.querySelectorAll && node.querySelectorAll('slot');
const slotNodesLen = slotNodes.length;
for (let a = 0; a < slotNodesLen; a++) {
addSlotToRoot(root, slotNodes[a]);
}
}
});
addNodeToNode(root, node, insertBefore);
}
function addSlotToRoot (root, node) {
const slotName = getSlotNameFromSlot(node);
slotToModeMap.set(node, true);
rootToSlotMap.get(root)[slotName] = node;
eachChildNode(rootToHostMap.get(root), function (eachNode) {
if (!eachNode.assignedSlot && slotName === getSlotNameFromNode(eachNode)) {
slotNodeIntoSlot(node, eachNode);
}
});
}
function removeNodeFromNode (host, node) {
unregisterNode(host, node, function () {
host.__removeChild(node);
});
}
function removeNodeFromHost (host, node) {
unregisterNode(host, node, function () {
slotNodeFromSlot(node);
});
}
function removeNodeFromRoot (root, node) {
unregisterNode(root, node, function () {
if (isSlotNode(node)) {
removeSlotFromRoot(root, node);
} else {
const nodes = node.querySelectorAll && node.querySelectorAll('slot');
for (let a = 0; a < nodes.length; a++) {
removeSlotFromRoot(root, nodes[a]);
}
}
});
}
function removeSlotFromRoot (root, node) {
node.getAssignedNodes().forEach(slotNodeFromSlot);
delete rootToSlotMap.get(root)[getSlotNameFromSlot(node)];
}
function appendChildOrInsertBefore (host, newNode, refNode) {
const nodeType = getNodeType(host);
const parentNode = newNode.parentNode;
if (!canPatchNativeAccessors && !host.childNodes.push) {
staticProp(host, 'childNodes', []);
}
// If we append a child to a host, the host tells the shadow root to distribute
// it. If the root decides it doesn't need to be distributed, it is never
// removed from the old parent because in polyfill land we store a reference
// to the node but we don't move it. Due to that, we must explicitly remove the
// node from its old parent.
if (parentNode && getNodeType(parentNode) === 'host') {
if (canPatchNativeAccessors) {
nodeToParentNodeMap.set(newNode, null);
} else {
staticProp(newNode, 'parentNode', null);
}
}
if (nodeType === 'node') {
if (canPatchNativeAccessors) {
return host.__insertBefore(newNode, refNode);
} else {
return addNodeToNode(host, newNode, refNode);
}
}
if (nodeType === 'slot') {
return addNodeToNode(host, newNode, refNode);
}
if (nodeType === 'host') {
return addNodeToHost(host, newNode, refNode);
}
if (nodeType === 'root') {
return addNodeToRoot(host, newNode, refNode);
}
}
const members = {
// For testing purposes.
____assignedNodes: {
get () {
return this.______assignedNodes || (this.______assignedNodes = []);
}
},
// For testing purposes.
____isInFallbackMode: {
get () {
return slotToModeMap.get(this);
}
},
____slotChangeListeners: {
get () {
if (typeof this.______slotChangeListeners === 'undefined') {
this.______slotChangeListeners = 0;
}
return this.______slotChangeListeners;
},
set (value) {
this.______slotChangeListeners = value;
}
},
____triggerSlotChangeEvent: {
value: debounce(function () {
if (this.____slotChangeListeners) {
this.dispatchEvent(new CustomEvent('slotchange', {
bubbles: false,
cancelable: false
}));
}
})
},
addEventListener: {
value (name, func, opts) {
if (name === 'slotchange' && isSlotNode(this)) {
this.____slotChangeListeners++;
}
return this.__addEventListener(name, func, opts);
}
},
appendChild: {
value (newNode) {
return appendChildOrInsertBefore(this, newNode);
}
},
assignedSlot: {
get () {
return nodeToSlotMap.get(this) || null;
}
},
attachShadow: {
value (opts) {
const mode = opts && opts.mode;
if (mode !== 'closed' && mode !== 'open') {
throw new Error('You must specify { mode } as "open" or "closed" to attachShadow().');
}
// Return the existing shadow root if it exists.
const existingShadowRoot = hostToRootMap.get(this);
if (existingShadowRoot) {
return existingShadowRoot;
}
const lightNodes = makeLikeNodeList([].slice.call(this.childNodes));
const shadowRoot = document.createElement(opts.polyfillShadowRootTagName || defaultShadowRootTagName);
// Host and shadow root data.
hostToModeMap.set(this, mode);
hostToRootMap.set(this, shadowRoot);
rootToHostMap.set(shadowRoot, this);
rootToSlotMap.set(shadowRoot, {});
if (canPatchNativeAccessors) {
nodeToChildNodesMap.set(this, lightNodes);
} else {
staticProp(this, 'childNodes', lightNodes);
}
// Existing children should be removed from being displayed, but still
// appear to be child nodes. This is how light DOM works; they're still
// child nodes but not in the composed DOM yet as there won't be any
// slots for them to go into.
eachChildNode(this, node => this.__removeChild(node));
// The shadow root is actually the only child of the host.
return this.__appendChild(shadowRoot);
}
},
childElementCount: {
get () {
return this.children.length;
}
},
childNodes: {
get () {
if (canPatchNativeAccessors && getNodeType(this) === 'node') {
return this.__childNodes;
}
let childNodes = nodeToChildNodesMap.get(this);
childNodes || nodeToChildNodesMap.set(this, childNodes = makeLikeNodeList([]));
return childNodes;
}
},
children: {
get () {
const chs = [];
eachChildNode(this, function (node) {
if (node.nodeType === 1) {
chs.push(node);
}
});
return makeLikeNodeList(chs);
}
},
firstChild: {
get () {
return this.childNodes[0] || null;
}
},
firstElementChild: {
get () {
return this.children[0] || null;
}
},
getAssignedNodes: {
value () {
if (isSlotNode(this)) {
let assigned = assignedToSlotMap.get(this);
assigned || assignedToSlotMap.set(this, assigned = []);
return assigned;
}
}
},
hasChildNodes: {
value () {
return this.childNodes.length > 0;
}
},
innerHTML: {
get () {
let innerHTML = '';
eachChildNode(this, function (node) {
innerHTML += node.nodeType === 1 ? node.outerHTML : node.textContent;
});
return innerHTML;
},
set (innerHTML) {
const parsed = parse(innerHTML);
while (this.hasChildNodes()) {
this.removeChild(this.firstChild);
}
while (parsed.hasChildNodes()) {
const firstChild = parsed.firstChild;
// When we polyfill everything on HTMLElement.prototype, we overwrite
// properties. This makes it so that parentNode reports null even though
// it's actually a parent of the HTML parser. For this reason,
// cleanNode() won't work and we must manually remove it from the
// parser before it is moved to the host just in case it's added as a
// light node but not assigned to a slot.
parsed.removeChild(firstChild);
this.appendChild(firstChild);
}
}
},
insertBefore: {
value (newNode, refNode) {
return appendChildOrInsertBefore(this, newNode, refNode);
}
},
lastChild: {
get () {
const ch = this.childNodes;
return ch[ch.length - 1] || null;
}
},
lastElementChild: {
get () {
const ch = this.children;
return ch[ch.length - 1] || null;
}
},
name: {
get () {
return this.getAttribute('name');
},
set (name) {
return this.setAttribute('name', name);
}
},
nextSibling: {
get () {
const host = this;
return eachChildNode(this.parentNode, function (child, index, nodes) {
if (host === child) {
return nodes[index + 1] || null;
}
});
}
},
nextElementSibling: {
get () {
const host = this;
let found;
return eachChildNode(this.parentNode, function (child) {
if (found && child.nodeType === 1) {
return child;
}
if (host === child) {
found = true;
}
});
}
},
outerHTML: {
get () {
const name = this.tagName.toLowerCase();
const attributes = Array.prototype.slice.call(this.attributes).map(function (attr) {
return ` ${attr.name}${attr.value ? `="${attr.value}"` : ''}`;
}).join('');
return `<${name}${attributes}>${this.innerHTML}</${name}>`;
}
},
parentElement: {
get () {
return findClosest(this.parentNode, function (node) {
return node.nodeType === 1;
});
}
},
parentNode: {
get () {
return nodeToParentNodeMap.get(this) || this.__parentNode || null;
}
},
previousSibling: {
get () {
const host = this;
return eachChildNode(this.parentNode, function (child, index, nodes) {
if (host === child) {
return nodes[index - 1] || null;
}
});
}
},
previousElementSibling: {
get () {
const host = this;
let found;
return eachChildNode(this.parentNode, function (child) {
if (found && host === child) {
return found;
}
if (child.nodeType === 1) {
found = child;
}
});
}
},
removeChild: {
value (refNode) {
const nodeType = getNodeType(this);
if (nodeType === 'node') {
if (canPatchNativeAccessors) {
return this.__removeChild(refNode);
} else {
return removeNodeFromNode(this, refNode);
}
}
if (nodeType === 'slot') {
return removeNodeFromNode(this, refNode);
}
if (nodeType === 'host') {
return removeNodeFromHost(this, refNode);
}
if (nodeType === 'root') {
return removeNodeFromRoot(this, refNode);
}
}
},
removeEventListener: {
value (name, func, opts) {
if (name === 'slotchange' && this.____slotChangeListeners && isSlotNode(this)) {
this.____slotChangeListeners--;
}
return this.__removeEventListener(name, func, opts);
}
},
replaceChild: {
value (newNode, refNode) {
this.insertBefore(newNode, refNode);
return this.removeChild(refNode);
}
},
shadowRoot: {
get () {
return hostToModeMap.get(this) === 'open' ? hostToRootMap.get(this) : null;
}
},
textContent: {
get () {
let textContent = '';
eachChildNode(this, function (node) {
textContent += node.textContent;
});
return textContent;
},
set (textContent) {
while (this.hasChildNodes()) {
this.removeChild(this.firstChild);
}
this.appendChild(document.createTextNode(textContent));
}
}
};
function findDescriptorFor (name) {
for (let a = 0; a < protos.length; a++) {
const ctor = window[protos[a]];
if (!ctor) {
continue;
}
const proto = ctor.prototype;
if (proto.hasOwnProperty(name)) {
return Object.getOwnPropertyDescriptor(proto, name);
}
}
}
if (!('attachShadow' in document.createElement('div'))) {
const elementProto = HTMLElement.prototype;
Object.keys(members).forEach(function (memberName) {
const memberProperty = members[memberName];
// All properties should be configurable.
memberProperty.configurable = true;
// Polyfill as much as we can and work around WebKit in other areas.
if (canPatchNativeAccessors || polyfilAtRuntime.indexOf(memberName) === -1) {
const nativeDescriptor = findDescriptorFor(memberName);
Object.defineProperty(elementProto, memberName, memberProperty);
if (nativeDescriptor && nativeDescriptor.configurable) {
Object.defineProperty(elementProto, '__' + memberName, nativeDescriptor);
}
}
});
}
export default version;

@@ -1,4 +0,10 @@

import render from '../src/render';
import create from './lib/create';
import version from '../src/version';
import './unit/light/polyfill';
import './unit/shadow/polyfill';
import './unit/slot/polyfill';
import './unit/slot/fallback-content';
import './unit/slot/change-event.js';
describe('skatejs-named-slots', function () {

@@ -9,1 +15,251 @@ it('version', function () {

});
describe('skatejs-named-slots', function () {
let host, root, slot;
function add () {
host.appendChild(document.createElement('div'));
}
function remove() {
host.removeChild(host.firstChild);
}
beforeEach(function () {
host = document.createElement('div');
root = host.attachShadow({ mode: 'closed' });
slot = create('slot');
// Now it has something to assign nodes to.
root.appendChild(slot);
});
describe('methods', function () {
it('appendChild()', function () {
const light1 = document.createElement('light-1');
const light2 = document.createElement('light-2');
host.appendChild(light1);
expect(host.childNodes[0]).to.equal(light1, 'internal light dom');
expect(slot.getAssignedNodes().length).to.equal(1, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light1, 'slot');
expect(host.childNodes.length).to.equal(1, 'light');
expect(host.childNodes[0]).to.equal(light1, 'light');
host.appendChild(light2);
expect(host.childNodes[1]).to.equal(light2, 'internal light dom');
expect(slot.getAssignedNodes().length).to.equal(2, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light1, 'slot');
expect(slot.getAssignedNodes()[1]).to.equal(light2, 'slot');
expect(host.childNodes.length).to.equal(2, 'light');
expect(host.childNodes[0]).to.equal(light1, 'light');
expect(host.childNodes[1]).to.equal(light2, 'light');
});
it('hasChildNodes', function () {
expect(host.hasChildNodes()).to.equal(false);
host.appendChild(document.createElement('div'));
expect(host.hasChildNodes()).to.equal(true);
host.removeChild(host.firstChild);
expect(host.hasChildNodes()).to.equal(false);
});
it('insertBefore()', function () {
const light1 = document.createElement('light1');
const light2 = document.createElement('light2');
host.insertBefore(light2);
expect(host.childNodes[0]).to.equal(light2, 'internal light dom');
expect(slot.getAssignedNodes().length).to.equal(1, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light2, 'slot');
expect(host.childNodes.length).to.equal(1, 'light');
expect(host.childNodes[0]).to.equal(light2, 'light');
host.insertBefore(light1, light2);
expect(host.childNodes[0]).to.equal(light1, 'internal light dom');
expect(host.childNodes[1]).to.equal(light2, 'internal light dom');
expect(slot.getAssignedNodes().length).to.equal(2, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light1, 'slot');
expect(slot.getAssignedNodes()[1]).to.equal(light2, 'slot');
expect(host.childNodes.length).to.equal(2, 'light');
expect(host.childNodes[0]).to.equal(light1, 'light');
expect(host.childNodes[1]).to.equal(light2, 'light');
});
it('removeChild()', function () {
const light1 = document.createElement('div');
const light2 = document.createElement('div');
host.appendChild(light1);
host.appendChild(light2);
expect(slot.getAssignedNodes().length).to.equal(2, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light1, 'slot');
expect(slot.getAssignedNodes()[1]).to.equal(light2, 'slot');
expect(host.childNodes.length).to.equal(2, 'light');
expect(host.childNodes[0]).to.equal(light1, 'light');
expect(host.childNodes[1]).to.equal(light2, 'light');
host.removeChild(light1);
expect(host.childNodes.length).to.equal(1);
expect(host.childNodes[0]).to.equal(light2, 'internal light dom');
expect(slot.getAssignedNodes().length).to.equal(1, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light2, 'slot');
expect(host.childNodes.length).to.equal(1, 'light');
expect(host.childNodes[0]).to.equal(light2, 'light');
host.removeChild(light2);
expect(host.childNodes.length).to.equal(0);
expect(slot.getAssignedNodes().length).to.equal(0, 'slot');
expect(host.childNodes.length).to.equal(0, 'light');
});
it('replaceChild()', function () {
const light1 = document.createElement('div');
const light2 = document.createElement('div');
host.appendChild(light1);
expect(slot.getAssignedNodes().length).to.equal(1, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light1, 'slot');
expect(host.childNodes.length).to.equal(1, 'light');
expect(host.childNodes[0]).to.equal(light1, 'light');
host.replaceChild(light2, light1);
expect(host.childNodes.length).to.equal(1);
expect(host.childNodes[0]).to.equal(light2, 'internal light dom');
expect(slot.getAssignedNodes().length).to.equal(1, 'slot');
expect(slot.getAssignedNodes()[0]).to.equal(light2, 'slot');
expect(host.childNodes.length).to.equal(1, 'light');
expect(host.childNodes[0]).to.equal(light2, 'light');
});
});
describe('properties', function () {
it('childElementCount', function () {
expect(host.childElementCount).to.equal(0);
add();
expect(host.childElementCount).to.equal(1);
remove();
expect(host.childElementCount).to.equal(0);
});
it('childNodes', function () {
expect(host.childNodes).to.be.an('array');
expect(host.childNodes.item).to.be.a('function');
expect(host.childNodes.length).to.equal(0);
add();
expect(host.childNodes.length).to.equal(1);
remove();
expect(host.childNodes.length).to.equal(0);
});
it('children', function () {
expect(host.childNodes).to.be.an('array');
expect(host.childNodes.item).to.be.a('function');
expect(host.childNodes.length).to.equal(0);
add();
expect(host.childNodes.length).to.equal(1);
remove();
expect(host.childNodes.length).to.equal(0);
});
it('firstChild', function () {
expect(host.firstChild).to.equal(null);
add();
expect(host.firstChild).to.not.equal(null);
expect(host.firstChild.tagName).to.equal('DIV');
remove();
expect(host.firstChild).to.equal(null);
});
it('firstElementChild', function () {
expect(host.firstChild).to.equal(null);
add();
expect(host.firstChild).to.not.equal(null);
expect(host.firstChild.tagName).to.equal('DIV');
remove();
expect(host.firstChild).to.equal(null);
});
it('innerHTML', function () {
expect(host.innerHTML).to.equal('');
host.innerHTML = '<div slot="custom"></div>';
expect(host.innerHTML).to.equal('<div slot="custom"></div>');
expect(host.childNodes.length).to.equal(1);
expect(slot.getAssignedNodes().length).to.equal(0);
host.innerHTML = '<div></div>';
expect(host.innerHTML).to.equal('<div></div>');
expect(host.childNodes.length).to.equal(1);
expect(slot.getAssignedNodes().length).to.equal(1);
host.innerHTML = '<div></div>';
expect(host.innerHTML).to.equal('<div></div>');
expect(host.childNodes.length).to.equal(1);
expect(slot.getAssignedNodes().length).to.equal(1);
});
it('lastChild', function () {
expect(host.lastChild).to.equal(null);
add();
expect(host.lastChild).to.not.equal(null);
expect(host.lastChild.tagName).to.equal('DIV');
remove();
expect(host.lastChild).to.equal(null);
});
it('lastElementChild', function () {
expect(host.lastElementChild).to.equal(null);
add();
expect(host.lastElementChild).to.not.equal(null);
expect(host.lastElementChild.tagName).to.equal('DIV');
remove();
expect(host.lastElementChild).to.equal(null);
});
it('outerHTML', function () {
expect(host.outerHTML).to.equal('<div></div>');
host.innerHTML = '<div slot="custom"></div>';
expect(host.outerHTML).to.equal('<div><div slot="custom"></div></div>');
expect(host.childNodes.length).to.equal(1);
expect(slot.getAssignedNodes().length).to.equal(0);
host.innerHTML = '<div></div>';
expect(host.outerHTML).to.equal('<div><div></div></div>');
expect(host.childNodes.length).to.equal(1);
expect(slot.getAssignedNodes().length).to.equal(1);
host.innerHTML = '<div></div>';
expect(host.outerHTML).to.equal('<div><div></div></div>');
expect(host.childNodes.length).to.equal(1);
expect(slot.getAssignedNodes().length).to.equal(1);
});
it('textContent', function () {
expect(host.textContent).to.equal('');
host.textContent = '<test />';
expect(host.textContent).to.equal('<test />');
// Ensure value was escaped.
expect(host.firstChild.nodeType).to.equal(3);
});
});
});
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