@litejs/dom 24.8.0 → 25.1.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.
package/README.md CHANGED
@@ -10,16 +10,13 @@
10
10
  LiteJS DOM – [![Coverage][1]][2] [![Size][3]][4] [![Buy Me A Tea][5]][6]
11
11
  ==========
12
12
 
13
- Dependency-free DOM library for handling HTML files on server-side.
14
- [DOM spec](https://dom.spec.whatwg.org/) |
15
- [Selectors Level 3](http://www.w3.org/TR/selectors/)
13
+ Dependency-free DOM library for handling HTML and CSS files on server-side.
16
14
 
17
15
 
18
- Examples
19
- --------
20
-
21
16
  ```javascript
22
- const { document, DOMParser, XMLSerializer } = require("@litejs/dom");
17
+ // const { document } = require("@litejs/dom");
18
+ import { document, CSSStyleSheet, DOMParser, XMLSerializer } from "@litejs/dom";
19
+ import { XMLHttpRequest } from "@litejs/dom/net.js";
23
20
 
24
21
  // Build DOM manually
25
22
  const el = document.createElement("h1");
@@ -30,29 +27,34 @@ const fragment = document.createDocumentFragment();
30
27
  fragment.appendChild(document.createTextNode("hello"));
31
28
  el.appendChild(fragment);
32
29
 
33
- // Replace the DOM tree with parsed HTML
30
+ // Replace the DOM tree with HTML
34
31
  el.innerHTML = "<b>hello world</b>";
35
- el.toString();
32
+ console.log(el.toString());
36
33
  // <h1 id="123" class="large"><b>hello world</b></h1>
37
34
 
38
- // minify output
39
- el.toString(true);
35
+ // Minify HTML
36
+ console.log(el.toString(true));
40
37
  // <h1 id=123 class=large><b>hello world</b></h1>
41
38
 
42
39
  el.querySelectorAll("b");
43
40
  // [ "<b>hello world</b>" ]
44
41
 
45
42
  // Use XMLHttpRequest in server side
46
- const { XMLHttpRequest } = require("@litejs/dom/net.js");
47
43
  const xhr = new XMLHttpRequest();
48
44
  xhr.open("GET", "https://litejs.com");
49
45
  xhr.responseType = "document";
50
46
  xhr.onload = function() {
51
- const document = xhr.responseXML;
47
+ const doc = xhr.responseXML;
52
48
  // Work with DOM in familiar way
53
- console.log(document.querySelector("title").textContent);
49
+ console.log(doc.querySelector("title").textContent);
54
50
  }
55
51
  xhr.send();
52
+
53
+ // Minify CSS
54
+ const sheet = new CSSStyleSheet({ min: { color: true } })
55
+ sheet.replaceSync(".a { color: hsl(0 0% 100%) }")
56
+ console.log(sheet.toString())
57
+ // .a{color:#fff}
56
58
  ```
57
59
 
58
60
  ## Contributing
@@ -61,7 +63,7 @@ Follow [Coding Style Guide](https://github.com/litejs/litejs/wiki/Style-Guide),
61
63
  run tests `npm install; npm test`.
62
64
 
63
65
 
64
- > Copyright (c) 2014-2024 Lauri Rooden &lt;lauri@rooden.ee&gt;
66
+ > Copyright (c) 2014-2025 Lauri Rooden &lt;lauri@rooden.ee&gt;
65
67
  [MIT License](https://litejs.com/MIT-LICENSE.txt) |
66
68
  [GitHub repo](https://github.com/litejs/dom) |
67
69
  [npm package](https://npmjs.org/package/@litejs/dom) |
package/css.js CHANGED
@@ -1,32 +1,262 @@
1
1
 
2
2
  /*! litejs.com/MIT-LICENSE.txt */
3
3
 
4
+ exports.selectorSplit = selectorSplit
4
5
  exports.CSSStyleDeclaration = CSSStyleDeclaration
6
+ exports.CSSStyleSheet = CSSStyleSheet
5
7
 
6
- // CSSStyleDeclaration is a single CSS declaration block,
7
- // accessible via HTMLElement.style for inline styles, document.styleSheets[0].cssRules[0].style, and getComputedStyle()
8
- function CSSStyleDeclaration(style) {
9
- this.cssText = style
8
+ var fs = require("fs")
9
+ , path = require("path")
10
+ , urlRe = /(["']).*?\1|url\((['"]?)(?!\/|data:|https?:)(.*?)\2\)/g
11
+ , clearFn = (_, q, str, c) =>
12
+ q ? (q = str.indexOf("'") == -1 ? "'" : "\"", q + str.replace(q === "'" ? /\\(")/g : /\\(')/g, "$1")) + q :
13
+ c ? "" :
14
+ _.replace(/[\t\n]+/g, " ")
15
+ .replace(/ *([,;{}>~+\/]) */g, "$1")
16
+ .replace(/;(?=})/g, "")
17
+ .replace(/: +/g, ":")
18
+ .replace(/([ :,])0\.([0-9])/g, "$1.$2")
19
+ , clear = s => s
20
+ .replace(/("|')((?:\\\1|[^\1])*?)\1|\s*(\/)\*(?:[^*]|\*(?!\/))*\*\/\s*|(?:[^"'\/]|\/(?!\*))+/g, clearFn)
21
+ .replace(/(["']).*?\1|url\(("|')([^'"()\s]+)\2\)/g, (m,q1,q2,u) => q1 ? m : "url(" + u + ")")
22
+ , read = (sheet, url, enc = "utf8") => fs.readFileSync(path.resolve(sheet.min.root || "", sheet.baseURI, url), enc)
23
+ , toRgb = {
24
+ rgb(r, g, b) {
25
+ var f = n => ((n | 0) + 256).toString(16).slice(1)
26
+ return f(r) + f(g) + f(b)
27
+ },
28
+ hsl(h, s, l) {
29
+ l /= 100
30
+ s /= 100 / (l < 0.5 ? l : 1 - l)
31
+ function f(n) {
32
+ n = (n + h / 30) % 12
33
+ return (0 | 256.5 + (255 * (l - s * (n < 2 || n > 10 ? -1 : n < 4 ? n - 3 : n > 8 ? 9 - n : 1)))).toString(16).slice(1)
34
+ }
35
+ return f(0) + f(8) + f(4)
36
+ }
10
37
  }
11
-
12
- CSSStyleDeclaration.prototype = {
13
- get cssText() {
14
- return Object.keys(this).map(function(key) {
15
- return (key === "cssFloat" ? "float:" : hyphenCase(key) + ":") + this[key]
16
- }, this).join(";")
38
+ , toRgba = (rgbHex, alpha) => ("rgba(" + rgbHex.replace(/../g, x => parseInt(x, 16)+",") + alpha + ")").replace("0.", ".")
39
+ , colorRe = /\b(rgb|hsl)a?\s*\(\s*(\d+)(?:deg)?[\s,]+(\d+)[\s,%]+(\d+)%?(?:[\s,\/]+(0?\.?\d+)(%?))?\s*\)/g
40
+ , colorFn = (_, name, a, b, c, d, p) => (_ = toRgb[name](a, b, c), (p ? d/=100 : d) < 1 ? toRgba(_, d) : "#" + _.replace(/(\w)\1(\w)\2(\w)\3/, "$1$2$3"))
41
+ , styleHandler = {
42
+ get(style, prop) {
43
+ if (prop === "cssText") {
44
+ var min = style.parentRule && style.parentRule.parentStyleSheet.min
45
+ for (var out = [], i = style.length; i--; ) {
46
+ out[i] = joinProp(style[i], style.__[i] || style[style[i]])
47
+ }
48
+ return out.join(";")
49
+ }
50
+ return style[prop] || ""
51
+ function joinProp(name, value) {
52
+ if (min && min.color) value = value.replace(colorRe, colorFn)
53
+ return name + ":" + value
54
+ }
55
+ },
56
+ set(style, prop, val) {
57
+ if (prop === "cssText") {
58
+ var m, k
59
+ , sheet = style.parentRule && style.parentRule.parentStyleSheet
60
+ , min = sheet && sheet.min
61
+ , re = /([*_]?[-a-z]+)\s*:((?:("|')(?:\\.|(?!\3)[^\\])*?\3|[^"';])+)|\/\*!?((?:[^*]|\*(?!\/))*)\*\//ig
62
+ , len = 0
63
+ , lastIdx = {}
64
+ for (; (m = re.exec(val)); ) {
65
+ if (m[4]) {
66
+ if (min && len) style[k = style[len - 1]] = clear(transformValue(m[4].trim(), style[k]))
67
+ continue
68
+ } else {
69
+ k = m[1]
70
+ if (lastIdx[k] >= 0) style.__[lastIdx[k]] = style[k]
71
+ style[style[lastIdx[k] = len++] = k] = style[
72
+ k === "float" ? "cssFloat" : k.replace(/-([a-z])/g, (_, a) => a.toUpperCase())
73
+ ] = clear(m[2]).trim()
74
+ }
75
+ }
76
+ style.length = len
77
+ } else {
78
+ if (!style[prop]) style[style.length++] = prop
79
+ style[prop] = style[prop === "cssFloat" ? "float" : prop.replace(/[A-Z]/g, "-$&").toLowerCase()] = clear(val)
80
+ }
81
+ function transformValue(cmd, v) {
82
+ var { DOMParser } = require("./dom.js")
83
+ if (cmd === "data-uri") {
84
+ return v.replace(urlRe, function(_, q1, q2, url) {
85
+ if (q1) return _
86
+ var ext = url.split(".").pop()
87
+ , enc = ext === "svg" ? "utf8" : "base64"
88
+ url = read(sheet, url, enc)
89
+ if (ext === "svg") {
90
+ enc = ""
91
+ ext += "+xml"
92
+ url = new DOMParser().parseFromString(url, "application/xml").toString(true).replace(/#/g, "%23")
93
+ }
94
+ return "url('data:image/" + ext + ";" + enc + "," + url + "')"
95
+ })
96
+ }
97
+ return v
98
+ }
99
+ }
100
+ }
101
+ , ruleTypes = {
102
+ style: {
103
+ type: 1,
104
+ get cssText() {
105
+ return this.style.length > 0 ? this.selectorText + "{" + this.style.cssText + "}" : ""
106
+ },
107
+ set cssText(text) {
108
+ var idx = text.indexOf("{")
109
+ this.selectorText = clear(text.slice(0, idx).trim())
110
+ this.style = CSSStyleDeclaration(text.slice(idx + 1).replace(/}\s*/, ""), this)
111
+ }
112
+ },
113
+ import: {
114
+ get cssText() {
115
+ var sheet = this.parentStyleSheet
116
+ , min = sheet.min
117
+ , text = this.text
118
+ , urlFn = (m,q1,q2,u) => q1 ? m : "url('" + path.join(text.baseURI, u) + "')"
119
+ if (min && min.import) {
120
+ text = new CSSStyleSheet({
121
+ parentStyleSheet: sheet,
122
+ href: this.href,
123
+ min
124
+ }, read(sheet, this.href))
125
+ if (sheet.baseURI !== text.baseURI) {
126
+ text.rules.forEach(rule => {
127
+ if (rule.type === 1) for (let style = rule.style, i = style.length; i--; ) {
128
+ if (urlRe.test(style[style[i]])) style[style[i]] = style[style[i]].replace(urlRe, urlFn)
129
+ }
130
+ })
131
+ }
132
+ text += ""
133
+ }
134
+ return text
135
+ },
136
+ set cssText(text) {
137
+ this.href = text.split(/['"()]+/)[1]
138
+ this.text = clear(text.replace(/url\(("|')(.+?)\1\)/g, "'$2'"))
139
+ }
140
+ },
141
+ "}": {
142
+ get cssText() {
143
+ var style = new CSSStyleSheet({})
144
+ style.replaceSync(this.text)
145
+ var body = "" + style
146
+ return body.length > 0 ? this.mediaText + "{" + body + "}" : ""
147
+ },
148
+ set cssText(text) {
149
+ var idx = text.indexOf("{")
150
+ this.mediaText = clear(text.slice(0, idx).trim())
151
+ this.text = text.slice(idx + 1, -1)
152
+ }
17
153
  },
18
- set cssText(style) {
19
- for (var m, re = /(?:^|;)\s*([-a-z]+)\s*:((?:("|')(?:\\.|(?!\3)[^\\])*?\3|[^"';])+)(?=;|$)/ig; (m = re.exec(style)); ) {
20
- this[m[1] === "float" ? "cssFloat" : camelCase(m[1])] = m[2].trim()
154
+ ";": {
155
+ get cssText() {
156
+ return clear(this.text)
157
+ },
158
+ set cssText(text) {
159
+ this.text = clear(text)
21
160
  }
22
161
  }
23
162
  }
24
163
 
25
- function camelCase(str) {
26
- return str.replace(/-([a-z])/g, function(_, a) { return a.toUpperCase() })
164
+ function CSSRule(text, parentStyleSheet, atType, parentRule = null) {
165
+ // Clear comments and trim
166
+ text = text.trim()
167
+ var type = text[0] === "@" && text.slice(1, text.indexOf(" ")) || "style"
168
+ , rule = Object.create(ruleTypes[type] || ruleTypes[type === "page" || type === "font-face" || type === "counter-style" ? "style" : atType])
169
+ rule.parentStyleSheet = parentStyleSheet
170
+ rule.parentRule = parentRule
171
+ rule.cssText = rule.type === 1 ? text : text.replace(/\/\*(?:[^*]|\*(?!\/))*\*\//g, "")
172
+ return rule
27
173
  }
28
174
 
29
- function hyphenCase(str) {
30
- return str.replace(/[A-Z]/g, "-$&").toLowerCase()
175
+ function CSSStyleDeclaration(text, parentRule = null) {
176
+ var style = new Proxy({__: {}, parentRule}, styleHandler)
177
+ style.cssText = text
178
+ return style
31
179
  }
32
180
 
181
+ function CSSStyleSheet(opts, text = "") {
182
+ Object.assign(this, opts)
183
+ if (opts && opts.href) {
184
+ this.baseURI = path.join(
185
+ (opts.parentStyleSheet || opts.ownerNode && opts.ownerNode.ownerDocument).baseURI || "",
186
+ opts.href,
187
+ ".."
188
+ )
189
+ }
190
+ this.replaceSync(text)
191
+ }
192
+
193
+ CSSStyleSheet.prototype = {
194
+ baseURI: "",
195
+ root: "",
196
+ disabled: false,
197
+ type: "text/css",
198
+ deleteRule(idx) {
199
+ this.rules.splice(idx, 1)
200
+ },
201
+ insertRule(rule, idx) {
202
+ this.rules.splice(idx > -1 ? idx : this.rules.length, 0, CSSRule(rule, this))
203
+ },
204
+ replaceSync(text) {
205
+ var qpos, sheet = this
206
+ sheet.rules = sheet.cssRules = []
207
+ sheet.warnings = []
208
+ for (var char, inQuote, depth = 0, start = 0, pos = 0, len = text.length; pos < len; pos++) {
209
+ char = text[pos]
210
+ if (char === "\\" && inQuote !== "/") {
211
+ pos++
212
+ } else if (inQuote) {
213
+ if (char === inQuote) {
214
+ if (char !== "/" || text[pos - 1] === "*") {
215
+ if (char === "/" && depth < 1) {
216
+ // Remove root level comments
217
+ text = text.slice(0, qpos) + text.slice(pos + 1)
218
+ pos = qpos - 1
219
+ len = text.length
220
+ }
221
+ inQuote = ""
222
+ }
223
+ }
224
+ } else if (char === "'" || char === "\"" || char === "/" && text[pos+1] === "*") {
225
+ inQuote = char
226
+ qpos = pos
227
+ } else if (char === "{") {
228
+ depth++
229
+ } else if (char === "}" && --depth < 1 || char === ";" && depth < 1) {
230
+ if (depth < 0) throw "Invalid css"
231
+ sheet.rules.push(CSSRule(text.slice(start, start = pos + 1), sheet, char))
232
+ }
233
+ }
234
+ },
235
+ toString(min) {
236
+ if (min) this.min = min
237
+ return this.rules.map(rule => rule.cssText).filter(Boolean).join("\n")
238
+ }
239
+ }
240
+
241
+ function selectorSplit(text) {
242
+ for (var char, inQuote, depth = 0, start = 0, pos = 0, len = text.length, out = []; pos < len; ) {
243
+ char = text[pos++]
244
+ if (char === "\\") {
245
+ pos++
246
+ } else if (inQuote) {
247
+ if (char === inQuote) inQuote = ""
248
+ } else if (char === "'" || char === "\"") {
249
+ inQuote = char
250
+ } else if (char === "(" || char === "[") {
251
+ depth++
252
+ } else if (char === ")" || char === "]") {
253
+ depth--
254
+ } else if (char === "," && depth === 0) {
255
+ out.push(text.slice(start, (start = pos) - 1).trim())
256
+ }
257
+ }
258
+ out.push(text.slice(start).trim())
259
+ return out
260
+ }
261
+
262
+
package/dom.js CHANGED
@@ -5,7 +5,7 @@ var boolAttrs = {
5
5
  async:1, autoplay:1, loop:1, checked:1, defer:1, disabled:1, muted:1, multiple:1, nomodule:1, playsinline:1, readonly:1, required:1, selected:1
6
6
  }
7
7
  , numAttrs = "height maxLength minLength size tabIndex width"
8
- , strAttrs = "accept accesskey autocapitalize autofocus capture class contenteditable crossorigin dir for hidden href id integrity lang name nonce slot spellcheck src title type translate"
8
+ , strAttrs = "accept accesskey autocapitalize autofocus capture class contenteditable crossorigin dir for hidden href id integrity lang name nonce rel slot spellcheck src title type translate"
9
9
  , defaultAttrs = {
10
10
  "form method get":1, "input type text":1,
11
11
  "script type text/javascript":1, "style type text/css":1
@@ -13,10 +13,13 @@ var boolAttrs = {
13
13
  , voidElements = {
14
14
  AREA:1, BASE:1, BR:1, COL:1, EMBED:1, HR:1, IMG:1, INPUT:1, KEYGEN:1, LINK:1, MENUITEM:1, META:1, PARAM:1, SOURCE:1, TRACK:1, WBR:1
15
15
  }
16
+ , svgVoidElements = {
17
+ circle:1, ellipse:1, image:1, line:1, path:1, polygon:1, polyline:1, rect:1, stop:1, use:1,
18
+ }
16
19
  , rawTextElements = { SCRIPT: /<(?=\/script)/i, STYLE: /<(?=\/style)/i }
17
20
  , rawTextEscape = { SCRIPT: /<(?=\/script|!--)/ig, STYLE: /<(?=\/style|!--)/ig }
18
21
  , hasOwn = voidElements.hasOwnProperty
19
- , CSSStyleDeclaration = require("./css.js").CSSStyleDeclaration
22
+ , { selectorSplit, CSSStyleDeclaration, CSSStyleSheet } = require("./css.js")
20
23
  , selector = require("./selector.js")
21
24
  , Node = {
22
25
  ELEMENT_NODE: 1,
@@ -37,9 +40,7 @@ var boolAttrs = {
37
40
  if (this.nodeType === 3 || this.nodeType === 8) this.data = text
38
41
  },
39
42
  get textContent() {
40
- return this.nodeType === 3 || this.nodeType === 8 ? this.data : this.childNodes.map(function(child) {
41
- return child.textContent
42
- }).join("")
43
+ return this.nodeType === 3 || this.nodeType === 8 ? this.data : this.childNodes.map(node => node.textContent).join("")
43
44
  },
44
45
  set textContent(text) {
45
46
  if (this.nodeType === 3 || this.nodeType === 8) this.data = text
@@ -71,6 +72,7 @@ var boolAttrs = {
71
72
  , attrRe = /([^=\s]+)(?:\s*=\s*(("|')((?:\\\3|[\s\S])*?)\3|[^\s"'`=<>]+)|)/g
72
73
  , frag = doc.createDocumentFragment()
73
74
  , tree = frag
75
+ , voidEl = doc.documentElement.tagName === "svg" ? svgVoidElements : voidElements
74
76
 
75
77
  for (; (m = tagRe.exec(html)); ) {
76
78
  if (m[4]) {
@@ -85,7 +87,7 @@ var boolAttrs = {
85
87
  for (text = ""; (m = tagRe.exec(html)) && !re.test(m[0]); text += m[3] || m[0]);
86
88
  child.textContent = text.replace(unescRe, unescFn)
87
89
  if (!m) break
88
- } else if (!voidElements[child.tagName] && !m[8]) tree = child
90
+ } else if (!voidEl[child.tagName] && !m[8]) tree = child
89
91
  } else {
90
92
  tree.appendChild(
91
93
  m[2] ? doc.createComment(m[2].replace(unescRe, unescFn)) :
@@ -108,26 +110,29 @@ var boolAttrs = {
108
110
  frag.innerHTML = html
109
111
  this.parentNode.replaceChild(frag, this)
110
112
  },
113
+ get sheet() {
114
+ return makeSheet(this)
115
+ },
111
116
  get style() {
112
- return this._style || (this._style = new CSSStyleDeclaration(this.getAttribute("style") || ""))
117
+ return this._style || (this._style = CSSStyleDeclaration(this.getAttribute("style") || ""))
113
118
  },
114
119
  set style(value) {
115
120
  this.style.cssText = value
116
121
  },
117
- contains: function (el) {
122
+ contains(el) {
118
123
  for (; el; el = el.parentNode) if (el === this) return true
119
124
  return false
120
125
  },
121
- hasChildNodes: function() {
122
- return this.childNodes && this.childNodes.length > 0
126
+ hasChildNodes() {
127
+ return !!this.firstChild
123
128
  },
124
- getElementById: function(id) {
129
+ getElementById(id) {
125
130
  return selector.find(this, "#" + id, 1)
126
131
  },
127
- appendChild: function(el) {
132
+ appendChild(el) {
128
133
  return this.insertBefore(el)
129
134
  },
130
- insertBefore: function(el, ref) {
135
+ insertBefore(el, ref) {
131
136
  var node = this
132
137
  , childs = node.childNodes
133
138
 
@@ -146,7 +151,7 @@ var boolAttrs = {
146
151
  }
147
152
  return el
148
153
  },
149
- removeChild: function(el) {
154
+ removeChild(el) {
150
155
  var node = this
151
156
  , index = node.childNodes.indexOf(el)
152
157
  if (index === -1) throw Error("NOT_FOUND_ERR")
@@ -155,38 +160,33 @@ var boolAttrs = {
155
160
  el.parentNode = null
156
161
  return el
157
162
  },
158
- replaceChild: function(el, ref) {
163
+ replaceChild(el, ref) {
159
164
  this.insertBefore(el, ref)
160
165
  return this.removeChild(ref)
161
166
  },
162
- cloneNode: function(deep) {
167
+ cloneNode(deep) {
163
168
  var node = this
164
169
  , clone = new node.constructor(node.tagName || node.data)
165
170
  clone.ownerDocument = node.ownerDocument
166
171
 
167
- if (node.attributes) {
168
- node.attributes.names().forEach(function(attr) {
169
- clone.setAttribute(attr, node.getAttribute(attr))
170
- })
171
- }
172
+ mergeAttributes(node, clone)
172
173
 
173
174
  if (deep && node.hasChildNodes()) {
174
- node.childNodes.forEach(function(child) {
175
- clone.appendChild(child.cloneNode(deep))
176
- })
175
+ node.childNodes.forEach(child => clone.appendChild(child.cloneNode(deep)))
177
176
  }
178
177
  return clone
179
178
  },
180
- querySelector: function(sel) {
179
+ querySelector(sel) {
181
180
  return selector.find(this, sel, 1)
182
181
  },
183
- querySelectorAll: function(sel) {
182
+ querySelectorAll(sel) {
184
183
  return selector.find(this, sel)
185
184
  },
186
- toString: function(minify) {
187
- return rawTextElements[this.tagName] ? this.textContent : this.childNodes.reduce(function(memo, node) {
188
- return memo + node.toString(minify)
189
- }, "")
185
+ toString(min) {
186
+ return rawTextElements[this.tagName] ? (
187
+ this.tagName === "STYLE" && (min === true || min && min.css) ? "\n" + makeSheet(this, min.css || true) + "\n" :
188
+ this.textContent
189
+ ) : this.childNodes.map(node => node.toString(min)).join("")
190
190
  }
191
191
  }
192
192
  , Element = {
@@ -203,29 +203,31 @@ var boolAttrs = {
203
203
  return getSibling(this, -1, 1)
204
204
  },
205
205
  replaceChildren: replaceChildren,
206
- hasAttribute: function(name) {
206
+ hasAttribute(name) {
207
207
  return this.attributes.getNamedItem(name) != null
208
208
  },
209
- getAttribute: function(name) {
209
+ getAttribute(name) {
210
210
  var attr = this.attributes.getNamedItem(name)
211
211
  return attr ? attr.value : null
212
212
  },
213
- setAttribute: function(name, value) {
213
+ setAttribute(name, value) {
214
214
  this.attributes.setNamedItem(new Attr(this, name, value))
215
215
  },
216
- removeAttribute: function(name) {
216
+ removeAttribute(name) {
217
217
  this.attributes.removeNamedItem(name)
218
218
  },
219
- getElementsByTagName: function(tag) {
219
+ getElementsByTagName(tag) {
220
220
  return selector.find(this, tag)
221
221
  },
222
- getElementsByClassName: function(sel) {
222
+ getElementsByClassName(sel) {
223
223
  return selector.find(this, "." + sel.replace(/\s+/g, "."))
224
224
  }
225
225
  }
226
226
  , quotedAttrRe = /[\s"'`=<>]/
227
227
  , escRe = /<|&(?=[a-z#])/gi
228
+ , escFn = chr => chr === "<" ? "&lt;" : "&amp;"
228
229
  , unescRe = /&[a-z]{1,31};?|&#(x|)([\da-f]+);/ig
230
+ , unescFn = (ent, hex, num) => num ? String.fromCharCode(parseInt(num, hex === "" ? 10 : 16)) : entities[ent] || ent
229
231
  , entities = {
230
232
  "&amp;": "&", "&apos;": "'", "&cent;": "¢", "&copy;": "©", "&curren;": "¤",
231
233
  "&deg;": "°", "&euro;": "€", "&gt;": ">", "&lt;": "<", "&nbsp;": " ",
@@ -247,20 +249,12 @@ function addGetter(key) {
247
249
  this.isNum ? function() { return +this.getAttribute(attr) || 0 } :
248
250
  function() { return this.getAttribute(attr) || "" }
249
251
  ),
250
- set: function(value) {
252
+ set(value) {
251
253
  this.setAttribute(attr, value)
252
254
  }
253
255
  })
254
256
  }
255
257
 
256
- function escFn(chr) {
257
- return chr === "<" ? "&lt;" : "&amp;"
258
- }
259
-
260
- function unescFn(ent, hex, num) {
261
- return num ? String.fromCharCode(parseInt(num, hex === "" ? 10 : 16)) : entities[ent] || ent
262
- }
263
-
264
258
  ;["hasAttribute", "getAttribute", "setAttribute", "removeAttribute"].forEach(function(name) {
265
259
  Element[name + "NS"] = function(ns, a, b) {
266
260
  return this[name].call(this, a, b)
@@ -274,16 +268,16 @@ function Attr(node, name, value) {
274
268
  }
275
269
 
276
270
  function NamedNodeMap(node) {
277
- Object.defineProperty(this, "length", { get: function() { return this.names().length } })
271
+ Object.defineProperty(this, "length", { get() { return this.names().length } })
278
272
  Object.defineProperty(this, "ownerElement", { value: node })
279
273
  }
280
274
 
281
275
  NamedNodeMap.prototype = {
282
- names: function() {
276
+ names() {
283
277
  this.getNamedItem("style")
284
278
  return Object.keys(this)
285
279
  },
286
- getNamedItem: function(name) {
280
+ getNamedItem(name) {
287
281
  var loName = name.toLowerCase()
288
282
  , attr = this[loName] || null
289
283
  if (loName === "style" && this.ownerElement._style) {
@@ -292,24 +286,24 @@ NamedNodeMap.prototype = {
292
286
  }
293
287
  return attr
294
288
  },
295
- removeNamedItem: function(name) {
289
+ removeNamedItem(name) {
296
290
  var loName = name.toLowerCase()
297
291
  , attr = this[loName] || null
298
292
  if (loName === "style") delete this.ownerElement._style
299
293
  if (attr !== null) delete this[loName]
300
294
  return attr
301
295
  },
302
- setNamedItem: function(attr) {
296
+ setNamedItem(attr) {
303
297
  var oldAttr = this.getNamedItem(attr.name)
304
- if (attr.name === "style") attr.value = new CSSStyleDeclaration(attr.value).cssText
305
298
  this[attr.name] = attr
306
299
  return oldAttr
307
300
  },
308
- toString: function(minify) {
301
+ toString(minify) {
309
302
  var map = this
310
303
  , tagName = map.ownerElement.tagName
311
304
  , isXml = map.ownerElement.ownerDocument.contentType === "application/xml"
312
- return map.names().map(function(loName) {
305
+ return map.names().map(loName => {
306
+ if (loName === "style" && minify && map.ownerElement.style) { /* Access to style makes _style */ }
313
307
  var attr = map.getNamedItem(loName)
314
308
  , name = attr.name
315
309
  , value = attr.value.replace(escRe, escFn)
@@ -346,19 +340,22 @@ function HTMLElement(tag) {
346
340
 
347
341
  extendNode(HTMLElement, Element, {
348
342
  nodeType: 1,
349
- matches: function(sel) {
343
+ matches(sel) {
350
344
  return selector.matches(this, sel)
351
345
  },
352
- closest: function(sel) {
346
+ closest(sel) {
353
347
  return selector.closest(this, sel)
354
348
  },
355
349
  namespaceURI: "http://www.w3.org/1999/xhtml",
356
350
  localName: null,
357
351
  tagName: null,
358
- toString: function(minify) {
352
+ toString(minify) {
359
353
  var attrs = this.attributes.toString(minify)
360
- return "<" + this.localName + (attrs ? " " + attrs + (attrs.slice(-1) === "/" ? " >" : ">") : ">") +
361
- (voidElements[this.tagName] ? "" : Node.toString.call(this, minify) + "</" + this.localName + ">")
354
+ , isXml = this.ownerDocument.contentType === "application/xml"
355
+ , voidEl = this.ownerDocument.documentElement.tagName === "svg" ? svgVoidElements : voidElements
356
+ return "<" + this.localName +
357
+ (attrs ? " " + (attrs.slice(-1) === "/" ? attrs + " " : attrs) : "") +
358
+ (voidEl[this.tagName] ? (isXml ? "/>" : ">") : ">" + Node.toString.call(this, minify) + "</" + this.localName + ">")
362
359
  }
363
360
  })
364
361
 
@@ -379,7 +376,7 @@ function Text(data) {
379
376
  extendNode(Text, {
380
377
  nodeType: 3,
381
378
  nodeName: "#text",
382
- toString: function(minify) {
379
+ toString(minify) {
383
380
  return (minify ? ("" + this.data).trim() : "" + this.data).replace(escRe, escFn)
384
381
  }
385
382
  })
@@ -391,7 +388,7 @@ function Comment(data) {
391
388
  extendNode(Comment, {
392
389
  nodeType: 8,
393
390
  nodeName: "#comment",
394
- toString: function(minify) {
391
+ toString(minify) {
395
392
  return minify ? "" : "<!--" + this.data + "-->"
396
393
  }
397
394
  })
@@ -402,7 +399,7 @@ function DocumentType(data) {
402
399
 
403
400
  extendNode(DocumentType, {
404
401
  nodeType: 10,
405
- toString: function() {
402
+ toString() {
406
403
  return "<" + this.data + ">"
407
404
  // var node = document.doctype
408
405
  // return "<!DOCTYPE " + node.name +
@@ -420,6 +417,9 @@ function Document() {
420
417
  }
421
418
 
422
419
  extendNode(Document, Element, {
420
+ get styleSheets() {
421
+ return selector.find(this, "style,link[rel=stylesheet][href]").map(el => el.sheet)
422
+ },
423
423
  get title() {
424
424
  var el = selector.find(this, "title", 1)
425
425
  return el && el.textContent || ""
@@ -442,15 +442,13 @@ extendNode(Document, Element, {
442
442
  function DOMParser() {}
443
443
  function XMLSerializer() {}
444
444
 
445
- DOMParser.prototype.parseFromString = function(str, mime) {
445
+ DOMParser.prototype.parseFromString = (str, mime) => {
446
446
  var doc = new Document()
447
447
  doc.contentType = mime || "text/html"
448
448
  doc.documentElement.outerHTML = str
449
449
  return doc
450
450
  }
451
- XMLSerializer.prototype.serializeToString = function(doc) {
452
- return doc.toString()
453
- }
451
+ XMLSerializer.prototype.serializeToString = doc => doc.toString()
454
452
 
455
453
 
456
454
  function own(Class) {
@@ -490,8 +488,26 @@ function getSibling(node, step, type) {
490
488
  return type > 0 ? getElement(silbings, index + step, step, type) : silbings && silbings[index + step] || null
491
489
  }
492
490
 
491
+ function makeSheet(el, min) {
492
+ if (el.tagName === "STYLE" || el.tagName === "LINK" && el.rel === "stylesheet" && el.href) return new CSSStyleSheet({
493
+ href: el.href,
494
+ ownerNode: el,
495
+ min
496
+ }, el.tagName === "STYLE" && el.textContent)
497
+ }
498
+
499
+ function mergeAttributes(source, target) {
500
+ if (source && target && source.attributes) {
501
+ source.attributes.names().forEach(attr => target.setAttribute(attr, source.getAttribute(attr)))
502
+ }
503
+ }
504
+
493
505
  exports.document = new Document()
494
506
  exports.entities = entities
507
+ exports.mergeAttributes = mergeAttributes
508
+ exports.selectorSplit = selectorSplit
509
+ exports.CSSStyleDeclaration = CSSStyleDeclaration
510
+ exports.CSSStyleSheet = CSSStyleSheet
495
511
  exports.DOMParser = DOMParser
496
512
  exports.Document = Document
497
513
  exports.DocumentFragment = DocumentFragment
package/interactive.js CHANGED
@@ -5,10 +5,10 @@
5
5
 
6
6
  var DOM = module.exports = require(".")
7
7
  , HTMLElementExtra = {
8
- focus: function() {
8
+ focus() {
9
9
  this.ownerDocument.activeElement = this
10
10
  },
11
- blur: function() {
11
+ blur() {
12
12
  this.ownerDocument.activeElement = null
13
13
  }
14
14
  }
package/net.js CHANGED
@@ -1,12 +1,16 @@
1
- /* global unescape */
2
1
 
3
2
  /*! litejs.com/MIT-LICENSE.txt */
4
3
 
5
-
6
4
  var DOM = require(".")
7
5
  , URL = require("url").URL
8
6
  , parser = new DOM.DOMParser()
9
- , dataUrlRe = /^([^;,]*?)(;[^,]+?|),(.*)$/
7
+ , setState = (xhr, state) => {
8
+ if (xhr.readyState !== state) {
9
+ xhr.readyState = state
10
+ if (xhr.onreadystatechange) xhr.onreadystatechange()
11
+ if (state === xhr.DONE && xhr.onload) xhr.onload()
12
+ }
13
+ }
10
14
 
11
15
  exports.XMLHttpRequest = XMLHttpRequest
12
16
  exports.defaultHeaders = {
@@ -18,14 +22,6 @@ function XMLHttpRequest() {
18
22
  this._reqHeaders = Object.assign({}, exports.defaultHeaders)
19
23
  }
20
24
 
21
- function setState(xhr, state) {
22
- if (xhr.readyState !== state) {
23
- xhr.readyState = state
24
- if (xhr.onreadystatechange) xhr.onreadystatechange()
25
- if (state === xhr.DONE && xhr.onload) xhr.onload()
26
- }
27
- }
28
-
29
25
  XMLHttpRequest.prototype = {
30
26
  UNSENT: 0,
31
27
  OPENED: 1,
@@ -49,23 +45,23 @@ XMLHttpRequest.prototype = {
49
45
  parser.parseFromString(xhr.responseText, mime)
50
46
  )
51
47
  },
52
- getAllResponseHeaders: function () {
48
+ getAllResponseHeaders() {
53
49
  var xhr = this
54
- return xhr.readyState >= xhr.HEADERS_RECEIVED && Object.keys(xhr._headers).map(function(name) {
55
- return name + ": " + xhr._headers[name] + "\r\n"
56
- }).join("") || null
50
+ return xhr.readyState >= xhr.HEADERS_RECEIVED && Object.keys(xhr._headers).map(
51
+ name => name + ": " + xhr._headers[name] + "\r\n"
52
+ ).join("") || null
57
53
  },
58
- getResponseHeader: function (name) {
54
+ getResponseHeader(name) {
59
55
  var xhr = this
60
56
  return xhr.readyState >= xhr.HEADERS_RECEIVED && xhr._headers[name.toLowerCase()] || null
61
57
  },
62
- setRequestHeader: function(name, value) {
58
+ setRequestHeader(name, value) {
63
59
  this._reqHeaders[name.toLowerCase()] = [name, value]
64
60
  },
65
- abort: function() {
61
+ abort() {
66
62
  throw Error("XMLHttpRequest abort/reuse not implemented")
67
63
  },
68
- open: function (method, url, isAsync) {
64
+ open(method, url, isAsync) {
69
65
  var xhr = this
70
66
  xhr._sync = isAsync === false
71
67
 
@@ -77,20 +73,38 @@ XMLHttpRequest.prototype = {
77
73
  xhr.responseURL = url
78
74
  setState(xhr, xhr.OPENED)
79
75
  },
80
- send: function (data) {
76
+ send(data) {
81
77
  var xhr = this
82
78
  , url = new URL(xhr.responseURL, XMLHttpRequest.base)
83
79
  , proto = url.protocol.slice(0, -1)
80
+ , head = (code, text, headers) => {
81
+ xhr.status = code
82
+ xhr.statusText = text
83
+ xhr._headers = headers
84
+ setState(xhr, xhr.HEADERS_RECEIVED)
85
+ }
86
+ , fillBody = chunk => {
87
+ xhr.responseText += chunk.toString("utf8")
88
+ setState(xhr, xhr.LOADING)
89
+ }
90
+ , done = err => {
91
+ if (err) {
92
+ if (xhr.onerror) xhr.onerror(err)
93
+ else throw err
94
+ }
95
+ else if (xhr._sync) setState(xhr, xhr.DONE)
96
+ else process.nextTick(setState, xhr, xhr.DONE)
97
+ }
84
98
 
85
99
  if (proto === "http" || proto === "https") {
86
100
  if (xhr._sync) throw Error("XMLHttpRequest sync not implemented")
87
101
  url.method = xhr.method
88
- url.headers = Object.keys(xhr._reqHeaders).reduce(function(result, key) {
102
+ url.headers = Object.keys(xhr._reqHeaders).reduce((result, key) => {
89
103
  var entrie = xhr._reqHeaders[key]
90
104
  result[entrie[0]] = entrie[1]
91
105
  return result
92
106
  }, {})
93
- var req = require(proto).request(url, function(res) {
107
+ var req = require(proto).request(url, res => {
94
108
  head(res.statusCode, res.statusMessage, res.headers)
95
109
  res.on("data", fillBody)
96
110
  res.on("end", done)
@@ -100,7 +114,7 @@ XMLHttpRequest.prototype = {
100
114
  return
101
115
  }
102
116
  if (proto === "data") {
103
- var match = dataUrlRe.exec(url.pathname)
117
+ var match = /^([^;,]*?)(;[^,]+?|),(.*)$/.exec(url.pathname)
104
118
  if (match) {
105
119
  head(200, "OK", { "content-type": match[1] || "text/plain" })
106
120
  fillBody(match[2] ? Buffer.from(match[3], "base64") : unescape(match[3]))
@@ -110,7 +124,7 @@ XMLHttpRequest.prototype = {
110
124
  }
111
125
 
112
126
  if (proto === "file") {
113
- require("fs").readFile(url, function(err, chunk) {
127
+ require("fs").readFile(url, (err, chunk) => {
114
128
  if (err) {
115
129
  head(404, "Not Found", {})
116
130
  } else {
@@ -128,25 +142,6 @@ XMLHttpRequest.prototype = {
128
142
  }
129
143
 
130
144
  throw Error("Unsuported protocol in: " + url)
131
-
132
- function head(code, text, headers) {
133
- xhr.status = code
134
- xhr.statusText = text
135
- xhr._headers = headers
136
- setState(xhr, xhr.HEADERS_RECEIVED)
137
- }
138
- function fillBody(chunk) {
139
- xhr.responseText += chunk.toString("utf8")
140
- setState(xhr, xhr.LOADING)
141
- }
142
- function done(err) {
143
- if (err) {
144
- if (xhr.onerror) xhr.onerror(err)
145
- else throw err
146
- }
147
- else if (xhr._sync) setState(xhr, xhr.DONE)
148
- else process.nextTick(setState, xhr, xhr.DONE)
149
- }
150
145
  }
151
146
  }
152
147
 
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@litejs/dom",
3
- "version": "24.8.0",
3
+ "version": "25.1.0",
4
4
  "description": "A small DOM library for server-side testing, rendering, and handling of HTML files",
5
5
  "license": "MIT",
6
6
  "author": "Lauri Rooden <lauri@rooden.ee>",
7
7
  "keywords": [
8
8
  "document",
9
- "dom",
10
- "html",
9
+ "DOM",
10
+ "HTML",
11
11
  "DOMParser",
12
+ "CSSOM",
13
+ "CSSStyleSheet",
12
14
  "XMLHttpRequest",
13
15
  "XMLSerializer",
14
16
  "litejs"
@@ -18,11 +20,10 @@
18
20
  "*.js"
19
21
  ],
20
22
  "scripts": {
21
- "lint": "npx jshint -c .github/jshint.json *.js",
22
- "test": "lj t test/*.js"
23
+ "test": "lj t"
23
24
  },
24
25
  "repository": "github:litejs/dom",
25
26
  "devDependencies": {
26
- "@litejs/cli": "23.11.1"
27
+ "@litejs/cli": "25.1.0"
27
28
  }
28
29
  }