@d3plus/dom 3.0.16 → 3.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/umd/d3plus-dom.js CHANGED
@@ -4,141 +4,34 @@
4
4
  Copyright (c) 2026 D3plus - https://d3plus.org
5
5
  @license MIT
6
6
  */
7
-
8
- (function (factory) {
9
- typeof define === 'function' && define.amd ? define(factory) :
10
- factory();
11
- })((function () { 'use strict';
12
-
13
- if (typeof window !== "undefined") {
14
- (function () {
15
- try {
16
- if (typeof SVGElement === 'undefined' || Boolean(SVGElement.prototype.innerHTML)) {
17
- return;
18
- }
19
- } catch (e) {
20
- return;
21
- }
22
-
23
- function serializeNode (node) {
24
- switch (node.nodeType) {
25
- case 1:
26
- return serializeElementNode(node);
27
- case 3:
28
- return serializeTextNode(node);
29
- case 8:
30
- return serializeCommentNode(node);
31
- }
32
- }
33
-
34
- function serializeTextNode (node) {
35
- return node.textContent.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
36
- }
37
-
38
- function serializeCommentNode (node) {
39
- return '<!--' + node.nodeValue + '-->'
40
- }
41
-
42
- function serializeElementNode (node) {
43
- var output = '';
44
-
45
- output += '<' + node.tagName;
46
-
47
- if (node.hasAttributes()) {
48
- [].forEach.call(node.attributes, function(attrNode) {
49
- output += ' ' + attrNode.name + '="' + attrNode.value + '"';
50
- });
51
- }
52
-
53
- output += '>';
54
-
55
- if (node.hasChildNodes()) {
56
- [].forEach.call(node.childNodes, function(childNode) {
57
- output += serializeNode(childNode);
58
- });
59
- }
60
-
61
- output += '</' + node.tagName + '>';
62
-
63
- return output;
64
- }
65
-
66
- Object.defineProperty(SVGElement.prototype, 'innerHTML', {
67
- get: function () {
68
- var output = '';
69
-
70
- [].forEach.call(this.childNodes, function(childNode) {
71
- output += serializeNode(childNode);
72
- });
73
-
74
- return output;
75
- },
76
- set: function (markup) {
77
- while (this.firstChild) {
78
- this.removeChild(this.firstChild);
79
- }
80
-
81
- try {
82
- var dXML = new DOMParser();
83
- dXML.async = false;
84
-
85
- var sXML = '<svg xmlns=\'http://www.w3.org/2000/svg\' xmlns:xlink=\'http://www.w3.org/1999/xlink\'>' + markup + '</svg>';
86
- var svgDocElement = dXML.parseFromString(sXML, 'text/xml').documentElement;
87
-
88
- [].forEach.call(svgDocElement.childNodes, function(childNode) {
89
- this.appendChild(this.ownerDocument.importNode(childNode, true));
90
- }.bind(this));
91
- } catch (e) {
92
- throw new Error('Error parsing markup string');
93
- }
94
- }
95
- });
96
-
97
- Object.defineProperty(SVGElement.prototype, 'innerSVG', {
98
- get: function () {
99
- return this.innerHTML;
100
- },
101
- set: function (markup) {
102
- this.innerHTML = markup;
103
- }
104
- });
105
-
106
- })();
107
- }
108
-
109
- }));
110
-
111
7
  (function (global, factory) {
112
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-transition'), require('@d3plus/text')) :
113
- typeof define === 'function' && define.amd ? define('@d3plus/dom', ['exports', 'd3-selection', 'd3-transition', '@d3plus/text'], factory) :
114
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3plus = {}, global.d3Selection, global.d3Transition, global.text));
115
- })(this, (function (exports, d3Selection, d3Transition, text) { 'use strict';
8
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-selection'), require('d3-transition'), require('@chenglou/pretext')) :
9
+ typeof define === 'function' && define.amd ? define('@d3plus/dom', ['exports', 'd3-selection', 'd3-transition', '@chenglou/pretext'], factory) :
10
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3plus = {}, global.d3Selection, global.d3Transition, global.pretext));
11
+ })(this, (function (exports, d3Selection, d3Transition, pretext) { 'use strict';
116
12
 
117
13
  /**
118
- @function isObject
119
- @desc Detects if a variable is a javascript Object.
120
- @param {*} item
14
+ Detects if a variable is a javascript Object.
15
+ @param item The value to test.
121
16
  */ function isObject(item) {
122
17
  return item && typeof item === "object" && (typeof window === "undefined" || item !== window && item !== window.document && !(item instanceof Element)) && !Array.isArray(item) ? true : false;
123
18
  }
124
19
 
125
20
  /**
126
- @function validObject
127
- @desc Determines if the object passed is the document or window.
128
- @param {Object} obj
129
- @private
21
+ Determines if the object passed is the document or window.
22
+ @param obj @private
130
23
  */ function validObject(obj) {
131
24
  if (typeof window === "undefined") return true;
132
25
  else return obj !== window && obj !== document;
133
26
  }
134
27
  /**
135
- @function assign
136
- @desc A deeply recursive version of `Object.assign`.
137
- @param {...Object} objects
138
- @example <caption>this</caption>
28
+ A deeply recursive version of `Object.assign`.
29
+
30
+ @example <caption>this</caption>
139
31
  assign({id: "foo", deep: {group: "A"}}, {id: "bar", deep: {value: 20}}));
140
32
  @example <caption>returns this</caption>
141
33
  {id: "bar", deep: {group: "A", value: 20}}
34
+ @param objects The source objects to merge into the target.
142
35
  */ function assign(...objects) {
143
36
  const target = objects[0];
144
37
  for(let i = 1; i < objects.length; i++){
@@ -157,19 +50,31 @@
157
50
  }
158
51
 
159
52
  /**
160
- @function attrize
161
- @desc Applies each key/value in an object as an attr.
162
- @param {D3selection} elem The D3 element to apply the styles to.
163
- @param {Object} attrs An object of key/value attr pairs.
53
+ Given a DOM element, returns its background color by walking up the
54
+ ancestor chain until a non-transparent background is found. Falls back
55
+ to "rgb(255, 255, 255)" (white) if every ancestor is transparent.
56
+ @param elem The DOM element to check.
57
+ */ function backgroundColor(elem) {
58
+ let node = elem;
59
+ while(node){
60
+ const bg = getComputedStyle(node).backgroundColor;
61
+ if (bg && bg !== "transparent" && bg !== "rgba(0, 0, 0, 0)") return bg;
62
+ node = node.parentElement;
63
+ }
64
+ return "rgb(255, 255, 255)";
65
+ }
66
+
67
+ /**
68
+ Applies each key/value in an object as an attr.
69
+ @param e The d3 selection to apply attributes to.
70
+ @param a An object of key/value attr pairs.
164
71
  */ function attrize(e, a = {}) {
165
72
  for(const k in a)if (({}).hasOwnProperty.call(a, k)) e.attr(k, a[k]);
166
73
  }
167
74
 
168
75
  /**
169
- @function date
170
- @summary Parses numbers and strings to valid Javascript Date objects.
171
- @description Returns a javascript Date object for a given a Number (representing either a 4-digit year or milliseconds since epoch), a String representing a Quarter (ie. "Q2 1987", mapping to the last day in that quarter), or a String that is in [valid dateString format](http://dygraphs.com/date-formats.html). Besides the 4-digit year parsing, this function is useful when needing to parse negative (BC) years, which the vanilla Date object cannot parse.
172
- @param {Number|String} *date*
76
+ Parses numbers and strings into valid JavaScript Date objects, supporting years, quarters, months, and ISO 8601 formats.
77
+ @param d The date value to parse (number, string, or Date).
173
78
  */ function date(d) {
174
79
  // returns if falsey or already Date object
175
80
  if ([
@@ -208,8 +113,8 @@
208
113
  return date;
209
114
  }
210
115
  // tests for monthly formats (ie. "MM-YYYY" and "YYYY-MM")
211
- const monthPrefix = new RegExp(/^([-*\d]{1,2})\-(-*\d{1,4})$/g).exec(s);
212
- const monthSuffix = new RegExp(/^(-*\d{1,4})\-([-*\d]{1,2})$/g).exec(s);
116
+ const monthPrefix = new RegExp(/^([-*\d]{1,2})-(-*\d{1,4})$/g).exec(s);
117
+ const monthSuffix = new RegExp(/^(-*\d{1,4})-([-*\d]{1,2})$/g).exec(s);
213
118
  if (monthPrefix || monthSuffix) {
214
119
  const month = +(monthPrefix ? monthPrefix[1] : monthSuffix[2]);
215
120
  const year = +(monthPrefix ? monthPrefix[2] : monthSuffix[1]);
@@ -230,19 +135,12 @@
230
135
  }
231
136
 
232
137
  /**
233
- @function elem
234
- @desc Manages the enter/update/exit pattern for a single DOM element.
235
- @param {String} selector A D3 selector, which must include the tagname and a class and/or ID.
236
- @param {Object} params Additional parameters.
237
- @param {Boolean} [params.condition = true] Whether or not the element should be rendered (or removed).
238
- @param {Object} [params.enter = {}] A collection of key/value pairs that map to attributes to be given on enter.
239
- @param {Object} [params.exit = {}] A collection of key/value pairs that map to attributes to be given on exit.
240
- @param {D3Selection} [params.parent = d3.select("body")] The parent element for this new element to be appended to.
241
- @param {Number} [params.duration = 0] The duration for the d3 transition.
242
- @param {Object} [params.update = {}] A collection of key/value pairs that map to attributes to be given on update.
138
+ Manages the enter/update/exit pattern for a single DOM element, applying enter, update, and exit attributes with optional transitions.
139
+ @param selector A CSS selector string for the element tag and classes.
140
+ @param p Configuration object with enter, exit, update, and parent options.
243
141
  */ function elem(selector, p) {
244
142
  // overrides default params
245
- p = Object.assign({}, {
143
+ const params = Object.assign({}, {
246
144
  condition: true,
247
145
  enter: {},
248
146
  exit: {},
@@ -250,18 +148,18 @@
250
148
  parent: d3Selection.select("body"),
251
149
  update: {}
252
150
  }, p);
253
- const className = /\.([^#]+)/g.exec(selector), id = /#([^.]+)/g.exec(selector), t = d3Transition.transition().duration(p.duration), tag = /^([^.^#]+)/g.exec(selector)[1];
254
- const elem = p.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector).data(p.condition ? [
151
+ const className = /\.([^#]+)/g.exec(selector), id = /#([^.]+)/g.exec(selector), t = d3Transition.transition().duration(params.duration), tag = /^([^.^#]+)/g.exec(selector)[1];
152
+ const elem = params.parent.selectAll(selector.includes(":") ? selector.split(":")[1] : selector).data(params.condition ? [
255
153
  null
256
154
  ] : []);
257
- const enter = elem.enter().append(tag).call(attrize, p.enter);
155
+ const enter = elem.enter().append(tag).call(attrize, params.enter);
258
156
  if (id) enter.attr("id", id[1]);
259
157
  if (className) enter.attr("class", className[1]);
260
- if (p.duration) elem.exit().transition(t).call(attrize, p.exit).remove();
261
- else elem.exit().call(attrize, p.exit).remove();
158
+ if (params.duration) elem.exit().transition(t).call(attrize, params.exit).remove();
159
+ else elem.exit().call(attrize, params.exit).remove();
262
160
  const update = enter.merge(elem);
263
- if (p.duration) update.transition(t).call(attrize, p.update);
264
- else update.call(attrize, p.update);
161
+ if (params.duration) update.transition(t).call(attrize, params.update);
162
+ else update.call(attrize, params.update);
265
163
  return update;
266
164
  }
267
165
 
@@ -274,36 +172,37 @@
274
172
  return doc.documentElement ? doc.documentElement.textContent : input;
275
173
  }
276
174
  /**
277
- @function textWidth
278
- @desc Given a text string, returns the predicted pixel width of the string when placed into DOM.
279
- @param {String|Array} text Can be either a single string or an array of strings to analyze.
280
- @param {Object} [style] An object of CSS font styles to apply. Accepts any of the valid [CSS font property](http://www.w3schools.com/cssref/pr_font_font.asp) values.
281
- */ function textWidth(text, style) {
282
- style = Object.assign({
283
- "font-size": 10,
284
- "font-family": "sans-serif",
285
- "font-style": "normal",
286
- "font-weight": 400,
287
- "font-variant": "normal"
288
- }, style);
289
- const context = document.createElement("canvas").getContext("2d");
290
- const font = [];
291
- font.push(style["font-style"]);
292
- font.push(style["font-variant"]);
293
- font.push(style["font-weight"]);
294
- font.push(typeof style["font-size"] === "string" ? style["font-size"] : `${style["font-size"]}px`);
295
- font.push(style["font-family"]);
296
- context.font = font.join(" ");
297
- if (text instanceof Array) return text.map((t)=>context.measureText(htmlDecode(t)).width);
298
- return context.measureText(htmlDecode(text)).width;
175
+ * Builds a CSS font shorthand string from a style object.
176
+ * @param {Object} styleObj
177
+ */ function buildFont(styleObj) {
178
+ const style = styleObj["font-style"] || "normal";
179
+ const variant = styleObj["font-variant"] || "normal";
180
+ const weight = styleObj["font-weight"] || 400;
181
+ const size = typeof styleObj["font-size"] === "string" ? styleObj["font-size"] : `${styleObj["font-size"] || 10}px`;
182
+ const family = styleObj["font-family"] || "sans-serif";
183
+ return `${style} ${variant} ${weight} ${size} ${family}`;
184
+ }
185
+ /**
186
+ * Measures the width of a single text string using pretext.
187
+ * @param {String} text
188
+ * @param {String} font CSS font shorthand
189
+ */ function measureWidth(text, font) {
190
+ if (!text) return 0;
191
+ const prepared = pretext.prepareWithSegments(text, font);
192
+ const result = pretext.layoutWithLines(prepared, Infinity, 20);
193
+ return result.lines.length ? result.lines[0].width : 0;
194
+ }
195
+ function textWidth(text, style = {}) {
196
+ const font = buildFont(style);
197
+ if (text instanceof Array) return text.map((t)=>measureWidth(htmlDecode(t), font));
198
+ return measureWidth(htmlDecode(text), font);
299
199
  }
300
200
 
301
201
  const alpha = "abcdefghiABCDEFGHI_!@#$%^&*()_+1234567890", checked = {}, height = 32;
302
202
  let dejavu, macos, monospace, proportional;
303
203
  /**
304
- @function fontExists
305
- @desc Given either a single font-family or a list of fonts, returns the name of the first font that can be rendered, or `false` if none are installed on the user's machine.
306
- @param {String|Array} font Can be either a valid CSS font-family string (single or comma-separated names) or an Array of string names.
204
+ Given either a single font-family or a list of fonts, returns the name of the first font that can be rendered, or `false` if none are installed on the user's machine.
205
+ @param font Can be either a valid CSS font-family string (single or comma-separated names) or an Array of string names.
307
206
  */ const fontExists = (font)=>{
308
207
  if (!dejavu) {
309
208
  dejavu = textWidth(alpha, {
@@ -324,7 +223,7 @@
324
223
  });
325
224
  }
326
225
  if (!(font instanceof Array)) font = font.split(",");
327
- font = font.map((f)=>text.trim(f));
226
+ font = font.map((f)=>f.trim());
328
227
  for(let i = 0; i < font.length; i++){
329
228
  const fam = font[i];
330
229
  if (checked[fam] || [
@@ -348,7 +247,7 @@
348
247
  };
349
248
 
350
249
  /**
351
- @desc Given an HTMLElement and a "width" or "height" string, this function returns the current calculated size for the DOM element.
250
+ Given an HTMLElement and a "width" or "height" string, this function returns the current calculated size for the DOM element.
352
251
  @private
353
252
  */ function _elementSize(element, s) {
354
253
  if (!element) return undefined;
@@ -359,32 +258,37 @@
359
258
  let val = window[`inner${s.charAt(0).toUpperCase() + s.slice(1)}`];
360
259
  const elem = d3Selection.select(element);
361
260
  if (s === "width") {
362
- val -= parseFloat(elem.style("margin-left"), 10);
363
- val -= parseFloat(elem.style("margin-right"), 10);
364
- val -= parseFloat(elem.style("padding-left"), 10);
365
- val -= parseFloat(elem.style("padding-right"), 10);
261
+ val -= parseFloat(elem.style("margin-left"));
262
+ val -= parseFloat(elem.style("margin-right"));
263
+ val -= parseFloat(elem.style("padding-left"));
264
+ val -= parseFloat(elem.style("padding-right"));
366
265
  } else {
367
- val -= parseFloat(elem.style("margin-top"), 10);
368
- val -= parseFloat(elem.style("margin-bottom"), 10);
369
- val -= parseFloat(elem.style("padding-top"), 10);
370
- val -= parseFloat(elem.style("padding-bottom"), 10);
266
+ val -= parseFloat(elem.style("margin-top"));
267
+ val -= parseFloat(elem.style("margin-bottom"));
268
+ val -= parseFloat(elem.style("padding-top"));
269
+ val -= parseFloat(elem.style("padding-bottom"));
371
270
  }
372
271
  return val;
373
272
  } else {
374
- let val = parseFloat(d3Selection.select(element).style(s), 10);
273
+ let val = element.getBoundingClientRect()[s];
375
274
  if (typeof val === "number" && val > 0) {
376
275
  if (s === "height") {
377
- val -= parseFloat(d3Selection.select(element).style("padding-top"), 10);
378
- val -= parseFloat(d3Selection.select(element).style("padding-bottom"), 10);
276
+ val -= parseFloat(d3Selection.select(element).style("padding-top"));
277
+ val -= parseFloat(d3Selection.select(element).style("padding-bottom"));
278
+ val -= parseFloat(d3Selection.select(element).style("border-top"));
279
+ val -= parseFloat(d3Selection.select(element).style("border-bottom"));
280
+ } else {
281
+ val -= parseFloat(d3Selection.select(element).style("padding-left"));
282
+ val -= parseFloat(d3Selection.select(element).style("padding-right"));
283
+ val -= parseFloat(d3Selection.select(element).style("border-left"));
284
+ val -= parseFloat(d3Selection.select(element).style("border-right"));
379
285
  }
380
286
  return val;
381
287
  } else return _elementSize(element.parentNode, s);
382
288
  }
383
289
  }
384
290
  /**
385
- @function getSize
386
- @desc Finds the available width and height for a specified HTMLElement, traversing it's parents until it finds something with constrained dimensions. Falls back to the inner dimensions of the browser window if none is found.
387
- @param {HTMLElement} elem The HTMLElement to find dimensions for.
291
+ Finds the available width and height for a specified HTMLElement, traversing it's parents until it finds something with constrained dimensions. Falls back to the inner dimensions of the browser window if none is found.
388
292
  @private
389
293
  */ function getSize(elem) {
390
294
  return [
@@ -394,27 +298,24 @@
394
298
  }
395
299
 
396
300
  /**
397
- @module inViewport
398
- @desc Returns a *Boolean* denoting whether or not a given DOM element is visible in the current window.
399
- @param {DOMElement} elem The DOM element to analyze.
400
- @param {Number} [buffer = 0] A pixel offset from the edge of the top and bottom of the screen. If a positive value, the element will be deemed visible when it is that many pixels away from entering the viewport. If negative, the element will have to enter the viewport by that many pixels before being deemed visible.
401
- @private
301
+ Determines whether a given DOM element is visible within the current viewport, with an optional pixel buffer.
302
+ @param elem The DOM element to check.
303
+ @param buffer Extra pixel margin around the viewport boundary.
402
304
  */ function inViewport(elem, buffer = 0) {
403
- const pageX = window.pageXOffset !== undefined ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
404
- const pageY = window.pageYOffset !== undefined ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
305
+ const pageX = window.scrollX;
306
+ const pageY = window.scrollY;
405
307
  const bounds = elem.getBoundingClientRect();
406
308
  const height = bounds.height, left = bounds.left + pageX, top = bounds.top + pageY, width = bounds.width;
407
309
  return pageY + window.innerHeight > top + buffer && pageY + buffer < top + height && pageX + window.innerWidth > left + buffer && pageX + buffer < left + width;
408
310
  }
409
311
 
410
312
  /**
411
- @function parseSides
412
- @desc Converts a string of directional CSS shorthand values into an object with the values expanded.
413
- @param {String|Number} sides The CSS shorthand string to expand.
313
+ Converts a string of directional CSS shorthand values into an object with the values expanded.
314
+ @param sides The CSS shorthand string to expand.
414
315
  */ function parseSides(sides) {
415
316
  let values;
416
317
  if (typeof sides === "number") values = [
417
- sides
318
+ `${sides}`
418
319
  ];
419
320
  else values = sides.split(/\s+/);
420
321
  if (values.length === 1) values = [
@@ -438,32 +339,22 @@
438
339
  }
439
340
 
440
341
  /**
441
- @function prefix
442
- @desc Returns the appropriate CSS vendor prefix, given the current browser.
443
- */ function prefix() {
444
- if ("-webkit-transform" in document.body.style) return "-webkit-";
445
- else if ("-moz-transform" in document.body.style) return "-moz-";
446
- else if ("-ms-transform" in document.body.style) return "-ms-";
447
- else if ("-o-transform" in document.body.style) return "-o-";
448
- else return "";
342
+ Returns `true` if the HTML or body element has either the "dir" HTML attribute or the "direction" CSS property set to "rtl".
343
+ */ function rtl() {
344
+ return document.documentElement.dir === "rtl" || document.body.dir === "rtl" || getComputedStyle(document.documentElement).direction === "rtl" || getComputedStyle(document.body).direction === "rtl";
449
345
  }
450
346
 
451
347
  /**
452
- @function rtl
453
- @desc Returns `true` if the HTML or body element has either the "dir" HTML attribute or the "direction" CSS property set to "rtl".
454
- */ var rtl = (()=>d3Selection.select("html").attr("dir") === "rtl" || d3Selection.select("body").attr("dir") === "rtl" || d3Selection.select("html").style("direction") === "rtl" || d3Selection.select("body").style("direction") === "rtl");
455
-
456
- /**
457
- @function stylize
458
- @desc Applies each key/value in an object as a style.
459
- @param {D3selection} elem The D3 element to apply the styles to.
460
- @param {Object} styles An object of key/value style pairs.
348
+ Applies each key/value in an object as a style.
349
+ @param e The d3 selection to apply styles to.
350
+ @param s An object of key/value style pairs.
461
351
  */ function stylize(e, s = {}) {
462
352
  for(const k in s)if (({}).hasOwnProperty.call(s, k)) e.style(k, s[k]);
463
353
  }
464
354
 
465
355
  exports.assign = assign;
466
356
  exports.attrize = attrize;
357
+ exports.backgroundColor = backgroundColor;
467
358
  exports.date = date;
468
359
  exports.elem = elem;
469
360
  exports.fontExists = fontExists;
@@ -471,7 +362,6 @@
471
362
  exports.inViewport = inViewport;
472
363
  exports.isObject = isObject;
473
364
  exports.parseSides = parseSides;
474
- exports.prefix = prefix;
475
365
  exports.rtl = rtl;
476
366
  exports.stylize = stylize;
477
367
  exports.textWidth = textWidth;