@simplysm/core-browser 13.0.0-beta.5 → 13.0.0-beta.7
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/package.json +5 -2
- package/.cache/typecheck-browser.tsbuildinfo +0 -1
- package/.cache/typecheck-tests-browser.tsbuildinfo +0 -1
- package/src/extensions/element-ext.ts +0 -246
- package/src/extensions/html-element-ext.ts +0 -117
- package/src/index.ts +0 -11
- package/src/utils/blob.ts +0 -19
- package/src/utils/download.ts +0 -66
- package/tests/extensions/element-ext.spec.ts +0 -729
- package/tests/extensions/html-element-ext.spec.ts +0 -190
- package/tests/utils/blob.spec.ts +0 -68
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { ArgumentError } from "@simplysm/core-common";
|
|
2
|
-
|
|
3
|
-
declare global {
|
|
4
|
-
interface HTMLElement {
|
|
5
|
-
/**
|
|
6
|
-
* 강제 리페인트 (reflow 트리거)
|
|
7
|
-
*/
|
|
8
|
-
repaint(): void;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 부모 요소 기준 상대 위치 계산 (CSS 포지셔닝용)
|
|
12
|
-
*
|
|
13
|
-
* @remarks
|
|
14
|
-
* 이 함수는 요소의 위치를 부모 요소 기준으로 계산하되, `window.scrollX/Y`를 포함하여
|
|
15
|
-
* CSS `top`/`left` 속성에 직접 사용할 수 있는 문서 기준 좌표를 반환한다.
|
|
16
|
-
*
|
|
17
|
-
* 주요 사용 사례:
|
|
18
|
-
* - 드롭다운, 팝업 등을 `document.body`에 append 후 위치 지정
|
|
19
|
-
* - 스크롤된 페이지에서도 올바르게 동작
|
|
20
|
-
*
|
|
21
|
-
* 계산에 포함되는 요소:
|
|
22
|
-
* - 뷰포트 기준 위치 (getBoundingClientRect)
|
|
23
|
-
* - 문서 스크롤 위치 (window.scrollX/Y)
|
|
24
|
-
* - 부모 요소 내부 스크롤 (parentEl.scrollTop/Left)
|
|
25
|
-
* - 중간 요소들의 border 두께
|
|
26
|
-
* - CSS transform 변환
|
|
27
|
-
*
|
|
28
|
-
* @param parent - 기준이 될 부모 요소 또는 셀렉터 (예: document.body, ".container")
|
|
29
|
-
* @returns CSS top/left 속성에 사용할 수 있는 좌표
|
|
30
|
-
* @throws {ArgumentError} 부모 요소를 찾을 수 없는 경우
|
|
31
|
-
*/
|
|
32
|
-
getRelativeOffset(parent: HTMLElement | string): { top: number; left: number };
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 대상이 offset 영역(고정 헤더/고정 열 등)에 가려진 경우, 보이도록 스크롤
|
|
36
|
-
*
|
|
37
|
-
* @remarks
|
|
38
|
-
* 이 함수는 대상이 스크롤 영역의 위쪽/왼쪽 경계를 벗어난 경우만 처리한다.
|
|
39
|
-
* 아래쪽/오른쪽으로 스크롤이 필요한 경우는 브라우저의 기본 포커스 스크롤 동작에 의존한다.
|
|
40
|
-
* 주로 고정 헤더나 고정 열이 있는 테이블에서 포커스 이벤트와 함께 사용된다.
|
|
41
|
-
*
|
|
42
|
-
* @param target - 대상의 컨테이너 내 위치 (offsetTop, offsetLeft)
|
|
43
|
-
* @param offset - 가려지면 안 되는 영역 크기 (예: 고정 헤더 높이, 고정 열 너비)
|
|
44
|
-
*/
|
|
45
|
-
scrollIntoViewIfNeeded(target: { top: number; left: number }, offset?: { top: number; left: number }): void;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
HTMLElement.prototype.repaint = function (): void {
|
|
50
|
-
// offsetHeight 접근 시 브라우저는 동기적 레이아웃 계산(forced synchronous layout)을 수행하며,
|
|
51
|
-
// 이로 인해 현재 배치된 스타일 변경사항이 즉시 적용되어 리페인트가 트리거된다.
|
|
52
|
-
void this.offsetHeight;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
HTMLElement.prototype.getRelativeOffset = function (parent: HTMLElement | string): { top: number; left: number } {
|
|
56
|
-
const parentEl = typeof parent === "string" ? this.closest(parent) : parent;
|
|
57
|
-
|
|
58
|
-
if (!(parentEl instanceof HTMLElement)) {
|
|
59
|
-
throw new ArgumentError({ parent });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const elementRect = this.getBoundingClientRect();
|
|
63
|
-
const parentRect = parentEl.getBoundingClientRect();
|
|
64
|
-
|
|
65
|
-
const scrollLeft = window.scrollX;
|
|
66
|
-
const scrollTop = window.scrollY;
|
|
67
|
-
|
|
68
|
-
const relativeOffset = {
|
|
69
|
-
top: elementRect.top - parentRect.top + scrollTop + (parentEl.scrollTop || 0),
|
|
70
|
-
left: elementRect.left - parentRect.left + scrollLeft + (parentEl.scrollLeft || 0),
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
let currentEl = this.parentElement;
|
|
74
|
-
while (currentEl !== null && currentEl !== parentEl) {
|
|
75
|
-
const style = getComputedStyle(currentEl);
|
|
76
|
-
relativeOffset.top += parseFloat(style.borderTopWidth) || 0;
|
|
77
|
-
relativeOffset.left += parseFloat(style.borderLeftWidth) || 0;
|
|
78
|
-
currentEl = currentEl.parentElement;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const elTransform = getComputedStyle(this).transform;
|
|
82
|
-
const parentTransform = getComputedStyle(parentEl).transform;
|
|
83
|
-
|
|
84
|
-
if (elTransform !== "none" || parentTransform !== "none") {
|
|
85
|
-
const elementMatrix = new DOMMatrix(elTransform);
|
|
86
|
-
const parentMatrix = new DOMMatrix(parentTransform);
|
|
87
|
-
|
|
88
|
-
if (!elementMatrix.isIdentity || !parentMatrix.isIdentity) {
|
|
89
|
-
const transformedPoint = parentMatrix
|
|
90
|
-
.inverse()
|
|
91
|
-
.multiply(elementMatrix)
|
|
92
|
-
.transformPoint(new DOMPoint(relativeOffset.left, relativeOffset.top));
|
|
93
|
-
|
|
94
|
-
relativeOffset.left = transformedPoint.x;
|
|
95
|
-
relativeOffset.top = transformedPoint.y;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return relativeOffset;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
HTMLElement.prototype.scrollIntoViewIfNeeded = function (
|
|
103
|
-
target: { top: number; left: number },
|
|
104
|
-
offset: { top: number; left: number } = { top: 0, left: 0 },
|
|
105
|
-
): void {
|
|
106
|
-
const scroll = {
|
|
107
|
-
top: this.scrollTop,
|
|
108
|
-
left: this.scrollLeft,
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
if (target.top - scroll.top < offset.top) {
|
|
112
|
-
this.scrollTop = target.top - offset.top;
|
|
113
|
-
}
|
|
114
|
-
if (target.left - scroll.left < offset.left) {
|
|
115
|
-
this.scrollLeft = target.left - offset.left;
|
|
116
|
-
}
|
|
117
|
-
};
|
package/src/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// core-browser: 브라우저 전용 유틸리티
|
|
2
|
-
|
|
3
|
-
// extensions (side-effect)
|
|
4
|
-
import "./extensions/element-ext";
|
|
5
|
-
import "./extensions/html-element-ext";
|
|
6
|
-
|
|
7
|
-
// re-exports
|
|
8
|
-
export * from "./extensions/element-ext";
|
|
9
|
-
export * from "./extensions/html-element-ext";
|
|
10
|
-
export * from "./utils/blob";
|
|
11
|
-
export * from "./utils/download";
|
package/src/utils/blob.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export namespace BlobUtils {
|
|
2
|
-
/**
|
|
3
|
-
* Blob을 파일로 다운로드
|
|
4
|
-
*
|
|
5
|
-
* @param blob - 다운로드할 Blob 객체
|
|
6
|
-
* @param fileName - 저장될 파일 이름
|
|
7
|
-
*/
|
|
8
|
-
export function download(blob: Blob, fileName: string): void {
|
|
9
|
-
const url = URL.createObjectURL(blob);
|
|
10
|
-
try {
|
|
11
|
-
const link = document.createElement("a");
|
|
12
|
-
link.href = url;
|
|
13
|
-
link.download = fileName;
|
|
14
|
-
link.click();
|
|
15
|
-
} finally {
|
|
16
|
-
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
package/src/utils/download.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
export interface DownloadProgress {
|
|
2
|
-
receivedLength: number;
|
|
3
|
-
contentLength: number;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* URL에서 바이너리 데이터 다운로드 (진행률 콜백 지원)
|
|
8
|
-
*/
|
|
9
|
-
export async function downloadBytes(
|
|
10
|
-
url: string,
|
|
11
|
-
options?: { onProgress?: (progress: DownloadProgress) => void },
|
|
12
|
-
): Promise<Uint8Array> {
|
|
13
|
-
const response = await fetch(url);
|
|
14
|
-
if (!response.ok) {
|
|
15
|
-
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const contentLength = Number(response.headers.get("Content-Length") ?? 0);
|
|
19
|
-
const reader = response.body?.getReader();
|
|
20
|
-
if (!reader) {
|
|
21
|
-
throw new Error("Response body is not readable");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
// Content-Length를 알 수 있으면 미리 할당하여 메모리 효율성 향상
|
|
26
|
-
if (contentLength > 0) {
|
|
27
|
-
const result = new Uint8Array(contentLength);
|
|
28
|
-
let receivedLength = 0;
|
|
29
|
-
|
|
30
|
-
while (true) {
|
|
31
|
-
const { done, value } = await reader.read();
|
|
32
|
-
if (done) break;
|
|
33
|
-
|
|
34
|
-
result.set(value, receivedLength);
|
|
35
|
-
receivedLength += value.length;
|
|
36
|
-
options?.onProgress?.({ receivedLength, contentLength });
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return result;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Content-Length를 모르면 청크 수집 후 병합 (chunked encoding)
|
|
43
|
-
const chunks: Uint8Array[] = [];
|
|
44
|
-
let receivedLength = 0;
|
|
45
|
-
|
|
46
|
-
while (true) {
|
|
47
|
-
const { done, value } = await reader.read();
|
|
48
|
-
if (done) break;
|
|
49
|
-
|
|
50
|
-
chunks.push(value);
|
|
51
|
-
receivedLength += value.length;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 청크 병합
|
|
55
|
-
const result = new Uint8Array(receivedLength);
|
|
56
|
-
let position = 0;
|
|
57
|
-
for (const chunk of chunks) {
|
|
58
|
-
result.set(chunk, position);
|
|
59
|
-
position += chunk.length;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return result;
|
|
63
|
-
} finally {
|
|
64
|
-
reader.releaseLock();
|
|
65
|
-
}
|
|
66
|
-
}
|