@simplysm/core-browser 13.0.68 → 13.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -224
- package/dist/extensions/element-ext.d.ts +36 -36
- package/dist/extensions/element-ext.d.ts.map +1 -1
- package/dist/extensions/element-ext.js +1 -1
- package/dist/extensions/element-ext.js.map +1 -1
- package/dist/extensions/html-element-ext.d.ts +22 -22
- package/dist/utils/download.d.ts +3 -3
- package/dist/utils/fetch.d.ts +1 -1
- package/dist/utils/file-dialog.d.ts +1 -1
- package/package.json +6 -5
- package/src/extensions/element-ext.ts +41 -41
- package/src/extensions/html-element-ext.ts +24 -24
- package/src/index.ts +1 -1
- package/src/utils/download.ts +3 -3
- package/src/utils/fetch.ts +4 -4
- package/src/utils/file-dialog.ts +1 -1
- package/tests/extensions/element-ext.spec.ts +737 -0
- package/tests/extensions/html-element-ext.spec.ts +190 -0
- package/tests/utils/download.spec.ts +66 -0
|
@@ -2,82 +2,82 @@ import { isFocusable } from "tabbable";
|
|
|
2
2
|
import { TimeoutError } from "@simplysm/core-common";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Element bounds information type
|
|
6
6
|
*/
|
|
7
7
|
export interface ElementBounds {
|
|
8
|
-
/**
|
|
8
|
+
/** Element to be measured */
|
|
9
9
|
target: Element;
|
|
10
|
-
/**
|
|
10
|
+
/** Top position relative to viewport */
|
|
11
11
|
top: number;
|
|
12
|
-
/**
|
|
12
|
+
/** Left position relative to viewport */
|
|
13
13
|
left: number;
|
|
14
|
-
/**
|
|
14
|
+
/** Element width */
|
|
15
15
|
width: number;
|
|
16
|
-
/**
|
|
16
|
+
/** Element height */
|
|
17
17
|
height: number;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
declare global {
|
|
21
21
|
interface Element {
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Find all child elements matching selector
|
|
24
24
|
*
|
|
25
|
-
* @param selector - CSS
|
|
26
|
-
* @returns
|
|
25
|
+
* @param selector - CSS selector
|
|
26
|
+
* @returns Array of matching elements (empty selector returns empty array)
|
|
27
27
|
*/
|
|
28
28
|
findAll<T extends Element = Element>(selector: string): T[];
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Find first element matching selector
|
|
32
32
|
*
|
|
33
|
-
* @param selector - CSS
|
|
34
|
-
* @returns
|
|
33
|
+
* @param selector - CSS selector
|
|
34
|
+
* @returns First matching element or undefined (empty selector returns undefined)
|
|
35
35
|
*/
|
|
36
36
|
findFirst<T extends Element = Element>(selector: string): T | undefined;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
39
|
+
* Insert element as first child
|
|
40
40
|
*
|
|
41
|
-
* @param child -
|
|
42
|
-
* @returns
|
|
41
|
+
* @param child - Child element to insert
|
|
42
|
+
* @returns Inserted child element
|
|
43
43
|
*/
|
|
44
44
|
prependChild<T extends Element>(child: T): T;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Get all parent elements (in order of proximity)
|
|
48
48
|
*
|
|
49
|
-
* @returns
|
|
49
|
+
* @returns Array of parent elements (from closest to farthest)
|
|
50
50
|
*/
|
|
51
51
|
getParents(): Element[];
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* Find first focusable parent element (using tabbable)
|
|
55
55
|
*
|
|
56
|
-
* @returns
|
|
56
|
+
* @returns First focusable parent element or undefined
|
|
57
57
|
*/
|
|
58
58
|
findFocusableParent(): HTMLElement | undefined;
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
61
|
+
* Find first focusable child element (using tabbable)
|
|
62
62
|
*
|
|
63
|
-
* @returns
|
|
63
|
+
* @returns First focusable child element or undefined
|
|
64
64
|
*/
|
|
65
65
|
findFirstFocusableChild(): HTMLElement | undefined;
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
68
|
+
* Check if element is an offset parent (position: relative/absolute/fixed/sticky)
|
|
69
69
|
*
|
|
70
|
-
* @returns position
|
|
70
|
+
* @returns true if position property is one of relative, absolute, fixed, or sticky
|
|
71
71
|
*/
|
|
72
72
|
isOffsetElement(): boolean;
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Check if element is visible on screen
|
|
76
76
|
*
|
|
77
77
|
* @remarks
|
|
78
|
-
*
|
|
78
|
+
* Checks existence of clientRects, visibility: hidden, and opacity: 0.
|
|
79
79
|
*
|
|
80
|
-
* @returns
|
|
80
|
+
* @returns true if element is visible on screen
|
|
81
81
|
*/
|
|
82
82
|
isVisible(): boolean;
|
|
83
83
|
}
|
|
@@ -144,13 +144,13 @@ Element.prototype.isVisible = function (): boolean {
|
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
// ============================================================================
|
|
147
|
-
//
|
|
147
|
+
// Static functions (for event handlers or multiple elements)
|
|
148
148
|
// ============================================================================
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
*
|
|
151
|
+
* Copy element content to clipboard (use with copy event handler)
|
|
152
152
|
*
|
|
153
|
-
* @param event - copy
|
|
153
|
+
* @param event - copy event object
|
|
154
154
|
*/
|
|
155
155
|
export function copyElement(event: ClipboardEvent): void {
|
|
156
156
|
const clipboardData = event.clipboardData;
|
|
@@ -167,13 +167,13 @@ export function copyElement(event: ClipboardEvent): void {
|
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
|
-
*
|
|
170
|
+
* Paste clipboard content to element (use with paste event handler)
|
|
171
171
|
*
|
|
172
172
|
* @remarks
|
|
173
|
-
*
|
|
174
|
-
*
|
|
173
|
+
* Finds the first input/textarea within the target element and replaces its entire value with clipboard content.
|
|
174
|
+
* Does not consider cursor position or selection.
|
|
175
175
|
*
|
|
176
|
-
* @param event - paste
|
|
176
|
+
* @param event - paste event object
|
|
177
177
|
*/
|
|
178
178
|
export function pasteToElement(event: ClipboardEvent): void {
|
|
179
179
|
const clipboardData = event.clipboardData;
|
|
@@ -191,20 +191,20 @@ export function pasteToElement(event: ClipboardEvent): void {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
|
-
*
|
|
194
|
+
* Get bounds information for elements using IntersectionObserver
|
|
195
195
|
*
|
|
196
|
-
* @param els -
|
|
197
|
-
* @param timeout -
|
|
198
|
-
* @throws {TimeoutError}
|
|
196
|
+
* @param els - Array of target elements
|
|
197
|
+
* @param timeout - Timeout in milliseconds (default: 5000)
|
|
198
|
+
* @throws {TimeoutError} If no response within timeout duration
|
|
199
199
|
*/
|
|
200
200
|
export async function getBounds(els: Element[], timeout: number = 5000): Promise<ElementBounds[]> {
|
|
201
|
-
//
|
|
201
|
+
// Index map to remove duplicates and sort results in input order
|
|
202
202
|
const indexMap = new Map(els.map((el, i) => [el, i] as const));
|
|
203
203
|
if (indexMap.size === 0) {
|
|
204
204
|
return [];
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
//
|
|
207
|
+
// Index map for sorting performance optimization
|
|
208
208
|
const sortIndexMap = new Map(els.map((el, i) => [el, i] as const));
|
|
209
209
|
|
|
210
210
|
let observer: IntersectionObserver | undefined;
|
|
@@ -231,7 +231,7 @@ export async function getBounds(els: Element[], timeout: number = 5000): Promise
|
|
|
231
231
|
|
|
232
232
|
if (indexMap.size === 0) {
|
|
233
233
|
observer?.disconnect();
|
|
234
|
-
//
|
|
234
|
+
// Sort in input order
|
|
235
235
|
resolve(
|
|
236
236
|
results.sort((a, b) => sortIndexMap.get(a.target)! - sortIndexMap.get(b.target)!),
|
|
237
237
|
);
|
|
@@ -243,7 +243,7 @@ export async function getBounds(els: Element[], timeout: number = 5000): Promise
|
|
|
243
243
|
}
|
|
244
244
|
}),
|
|
245
245
|
new Promise<ElementBounds[]>((_, reject) =>
|
|
246
|
-
setTimeout(() => reject(new TimeoutError(undefined, `${timeout}ms
|
|
246
|
+
setTimeout(() => reject(new TimeoutError(undefined, `${timeout}ms timeout`)), timeout),
|
|
247
247
|
),
|
|
248
248
|
]);
|
|
249
249
|
} finally {
|
|
@@ -3,44 +3,44 @@ import { ArgumentError } from "@simplysm/core-common";
|
|
|
3
3
|
declare global {
|
|
4
4
|
interface HTMLElement {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Force repaint (triggers reflow)
|
|
7
7
|
*/
|
|
8
8
|
repaint(): void;
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Calculate relative position based on parent element (for CSS positioning)
|
|
12
12
|
*
|
|
13
13
|
* @remarks
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Calculates element position relative to parent element, returning document-based coordinates
|
|
15
|
+
* including `window.scrollX/Y` that can be directly used in CSS `top`/`left` properties.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
* -
|
|
17
|
+
* Common use cases:
|
|
18
|
+
* - Position dropdowns, popups after appending to `document.body`
|
|
19
|
+
* - Works correctly on scrolled pages
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
-
* - CSS transform
|
|
21
|
+
* Factors included in calculation:
|
|
22
|
+
* - Viewport-relative position (getBoundingClientRect)
|
|
23
|
+
* - Document scroll position (window.scrollX/Y)
|
|
24
|
+
* - Parent element internal scroll (parentEl.scrollTop/Left)
|
|
25
|
+
* - Border thickness of intermediate elements
|
|
26
|
+
* - CSS transform transformations
|
|
27
27
|
*
|
|
28
|
-
* @param parent -
|
|
29
|
-
* @returns CSS top/left
|
|
30
|
-
* @throws {ArgumentError}
|
|
28
|
+
* @param parent - Parent element or selector to use as reference (e.g., document.body, ".container")
|
|
29
|
+
* @returns Coordinates usable in CSS top/left properties
|
|
30
|
+
* @throws {ArgumentError} If parent element cannot be found
|
|
31
31
|
*/
|
|
32
32
|
getRelativeOffset(parent: HTMLElement | string): { top: number; left: number };
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Scroll to make target visible if hidden by offset area (e.g., fixed header/column)
|
|
36
36
|
*
|
|
37
37
|
* @remarks
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* Only handles cases where target extends beyond top/left boundaries of scroll area.
|
|
39
|
+
* For scrolling needed downward/rightward, relies on browser's default focus scroll behavior.
|
|
40
|
+
* Typically used with focus events on tables with fixed headers or columns.
|
|
41
41
|
*
|
|
42
|
-
* @param target -
|
|
43
|
-
* @param offset -
|
|
42
|
+
* @param target - Target position within container (offsetTop, offsetLeft)
|
|
43
|
+
* @param offset - Size of area that must not be obscured (e.g., fixed header height, fixed column width)
|
|
44
44
|
*/
|
|
45
45
|
scrollIntoViewIfNeeded(
|
|
46
46
|
target: { top: number; left: number },
|
|
@@ -50,8 +50,8 @@ declare global {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
HTMLElement.prototype.repaint = function (): void {
|
|
53
|
-
// offsetHeight
|
|
54
|
-
//
|
|
53
|
+
// Accessing offsetHeight triggers forced synchronous layout in browser,
|
|
54
|
+
// causing style changes in current layout to be applied immediately and triggering repaint.
|
|
55
55
|
void this.offsetHeight;
|
|
56
56
|
};
|
|
57
57
|
|
package/src/index.ts
CHANGED
package/src/utils/download.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Blob
|
|
2
|
+
* Download Blob as file
|
|
3
3
|
*
|
|
4
|
-
* @param blob -
|
|
5
|
-
* @param fileName -
|
|
4
|
+
* @param blob - Blob object to download
|
|
5
|
+
* @param fileName - File name to save as
|
|
6
6
|
*/
|
|
7
7
|
export function downloadBlob(blob: Blob, fileName: string): void {
|
|
8
8
|
const url = URL.createObjectURL(blob);
|
package/src/utils/fetch.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface DownloadProgress {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Download binary data from URL (with progress callback support)
|
|
8
8
|
*/
|
|
9
9
|
export async function fetchUrlBytes(
|
|
10
10
|
url: string,
|
|
@@ -22,7 +22,7 @@ export async function fetchUrlBytes(
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
|
-
// Content-Length
|
|
25
|
+
// If Content-Length is known, pre-allocate for memory efficiency
|
|
26
26
|
if (contentLength > 0) {
|
|
27
27
|
const result = new Uint8Array(contentLength);
|
|
28
28
|
let receivedLength = 0;
|
|
@@ -39,7 +39,7 @@ export async function fetchUrlBytes(
|
|
|
39
39
|
return result;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// Content-Length
|
|
42
|
+
// If Content-Length is unknown, collect chunks then merge (chunked encoding)
|
|
43
43
|
const chunks: Uint8Array[] = [];
|
|
44
44
|
let receivedLength = 0;
|
|
45
45
|
|
|
@@ -51,7 +51,7 @@ export async function fetchUrlBytes(
|
|
|
51
51
|
receivedLength += value.length;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
//
|
|
54
|
+
// Merge chunks
|
|
55
55
|
const result = new Uint8Array(receivedLength);
|
|
56
56
|
let position = 0;
|
|
57
57
|
for (const chunk of chunks) {
|
package/src/utils/file-dialog.ts
CHANGED