@slickgrid-universal/utils 5.0.0-beta.2 → 5.0.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.
@@ -3,6 +3,7 @@
3
3
  * The reason for the reimplementation was mostly because the original project is not ESM compatible
4
4
  * and written with old ES6 IIFE syntax, the goal was to reimplement and fix these old syntax and build problems.
5
5
  * e.g. it used `var` everywhere, it used `arguments` to get function arguments, ...
6
+ * See `jQuery.extend()` for multiple usage demos: https://api.jquery.com/jquery.extend/
6
7
  *
7
8
  * The previous lib can be found here at this Github link:
8
9
  * https://github.com/justmoon/node-extend
@@ -1 +1 @@
1
- {"version":3,"file":"nodeExtend.d.ts","sourceRoot":"","sources":["../../src/nodeExtend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8DH,wBAAgB,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAyDjD"}
1
+ {"version":3,"file":"nodeExtend.d.ts","sourceRoot":"","sources":["../../src/nodeExtend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA8DH,wBAAgB,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAyDjD"}
@@ -19,10 +19,10 @@ export declare function addWhiteSpaces(nbSpaces: number, spaceChar?: string): st
19
19
  */
20
20
  export declare function arrayRemoveItemByIndex<T>(array: T[], index: number): T[];
21
21
  /**
22
- * Create an immutable clone of an array or object
23
- * (c) 2019 Chris Ferdinandi, MIT License, https://gomakethings.com
24
- * @param {Array|Object} objectOrArray - the array or object to copy
25
- * @return {Array|Object} - the clone of the array or object
22
+ * @use `extend()` when possible.
23
+ * Create an immutable clone of an array or object (native type will be returned "as is").
24
+ * This function will deep copy whatever is provided as the first argument, but nonetheless it is now an alias to `extend()` (node-extend) function.
25
+ * We'll keep the method to avoid breaking users of `deepCopy()`, however we suggest to directly use `extend(true, {}, obj)` whenever possible
26
26
  */
27
27
  export declare function deepCopy(objectOrArray: any | any[]): any | any[];
28
28
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,cAAc,SAAO,QAWpG;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,MAAM,CAOxE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,aAAa,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,GAAG,EAAE,CAuChE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,CAqC7D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,OAYnC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,WAAW,EAAE,SAAS,UAAO;;;;EAmCnE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAK/C;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC,CAEpE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,GAAG,WAEjC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,WAExC;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,WAEzC;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAE3C;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,UAAQ,WAKlD;AAED,yHAAyH;AACzH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,WAEzC;AAED,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAEhD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,UAAQ,UAGzE;AAED,sGAAsG;AACtG,wBAAgB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,QAmBpF;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,yBAAyB,UAAQ,GAAG,MAAM,CAUrF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAYpD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAOlD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,YAAY,SAAO,GAAG,GAAG,EAAE,CAiBxE"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slickgrid-universal/utils",
3
- "version": "5.0.0-beta.2",
3
+ "version": "5.0.0",
4
4
  "description": "Common set of small utilities",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -37,5 +37,5 @@
37
37
  "> 1%",
38
38
  "not dead"
39
39
  ],
40
- "gitHead": "821e9e33886faa626166edc9857912e788a933ad"
40
+ "gitHead": "526fc83e33b1e6ea236082b1b4cb50be623f4569"
41
41
  }
package/src/domUtils.ts CHANGED
@@ -1,267 +1,267 @@
1
- import type { HtmlElementPosition, InferDOMType } from './models/index';
2
-
3
- /** calculate available space for each side of the DOM element */
4
- export function calculateAvailableSpace(element: HTMLElement): { top: number; bottom: number; left: number; right: number; } {
5
- let bottom = 0;
6
- let top = 0;
7
- let left = 0;
8
- let right = 0;
9
-
10
- const windowHeight = window.innerHeight ?? 0;
11
- const windowWidth = window.innerWidth ?? 0;
12
- const scrollPosition = windowScrollPosition();
13
- const pageScrollTop = scrollPosition.top;
14
- const pageScrollLeft = scrollPosition.left;
15
- const elmOffset = getOffset(element);
16
-
17
- if (elmOffset) {
18
- const elementOffsetTop = elmOffset.top ?? 0;
19
- const elementOffsetLeft = elmOffset.left ?? 0;
20
- top = elementOffsetTop - pageScrollTop;
21
- bottom = windowHeight - (elementOffsetTop - pageScrollTop);
22
- left = elementOffsetLeft - pageScrollLeft;
23
- right = windowWidth - (elementOffsetLeft - pageScrollLeft);
24
- }
25
-
26
- return { top, bottom, left, right };
27
- }
28
-
29
- /**
30
- * Create a DOM Element with any optional attributes or properties.
31
- * It will only accept valid DOM element properties that `createElement` would accept.
32
- * For example: `createDomElement('div', { className: 'my-css-class' })`,
33
- * for style or dataset you need to use nested object `{ style: { display: 'none' }}
34
- * The last argument is to optionally append the created element to a parent container element.
35
- * @param {String} tagName - html tag
36
- * @param {Object} options - element properties
37
- * @param {[Element]} appendToParent - parent element to append to
38
- */
39
- export function createDomElement<T extends keyof HTMLElementTagNameMap, K extends keyof HTMLElementTagNameMap[T]>(
40
- tagName: T,
41
- elementOptions?: null | { [P in K]: InferDOMType<HTMLElementTagNameMap[T][P]> },
42
- appendToParent?: Element
43
- ): HTMLElementTagNameMap[T] {
44
- const elm = document.createElement<T>(tagName);
45
-
46
- if (elementOptions) {
47
- Object.keys(elementOptions).forEach((elmOptionKey) => {
48
- if (elmOptionKey === 'innerHTML') {
49
- console.warn(`[Slickgrid-Universal] For better CSP (Content Security Policy) support, do not use "innerHTML" directly in "createDomElement('${tagName}', { innerHTML: 'some html'})", ` +
50
- `it is better as separate assignment: "const elm = createDomElement('span'); elm.innerHTML = 'some html';"`);
51
- }
52
- const elmValue = elementOptions[elmOptionKey as keyof typeof elementOptions];
53
- if (typeof elmValue === 'object') {
54
- Object.assign(elm[elmOptionKey as K] as object, elmValue);
55
- } else {
56
- elm[elmOptionKey as K] = (elementOptions as any)[elmOptionKey as keyof typeof elementOptions];
57
- }
58
- });
59
- }
60
- if (appendToParent?.appendChild) {
61
- appendToParent.appendChild(elm);
62
- }
63
- return elm;
64
- }
65
-
66
- /**
67
- * Accepts string containing the class or space-separated list of classes, and
68
- * returns list of individual classes.
69
- * Method properly takes into account extra whitespaces in the `className`
70
- * e.g.: " class1 class2 " => will result in `['class1', 'class2']`.
71
- * @param {String} className - space separated list of class names
72
- */
73
- export function classNameToList(className = ''): string[] {
74
- return className.split(' ').filter(cls => cls); // filter will remove whitespace entries
75
- }
76
-
77
- /**
78
- * Loop through all properties of an object and nullify any properties that are instanceof HTMLElement,
79
- * if we detect an array then use recursion to go inside it and apply same logic
80
- * @param obj - object containing 1 or more properties with DOM Elements
81
- */
82
- export function destroyAllElementProps(obj: any) {
83
- if (typeof obj === 'object') {
84
- Object.keys(obj).forEach(key => {
85
- if (Array.isArray(obj[key])) {
86
- destroyAllElementProps(obj[key]);
87
- }
88
- if (obj[key] instanceof HTMLElement) {
89
- obj[key] = null;
90
- }
91
- });
92
- }
93
- }
94
-
95
- /**
96
- * Empty a DOM element by removing all of its DOM element children leaving with an empty element (basically an empty shell)
97
- * @return {object} element - updated element
98
- */
99
- export function emptyElement<T extends Element = Element>(element?: T | null): T | undefined | null {
100
- while (element?.firstChild) {
101
- element.removeChild(element.firstChild);
102
- }
103
- return element;
104
- }
105
-
106
- /**
107
- * From any input provided, return the HTML string (when a string is provided, it will be returned "as is" but when it's a number it will be converted to string)
108
- * When detecting HTMLElement/DocumentFragment, we can also specify which HTML type to retrieve innerHTML or outerHTML.
109
- * We can get the HTML by looping through all fragment `childNodes`
110
- * @param {DocumentFragment | HTMLElement | string | number} input
111
- * @param {'innerHTML' | 'outerHTML'} [type] - when the input is a DocumentFragment or HTMLElement, which type of HTML do you want to return? 'innerHTML' or 'outerHTML'
112
- * @returns {String}
113
- */
114
- export function getHtmlStringOutput(input: DocumentFragment | HTMLElement | string | number, type: 'innerHTML' | 'outerHTML' = 'innerHTML'): string {
115
- if (input instanceof DocumentFragment) {
116
- // a DocumentFragment doesn't have innerHTML/outerHTML, but we can loop through all children and concatenate them all to an HTML string
117
- return [].map.call(input.childNodes, (x: HTMLElement) => x[type]).join('') || input.textContent || '';
118
- } else if (input instanceof HTMLElement) {
119
- return input[type];
120
- }
121
- return String(input ?? ''); // reaching this line means it's already a string (or number) so just return it as string
122
- }
123
-
124
- /** Get offset of HTML element relative to a parent element */
125
- export function getOffsetRelativeToParent(parentElm: HTMLElement | null, childElm: HTMLElement | null) {
126
- if (!parentElm || !childElm) {
127
- return undefined;
128
- }
129
- const parentPos = parentElm.getBoundingClientRect();
130
- const childPos = childElm.getBoundingClientRect();
131
- return {
132
- top: childPos.top - parentPos.top,
133
- right: childPos.right - parentPos.right,
134
- bottom: childPos.bottom - parentPos.bottom,
135
- left: childPos.left - parentPos.left,
136
- };
137
- }
138
-
139
- /** Get HTML element offset with pure JS */
140
- export function getOffset(elm?: HTMLElement | null): HtmlElementPosition | undefined {
141
- if (!elm || !elm.getBoundingClientRect) {
142
- return undefined;
143
- }
144
- const box = elm.getBoundingClientRect();
145
- const docElem = document.documentElement;
146
-
147
- let top = 0;
148
- let left = 0;
149
- let bottom = 0;
150
- let right = 0;
151
-
152
- if (box?.top !== undefined && box.left !== undefined) {
153
- top = box.top + window.pageYOffset - docElem.clientTop;
154
- left = box.left + window.pageXOffset - docElem.clientLeft;
155
- right = box.right;
156
- bottom = box.bottom;
157
- }
158
- return { top, left, bottom, right };
159
- }
160
-
161
- export function getInnerSize(elm: HTMLElement, type: 'height' | 'width') {
162
- let size = 0;
163
-
164
- if (elm) {
165
- const clientSize = type === 'height' ? 'clientHeight' : 'clientWidth';
166
- const sides = type === 'height' ? ['top', 'bottom'] : ['left', 'right'];
167
- size = elm[clientSize];
168
- for (const side of sides) {
169
- const sideSize = (parseFloat(getStyleProp(elm, `padding-${side}`) || '') || 0);
170
- size -= sideSize;
171
- }
172
- }
173
- return size;
174
- }
175
-
176
- /** Get a DOM element style property value by calling getComputedStyle() on the element */
177
- export function getStyleProp(elm: HTMLElement, property: string) {
178
- if (elm) {
179
- return window.getComputedStyle(elm).getPropertyValue(property);
180
- }
181
- return null;
182
- }
183
-
184
- export function findFirstAttribute(inputElm: Element | null | undefined, attributes: string[]): string | null {
185
- if (inputElm) {
186
- for (const attribute of attributes) {
187
- const attrData = inputElm.getAttribute(attribute);
188
- if (attrData) {
189
- return attrData;
190
- }
191
- }
192
- }
193
- return null;
194
- }
195
-
196
- /**
197
- * Provide a width as a number or a string and find associated value in valid css style format or use default value when provided (or "auto" otherwise).
198
- * @param {Number|String} inputWidth - input width, could be a string or number
199
- * @param {Number | String} defaultValue [defaultValue=auto] - optional default value or use "auto" when nothing is provided
200
- * @returns {String} string output
201
- */
202
- export function findWidthOrDefault(inputWidth?: number | string | null, defaultValue = 'auto'): string {
203
- return (/^[0-9]+$/i.test(`${inputWidth}`) ? `${+(inputWidth as number)}px` : inputWidth as string) || defaultValue;
204
- }
205
-
206
- /**
207
- * HTML encode using a plain <div>
208
- * Create a in-memory div, set it's inner text(which a div can encode)
209
- * then grab the encoded contents back out. The div never exists on the page.
210
- * @param {String} inputValue - input value to be encoded
211
- * @return {String}
212
- */
213
- export function htmlEncode(inputValue: string): string {
214
- const val = typeof inputValue === 'string' ? inputValue : String(inputValue);
215
- const entityMap: { [char: string]: string; } = {
216
- '&': '&amp;',
217
- '<': '&lt;',
218
- '>': '&gt;',
219
- '"': '&quot;',
220
- '\'': '&#39;',
221
- };
222
- return (val || '').toString().replace(/[&<>"']/g, (s) => entityMap[s as keyof { [char: string]: string; }]);
223
- }
224
-
225
- /**
226
- * Decode text into html entity
227
- * @param string text: input text
228
- * @param string text: output text
229
- */
230
- export function htmlEntityDecode(input: string): string {
231
- return input.replace(/&#(\d+);/g, (_match, dec) => {
232
- return String.fromCharCode(dec);
233
- });
234
- }
235
-
236
- /**
237
- * Encode string to html special char and add html space padding defined
238
- * @param {string} inputStr - input string
239
- * @param {number} paddingLength - padding to add
240
- */
241
- export function htmlEncodeWithPadding(inputStr: string, paddingLength: number): string {
242
- const inputStrLn = inputStr.length;
243
- let outputStr = htmlEncode(inputStr);
244
-
245
- if (inputStrLn < paddingLength) {
246
- for (let i = inputStrLn; i < paddingLength; i++) {
247
- outputStr += `&nbsp;`;
248
- }
249
- }
250
- return outputStr;
251
- }
252
-
253
- /** insert an HTML Element after a target Element in the DOM */
254
- export function insertAfterElement(referenceNode: HTMLElement, newNode: HTMLElement) {
255
- referenceNode.parentNode?.insertBefore(newNode, referenceNode.nextSibling);
256
- }
257
-
258
- /**
259
- * Get the Window Scroll top/left Position
260
- * @returns
261
- */
262
- export function windowScrollPosition(): { left: number; top: number; } {
263
- return {
264
- left: window.pageXOffset || document.documentElement.scrollLeft || 0,
265
- top: window.pageYOffset || document.documentElement.scrollTop || 0,
266
- };
1
+ import type { HtmlElementPosition, InferDOMType } from './models/index';
2
+
3
+ /** calculate available space for each side of the DOM element */
4
+ export function calculateAvailableSpace(element: HTMLElement): { top: number; bottom: number; left: number; right: number; } {
5
+ let bottom = 0;
6
+ let top = 0;
7
+ let left = 0;
8
+ let right = 0;
9
+
10
+ const windowHeight = window.innerHeight ?? 0;
11
+ const windowWidth = window.innerWidth ?? 0;
12
+ const scrollPosition = windowScrollPosition();
13
+ const pageScrollTop = scrollPosition.top;
14
+ const pageScrollLeft = scrollPosition.left;
15
+ const elmOffset = getOffset(element);
16
+
17
+ if (elmOffset) {
18
+ const elementOffsetTop = elmOffset.top ?? 0;
19
+ const elementOffsetLeft = elmOffset.left ?? 0;
20
+ top = elementOffsetTop - pageScrollTop;
21
+ left = elementOffsetLeft - pageScrollLeft;
22
+ bottom = windowHeight - (elementOffsetTop - pageScrollTop + element.clientHeight);
23
+ right = windowWidth - (elementOffsetLeft - pageScrollLeft + element.clientWidth);
24
+ }
25
+
26
+ return { top, bottom, left, right };
27
+ }
28
+
29
+ /**
30
+ * Create a DOM Element with any optional attributes or properties.
31
+ * It will only accept valid DOM element properties that `createElement` would accept.
32
+ * For example: `createDomElement('div', { className: 'my-css-class' })`,
33
+ * for style or dataset you need to use nested object `{ style: { display: 'none' }}
34
+ * The last argument is to optionally append the created element to a parent container element.
35
+ * @param {String} tagName - html tag
36
+ * @param {Object} options - element properties
37
+ * @param {[Element]} appendToParent - parent element to append to
38
+ */
39
+ export function createDomElement<T extends keyof HTMLElementTagNameMap, K extends keyof HTMLElementTagNameMap[T]>(
40
+ tagName: T,
41
+ elementOptions?: null | { [P in K]: InferDOMType<HTMLElementTagNameMap[T][P]> },
42
+ appendToParent?: Element
43
+ ): HTMLElementTagNameMap[T] {
44
+ const elm = document.createElement<T>(tagName);
45
+
46
+ if (elementOptions) {
47
+ Object.keys(elementOptions).forEach((elmOptionKey) => {
48
+ if (elmOptionKey === 'innerHTML') {
49
+ console.warn(`[Slickgrid-Universal] For better CSP (Content Security Policy) support, do not use "innerHTML" directly in "createDomElement('${tagName}', { innerHTML: 'some html'})", ` +
50
+ `it is better as separate assignment: "const elm = createDomElement('span'); elm.innerHTML = 'some html';"`);
51
+ }
52
+ const elmValue = elementOptions[elmOptionKey as keyof typeof elementOptions];
53
+ if (typeof elmValue === 'object') {
54
+ Object.assign(elm[elmOptionKey as K] as object, elmValue);
55
+ } else {
56
+ elm[elmOptionKey as K] = (elementOptions as any)[elmOptionKey as keyof typeof elementOptions];
57
+ }
58
+ });
59
+ }
60
+ if (appendToParent?.appendChild) {
61
+ appendToParent.appendChild(elm);
62
+ }
63
+ return elm;
64
+ }
65
+
66
+ /**
67
+ * Accepts string containing the class or space-separated list of classes, and
68
+ * returns list of individual classes.
69
+ * Method properly takes into account extra whitespaces in the `className`
70
+ * e.g.: " class1 class2 " => will result in `['class1', 'class2']`.
71
+ * @param {String} className - space separated list of class names
72
+ */
73
+ export function classNameToList(className = ''): string[] {
74
+ return className.split(' ').filter(cls => cls); // filter will remove whitespace entries
75
+ }
76
+
77
+ /**
78
+ * Loop through all properties of an object and nullify any properties that are instanceof HTMLElement,
79
+ * if we detect an array then use recursion to go inside it and apply same logic
80
+ * @param obj - object containing 1 or more properties with DOM Elements
81
+ */
82
+ export function destroyAllElementProps(obj: any) {
83
+ if (typeof obj === 'object') {
84
+ Object.keys(obj).forEach(key => {
85
+ if (Array.isArray(obj[key])) {
86
+ destroyAllElementProps(obj[key]);
87
+ }
88
+ if (obj[key] instanceof HTMLElement) {
89
+ obj[key] = null;
90
+ }
91
+ });
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Empty a DOM element by removing all of its DOM element children leaving with an empty element (basically an empty shell)
97
+ * @return {object} element - updated element
98
+ */
99
+ export function emptyElement<T extends Element = Element>(element?: T | null): T | undefined | null {
100
+ while (element?.firstChild) {
101
+ element.removeChild(element.firstChild);
102
+ }
103
+ return element;
104
+ }
105
+
106
+ /**
107
+ * From any input provided, return the HTML string (when a string is provided, it will be returned "as is" but when it's a number it will be converted to string)
108
+ * When detecting HTMLElement/DocumentFragment, we can also specify which HTML type to retrieve innerHTML or outerHTML.
109
+ * We can get the HTML by looping through all fragment `childNodes`
110
+ * @param {DocumentFragment | HTMLElement | string | number} input
111
+ * @param {'innerHTML' | 'outerHTML'} [type] - when the input is a DocumentFragment or HTMLElement, which type of HTML do you want to return? 'innerHTML' or 'outerHTML'
112
+ * @returns {String}
113
+ */
114
+ export function getHtmlStringOutput(input: DocumentFragment | HTMLElement | string | number, type: 'innerHTML' | 'outerHTML' = 'innerHTML'): string {
115
+ if (input instanceof DocumentFragment) {
116
+ // a DocumentFragment doesn't have innerHTML/outerHTML, but we can loop through all children and concatenate them all to an HTML string
117
+ return [].map.call(input.childNodes, (x: HTMLElement) => x[type]).join('') || input.textContent || '';
118
+ } else if (input instanceof HTMLElement) {
119
+ return input[type];
120
+ }
121
+ return String(input ?? ''); // reaching this line means it's already a string (or number) so just return it as string
122
+ }
123
+
124
+ /** Get offset of HTML element relative to a parent element */
125
+ export function getOffsetRelativeToParent(parentElm: HTMLElement | null, childElm: HTMLElement | null) {
126
+ if (!parentElm || !childElm) {
127
+ return undefined;
128
+ }
129
+ const parentPos = parentElm.getBoundingClientRect();
130
+ const childPos = childElm.getBoundingClientRect();
131
+ return {
132
+ top: childPos.top - parentPos.top,
133
+ right: childPos.right - parentPos.right,
134
+ bottom: childPos.bottom - parentPos.bottom,
135
+ left: childPos.left - parentPos.left,
136
+ };
137
+ }
138
+
139
+ /** Get HTML element offset with pure JS */
140
+ export function getOffset(elm?: HTMLElement | null): HtmlElementPosition | undefined {
141
+ if (!elm || !elm.getBoundingClientRect) {
142
+ return undefined;
143
+ }
144
+ const box = elm.getBoundingClientRect();
145
+ const docElem = document.documentElement;
146
+
147
+ let top = 0;
148
+ let left = 0;
149
+ let bottom = 0;
150
+ let right = 0;
151
+
152
+ if (box?.top !== undefined && box.left !== undefined) {
153
+ top = box.top + window.pageYOffset - docElem.clientTop;
154
+ left = box.left + window.pageXOffset - docElem.clientLeft;
155
+ right = box.right;
156
+ bottom = box.bottom;
157
+ }
158
+ return { top, left, bottom, right };
159
+ }
160
+
161
+ export function getInnerSize(elm: HTMLElement, type: 'height' | 'width') {
162
+ let size = 0;
163
+
164
+ if (elm) {
165
+ const clientSize = type === 'height' ? 'clientHeight' : 'clientWidth';
166
+ const sides = type === 'height' ? ['top', 'bottom'] : ['left', 'right'];
167
+ size = elm[clientSize];
168
+ for (const side of sides) {
169
+ const sideSize = (parseFloat(getStyleProp(elm, `padding-${side}`) || '') || 0);
170
+ size -= sideSize;
171
+ }
172
+ }
173
+ return size;
174
+ }
175
+
176
+ /** Get a DOM element style property value by calling getComputedStyle() on the element */
177
+ export function getStyleProp(elm: HTMLElement, property: string) {
178
+ if (elm) {
179
+ return window.getComputedStyle(elm).getPropertyValue(property);
180
+ }
181
+ return null;
182
+ }
183
+
184
+ export function findFirstAttribute(inputElm: Element | null | undefined, attributes: string[]): string | null {
185
+ if (inputElm) {
186
+ for (const attribute of attributes) {
187
+ const attrData = inputElm.getAttribute(attribute);
188
+ if (attrData) {
189
+ return attrData;
190
+ }
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+
196
+ /**
197
+ * Provide a width as a number or a string and find associated value in valid css style format or use default value when provided (or "auto" otherwise).
198
+ * @param {Number|String} inputWidth - input width, could be a string or number
199
+ * @param {Number | String} defaultValue [defaultValue=auto] - optional default value or use "auto" when nothing is provided
200
+ * @returns {String} string output
201
+ */
202
+ export function findWidthOrDefault(inputWidth?: number | string | null, defaultValue = 'auto'): string {
203
+ return (/^[0-9]+$/i.test(`${inputWidth}`) ? `${+(inputWidth as number)}px` : inputWidth as string) || defaultValue;
204
+ }
205
+
206
+ /**
207
+ * HTML encode using a plain <div>
208
+ * Create a in-memory div, set it's inner text(which a div can encode)
209
+ * then grab the encoded contents back out. The div never exists on the page.
210
+ * @param {String} inputValue - input value to be encoded
211
+ * @return {String}
212
+ */
213
+ export function htmlEncode(inputValue: string): string {
214
+ const val = typeof inputValue === 'string' ? inputValue : String(inputValue);
215
+ const entityMap: { [char: string]: string; } = {
216
+ '&': '&amp;',
217
+ '<': '&lt;',
218
+ '>': '&gt;',
219
+ '"': '&quot;',
220
+ '\'': '&#39;',
221
+ };
222
+ return (val || '').toString().replace(/[&<>"']/g, (s) => entityMap[s as keyof { [char: string]: string; }]);
223
+ }
224
+
225
+ /**
226
+ * Decode text into html entity
227
+ * @param string text: input text
228
+ * @param string text: output text
229
+ */
230
+ export function htmlEntityDecode(input: string): string {
231
+ return input.replace(/&#(\d+);/g, (_match, dec) => {
232
+ return String.fromCharCode(dec);
233
+ });
234
+ }
235
+
236
+ /**
237
+ * Encode string to html special char and add html space padding defined
238
+ * @param {string} inputStr - input string
239
+ * @param {number} paddingLength - padding to add
240
+ */
241
+ export function htmlEncodeWithPadding(inputStr: string, paddingLength: number): string {
242
+ const inputStrLn = inputStr.length;
243
+ let outputStr = htmlEncode(inputStr);
244
+
245
+ if (inputStrLn < paddingLength) {
246
+ for (let i = inputStrLn; i < paddingLength; i++) {
247
+ outputStr += `&nbsp;`;
248
+ }
249
+ }
250
+ return outputStr;
251
+ }
252
+
253
+ /** insert an HTML Element after a target Element in the DOM */
254
+ export function insertAfterElement(referenceNode: HTMLElement, newNode: HTMLElement) {
255
+ referenceNode.parentNode?.insertBefore(newNode, referenceNode.nextSibling);
256
+ }
257
+
258
+ /**
259
+ * Get the Window Scroll top/left Position
260
+ * @returns
261
+ */
262
+ export function windowScrollPosition(): { left: number; top: number; } {
263
+ return {
264
+ left: window.pageXOffset || document.documentElement.scrollLeft || 0,
265
+ top: window.pageYOffset || document.documentElement.scrollTop || 0,
266
+ };
267
267
  }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- export * from './domUtils';
2
- export * from './nodeExtend';
3
- export * from './stripTagsUtil';
4
- export * from './models';
1
+ export * from './domUtils';
2
+ export * from './nodeExtend';
3
+ export * from './stripTagsUtil';
4
+ export * from './models';
5
5
  export * from './utils';
@@ -1,2 +1,2 @@
1
- export * from './interfaces';
2
- export * from './types';
1
+ export * from './interfaces';
2
+ export * from './types';
@@ -1,6 +1,6 @@
1
- export interface HtmlElementPosition {
2
- top: number;
3
- bottom: number;
4
- left: number;
5
- right: number;
6
- }
1
+ export interface HtmlElementPosition {
2
+ top: number;
3
+ bottom: number;
4
+ left: number;
5
+ right: number;
6
+ }
@@ -1,6 +1,6 @@
1
-
2
- export type InferDOMType<T> =
3
- T extends CSSStyleDeclaration ? Partial<CSSStyleDeclaration> :
4
- T extends infer R ? R : any;
5
-
1
+
2
+ export type InferDOMType<T> =
3
+ T extends CSSStyleDeclaration ? Partial<CSSStyleDeclaration> :
4
+ T extends infer R ? R : any;
5
+
6
6
  export type AnyFunction = (...args: any[]) => any;