@ocavue/utils 0.6.0 → 0.8.1

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/dist/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
+ //#region src/checker.d.ts
1
2
  /**
2
3
  * Checks if the given value is an object.
3
4
  */
4
5
  declare function isObject(value: unknown): value is Record<string | symbol | number, unknown>;
5
-
6
+ //#endregion
7
+ //#region src/dom.d.ts
6
8
  /**
7
9
  * Checks if the given DOM node is an Element.
8
10
  */
@@ -61,7 +63,22 @@ declare function getDocument(target?: Element | Window | Node | Document | null)
61
63
  * Gets a reference to the root node of the document based on the given target.
62
64
  */
63
65
  declare function getDocumentElement(target?: Element | Node | Window | Document | null): HTMLElement;
64
-
66
+ //#endregion
67
+ //#region src/format-bytes.d.ts
68
+ /**
69
+ * Formats a number of bytes into a human-readable string.
70
+ * @param bytes - The number of bytes to format.
71
+ * @returns A string representing the number of bytes in a human-readable format.
72
+ */
73
+ declare function formatBytes(bytes: number): string;
74
+ //#endregion
75
+ //#region src/get-id.d.ts
76
+ /**
77
+ * Generates a unique positive integer.
78
+ */
79
+ declare function getId(): number;
80
+ //#endregion
81
+ //#region src/once.d.ts
65
82
  /**
66
83
  * Creates a function that will only execute the provided function once.
67
84
  * Subsequent calls will return the cached result from the first execution.
@@ -76,12 +93,12 @@ declare function getDocumentElement(target?: Element | Node | Window | Document
76
93
  * ```
77
94
  */
78
95
  declare function once<T>(fn: () => T): () => T;
79
-
96
+ //#endregion
97
+ //#region src/sleep.d.ts
80
98
  /**
81
- * Formats a number of bytes into a human-readable string.
82
- * @param bytes - The number of bytes to format.
83
- * @returns A string representing the number of bytes in a human-readable format.
99
+ * Sleep for a given number of milliseconds.
84
100
  */
85
- declare function formatBytes(bytes: number): string;
86
-
87
- export { formatBytes, getDocument, getDocumentElement, getWindow, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMathMLElement, isNodeLike, isObject, isSVGElement, isShadowRoot, isTextNode, isWindowLike, once };
101
+ declare function sleep(ms: number): Promise<void>;
102
+ //#endregion
103
+ export { formatBytes, getDocument, getDocumentElement, getId, getWindow, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMathMLElement, isNodeLike, isObject, isSVGElement, isShadowRoot, isTextNode, isWindowLike, once, sleep };
104
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/checker.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/once.ts","../src/sleep.ts"],"sourcesContent":[],"mappings":";;AAGA;;iBAAgB,QAAA,2BAEJ;;;;AAFZ;;iBCGgB,SAAA,OAAgB,eAAe;;AAA/C;AAOA;AAOgB,iBAPA,UAAA,CAOoB,IAAe,EAPlB,IAOkB,CAAA,EAAA,IAAW,IAPd,IAOc;AAO9D;AAOA;AAUA;AAOgB,iBA/BA,aAAA,CA+ByB,IAAe,EA/BpB,IA+BoB,CAAA,EAAA,IAAA,IA/BL,WA+BqB;AAOxE;AAOA;AAOA;AAWgB,iBAxDA,YAAA,CAwDuC,IAAM,EAxD1B,IAwD0B,CAAA,EAAA,IAAA,IAxDX,UAwDW;AAQ7D;;;AAC+B,iBA1Df,eAAA,CA0De,IAAA,EA1DO,IA0DP,CAAA,EAAA,IAAA,IA1DsB,aA0DtB;;;;AAoBf,iBApEA,UAAA,CAoEW,IAAA,EApEM,IAoEN,CAAA,EAAA,IAAA,IApEqB,QAoErB;;;;AACU,iBA9DrB,kBAAA,CA8DqB,IAAA,EA9DI,IA8DJ,CAAA,EAAA,IAAA,IA9DmB,gBA8DnB;;;AAiBrC;AACW,iBAzEK,YAAA,CAyEL,IAAA,EAzEwB,IAyExB,CAAA,EAAA,IAAA,IAzEuC,UAyEvC;;;;AACR,iBAnEa,UAAA,CAmEb,KAAA,EAAA,OAAA,CAAA,EAAA,KAAA,IAnEkD,IAmElD;;;;iBA5Da,aAAA,2BAAwC;ACnExD;;;iBD8EgB,YAAA,2BAAuC;AE9EvD;;;;ACQgB,iBH8EA,SAAA,CG9E6B,MAAA,CAAA,EH+ElC,IG/EkC,GH+E3B,UG/E2B,GH+Ed,QG/Ec,GAAA,IAAA,CAAA,EHgF1C,MGhF0C,GAAA,OHgF1B,UGhF0B;;;;ACV7C;iBJ6GgB,WAAA,UACL,UAAU,SAAS,OAAO,kBAClC;;;;iBAgBa,kBAAA,UACL,UAAU,OAAO,SAAS,kBAClC;;;;ADjIH;;;;ACGgB,iBCDA,WAAA,CDC+B,KAAO,EAAA,MAAA,CAAA,EAAA,MAAA;;;;ADHtD;;iBGEgB,KAAA,CAAA;;;;AHFhB;;;;ACGA;AAOA;AAOA;AAOA;AAOA;AAUA;AAOA;AAOA;AAOgB,iBGpDA,IHoDU,CAA2B,CAAA,CAAA,CAAA,EAAI,EAAA,GAAA,GGpDvB,CHoDuB,CAAA,EAAA,GAAA,GGpDb,CHoDa;;;;AD9DzD;;iBKAgB,KAAA,cAAmB"}
package/dist/index.js CHANGED
@@ -1,114 +1,189 @@
1
- // src/checker.ts
1
+ //#region src/checker.ts
2
+ /**
3
+ * Checks if the given value is an object.
4
+ */
2
5
  function isObject(value) {
3
- return value != null && typeof value === "object";
6
+ return value != null && typeof value === "object";
4
7
  }
5
8
 
6
- // src/dom.ts
9
+ //#endregion
10
+ //#region src/dom-node-type.ts
11
+ const ELEMENT_NODE = 1;
12
+ const TEXT_NODE = 3;
13
+ const DOCUMENT_NODE = 9;
14
+ const DOCUMENT_FRAGMENT_NODE = 11;
15
+
16
+ //#endregion
17
+ //#region src/dom.ts
18
+ /**
19
+ * Checks if the given DOM node is an Element.
20
+ */
7
21
  function isElement(node) {
8
- return node.nodeType === 1 /* ELEMENT_NODE */;
22
+ return node.nodeType === ELEMENT_NODE;
9
23
  }
24
+ /**
25
+ * Checks if the given DOM node is a Text node.
26
+ */
10
27
  function isTextNode(node) {
11
- return node.nodeType === 3 /* TEXT_NODE */;
28
+ return node.nodeType === TEXT_NODE;
12
29
  }
30
+ /**
31
+ * Checks if the given DOM node is an HTMLElement.
32
+ */
13
33
  function isHTMLElement(node) {
14
- return isElement(node) && node.namespaceURI === "http://www.w3.org/1999/xhtml";
34
+ return isElement(node) && node.namespaceURI === "http://www.w3.org/1999/xhtml";
15
35
  }
36
+ /**
37
+ * Checks if the given DOM node is an SVGElement.
38
+ */
16
39
  function isSVGElement(node) {
17
- return isElement(node) && node.namespaceURI === "http://www.w3.org/2000/svg";
40
+ return isElement(node) && node.namespaceURI === "http://www.w3.org/2000/svg";
18
41
  }
42
+ /**
43
+ * Checks if the given DOM node is an MathMLElement.
44
+ */
19
45
  function isMathMLElement(node) {
20
- return isElement(node) && node.namespaceURI === "http://www.w3.org/1998/Math/MathML";
46
+ return isElement(node) && node.namespaceURI === "http://www.w3.org/1998/Math/MathML";
21
47
  }
48
+ /**
49
+ * Checks if the given DOM node is a Document.
50
+ */
22
51
  function isDocument(node) {
23
- return node.nodeType === 9 /* DOCUMENT_NODE */;
52
+ return node.nodeType === DOCUMENT_NODE;
24
53
  }
54
+ /**
55
+ * Checks if the given DOM node is a DocumentFragment.
56
+ */
25
57
  function isDocumentFragment(node) {
26
- return node.nodeType === 11 /* DOCUMENT_FRAGMENT_NODE */;
58
+ return node.nodeType === DOCUMENT_FRAGMENT_NODE;
27
59
  }
60
+ /**
61
+ * Checks if the given DOM node is a ShadowRoot.
62
+ */
28
63
  function isShadowRoot(node) {
29
- return isDocumentFragment(node) && "host" in node && isElementLike(node.host);
64
+ return isDocumentFragment(node) && "host" in node && isElementLike(node.host);
30
65
  }
66
+ /**
67
+ * Checks if an unknown value is likely a DOM node.
68
+ */
31
69
  function isNodeLike(value) {
32
- return isObject(value) && value.nodeType !== void 0;
70
+ return isObject(value) && value.nodeType !== void 0;
33
71
  }
72
+ /**
73
+ * Checks if an unknown value is likely a DOM element.
74
+ */
34
75
  function isElementLike(value) {
35
- return isObject(value) && value.nodeType === 1 /* ELEMENT_NODE */ && typeof value.nodeName === "string";
76
+ return isObject(value) && value.nodeType === ELEMENT_NODE && typeof value.nodeName === "string";
36
77
  }
78
+ /**
79
+ * Checks if the given value is likely a Window object.
80
+ */
37
81
  function isWindowLike(value) {
38
- return isObject(value) && value.window === value;
82
+ return isObject(value) && value.window === value;
39
83
  }
84
+ /**
85
+ * Gets the window object for the given target or the global window object if no
86
+ * target is provided.
87
+ */
40
88
  function getWindow(target) {
41
- if (target) {
42
- if (isShadowRoot(target)) {
43
- return getWindow(target.host);
44
- }
45
- if (isDocument(target)) {
46
- return target.defaultView || window;
47
- }
48
- if (isElement(target)) {
49
- return target.ownerDocument?.defaultView || window;
50
- }
51
- }
52
- return window;
53
- }
89
+ if (target) {
90
+ if (isShadowRoot(target)) return getWindow(target.host);
91
+ if (isDocument(target)) return target.defaultView || window;
92
+ if (isElement(target)) return target.ownerDocument?.defaultView || window;
93
+ }
94
+ return window;
95
+ }
96
+ /**
97
+ * Gets the document for the given target or the global document if no target is
98
+ * provided.
99
+ */
54
100
  function getDocument(target) {
55
- if (target) {
56
- if (isWindowLike(target)) {
57
- return target.document;
58
- }
59
- if (isDocument(target)) {
60
- return target;
61
- }
62
- return target.ownerDocument || document;
63
- }
64
- return document;
65
- }
101
+ if (target) {
102
+ if (isWindowLike(target)) return target.document;
103
+ if (isDocument(target)) return target;
104
+ return target.ownerDocument || document;
105
+ }
106
+ return document;
107
+ }
108
+ /**
109
+ * Gets a reference to the root node of the document based on the given target.
110
+ */
66
111
  function getDocumentElement(target) {
67
- return getDocument(target).documentElement;
112
+ return getDocument(target).documentElement;
113
+ }
114
+
115
+ //#endregion
116
+ //#region src/format-bytes.ts
117
+ /**
118
+ * Formats a number of bytes into a human-readable string.
119
+ * @param bytes - The number of bytes to format.
120
+ * @returns A string representing the number of bytes in a human-readable format.
121
+ */
122
+ function formatBytes(bytes) {
123
+ const units = [
124
+ "B",
125
+ "KB",
126
+ "MB",
127
+ "GB"
128
+ ];
129
+ let unitIndex = 0;
130
+ let num = bytes;
131
+ while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {
132
+ num /= 1024;
133
+ unitIndex++;
134
+ }
135
+ const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1;
136
+ return `${num.toFixed(fraction)}${units[unitIndex]}`;
68
137
  }
69
138
 
70
- // src/once.ts
139
+ //#endregion
140
+ //#region src/get-id.ts
141
+ let id = 0;
142
+ /**
143
+ * Generates a unique positive integer.
144
+ */
145
+ function getId() {
146
+ id = id % Number.MAX_SAFE_INTEGER + 1;
147
+ return id;
148
+ }
149
+
150
+ //#endregion
151
+ //#region src/once.ts
152
+ /**
153
+ * Creates a function that will only execute the provided function once.
154
+ * Subsequent calls will return the cached result from the first execution.
155
+ *
156
+ * @param fn The function to execute once
157
+ * @returns A function that will only execute the provided function once
158
+ * @example
159
+ * ```ts
160
+ * const getValue = once(() => expensiveOperation())
161
+ * getValue() // executes expensiveOperation
162
+ * getValue() // returns cached result
163
+ * ```
164
+ */
71
165
  function once(fn) {
72
- let called = false;
73
- let result;
74
- return () => {
75
- if (!called) {
76
- result = fn();
77
- called = true;
78
- fn = void 0;
79
- }
80
- return result;
81
- };
166
+ let called = false;
167
+ let result;
168
+ return () => {
169
+ if (!called) {
170
+ result = fn();
171
+ called = true;
172
+ fn = void 0;
173
+ }
174
+ return result;
175
+ };
82
176
  }
83
177
 
84
- // src/format-bytes.ts
85
- function formatBytes(bytes) {
86
- const units = ["B", "KB", "MB", "GB"];
87
- let unitIndex = 0;
88
- let num = bytes;
89
- while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {
90
- num /= 1024;
91
- unitIndex++;
92
- }
93
- const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1;
94
- return `${num.toFixed(fraction)}${units[unitIndex]}`;
95
- }
96
- export {
97
- formatBytes,
98
- getDocument,
99
- getDocumentElement,
100
- getWindow,
101
- isDocument,
102
- isDocumentFragment,
103
- isElement,
104
- isElementLike,
105
- isHTMLElement,
106
- isMathMLElement,
107
- isNodeLike,
108
- isObject,
109
- isSVGElement,
110
- isShadowRoot,
111
- isTextNode,
112
- isWindowLike,
113
- once
114
- };
178
+ //#endregion
179
+ //#region src/sleep.ts
180
+ /**
181
+ * Sleep for a given number of milliseconds.
182
+ */
183
+ function sleep(ms) {
184
+ return new Promise((resolve) => setTimeout(resolve, ms));
185
+ }
186
+
187
+ //#endregion
188
+ export { formatBytes, getDocument, getDocumentElement, getId, getWindow, isDocument, isDocumentFragment, isElement, isElementLike, isHTMLElement, isMathMLElement, isNodeLike, isObject, isSVGElement, isShadowRoot, isTextNode, isWindowLike, once, sleep };
189
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["NodeType.ELEMENT_NODE","NodeType.TEXT_NODE","NodeType.DOCUMENT_NODE","NodeType.DOCUMENT_FRAGMENT_NODE","result: T"],"sources":["../src/checker.ts","../src/dom-node-type.ts","../src/dom.ts","../src/format-bytes.ts","../src/get-id.ts","../src/once.ts","../src/sleep.ts"],"sourcesContent":["/**\n * Checks if the given value is an object.\n */\nexport function isObject(\n value: unknown,\n): value is Record<string | symbol | number, unknown> {\n return value != null && typeof value === 'object'\n}\n","// prettier-ignore\nexport const ELEMENT_NODE = 1 satisfies typeof Node.ELEMENT_NODE;\n// prettier-ignore\nexport const ATTRIBUTE_NODE = 2 satisfies typeof Node.ATTRIBUTE_NODE;\n// prettier-ignore\nexport const TEXT_NODE = 3 satisfies typeof Node.TEXT_NODE;\n// prettier-ignore\nexport const CDATA_SECTION_NODE = 4 satisfies typeof Node.CDATA_SECTION_NODE;\n// prettier-ignore\nexport const ENTITY_REFERENCE_NODE = 5 satisfies typeof Node.ENTITY_REFERENCE_NODE;\n// prettier-ignore\nexport const ENTITY_NODE = 6 satisfies typeof Node.ENTITY_NODE;\n// prettier-ignore\nexport const PROCESSING_INSTRUCTION_NODE = 7 satisfies typeof Node.PROCESSING_INSTRUCTION_NODE;\n// prettier-ignore\nexport const COMMENT_NODE = 8 satisfies typeof Node.COMMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_NODE = 9 satisfies typeof Node.DOCUMENT_NODE;\n// prettier-ignore\nexport const DOCUMENT_TYPE_NODE = 10 satisfies typeof Node.DOCUMENT_TYPE_NODE;\n// prettier-ignore\nexport const DOCUMENT_FRAGMENT_NODE = 11 satisfies typeof Node.DOCUMENT_FRAGMENT_NODE;\n// prettier-ignore\nexport const NOTATION_NODE = 12 satisfies typeof Node.NOTATION_NODE;\n","import { isObject } from './checker'\nimport * as NodeType from './dom-node-type'\n\n/**\n * Checks if the given DOM node is an Element.\n */\nexport function isElement(node: Node): node is Element {\n return node.nodeType === NodeType.ELEMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a Text node.\n */\nexport function isTextNode(node: Node): node is Text {\n return node.nodeType === NodeType.TEXT_NODE\n}\n\n/**\n * Checks if the given DOM node is an HTMLElement.\n */\nexport function isHTMLElement(node: Node): node is HTMLElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/1999/xhtml'\n}\n\n/**\n * Checks if the given DOM node is an SVGElement.\n */\nexport function isSVGElement(node: Node): node is SVGElement {\n return isElement(node) && node.namespaceURI === 'http://www.w3.org/2000/svg'\n}\n\n/**\n * Checks if the given DOM node is an MathMLElement.\n */\nexport function isMathMLElement(node: Node): node is MathMLElement {\n return (\n isElement(node) &&\n node.namespaceURI === 'http://www.w3.org/1998/Math/MathML'\n )\n}\n\n/**\n * Checks if the given DOM node is a Document.\n */\nexport function isDocument(node: Node): node is Document {\n return node.nodeType === NodeType.DOCUMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a DocumentFragment.\n */\nexport function isDocumentFragment(node: Node): node is DocumentFragment {\n return node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE\n}\n\n/**\n * Checks if the given DOM node is a ShadowRoot.\n */\nexport function isShadowRoot(node: Node): node is ShadowRoot {\n return isDocumentFragment(node) && 'host' in node && isElementLike(node.host)\n}\n\n/**\n * Checks if an unknown value is likely a DOM node.\n */\nexport function isNodeLike(value: unknown): value is Node {\n return isObject(value) && value.nodeType !== undefined\n}\n\n/**\n * Checks if an unknown value is likely a DOM element.\n */\nexport function isElementLike(value: unknown): value is Element {\n return (\n isObject(value) &&\n value.nodeType === NodeType.ELEMENT_NODE &&\n typeof value.nodeName === 'string'\n )\n}\n\n/**\n * Checks if the given value is likely a Window object.\n */\nexport function isWindowLike(value: unknown): value is Window {\n return isObject(value) && value.window === value\n}\n\n/**\n * Gets the window object for the given target or the global window object if no\n * target is provided.\n */\nexport function getWindow(\n target?: Node | ShadowRoot | Document | null,\n): Window & typeof globalThis {\n if (target) {\n if (isShadowRoot(target)) {\n return getWindow(target.host)\n }\n if (isDocument(target)) {\n return target.defaultView || window\n }\n if (isElement(target)) {\n return target.ownerDocument?.defaultView || window\n }\n }\n return window\n}\n\n/**\n * Gets the document for the given target or the global document if no target is\n * provided.\n */\nexport function getDocument(\n target?: Element | Window | Node | Document | null,\n): Document {\n if (target) {\n if (isWindowLike(target)) {\n return target.document\n }\n if (isDocument(target)) {\n return target\n }\n return target.ownerDocument || document\n }\n return document\n}\n\n/**\n * Gets a reference to the root node of the document based on the given target.\n */\nexport function getDocumentElement(\n target?: Element | Node | Window | Document | null,\n): HTMLElement {\n return getDocument(target).documentElement\n}\n","/**\n * Formats a number of bytes into a human-readable string.\n * @param bytes - The number of bytes to format.\n * @returns A string representing the number of bytes in a human-readable format.\n */\nexport function formatBytes(bytes: number): string {\n const units = ['B', 'KB', 'MB', 'GB']\n let unitIndex = 0\n let num = bytes\n while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {\n num /= 1024\n unitIndex++\n }\n const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1\n return `${num.toFixed(fraction)}${units[unitIndex]}`\n}\n","let id = 0\n\n/**\n * Generates a unique positive integer.\n */\nexport function getId(): number {\n id = (id % Number.MAX_SAFE_INTEGER) + 1\n return id\n}\n","/**\n * Creates a function that will only execute the provided function once.\n * Subsequent calls will return the cached result from the first execution.\n *\n * @param fn The function to execute once\n * @returns A function that will only execute the provided function once\n * @example\n * ```ts\n * const getValue = once(() => expensiveOperation())\n * getValue() // executes expensiveOperation\n * getValue() // returns cached result\n * ```\n */\nexport function once<T>(fn: () => T): () => T {\n let called = false\n let result: T\n return () => {\n if (!called) {\n result = fn()\n called = true\n // @ts-expect-error - micro-optimization for memory management\n fn = undefined\n }\n return result\n }\n}\n","/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"],"mappings":";;;;AAGA,SAAgB,SACd,OACoD;AACpD,QAAO,SAAS,QAAQ,OAAO,UAAU;;;;;ACL3C,MAAa,eAAe;AAI5B,MAAa,YAAY;AAYzB,MAAa,gBAAgB;AAI7B,MAAa,yBAAyB;;;;;;;ACftC,SAAgB,UAAU,MAA6B;AACrD,QAAO,KAAK,aAAaA;;;;;AAM3B,SAAgB,WAAW,MAA0B;AACnD,QAAO,KAAK,aAAaC;;;;;AAM3B,SAAgB,cAAc,MAAiC;AAC7D,QAAO,UAAU,KAAK,IAAI,KAAK,iBAAiB;;;;;AAMlD,SAAgB,aAAa,MAAgC;AAC3D,QAAO,UAAU,KAAK,IAAI,KAAK,iBAAiB;;;;;AAMlD,SAAgB,gBAAgB,MAAmC;AACjE,QACE,UAAU,KAAK,IACf,KAAK,iBAAiB;;;;;AAO1B,SAAgB,WAAW,MAA8B;AACvD,QAAO,KAAK,aAAaC;;;;;AAM3B,SAAgB,mBAAmB,MAAsC;AACvE,QAAO,KAAK,aAAaC;;;;;AAM3B,SAAgB,aAAa,MAAgC;AAC3D,QAAO,mBAAmB,KAAK,IAAI,UAAU,QAAQ,cAAc,KAAK,KAAK;;;;;AAM/E,SAAgB,WAAW,OAA+B;AACxD,QAAO,SAAS,MAAM,IAAI,MAAM,aAAa;;;;;AAM/C,SAAgB,cAAc,OAAkC;AAC9D,QACE,SAAS,MAAM,IACf,MAAM,aAAaH,gBACnB,OAAO,MAAM,aAAa;;;;;AAO9B,SAAgB,aAAa,OAAiC;AAC5D,QAAO,SAAS,MAAM,IAAI,MAAM,WAAW;;;;;;AAO7C,SAAgB,UACd,QAC4B;AAC5B,KAAI,QAAQ;AACV,MAAI,aAAa,OAAO,CACtB,QAAO,UAAU,OAAO,KAAK;AAE/B,MAAI,WAAW,OAAO,CACpB,QAAO,OAAO,eAAe;AAE/B,MAAI,UAAU,OAAO,CACnB,QAAO,OAAO,eAAe,eAAe;;AAGhD,QAAO;;;;;;AAOT,SAAgB,YACd,QACU;AACV,KAAI,QAAQ;AACV,MAAI,aAAa,OAAO,CACtB,QAAO,OAAO;AAEhB,MAAI,WAAW,OAAO,CACpB,QAAO;AAET,SAAO,OAAO,iBAAiB;;AAEjC,QAAO;;;;;AAMT,SAAgB,mBACd,QACa;AACb,QAAO,YAAY,OAAO,CAAC;;;;;;;;;;AChI7B,SAAgB,YAAY,OAAuB;CACjD,MAAM,QAAQ;EAAC;EAAK;EAAM;EAAM;EAAK;CACrC,IAAI,YAAY;CAChB,IAAI,MAAM;AACV,QAAO,KAAK,IAAI,IAAI,IAAI,QAAQ,YAAY,MAAM,SAAS,GAAG;AAC5D,SAAO;AACP;;CAEF,MAAM,WAAW,cAAc,KAAK,MAAM,MAAM,IAAI,IAAI;AACxD,QAAO,GAAG,IAAI,QAAQ,SAAS,GAAG,MAAM;;;;;ACd1C,IAAI,KAAK;;;;AAKT,SAAgB,QAAgB;AAC9B,MAAM,KAAK,OAAO,mBAAoB;AACtC,QAAO;;;;;;;;;;;;;;;;;;ACMT,SAAgB,KAAQ,IAAsB;CAC5C,IAAI,SAAS;CACb,IAAII;AACJ,cAAa;AACX,MAAI,CAAC,QAAQ;AACX,YAAS,IAAI;AACb,YAAS;AAET,QAAK;;AAEP,SAAO;;;;;;;;;ACpBX,SAAgB,MAAM,IAA2B;AAC/C,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@ocavue/utils",
3
3
  "type": "module",
4
- "version": "0.6.0",
5
- "description": "",
4
+ "version": "0.8.1",
5
+ "description": "A collection of utility functions for the browser and other environments",
6
6
  "author": "ocavue <ocavue@gmail.com>",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/ocavue",
@@ -33,21 +33,23 @@
33
33
  }
34
34
  },
35
35
  "files": [
36
+ "src",
36
37
  "dist"
37
38
  ],
38
39
  "devDependencies": {
39
- "@ocavue/eslint-config": "^3.2.0",
40
- "@ocavue/tsconfig": "^0.3.7",
40
+ "@ocavue/eslint-config": "^3.6.1",
41
+ "@ocavue/tsconfig": "^0.5.0",
41
42
  "@size-limit/preset-small-lib": "^11.2.0",
42
- "@types/node": "^20.17.27",
43
- "eslint": "^9.31.0",
44
- "jsdom": "^26.1.0",
43
+ "@types/node": "^24.9.2",
44
+ "@vitest/coverage-v8": "4.0.4",
45
+ "eslint": "^9.38.0",
46
+ "jsdom": "^27.0.1",
45
47
  "prettier": "^3.6.2",
46
48
  "size-limit": "^11.2.0",
47
- "tsup": "^8.5.0",
48
- "typescript": "^5.8.3",
49
- "vite": "^6.3.5",
50
- "vitest": "^3.2.4"
49
+ "tsdown": "^0.15.11",
50
+ "typescript": "^5.9.3",
51
+ "vite": "^7.1.12",
52
+ "vitest": "^4.0.4"
51
53
  },
52
54
  "publishConfig": {
53
55
  "access": "public"
@@ -63,8 +65,8 @@
63
65
  ]
64
66
  },
65
67
  "scripts": {
66
- "build": "tsup",
67
- "dev": "tsup --watch",
68
+ "build": "tsdown",
69
+ "dev": "tsdown --watch",
68
70
  "lint": "eslint .",
69
71
  "fix": "eslint --fix . && prettier --write .",
70
72
  "test": "vitest",
@@ -0,0 +1,40 @@
1
+ // @vitest-environment node
2
+
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import { isObject } from './checker'
6
+
7
+ describe('isObject', () => {
8
+ it('returns true for plain objects', () => {
9
+ expect(isObject({})).toBe(true)
10
+ expect(isObject({ foo: 'bar' })).toBe(true)
11
+ })
12
+
13
+ it('returns true for arrays', () => {
14
+ expect(isObject([])).toBe(true)
15
+ expect(isObject(['foo'])).toBe(true)
16
+ })
17
+
18
+ it('returns false for null and undefined', () => {
19
+ expect(isObject(null)).toBe(false)
20
+ expect(isObject(undefined)).toBe(false)
21
+ })
22
+
23
+ it('returns false for primitives', () => {
24
+ expect(isObject(42)).toBe(false)
25
+ expect(isObject(42n)).toBe(false)
26
+ expect(isObject(Number.NaN)).toBe(false)
27
+ expect(isObject(Infinity)).toBe(false)
28
+ expect(isObject(true)).toBe(false)
29
+ expect(isObject(false)).toBe(false)
30
+ expect(isObject(Symbol.iterator)).toBe(false)
31
+ expect(isObject(Symbol.for('foo'))).toBe(false)
32
+ expect(isObject(Symbol())).toBe(false)
33
+ expect(isObject('string')).toBe(false)
34
+ })
35
+
36
+ it('returns true for class instances', () => {
37
+ class TestClass {}
38
+ expect(isObject(new TestClass())).toBe(true)
39
+ })
40
+ })
package/src/checker.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Checks if the given value is an object.
3
+ */
4
+ export function isObject(
5
+ value: unknown,
6
+ ): value is Record<string | symbol | number, unknown> {
7
+ return value != null && typeof value === 'object'
8
+ }
@@ -0,0 +1,24 @@
1
+ // prettier-ignore
2
+ export const ELEMENT_NODE = 1 satisfies typeof Node.ELEMENT_NODE;
3
+ // prettier-ignore
4
+ export const ATTRIBUTE_NODE = 2 satisfies typeof Node.ATTRIBUTE_NODE;
5
+ // prettier-ignore
6
+ export const TEXT_NODE = 3 satisfies typeof Node.TEXT_NODE;
7
+ // prettier-ignore
8
+ export const CDATA_SECTION_NODE = 4 satisfies typeof Node.CDATA_SECTION_NODE;
9
+ // prettier-ignore
10
+ export const ENTITY_REFERENCE_NODE = 5 satisfies typeof Node.ENTITY_REFERENCE_NODE;
11
+ // prettier-ignore
12
+ export const ENTITY_NODE = 6 satisfies typeof Node.ENTITY_NODE;
13
+ // prettier-ignore
14
+ export const PROCESSING_INSTRUCTION_NODE = 7 satisfies typeof Node.PROCESSING_INSTRUCTION_NODE;
15
+ // prettier-ignore
16
+ export const COMMENT_NODE = 8 satisfies typeof Node.COMMENT_NODE;
17
+ // prettier-ignore
18
+ export const DOCUMENT_NODE = 9 satisfies typeof Node.DOCUMENT_NODE;
19
+ // prettier-ignore
20
+ export const DOCUMENT_TYPE_NODE = 10 satisfies typeof Node.DOCUMENT_TYPE_NODE;
21
+ // prettier-ignore
22
+ export const DOCUMENT_FRAGMENT_NODE = 11 satisfies typeof Node.DOCUMENT_FRAGMENT_NODE;
23
+ // prettier-ignore
24
+ export const NOTATION_NODE = 12 satisfies typeof Node.NOTATION_NODE;
@@ -0,0 +1,255 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import {
6
+ getDocument,
7
+ getDocumentElement,
8
+ getWindow,
9
+ isDocument,
10
+ isDocumentFragment,
11
+ isElement,
12
+ isElementLike,
13
+ isHTMLElement,
14
+ isMathMLElement,
15
+ isNodeLike,
16
+ isShadowRoot,
17
+ isSVGElement,
18
+ isTextNode,
19
+ isWindowLike,
20
+ } from './dom'
21
+
22
+ describe('isElement', () => {
23
+ it('can be used to check if a node is an element', () => {
24
+ const div = document.createElement('div')
25
+ expect(isElement(div)).toBe(true)
26
+ })
27
+ })
28
+
29
+ describe('isTextNode', () => {
30
+ it('can be used to check if a node is a text node', () => {
31
+ const text = document.createTextNode('Hello, world!')
32
+ expect(isTextNode(text)).toBe(true)
33
+ })
34
+ })
35
+
36
+ describe('isHTMLElement', () => {
37
+ it('can be used to check if a node is an HTML element', () => {
38
+ const div = document.createElement('div')
39
+ expect(isHTMLElement(div)).toBe(true)
40
+ })
41
+ })
42
+
43
+ describe('isSVGElement', () => {
44
+ it('can be used to check if a node is an SVG element', () => {
45
+ const div = document.createElement('div')
46
+ div.innerHTML = `<svg><rect width="200" height="100" x="10" y="10" rx="20" ry="20" fill="blue" /></svg>`
47
+ expect(isSVGElement(div.firstChild!)).toBe(true)
48
+ })
49
+
50
+ it('returns false for non-SVG elements', () => {
51
+ const div = document.createElement('div')
52
+ expect(isSVGElement(div)).toBe(false)
53
+ })
54
+ })
55
+
56
+ describe('isMathMLElement', () => {
57
+ it('can be used to check if a node is a MathML element', () => {
58
+ const div = document.createElement('div')
59
+ div.innerHTML = `<math><mn>1</mn><mo>+</mo><mn>2</mn><mo>+</mo><mn>3</mn></math>`
60
+ expect(isMathMLElement(div.firstChild!)).toBe(true)
61
+ })
62
+
63
+ it('returns false for non-MathML elements', () => {
64
+ const div = document.createElement('div')
65
+ expect(isMathMLElement(div)).toBe(false)
66
+ })
67
+ })
68
+
69
+ describe('isElementLike', () => {
70
+ it('returns true for HTML elements', () => {
71
+ const div = document.createElement('div')
72
+ expect(isElementLike(div)).toBe(true)
73
+ })
74
+
75
+ it('returns true for SVG elements', () => {
76
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
77
+ expect(isElementLike(svg)).toBe(true)
78
+ })
79
+
80
+ it('returns false for text nodes', () => {
81
+ const text = document.createTextNode('text')
82
+ expect(isElementLike(text)).toBe(false)
83
+ })
84
+
85
+ it('returns false for non-node values', () => {
86
+ expect(isElementLike({})).toBe(false)
87
+ expect(isElementLike(null)).toBe(false)
88
+ expect(isElementLike(undefined)).toBe(false)
89
+ expect(isElementLike('string')).toBe(false)
90
+ expect(isElementLike(123)).toBe(false)
91
+ expect(isElementLike(true)).toBe(false)
92
+ expect(isElementLike(() => 123)).toBe(false)
93
+ })
94
+ })
95
+
96
+ describe('isDocument', () => {
97
+ it('returns true for document', () => {
98
+ expect(isDocument(document)).toBe(true)
99
+ })
100
+
101
+ it('returns false for other nodes', () => {
102
+ const div = document.createElement('div')
103
+ expect(isDocument(div)).toBe(false)
104
+ })
105
+ })
106
+
107
+ describe('isDocumentFragment', () => {
108
+ it('returns true for document fragment', () => {
109
+ const fragment = document.createDocumentFragment()
110
+ expect(isDocumentFragment(fragment)).toBe(true)
111
+ })
112
+
113
+ it('returns false for other nodes', () => {
114
+ const div = document.createElement('div')
115
+ expect(isDocumentFragment(div)).toBe(false)
116
+ })
117
+ })
118
+
119
+ describe('isShadowRoot', () => {
120
+ it('returns true for shadow root', () => {
121
+ const div = document.createElement('div')
122
+ const shadowRoot = div.attachShadow({ mode: 'open' })
123
+ expect(isShadowRoot(shadowRoot)).toBe(true)
124
+ })
125
+
126
+ it('returns false for other nodes', () => {
127
+ const div = document.createElement('div')
128
+ expect(isShadowRoot(div)).toBe(false)
129
+ })
130
+ })
131
+
132
+ describe('isNodeLike', () => {
133
+ it('returns true for DOM nodes', () => {
134
+ const div = document.createElement('div')
135
+ expect(isNodeLike(div)).toBe(true)
136
+
137
+ const text = document.createTextNode('text')
138
+ expect(isNodeLike(text)).toBe(true)
139
+ })
140
+
141
+ it('returns false for non-node values', () => {
142
+ expect(isNodeLike({})).toBe(false)
143
+ expect(isNodeLike(null)).toBe(false)
144
+ expect(isNodeLike(undefined)).toBe(false)
145
+ expect(isNodeLike('string')).toBe(false)
146
+ })
147
+ })
148
+
149
+ describe('isWindowLike', () => {
150
+ it('returns true for window object', () => {
151
+ expect(isWindowLike(window)).toBe(true)
152
+ })
153
+
154
+ it('returns false for non-window values', () => {
155
+ const div = document.createElement('div')
156
+ expect(isWindowLike(div)).toBe(false)
157
+ expect(isWindowLike({})).toBe(false)
158
+ expect(isWindowLike(null)).toBe(false)
159
+ })
160
+ })
161
+
162
+ describe('getWindow', () => {
163
+ it('returns window when no target is provided', () => {
164
+ expect(getWindow()).toBe(window)
165
+ })
166
+
167
+ it('returns window for element', () => {
168
+ const div = document.createElement('div')
169
+ expect(getWindow(div)).toBe(window)
170
+ })
171
+
172
+ it('returns window for document', () => {
173
+ expect(getWindow(document)).toBe(window)
174
+ })
175
+
176
+ it('returns window for shadow root', () => {
177
+ const div = document.createElement('div')
178
+ const shadowRoot = div.attachShadow({ mode: 'open' })
179
+ expect(getWindow(shadowRoot)).toBe(window)
180
+ })
181
+
182
+ it('returns fallback window when document.defaultView is null', () => {
183
+ const doc = {} as Document
184
+ Object.defineProperty(doc, 'nodeType', { value: 9 }) // DOCUMENT_NODE
185
+ Object.defineProperty(doc, 'defaultView', { value: null })
186
+ expect(getWindow(doc)).toBe(window)
187
+ })
188
+
189
+ it('returns fallback window when element.ownerDocument is null', () => {
190
+ const el = {} as Element
191
+ Object.defineProperty(el, 'nodeType', { value: 1 }) // ELEMENT_NODE
192
+ Object.defineProperty(el, 'ownerDocument', { value: null })
193
+ expect(getWindow(el)).toBe(window)
194
+ })
195
+
196
+ it('returns fallback window when element.ownerDocument.defaultView is null', () => {
197
+ const doc = {} as Document
198
+ Object.defineProperty(doc, 'nodeType', { value: 9 }) // DOCUMENT_NODE
199
+ Object.defineProperty(doc, 'defaultView', { value: null })
200
+
201
+ const el = {} as Element
202
+ Object.defineProperty(el, 'nodeType', { value: 1 }) // ELEMENT_NODE
203
+ Object.defineProperty(el, 'ownerDocument', { value: doc })
204
+ expect(getWindow(el)).toBe(window)
205
+ })
206
+
207
+ it('returns window for text node', () => {
208
+ const text = document.createTextNode('hello')
209
+ expect(getWindow(text)).toBe(window)
210
+ })
211
+ })
212
+
213
+ describe('getDocument', () => {
214
+ it('returns document when no target is provided', () => {
215
+ expect(getDocument()).toBe(document)
216
+ })
217
+
218
+ it('returns document for window', () => {
219
+ expect(getDocument(window)).toBe(document)
220
+ })
221
+
222
+ it('returns document when passed a document', () => {
223
+ expect(getDocument(document)).toBe(document)
224
+ })
225
+
226
+ it('returns ownerDocument for element', () => {
227
+ const div = document.createElement('div')
228
+ expect(getDocument(div)).toBe(document)
229
+ })
230
+
231
+ it('returns fallback document when element.ownerDocument is null', () => {
232
+ const node = {} as Node
233
+ Object.defineProperty(node, 'ownerDocument', { value: null })
234
+ expect(getDocument(node)).toBe(document)
235
+ })
236
+ })
237
+
238
+ describe('getDocumentElement', () => {
239
+ it('returns documentElement when no target is provided', () => {
240
+ expect(getDocumentElement()).toBe(document.documentElement)
241
+ })
242
+
243
+ it('returns documentElement for element', () => {
244
+ const div = document.createElement('div')
245
+ expect(getDocumentElement(div)).toBe(document.documentElement)
246
+ })
247
+
248
+ it('returns documentElement for window', () => {
249
+ expect(getDocumentElement(window)).toBe(document.documentElement)
250
+ })
251
+
252
+ it('returns documentElement for document', () => {
253
+ expect(getDocumentElement(document)).toBe(document.documentElement)
254
+ })
255
+ })
package/src/dom.ts ADDED
@@ -0,0 +1,135 @@
1
+ import { isObject } from './checker'
2
+ import * as NodeType from './dom-node-type'
3
+
4
+ /**
5
+ * Checks if the given DOM node is an Element.
6
+ */
7
+ export function isElement(node: Node): node is Element {
8
+ return node.nodeType === NodeType.ELEMENT_NODE
9
+ }
10
+
11
+ /**
12
+ * Checks if the given DOM node is a Text node.
13
+ */
14
+ export function isTextNode(node: Node): node is Text {
15
+ return node.nodeType === NodeType.TEXT_NODE
16
+ }
17
+
18
+ /**
19
+ * Checks if the given DOM node is an HTMLElement.
20
+ */
21
+ export function isHTMLElement(node: Node): node is HTMLElement {
22
+ return isElement(node) && node.namespaceURI === 'http://www.w3.org/1999/xhtml'
23
+ }
24
+
25
+ /**
26
+ * Checks if the given DOM node is an SVGElement.
27
+ */
28
+ export function isSVGElement(node: Node): node is SVGElement {
29
+ return isElement(node) && node.namespaceURI === 'http://www.w3.org/2000/svg'
30
+ }
31
+
32
+ /**
33
+ * Checks if the given DOM node is an MathMLElement.
34
+ */
35
+ export function isMathMLElement(node: Node): node is MathMLElement {
36
+ return (
37
+ isElement(node) &&
38
+ node.namespaceURI === 'http://www.w3.org/1998/Math/MathML'
39
+ )
40
+ }
41
+
42
+ /**
43
+ * Checks if the given DOM node is a Document.
44
+ */
45
+ export function isDocument(node: Node): node is Document {
46
+ return node.nodeType === NodeType.DOCUMENT_NODE
47
+ }
48
+
49
+ /**
50
+ * Checks if the given DOM node is a DocumentFragment.
51
+ */
52
+ export function isDocumentFragment(node: Node): node is DocumentFragment {
53
+ return node.nodeType === NodeType.DOCUMENT_FRAGMENT_NODE
54
+ }
55
+
56
+ /**
57
+ * Checks if the given DOM node is a ShadowRoot.
58
+ */
59
+ export function isShadowRoot(node: Node): node is ShadowRoot {
60
+ return isDocumentFragment(node) && 'host' in node && isElementLike(node.host)
61
+ }
62
+
63
+ /**
64
+ * Checks if an unknown value is likely a DOM node.
65
+ */
66
+ export function isNodeLike(value: unknown): value is Node {
67
+ return isObject(value) && value.nodeType !== undefined
68
+ }
69
+
70
+ /**
71
+ * Checks if an unknown value is likely a DOM element.
72
+ */
73
+ export function isElementLike(value: unknown): value is Element {
74
+ return (
75
+ isObject(value) &&
76
+ value.nodeType === NodeType.ELEMENT_NODE &&
77
+ typeof value.nodeName === 'string'
78
+ )
79
+ }
80
+
81
+ /**
82
+ * Checks if the given value is likely a Window object.
83
+ */
84
+ export function isWindowLike(value: unknown): value is Window {
85
+ return isObject(value) && value.window === value
86
+ }
87
+
88
+ /**
89
+ * Gets the window object for the given target or the global window object if no
90
+ * target is provided.
91
+ */
92
+ export function getWindow(
93
+ target?: Node | ShadowRoot | Document | null,
94
+ ): Window & typeof globalThis {
95
+ if (target) {
96
+ if (isShadowRoot(target)) {
97
+ return getWindow(target.host)
98
+ }
99
+ if (isDocument(target)) {
100
+ return target.defaultView || window
101
+ }
102
+ if (isElement(target)) {
103
+ return target.ownerDocument?.defaultView || window
104
+ }
105
+ }
106
+ return window
107
+ }
108
+
109
+ /**
110
+ * Gets the document for the given target or the global document if no target is
111
+ * provided.
112
+ */
113
+ export function getDocument(
114
+ target?: Element | Window | Node | Document | null,
115
+ ): Document {
116
+ if (target) {
117
+ if (isWindowLike(target)) {
118
+ return target.document
119
+ }
120
+ if (isDocument(target)) {
121
+ return target
122
+ }
123
+ return target.ownerDocument || document
124
+ }
125
+ return document
126
+ }
127
+
128
+ /**
129
+ * Gets a reference to the root node of the document based on the given target.
130
+ */
131
+ export function getDocumentElement(
132
+ target?: Element | Node | Window | Document | null,
133
+ ): HTMLElement {
134
+ return getDocument(target).documentElement
135
+ }
@@ -0,0 +1,58 @@
1
+ // @vitest-environment node
2
+
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import { formatBytes } from './format-bytes'
6
+
7
+ describe('formatBytes', () => {
8
+ it('formats bytes correctly', () => {
9
+ expect(formatBytes(0)).toBe('0B')
10
+ expect(formatBytes(100)).toBe('100B')
11
+ expect(formatBytes(512)).toBe('512B')
12
+ expect(formatBytes(1023)).toBe('1023B')
13
+ })
14
+
15
+ it('formats kilobytes correctly', () => {
16
+ expect(formatBytes(1024)).toBe('1.0KB')
17
+ expect(formatBytes(1536)).toBe('1.5KB')
18
+ expect(formatBytes(2048)).toBe('2.0KB')
19
+ expect(formatBytes(1024 * 1023)).toBe('1023.0KB')
20
+ })
21
+
22
+ it('formats megabytes correctly', () => {
23
+ expect(formatBytes(1024 * 1024)).toBe('1.0MB')
24
+ expect(formatBytes(1024 * 1024 * 1.5)).toBe('1.5MB')
25
+ expect(formatBytes(1024 * 1024 * 2)).toBe('2.0MB')
26
+ expect(formatBytes(1024 * 1024 * 1023)).toBe('1023.0MB')
27
+ })
28
+
29
+ it('formats gigabytes correctly', () => {
30
+ expect(formatBytes(1024 * 1024 * 1024)).toBe('1.0GB')
31
+ expect(formatBytes(1024 * 1024 * 1024 * 1.5)).toBe('1.5GB')
32
+ expect(formatBytes(1024 * 1024 * 1024 * 2)).toBe('2.0GB')
33
+ })
34
+
35
+ it('handles very large numbers (stays at GB)', () => {
36
+ expect(formatBytes(1024 * 1024 * 1024 * 1024)).toBe('1024.0GB')
37
+ expect(formatBytes(1024 * 1024 * 1024 * 2048)).toBe('2048.0GB')
38
+ })
39
+
40
+ it('handles negative numbers', () => {
41
+ expect(formatBytes(-100)).toBe('-100B')
42
+ expect(formatBytes(-1024)).toBe('-1.0KB')
43
+ expect(formatBytes(-1024 * 1024)).toBe('-1.0MB')
44
+ expect(formatBytes(-1024 * 1024 * 1024)).toBe('-1.0GB')
45
+ })
46
+
47
+ it('handles decimal inputs', () => {
48
+ expect(formatBytes(100.5)).toBe('100.5B')
49
+ expect(formatBytes(1536.75)).toBe('1.5KB')
50
+ })
51
+
52
+ it('handles edge cases around unit boundaries', () => {
53
+ expect(formatBytes(1023.9)).toBe('1023.9B')
54
+ expect(formatBytes(1024.1)).toBe('1.0KB')
55
+ expect(formatBytes(1048575.9)).toBe('1024.0KB')
56
+ expect(formatBytes(1048576.1)).toBe('1.0MB')
57
+ })
58
+ })
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Formats a number of bytes into a human-readable string.
3
+ * @param bytes - The number of bytes to format.
4
+ * @returns A string representing the number of bytes in a human-readable format.
5
+ */
6
+ export function formatBytes(bytes: number): string {
7
+ const units = ['B', 'KB', 'MB', 'GB']
8
+ let unitIndex = 0
9
+ let num = bytes
10
+ while (Math.abs(num) >= 1024 && unitIndex < units.length - 1) {
11
+ num /= 1024
12
+ unitIndex++
13
+ }
14
+ const fraction = unitIndex === 0 && num % 1 === 0 ? 0 : 1
15
+ return `${num.toFixed(fraction)}${units[unitIndex]}`
16
+ }
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import { getId } from './get-id'
4
+
5
+ describe('getId', () => {
6
+ it('returns a positive number', () => {
7
+ const id = getId()
8
+ expect(id).toBeGreaterThan(0)
9
+ })
10
+
11
+ it('returns different values on consecutive calls', () => {
12
+ expect(getId()).not.toBe(getId())
13
+ })
14
+ })
package/src/get-id.ts ADDED
@@ -0,0 +1,9 @@
1
+ let id = 0
2
+
3
+ /**
4
+ * Generates a unique positive integer.
5
+ */
6
+ export function getId(): number {
7
+ id = (id % Number.MAX_SAFE_INTEGER) + 1
8
+ return id
9
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './checker'
2
+ export * from './dom'
3
+ export { formatBytes } from './format-bytes'
4
+ export { getId } from './get-id'
5
+ export { once } from './once'
6
+ export { sleep } from './sleep'
@@ -0,0 +1,26 @@
1
+ // @vitest-environment node
2
+
3
+ import { describe, it, expect, vi } from 'vitest'
4
+
5
+ import { once } from './once'
6
+
7
+ describe('once', () => {
8
+ it('executes the wrapped function exactly once', () => {
9
+ const spy = vi.fn(() => Math.random())
10
+ const getRandom = once(spy)
11
+
12
+ const first = getRandom()
13
+ const second = getRandom()
14
+ const third = getRandom()
15
+
16
+ expect(spy).toHaveBeenCalledTimes(1)
17
+ expect(second).toBe(first)
18
+ expect(third).toBe(first)
19
+ })
20
+
21
+ it("returns the underlying function's result", () => {
22
+ const value = { foo: 'bar' }
23
+ const getValue = once(() => value)
24
+ expect(getValue()).toBe(value)
25
+ })
26
+ })
package/src/once.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Creates a function that will only execute the provided function once.
3
+ * Subsequent calls will return the cached result from the first execution.
4
+ *
5
+ * @param fn The function to execute once
6
+ * @returns A function that will only execute the provided function once
7
+ * @example
8
+ * ```ts
9
+ * const getValue = once(() => expensiveOperation())
10
+ * getValue() // executes expensiveOperation
11
+ * getValue() // returns cached result
12
+ * ```
13
+ */
14
+ export function once<T>(fn: () => T): () => T {
15
+ let called = false
16
+ let result: T
17
+ return () => {
18
+ if (!called) {
19
+ result = fn()
20
+ called = true
21
+ // @ts-expect-error - micro-optimization for memory management
22
+ fn = undefined
23
+ }
24
+ return result
25
+ }
26
+ }
@@ -0,0 +1,44 @@
1
+ // @vitest-environment node
2
+
3
+ import { describe, it, expect, vi } from 'vitest'
4
+
5
+ import { sleep } from './sleep'
6
+
7
+ describe('sleep', () => {
8
+ it('resolves after the specified time', async () => {
9
+ vi.useFakeTimers()
10
+
11
+ const promise = sleep(1000)
12
+
13
+ // Advance time by 999ms - promise should not resolve yet
14
+ vi.advanceTimersByTime(999)
15
+ await Promise.resolve() // flush microtasks
16
+
17
+ let resolved = false
18
+ void promise.then(() => {
19
+ resolved = true
20
+ })
21
+ await Promise.resolve() // flush microtasks
22
+ expect(resolved).toBe(false)
23
+
24
+ // Advance time by 1ms more (total 1000ms) - promise should now resolve
25
+ vi.advanceTimersByTime(1)
26
+ await promise
27
+
28
+ expect(resolved).toBe(true)
29
+
30
+ vi.useRealTimers()
31
+ })
32
+
33
+ it('returns a promise that resolves to undefined', async () => {
34
+ vi.useFakeTimers()
35
+
36
+ const promise = sleep(100)
37
+ vi.advanceTimersByTime(100)
38
+
39
+ const result = await promise
40
+ expect(result).toBe(undefined)
41
+
42
+ vi.useRealTimers()
43
+ })
44
+ })
package/src/sleep.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Sleep for a given number of milliseconds.
3
+ */
4
+ export function sleep(ms: number): Promise<void> {
5
+ return new Promise((resolve) => setTimeout(resolve, ms))
6
+ }