pseudo-dom 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Parse an HTML string into the nodes it describes (siblings at the top level, exactly like the DOM's own HTML
3
+ * parsing does for innerHTML / insertAdjacentHTML - there is no single root unless the markup itself has one).
4
+ * @memberOf module:factories
5
+ * @param {string} html
6
+ * @param {*} ownerDocument The document the new nodes belong to (matches what innerHTML etc. would set), or null
7
+ * @param {function(new: *, {tagName: string})} ElementClass The class to build each parsed element with
8
+ * @returns {Array<*>}
9
+ */
10
+ declare const parseHTML: (html: string, ownerDocument: any, ElementClass: new (options: {
11
+ tagName: string;
12
+ }) => any) => Array<any>;
13
+ export default parseHTML;
@@ -0,0 +1,84 @@
1
+ 'use strict'
2
+
3
+ Object.defineProperty(exports, '__esModule', {
4
+ value: true
5
+ })
6
+ /**
7
+ * @file Parses an HTML string into pseudo-dom nodes, for innerHTML / outerHTML / insertAdjacentHTML. Built on
8
+ * htmlparser2's Parser (a SAX-style tokenizer) with a custom handler which builds pseudo-dom nodes directly -
9
+ * htmlparser2's own default DomHandler / domutils tree (which this never uses) is stubbed out of the browser bundle
10
+ * by the browser.ignore config, the same way css-select's unused default adapter already is.
11
+ *
12
+ * The class to build parsed elements with is given by the caller (rather than imported here) so this has no
13
+ * dependency on ElementService / HTMLElementService - importing either here, from a factory ElementService itself
14
+ * would need to call, would create an import cycle.
15
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
16
+ * @version 1.0.0
17
+ */
18
+ const htmlparser2_1 = require('htmlparser2')
19
+ const NodeService_1 = require('../services/NodeService')
20
+ /**
21
+ * Parse an HTML string into the nodes it describes (siblings at the top level, exactly like the DOM's own HTML
22
+ * parsing does for innerHTML / insertAdjacentHTML - there is no single root unless the markup itself has one).
23
+ * @memberOf module:factories
24
+ * @param {string} html
25
+ * @param {*} ownerDocument The document the new nodes belong to (matches what innerHTML etc. would set), or null
26
+ * @param {function(new: *, {tagName: string})} ElementClass The class to build each parsed element with
27
+ * @returns {Array<*>}
28
+ */
29
+ const parseHTML = (html, ownerDocument, ElementClass) => {
30
+ const roots = []
31
+ const stack = []
32
+ const appendNode = node => {
33
+ node.ownerDocumentStore = ownerDocument
34
+ if (stack.length) {
35
+ stack[stack.length - 1].appendChild(node)
36
+ } else {
37
+ roots.push(node)
38
+ }
39
+ }
40
+ const parser = new htmlparser2_1.Parser({
41
+ onopentagname (name) {
42
+ const element = new ElementClass({
43
+ tagName: name
44
+ })
45
+ appendNode(element)
46
+ stack.push(element)
47
+ },
48
+ onattribute (name, value) {
49
+ if (!stack.length) {
50
+ return
51
+ }
52
+ const element = stack[stack.length - 1]
53
+ // pseudo-dom's className / classList and style are not kept in sync with a literal 'class' / 'style'
54
+ // attribute the way setAttribute usually is (there is no such special-casing here, unlike a real DOM) - route
55
+ // them to the real objects directly, the same way cssSelectAdapter does for reading 'class' when matching a
56
+ // CSS selector, so parsed markup's class="..." / style="..." actually populates className/classList and style.
57
+ if (name === 'class') {
58
+ element.className = value
59
+ return
60
+ }
61
+ if (name === 'style' && element.style) {
62
+ element.style.cssText = value
63
+ return
64
+ }
65
+ element.setAttribute(name, value)
66
+ },
67
+ ontext (text) {
68
+ appendNode(new NodeService_1.TextService(text))
69
+ },
70
+ oncomment (data) {
71
+ appendNode(new NodeService_1.CommentService(data))
72
+ },
73
+ onclosetag () {
74
+ stack.pop()
75
+ }
76
+ }, {
77
+ lowerCaseTags: true,
78
+ lowerCaseAttributeNames: true
79
+ })
80
+ parser.write(String(html || ''))
81
+ parser.end()
82
+ return roots
83
+ }
84
+ exports.default = parseHTML
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const htmlparser2_1=require("htmlparser2"),NodeService_1=require("../services/NodeService"),parseHTML=(e,t,r)=>{const s=[],n=[],o=e=>{e.ownerDocumentStore=t,n.length?n[n.length-1].appendChild(e):s.push(e)},a=new htmlparser2_1.Parser({onopentagname(e){const t=new r({tagName:e});o(t),n.push(t)},onattribute(e,t){if(!n.length)return;const r=n[n.length-1];"class"!==e?"style"===e&&r.style?r.style.cssText=t:r.setAttribute(e,t):r.className=t},ontext(e){o(new NodeService_1.TextService(e))},oncomment(e){o(new NodeService_1.CommentService(e))},onclosetag(){n.pop()}},{lowerCaseTags:!0,lowerCaseAttributeNames:!0});return a.write(String(e||"")),a.end(),s};exports.default=parseHTML;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * A node's children, serialized in order (this is what innerHTML returns).
3
+ * @memberOf module:factories
4
+ * @param {*} node
5
+ * @returns {string}
6
+ */
7
+ declare const serializeChildren: (node: any) => string;
8
+ /**
9
+ * An element itself, serialized with its children (this is what outerHTML returns).
10
+ * @memberOf module:factories
11
+ * @param {*} element
12
+ * @returns {string}
13
+ */
14
+ declare const serializeOuter: (element: any) => string;
15
+ export { serializeChildren, serializeOuter };
@@ -0,0 +1,114 @@
1
+ 'use strict'
2
+
3
+ require('core-js/modules/esnext.iterator.constructor.js')
4
+ require('core-js/modules/esnext.iterator.filter.js')
5
+ require('core-js/modules/esnext.iterator.map.js')
6
+ require('core-js/modules/esnext.set.add-all.js')
7
+ require('core-js/modules/esnext.set.delete-all.js')
8
+ require('core-js/modules/esnext.set.difference.js')
9
+ require('core-js/modules/esnext.set.every.js')
10
+ require('core-js/modules/esnext.set.filter.js')
11
+ require('core-js/modules/esnext.set.find.js')
12
+ require('core-js/modules/esnext.set.intersection.js')
13
+ require('core-js/modules/esnext.set.is-disjoint-from.js')
14
+ require('core-js/modules/esnext.set.is-subset-of.js')
15
+ require('core-js/modules/esnext.set.is-superset-of.js')
16
+ require('core-js/modules/esnext.set.join.js')
17
+ require('core-js/modules/esnext.set.map.js')
18
+ require('core-js/modules/esnext.set.reduce.js')
19
+ require('core-js/modules/esnext.set.some.js')
20
+ require('core-js/modules/esnext.set.symmetric-difference.js')
21
+ require('core-js/modules/esnext.set.union.js')
22
+ Object.defineProperty(exports, '__esModule', {
23
+ value: true
24
+ })
25
+ exports.serializeOuter = exports.serializeChildren = void 0
26
+ /**
27
+ * @file Serializes pseudo-dom nodes back into an HTML string, for innerHTML / outerHTML.
28
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
29
+ * @version 1.0.0
30
+ */
31
+ const NodeService_1 = require('../services/NodeService')
32
+ // Void elements are self-closing and never serialize a closing tag or children (there is no HTML parsing of their
33
+ // content either - the DOM never lets them have children in the first place).
34
+ const VOID_ELEMENTS = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'])
35
+ // These property-backed names (see ElementService / HTMLElementService's constructors) are not real HTML content
36
+ // attributes - offsetWidth and friends describe layout, which does not exist here, and innerHTML is JS-only - so
37
+ // they must never appear in serialized markup, however getAttributeNames() may report them.
38
+ const NEVER_SERIALIZE = new Set(['innerHTML', 'clientHeight', 'clientLeft', 'clientTop', 'clientWidth', 'scrollHeight', 'scrollLeft', 'scrollTop', 'scrollWidth', 'offsetHeight', 'offsetLeft', 'offsetParent', 'offsetTop', 'offsetWidth'])
39
+ // The attribute name to serialize a property-backed name as, when it differs from the property name
40
+ const ATTRIBUTE_NAME = {
41
+ className: 'class'
42
+ }
43
+ // Boolean HTML attributes: present (with no value) when true, absent entirely when false
44
+ const BOOLEAN_ATTRIBUTES = new Set(['hidden'])
45
+ /**
46
+ * Escape text so it is safe inside HTML text content.
47
+ * @param {string} text
48
+ * @returns {string}
49
+ */
50
+ const escapeText = text => text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
51
+ /**
52
+ * Escape a value so it is safe inside a double-quoted HTML attribute.
53
+ * @param {string} value
54
+ * @returns {string}
55
+ */
56
+ const escapeAttributeValue = value => value.replace(/&/g, '&amp;').replace(/"/g, '&quot;')
57
+ /**
58
+ * Every attribute of the element, serialized (class instead of className, boolean attributes bare, the never-real
59
+ * mock properties left out, style added from the live CSSStyleDeclaration when it is not empty).
60
+ * @param {*} element
61
+ * @returns {string}
62
+ */
63
+ const serializeAttributes = element => {
64
+ let result = (element.getAttributeNames ? element.getAttributeNames() : []).filter(name => !NEVER_SERIALIZE.has(name)).map(name => {
65
+ if (BOOLEAN_ATTRIBUTES.has(name)) {
66
+ return element[name] ? ` ${name}` : ''
67
+ }
68
+ const value = element.getAttribute(name)
69
+ return value === '' ? '' : ` ${ATTRIBUTE_NAME[name] || name}="${escapeAttributeValue(value)}"`
70
+ }).join('')
71
+ const style = element.style
72
+ if (style && style.cssText) {
73
+ result += ` style="${escapeAttributeValue(style.cssText)}"`
74
+ }
75
+ return result
76
+ }
77
+ /**
78
+ * One node, serialized (its own markup only - see serializeChildren for its descendants too).
79
+ * @param {*} node
80
+ * @returns {string}
81
+ */
82
+ const serializeNode = node => {
83
+ if (node.nodeType === NodeService_1.NodeService.TEXT_NODE) {
84
+ return escapeText(node.nodeValue || '')
85
+ }
86
+ if (node.nodeType === NodeService_1.NodeService.COMMENT_NODE) {
87
+ return `<!--${node.nodeValue || ''}-->`
88
+ }
89
+ if (node.nodeType !== NodeService_1.NodeService.ELEMENT_NODE) {
90
+ return ''
91
+ }
92
+ const tagName = node.tagName
93
+ const attributes = serializeAttributes(node)
94
+ if (VOID_ELEMENTS.has(tagName)) {
95
+ return `<${tagName}${attributes}>`
96
+ }
97
+ return `<${tagName}${attributes}>${serializeChildren(node)}</${tagName}>`
98
+ }
99
+ /**
100
+ * A node's children, serialized in order (this is what innerHTML returns).
101
+ * @memberOf module:factories
102
+ * @param {*} node
103
+ * @returns {string}
104
+ */
105
+ const serializeChildren = node => Array.from(node.childNodes).map(serializeNode).join('')
106
+ exports.serializeChildren = serializeChildren
107
+ /**
108
+ * An element itself, serialized with its children (this is what outerHTML returns).
109
+ * @memberOf module:factories
110
+ * @param {*} element
111
+ * @returns {string}
112
+ */
113
+ const serializeOuter = element => serializeNode(element)
114
+ exports.serializeOuter = serializeOuter
@@ -0,0 +1 @@
1
+ "use strict";require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.filter.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js"),Object.defineProperty(exports,"__esModule",{value:!0}),exports.serializeOuter=exports.serializeChildren=void 0;const NodeService_1=require("../services/NodeService"),VOID_ELEMENTS=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),NEVER_SERIALIZE=new Set(["innerHTML","clientHeight","clientLeft","clientTop","clientWidth","scrollHeight","scrollLeft","scrollTop","scrollWidth","offsetHeight","offsetLeft","offsetParent","offsetTop","offsetWidth"]),ATTRIBUTE_NAME={className:"class"},BOOLEAN_ATTRIBUTES=new Set(["hidden"]),escapeText=e=>e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),escapeAttributeValue=e=>e.replace(/&/g,"&amp;").replace(/"/g,"&quot;"),serializeAttributes=e=>{let s=(e.getAttributeNames?e.getAttributeNames():[]).filter(e=>!NEVER_SERIALIZE.has(e)).map(s=>{if(BOOLEAN_ATTRIBUTES.has(s))return e[s]?` ${s}`:"";const r=e.getAttribute(s);return""===r?"":` ${ATTRIBUTE_NAME[s]||s}="${escapeAttributeValue(r)}"`}).join("");const r=e.style;return r&&r.cssText&&(s+=` style="${escapeAttributeValue(r.cssText)}"`),s},serializeNode=e=>{if(e.nodeType===NodeService_1.NodeService.TEXT_NODE)return escapeText(e.nodeValue||"");if(e.nodeType===NodeService_1.NodeService.COMMENT_NODE)return`\x3c!--${e.nodeValue||""}--\x3e`;if(e.nodeType!==NodeService_1.NodeService.ELEMENT_NODE)return"";const s=e.tagName,r=serializeAttributes(e);return VOID_ELEMENTS.has(s)?`<${s}${r}>`:`<${s}${r}>${serializeChildren(e)}</${s}>`},serializeChildren=e=>Array.from(e.childNodes).map(serializeNode).join("");exports.serializeChildren=serializeChildren;const serializeOuter=e=>serializeNode(e);exports.serializeOuter=serializeOuter;
@@ -618,10 +618,10 @@ export interface PseudoElement extends PseudoNode {
618
618
  */
619
619
  insertAdjacentElement(position: string, element: PseudoElement): PseudoElement | null;
620
620
  /**
621
- * Parses the text as HTML or XML and inserts the resulting nodes into the tree in the position given. Not implemented
622
- * yet (HTML parsing is out of scope for now).
621
+ * Parses the text as HTML and inserts the resulting nodes into the tree in the position given.
623
622
  * @param {string} position beforebegin, afterbegin, beforeend or afterend
624
623
  * @param {string} text The markup to parse
624
+ * @throws {Error} When the position is not one of the four above
625
625
  */
626
626
  insertAdjacentHTML(position: string, text: string): void;
627
627
  /**
@@ -1,12 +1,12 @@
1
- import { PseudoDocumentFragment } from './PseudoDocumentFragment';
2
- import { PseudoElement } from './PseudoElement';
3
- export interface PseudoShadowRoot extends PseudoDocumentFragment {
4
- /**
5
- * The element this shadow root is attached to.
6
- */
7
- get host(): PseudoElement;
8
- /**
9
- * 'open' (reachable via element.shadowRoot) or 'closed' (not).
10
- */
11
- get mode(): string;
12
- }
1
+ import { PseudoDocumentFragment } from './PseudoDocumentFragment';
2
+ import { PseudoElement } from './PseudoElement';
3
+ export interface PseudoShadowRoot extends PseudoDocumentFragment {
4
+ /**
5
+ * The element this shadow root is attached to.
6
+ */
7
+ get host(): PseudoElement;
8
+ /**
9
+ * 'open' (reachable via element.shadowRoot) or 'closed' (not).
10
+ */
11
+ get mode(): string;
12
+ }
@@ -41,7 +41,6 @@ type DOMRect = {
41
41
  */
42
42
  export declare class ElementService extends NodeService implements Partial<PseudoElement> {
43
43
  id: string;
44
- innerHTML: string;
45
44
  type: string;
46
45
  clientHeight: number;
47
46
  clientLeft: number;
@@ -113,6 +112,17 @@ export declare class ElementService extends NodeService implements Partial<Pseud
113
112
  * @returns {string|null}
114
113
  */
115
114
  get prefix(): string | null;
115
+ /**
116
+ * The HTML markup of this element's children. Only the getter is here (the setter, which needs to build new
117
+ * elements from parsed HTML, is on HTMLElementService - see its class comment).
118
+ * @returns {string}
119
+ */
120
+ get innerHTML(): string;
121
+ /**
122
+ * The HTML markup of this element itself, including its children. Only the getter is here (see innerHTML).
123
+ * @returns {string}
124
+ */
125
+ get outerHTML(): string;
116
126
  get nodeType(): number;
117
127
  get attributes(): PseudoNamedNodeMap;
118
128
  get classList(): PseudoDOMTokenList;
@@ -44,6 +44,7 @@ const cloneObject_1 = __importDefault(require('si-funciona/dist/helpers/objects/
44
44
  const isEqual_1 = __importDefault(require('si-funciona/dist/helpers/objects/isEqual'))
45
45
  const query_1 = require('../factories/query')
46
46
  const ShadowRootService_1 = require('./ShadowRootService')
47
+ const serializeHTML_1 = require('../factories/serializeHTML')
47
48
  const zeroRect = () => ({
48
49
  x: 0,
49
50
  y: 0,
@@ -104,9 +105,6 @@ class ElementService extends NodeService_1.NodeService {
104
105
  }, {
105
106
  name: 'id',
106
107
  value: ''
107
- }, {
108
- name: 'innerHTML',
109
- value: ''
110
108
  },
111
109
  // Layout is not really computed (there is no rendering engine here): these start at 0, like an unrendered
112
110
  // element's would, but can be set directly to whatever a test needs code under test to see.
@@ -241,6 +239,23 @@ class ElementService extends NodeService_1.NodeService {
241
239
  return null
242
240
  }
243
241
 
242
+ /**
243
+ * The HTML markup of this element's children. Only the getter is here (the setter, which needs to build new
244
+ * elements from parsed HTML, is on HTMLElementService - see its class comment).
245
+ * @returns {string}
246
+ */
247
+ get innerHTML () {
248
+ return (0, serializeHTML_1.serializeChildren)(this)
249
+ }
250
+
251
+ /**
252
+ * The HTML markup of this element itself, including its children. Only the getter is here (see innerHTML).
253
+ * @returns {string}
254
+ */
255
+ get outerHTML () {
256
+ return (0, serializeHTML_1.serializeOuter)(this)
257
+ }
258
+
244
259
  get nodeType () {
245
260
  return NodeService_1.NodeService.ELEMENT_NODE
246
261
  }
@@ -1 +1 @@
1
- "use strict";require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.every.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.for-each.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var __importDefault=function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.ElementService=void 0;const createEvent_1=__importDefault(require("../factories/createEvent")),NodeService_1=require("./NodeService"),AttrService_1=require("./AttrService"),DOMTokenListService_1=require("./DOMTokenListService"),NamedNodeMapService_1=require("./NamedNodeMapService"),getParentNodesFromAttribute_1=__importDefault(require("../functions/getParentNodesFromAttribute")),HTMLCollectionService_1=require("./HTMLCollectionService"),cloneObject_1=__importDefault(require("si-funciona/dist/helpers/objects/cloneObject")),isEqual_1=__importDefault(require("si-funciona/dist/helpers/objects/isEqual")),query_1=require("../factories/query"),ShadowRootService_1=require("./ShadowRootService"),zeroRect=()=>({x:0,y:0,width:0,height:0,top:0,right:0,bottom:0,left:0});class ElementService extends NodeService_1.NodeService{constructor({tagName:e="",attributes:t=[],parent:r=null,children:s=[]}={}){super(),this.boundingClientRect=zeroRect(),this.clientRects=[],this.animations=[],this.isVisible=!0,this.capturedPointers=new Set,this.shadowRootInstance=null,this.defaultEventApplied=!1,this.tokenList=new DOMTokenListService_1.DOMTokenListService,this.tag=e,this.attributeList=t.concat([{name:"className",value:""},{name:"id",value:""},{name:"innerHTML",value:""},{name:"clientHeight",value:0},{name:"clientLeft",value:0},{name:"clientTop",value:0},{name:"clientWidth",value:0},{name:"scrollHeight",value:0},{name:"scrollLeft",value:0},{name:"scrollTop",value:0},{name:"scrollWidth",value:0}]),this.propertyAttributes=this.attributeList.map(({name:e})=>e),this.attributeList.forEach(({name:e,value:t})=>{this[e]=t}),s.forEach(e=>{if(!e||"number"!=typeof e.nodeType)throw new TypeError("The children of an element must be nodes.");this.appendChild(e)}),r&&r.appendChild(this)}currentAttributes(){return this.attributeList.map(({name:e,value:t})=>({name:e,value:this.propertyAttributes.indexOf(e)>=0?this[e]:t}))}get nodeName(){return this.tag}cloneShallow(){const e=new this.constructor({tagName:this.tag});return e.attributeList.length=0,this.currentAttributes().forEach(({name:t,value:r})=>{const s="object"==typeof r&&null!==r?(0,cloneObject_1.default)(r):r;e.attributeList.push({name:t,value:s}),e.propertyAttributes.indexOf(t)>=0&&(e[t]=s)}),e.ownerDocumentStore=this.ownerDocumentStore,e}equalsShallow(e){const t=this.currentAttributes(),r=e.currentAttributes();return this.nodeName===e.nodeName&&t.length===r.length&&t.every(({name:e,value:t})=>{const s=r.find(t=>t.name===e);return void 0!==s&&(0,isEqual_1.default)(t,s.value)})}get tagName(){return this.tag}get localName(){return this.tag}get prefix(){return null}get nodeType(){return NodeService_1.NodeService.ELEMENT_NODE}get attributes(){return new NamedNodeMapService_1.NamedNodeMapService(this.currentAttributes().map(({name:e,value:t})=>new AttrService_1.AttrService(e,String(t),this)))}get classList(){return this.tokenList}get className(){return this.tokenList.value}set className(e){this.tokenList.value=e}get children(){return new HTMLCollectionService_1.HTMLCollectionService(this)}get childElementCount(){return this.children.length}get firstElementChild(){return this.children.item(0)}get lastElementChild(){const e=this.children;return e.item(e.length-1)}get nextElementSibling(){let e=this.nextSibling;for(;e&&e.nodeType!==NodeService_1.NodeService.ELEMENT_NODE;)e=e.nextSibling;return e}get previousElementSibling(){let e=this.previousSibling;for(;e&&e.nodeType!==NodeService_1.NodeService.ELEMENT_NODE;)e=e.previousSibling;return e}insertAdjacentElement(e,t){return this.insertAdjacent(e,t)}insertAdjacentText(e,t){this.insertAdjacent(e,t)}insertAdjacent(e,t){switch(e){case"beforebegin":return this.parentNode?(this.before(t),t):null;case"afterbegin":return this.prepend(t),t;case"beforeend":return this.append(t),t;case"afterend":return this.parentNode?(this.after(t),t):null;default:throw new Error(`"${e}" is not one of beforebegin, afterbegin, beforeend or afterend.`)}}insertAdjacentHTML(e,t){throw new Error("ElementService.insertAdjacentHTML() is not implemented yet.")}matches(e){return(0,query_1.matches)(this,e)}closest(e){return(0,query_1.closest)(this,e)}applyDefaultEvent(){let e=e=>{};if(this.defaultEventApplied)return e;switch(this.tagName){case"button":case"input":e=e=>{const t=String(this.getAttribute("type")||this.type||"").toLowerCase(),r="button"===this.tagName?"button"!==t&&"reset"!==t:/^(submit|image)$/.test(t),s=(0,getParentNodesFromAttribute_1.default)("tagName","form",this);r&&s.length&&!this.hasAttribute("disabled")&&s[s.length-1].dispatchEvent((0,createEvent_1.default)("submit",{},{browser:!0,trusted:e.isTrusted}))},super.setDefaultEvent("click",e),this.defaultEventApplied=!0}return e}childInserted(e){"function"==typeof e.applyDefaultEvent&&e.applyDefaultEvent()}hasAttribute(e){return this.attributeList.some(({name:t})=>t===e)}setAttribute(e,t){const r=this.attributeList.find(({name:t})=>t===e);r?r.value=t:this.attributeList.push({name:e,value:t}),this.propertyAttributes.indexOf(e)>=0&&(this[e]=t)}getAttribute(e){const t=this.currentAttributes().find(({name:t})=>t===e);return t?t.value:null}removeAttribute(e){const t=this.attributeList.findIndex(({name:t})=>t===e);t>=0&&this.attributeList.splice(t,1)}getAttributeNames(){return this.currentAttributes().map(({name:e})=>e)}hasAttributes(){return this.attributeList.length>0}toggleAttribute(e,t){const r=this.hasAttribute(e),s="boolean"==typeof t?t:!r;return s&&!r?this.setAttribute(e,""):!s&&r&&this.removeAttribute(e),s}getBoundingClientRect(){return this.boundingClientRect}getClientRects(){return this.clientRects}getAnimations(){return this.animations}checkVisibility(){return this.isVisible}computedStyleMap(){const e=this.style;return{get:t=>{const r=e?e.getPropertyValue(t):"";return""===r?void 0:r}}}hasPointerCapture(e){return this.capturedPointers.has(e)}setPointerCapture(e){this.capturedPointers.add(e)}releasePointerCapture(e){this.capturedPointers.delete(e)}scroll(e=0,t=0){if("number"==typeof e)return this.scrollLeft=e,void(this.scrollTop=t);"number"==typeof e.left&&(this.scrollLeft=e.left),"number"==typeof e.top&&(this.scrollTop=e.top)}scrollTo(e=0,t=0){this.scroll(e,t)}scrollBy(e=0,t=0){"number"!=typeof e?this.scroll({left:this.scrollLeft+(e.left||0),top:this.scrollTop+(e.top||0)}):this.scroll(this.scrollLeft+e,this.scrollTop+t)}scrollIntoView(){}requestFullscreen(){return Promise.resolve()}requestPointerLock(){return Promise.resolve()}attachShadow(e){if(this.shadowRootInstance)throw new Error("Shadow root cannot be created on a host which already hosts a shadow tree.");const t=new ShadowRootService_1.ShadowRootService;return t.host=this,t.mode=e.mode,this.shadowRootInstance=t,t}get shadowRoot(){return this.shadowRootInstance&&"open"===this.shadowRootInstance.mode?this.shadowRootInstance:null}}exports.ElementService=ElementService;
1
+ "use strict";require("core-js/modules/esnext.iterator.constructor.js"),require("core-js/modules/esnext.iterator.every.js"),require("core-js/modules/esnext.iterator.find.js"),require("core-js/modules/esnext.iterator.for-each.js"),require("core-js/modules/esnext.iterator.map.js"),require("core-js/modules/esnext.iterator.some.js"),require("core-js/modules/esnext.set.add-all.js"),require("core-js/modules/esnext.set.delete-all.js"),require("core-js/modules/esnext.set.difference.js"),require("core-js/modules/esnext.set.every.js"),require("core-js/modules/esnext.set.filter.js"),require("core-js/modules/esnext.set.find.js"),require("core-js/modules/esnext.set.intersection.js"),require("core-js/modules/esnext.set.is-disjoint-from.js"),require("core-js/modules/esnext.set.is-subset-of.js"),require("core-js/modules/esnext.set.is-superset-of.js"),require("core-js/modules/esnext.set.join.js"),require("core-js/modules/esnext.set.map.js"),require("core-js/modules/esnext.set.reduce.js"),require("core-js/modules/esnext.set.some.js"),require("core-js/modules/esnext.set.symmetric-difference.js"),require("core-js/modules/esnext.set.union.js");var __importDefault=function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.ElementService=void 0;const createEvent_1=__importDefault(require("../factories/createEvent")),NodeService_1=require("./NodeService"),AttrService_1=require("./AttrService"),DOMTokenListService_1=require("./DOMTokenListService"),NamedNodeMapService_1=require("./NamedNodeMapService"),getParentNodesFromAttribute_1=__importDefault(require("../functions/getParentNodesFromAttribute")),HTMLCollectionService_1=require("./HTMLCollectionService"),cloneObject_1=__importDefault(require("si-funciona/dist/helpers/objects/cloneObject")),isEqual_1=__importDefault(require("si-funciona/dist/helpers/objects/isEqual")),query_1=require("../factories/query"),ShadowRootService_1=require("./ShadowRootService"),serializeHTML_1=require("../factories/serializeHTML"),zeroRect=()=>({x:0,y:0,width:0,height:0,top:0,right:0,bottom:0,left:0});class ElementService extends NodeService_1.NodeService{constructor({tagName:e="",attributes:t=[],parent:r=null,children:s=[]}={}){super(),this.boundingClientRect=zeroRect(),this.clientRects=[],this.animations=[],this.isVisible=!0,this.capturedPointers=new Set,this.shadowRootInstance=null,this.defaultEventApplied=!1,this.tokenList=new DOMTokenListService_1.DOMTokenListService,this.tag=e,this.attributeList=t.concat([{name:"className",value:""},{name:"id",value:""},{name:"clientHeight",value:0},{name:"clientLeft",value:0},{name:"clientTop",value:0},{name:"clientWidth",value:0},{name:"scrollHeight",value:0},{name:"scrollLeft",value:0},{name:"scrollTop",value:0},{name:"scrollWidth",value:0}]),this.propertyAttributes=this.attributeList.map(({name:e})=>e),this.attributeList.forEach(({name:e,value:t})=>{this[e]=t}),s.forEach(e=>{if(!e||"number"!=typeof e.nodeType)throw new TypeError("The children of an element must be nodes.");this.appendChild(e)}),r&&r.appendChild(this)}currentAttributes(){return this.attributeList.map(({name:e,value:t})=>({name:e,value:this.propertyAttributes.indexOf(e)>=0?this[e]:t}))}get nodeName(){return this.tag}cloneShallow(){const e=new this.constructor({tagName:this.tag});return e.attributeList.length=0,this.currentAttributes().forEach(({name:t,value:r})=>{const s="object"==typeof r&&null!==r?(0,cloneObject_1.default)(r):r;e.attributeList.push({name:t,value:s}),e.propertyAttributes.indexOf(t)>=0&&(e[t]=s)}),e.ownerDocumentStore=this.ownerDocumentStore,e}equalsShallow(e){const t=this.currentAttributes(),r=e.currentAttributes();return this.nodeName===e.nodeName&&t.length===r.length&&t.every(({name:e,value:t})=>{const s=r.find(t=>t.name===e);return void 0!==s&&(0,isEqual_1.default)(t,s.value)})}get tagName(){return this.tag}get localName(){return this.tag}get prefix(){return null}get innerHTML(){return(0,serializeHTML_1.serializeChildren)(this)}get outerHTML(){return(0,serializeHTML_1.serializeOuter)(this)}get nodeType(){return NodeService_1.NodeService.ELEMENT_NODE}get attributes(){return new NamedNodeMapService_1.NamedNodeMapService(this.currentAttributes().map(({name:e,value:t})=>new AttrService_1.AttrService(e,String(t),this)))}get classList(){return this.tokenList}get className(){return this.tokenList.value}set className(e){this.tokenList.value=e}get children(){return new HTMLCollectionService_1.HTMLCollectionService(this)}get childElementCount(){return this.children.length}get firstElementChild(){return this.children.item(0)}get lastElementChild(){const e=this.children;return e.item(e.length-1)}get nextElementSibling(){let e=this.nextSibling;for(;e&&e.nodeType!==NodeService_1.NodeService.ELEMENT_NODE;)e=e.nextSibling;return e}get previousElementSibling(){let e=this.previousSibling;for(;e&&e.nodeType!==NodeService_1.NodeService.ELEMENT_NODE;)e=e.previousSibling;return e}insertAdjacentElement(e,t){return this.insertAdjacent(e,t)}insertAdjacentText(e,t){this.insertAdjacent(e,t)}insertAdjacent(e,t){switch(e){case"beforebegin":return this.parentNode?(this.before(t),t):null;case"afterbegin":return this.prepend(t),t;case"beforeend":return this.append(t),t;case"afterend":return this.parentNode?(this.after(t),t):null;default:throw new Error(`"${e}" is not one of beforebegin, afterbegin, beforeend or afterend.`)}}insertAdjacentHTML(e,t){throw new Error("ElementService.insertAdjacentHTML() is not implemented yet.")}matches(e){return(0,query_1.matches)(this,e)}closest(e){return(0,query_1.closest)(this,e)}applyDefaultEvent(){let e=e=>{};if(this.defaultEventApplied)return e;switch(this.tagName){case"button":case"input":e=e=>{const t=String(this.getAttribute("type")||this.type||"").toLowerCase(),r="button"===this.tagName?"button"!==t&&"reset"!==t:/^(submit|image)$/.test(t),s=(0,getParentNodesFromAttribute_1.default)("tagName","form",this);r&&s.length&&!this.hasAttribute("disabled")&&s[s.length-1].dispatchEvent((0,createEvent_1.default)("submit",{},{browser:!0,trusted:e.isTrusted}))},super.setDefaultEvent("click",e),this.defaultEventApplied=!0}return e}childInserted(e){"function"==typeof e.applyDefaultEvent&&e.applyDefaultEvent()}hasAttribute(e){return this.attributeList.some(({name:t})=>t===e)}setAttribute(e,t){const r=this.attributeList.find(({name:t})=>t===e);r?r.value=t:this.attributeList.push({name:e,value:t}),this.propertyAttributes.indexOf(e)>=0&&(this[e]=t)}getAttribute(e){const t=this.currentAttributes().find(({name:t})=>t===e);return t?t.value:null}removeAttribute(e){const t=this.attributeList.findIndex(({name:t})=>t===e);t>=0&&this.attributeList.splice(t,1)}getAttributeNames(){return this.currentAttributes().map(({name:e})=>e)}hasAttributes(){return this.attributeList.length>0}toggleAttribute(e,t){const r=this.hasAttribute(e),s="boolean"==typeof t?t:!r;return s&&!r?this.setAttribute(e,""):!s&&r&&this.removeAttribute(e),s}getBoundingClientRect(){return this.boundingClientRect}getClientRects(){return this.clientRects}getAnimations(){return this.animations}checkVisibility(){return this.isVisible}computedStyleMap(){const e=this.style;return{get:t=>{const r=e?e.getPropertyValue(t):"";return""===r?void 0:r}}}hasPointerCapture(e){return this.capturedPointers.has(e)}setPointerCapture(e){this.capturedPointers.add(e)}releasePointerCapture(e){this.capturedPointers.delete(e)}scroll(e=0,t=0){if("number"==typeof e)return this.scrollLeft=e,void(this.scrollTop=t);"number"==typeof e.left&&(this.scrollLeft=e.left),"number"==typeof e.top&&(this.scrollTop=e.top)}scrollTo(e=0,t=0){this.scroll(e,t)}scrollBy(e=0,t=0){"number"!=typeof e?this.scroll({left:this.scrollLeft+(e.left||0),top:this.scrollTop+(e.top||0)}):this.scroll(this.scrollLeft+e,this.scrollTop+t)}scrollIntoView(){}requestFullscreen(){return Promise.resolve()}requestPointerLock(){return Promise.resolve()}attachShadow(e){if(this.shadowRootInstance)throw new Error("Shadow root cannot be created on a host which already hosts a shadow tree.");const t=new ShadowRootService_1.ShadowRootService;return t.host=this,t.mode=e.mode,this.shadowRootInstance=t,t}get shadowRoot(){return this.shadowRootInstance&&"open"===this.shadowRootInstance.mode?this.shadowRootInstance:null}}exports.ElementService=ElementService;
@@ -1,87 +1,119 @@
1
- /**
2
- * @file Substitute for the DOM HTMLElement Class.
3
- * @author Joshua Heagle <joshuaheagle@gmail.com>
4
- * @version 1.0.0
5
- */
6
- import { PseudoHTMLElement } from '../interfaces/PseudoHTMLElement';
7
- import { PseudoNode } from '../interfaces/PseudoNode';
8
- import { NodeService } from './NodeService';
9
- import { ElementService } from './ElementService';
10
- import { CSSStyleDeclarationService } from './CSSStyleDeclarationService';
11
- /**
12
- * Simulate the behaviour of the HTMLElement Class when there is no DOM available.
13
- * @author Joshua Heagle <joshuaheagle@gmail.com>
14
- * @class
15
- * @augments PseudoElement
16
- * @property {boolean} hidden - State of whether element is visible
17
- * @property {number} offsetHeight - The height of the element as offset by the parent element
18
- * @property {number} offsetLeft - The position of the left side of the element based on the parent element
19
- * @property {PseudoHTMLElement} offsetParent - A reference to the closest positioned parent element
20
- * @property {number} offsetTop - The position of the top side of the element based on the parent element
21
- * @property {number} offsetWidth - The width of the element as offset by the parent element
22
- * @property {CSSStyleDeclarationService} style - The element's inline styles, live and settable per property
23
- * @property {Object.<string, string>} dataset - The element's data-* attributes, live, under their camelCase names
24
- * @property {string} title - The title attribute which affects the text visible on hover
25
- */
26
- export declare class HTMLElementService extends ElementService implements Partial<PseudoHTMLElement> {
27
- private readonly styleDeclaration;
28
- private readonly datasetProxy;
29
- /**
30
- * Simulate the HTMLElement object when the Dom is not available
31
- * @param {Object} [elementOptions={}]
32
- * @param {string} [elementOptions.tagName='']
33
- * @param {PseudoNode|Object} [elementOptions.parent={}]
34
- * @param {Array} [elementOptions.children=[]]
35
- * @constructor
36
- */
37
- constructor({ tagName, parent, children }?: {
38
- tagName?: string;
39
- parent?: PseudoNode | null;
40
- children?: Array<any>;
41
- });
42
- /**
43
- * The element's inline styles: a live CSSStyleDeclaration-like object, so both style.setProperty('color', 'red')
44
- * and style.color = 'red' work.
45
- * @returns {CSSStyleDeclarationService}
46
- */
47
- get style(): CSSStyleDeclarationService;
48
- /**
49
- * The element's data-* attributes, live, under their camelCase names (data-foo-bar <-> dataset.fooBar). Backed
50
- * directly by getAttribute / setAttribute, so it is never out of sync with the attributes themselves.
51
- * @returns {Object.<string, string>}
52
- */
53
- get dataset(): {
54
- [key: string]: string;
55
- };
56
- /**
57
- * Style is not attribute-backed like most properties (see the constructor), so cloneNode needs its own copy of it.
58
- * @returns {NodeService}
59
- */
60
- protected cloneShallow(): NodeService;
61
- /**
62
- * Style is not attribute-backed like most properties, so isEqualNode needs to compare it separately too.
63
- * @param {NodeService} other The node to compare with
64
- * @returns {boolean}
65
- */
66
- protected equalsShallow(other: NodeService): boolean;
67
- /**
68
- * Whether this element can have the focus: form controls and links which are not disabled, and anything with a tabindex.
69
- * @returns {boolean}
70
- */
71
- get canFocus(): boolean;
72
- /**
73
- * Click the element: a click event is sent to it, which bubbles and can be cancelled, like one from a user but a
74
- * script made it (so it is not trusted). A disabled element does nothing.
75
- */
76
- click(): void;
77
- /**
78
- * Give the element the focus. The element which had it gets blur then focusout, and this one gets focus then
79
- * focusin (blur and focus do not bubble, focusin and focusout do). Nothing happens when the element cannot have the
80
- * focus or already has it.
81
- */
82
- focus(): void;
83
- /**
84
- * Take the focus away from the element, when it has it: it gets blur then focusout.
85
- */
86
- blur(): void;
87
- }
1
+ /**
2
+ * @file Substitute for the DOM HTMLElement Class.
3
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
4
+ * @version 1.0.0
5
+ */
6
+ import { PseudoHTMLElement } from '../interfaces/PseudoHTMLElement';
7
+ import { PseudoNode } from '../interfaces/PseudoNode';
8
+ import { NodeService } from './NodeService';
9
+ import { ElementService } from './ElementService';
10
+ import { CSSStyleDeclarationService } from './CSSStyleDeclarationService';
11
+ /**
12
+ * Simulate the behaviour of the HTMLElement Class when there is no DOM available.
13
+ * @author Joshua Heagle <joshuaheagle@gmail.com>
14
+ * @class
15
+ * @augments PseudoElement
16
+ * @property {boolean} hidden - State of whether element is visible
17
+ * @property {number} offsetHeight - The height of the element as offset by the parent element
18
+ * @property {number} offsetLeft - The position of the left side of the element based on the parent element
19
+ * @property {PseudoHTMLElement} offsetParent - A reference to the closest positioned parent element
20
+ * @property {number} offsetTop - The position of the top side of the element based on the parent element
21
+ * @property {number} offsetWidth - The width of the element as offset by the parent element
22
+ * @property {CSSStyleDeclarationService} style - The element's inline styles, live and settable per property
23
+ * @property {Object.<string, string>} dataset - The element's data-* attributes, live, under their camelCase names
24
+ * @property {string} title - The title attribute which affects the text visible on hover
25
+ */
26
+ export declare class HTMLElementService extends ElementService implements Partial<PseudoHTMLElement> {
27
+ private readonly styleDeclaration;
28
+ private readonly datasetProxy;
29
+ /**
30
+ * Simulate the HTMLElement object when the Dom is not available
31
+ * @param {Object} [elementOptions={}]
32
+ * @param {string} [elementOptions.tagName='']
33
+ * @param {PseudoNode|Object} [elementOptions.parent={}]
34
+ * @param {Array} [elementOptions.children=[]]
35
+ * @constructor
36
+ */
37
+ constructor({ tagName, parent, children }?: {
38
+ tagName?: string;
39
+ parent?: PseudoNode | null;
40
+ children?: Array<any>;
41
+ });
42
+ /**
43
+ * The element's inline styles: a live CSSStyleDeclaration-like object, so both style.setProperty('color', 'red')
44
+ * and style.color = 'red' work.
45
+ * @returns {CSSStyleDeclarationService}
46
+ */
47
+ get style(): CSSStyleDeclarationService;
48
+ /**
49
+ * The element's data-* attributes, live, under their camelCase names (data-foo-bar <-> dataset.fooBar). Backed
50
+ * directly by getAttribute / setAttribute, so it is never out of sync with the attributes themselves.
51
+ * @returns {Object.<string, string>}
52
+ */
53
+ get dataset(): {
54
+ [key: string]: string;
55
+ };
56
+ /**
57
+ * Style is not attribute-backed like most properties (see the constructor), so cloneNode needs its own copy of it.
58
+ * @returns {NodeService}
59
+ */
60
+ protected cloneShallow(): NodeService;
61
+ /**
62
+ * Style is not attribute-backed like most properties, so isEqualNode needs to compare it separately too.
63
+ * @param {NodeService} other The node to compare with
64
+ * @returns {boolean}
65
+ */
66
+ protected equalsShallow(other: NodeService): boolean;
67
+ /**
68
+ * Parses html with this class building each new element (matches HTML: parsed elements behave like plain
69
+ * HTMLElements, not whatever specialized class happens to be setting innerHTML / outerHTML).
70
+ * @param {string} html
71
+ * @returns {Array<*>}
72
+ */
73
+ private parse;
74
+ /**
75
+ * Replace this element's children by parsing html. innerHTML's setter is here rather than on ElementService (which
76
+ * only has the getter) because building the new elements needs a concrete element class - see parse().
77
+ * @param {string} html
78
+ * @returns {undefined}
79
+ */
80
+ set innerHTML(html: string);
81
+ get innerHTML(): string;
82
+ /**
83
+ * Replace this element itself, in its parent, by parsing html. Does nothing when it has no parent, like
84
+ * replaceWith. outerHTML's setter is here rather than on ElementService for the same reason as innerHTML's.
85
+ * @param {string} html
86
+ * @returns {undefined}
87
+ */
88
+ set outerHTML(html: string);
89
+ get outerHTML(): string;
90
+ /**
91
+ * Parse html and insert the resulting nodes at the given position, like insertAdjacentElement /
92
+ * insertAdjacentText.
93
+ * @param {string} position beforebegin, afterbegin, beforeend or afterend
94
+ * @param {string} html The markup to parse
95
+ * @returns {undefined}
96
+ * @throws {Error} When the position is not one of the four above
97
+ */
98
+ insertAdjacentHTML(position: string, html: string): void;
99
+ /**
100
+ * Whether this element can have the focus: form controls and links which are not disabled, and anything with a tabindex.
101
+ * @returns {boolean}
102
+ */
103
+ get canFocus(): boolean;
104
+ /**
105
+ * Click the element: a click event is sent to it, which bubbles and can be cancelled, like one from a user but a
106
+ * script made it (so it is not trusted). A disabled element does nothing.
107
+ */
108
+ click(): void;
109
+ /**
110
+ * Give the element the focus. The element which had it gets blur then focusout, and this one gets focus then
111
+ * focusin (blur and focus do not bubble, focusin and focusout do). Nothing happens when the element cannot have the
112
+ * focus or already has it.
113
+ */
114
+ focus(): void;
115
+ /**
116
+ * Take the focus away from the element, when it has it: it gets blur then focusout.
117
+ */
118
+ blur(): void;
119
+ }