@simplysm/core-browser 13.0.100 → 14.0.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.
Files changed (42) hide show
  1. package/dist/extensions/element-ext.d.ts +36 -36
  2. package/dist/extensions/element-ext.d.ts.map +1 -1
  3. package/dist/extensions/element-ext.js +132 -111
  4. package/dist/extensions/element-ext.js.map +1 -6
  5. package/dist/extensions/html-element-ext.d.ts +22 -22
  6. package/dist/extensions/html-element-ext.js +50 -45
  7. package/dist/extensions/html-element-ext.js.map +1 -6
  8. package/dist/index.js +4 -1
  9. package/dist/index.js.map +1 -6
  10. package/dist/utils/IndexedDbStore.js +115 -112
  11. package/dist/utils/IndexedDbStore.js.map +1 -6
  12. package/dist/utils/IndexedDbVirtualFs.js +81 -83
  13. package/dist/utils/IndexedDbVirtualFs.js.map +1 -6
  14. package/dist/utils/download.d.ts +3 -3
  15. package/dist/utils/download.js +18 -14
  16. package/dist/utils/download.js.map +1 -6
  17. package/dist/utils/fetch.d.ts +1 -1
  18. package/dist/utils/fetch.d.ts.map +1 -1
  19. package/dist/utils/fetch.js +46 -36
  20. package/dist/utils/fetch.js.map +1 -6
  21. package/dist/utils/file-dialog.d.ts +1 -1
  22. package/dist/utils/file-dialog.js +19 -19
  23. package/dist/utils/file-dialog.js.map +1 -6
  24. package/package.json +7 -10
  25. package/src/extensions/element-ext.ts +40 -40
  26. package/src/extensions/html-element-ext.ts +24 -24
  27. package/src/index.ts +3 -3
  28. package/src/utils/IndexedDbStore.ts +3 -3
  29. package/src/utils/download.ts +3 -3
  30. package/src/utils/fetch.ts +17 -5
  31. package/src/utils/file-dialog.ts +1 -1
  32. package/README.md +0 -106
  33. package/docs/classes.md +0 -184
  34. package/docs/element-extensions.md +0 -134
  35. package/docs/html-element-extensions.md +0 -56
  36. package/docs/utilities.md +0 -71
  37. package/tests/extensions/element-ext.spec.ts +0 -693
  38. package/tests/extensions/html-element-ext.spec.ts +0 -175
  39. package/tests/utils/IndexedDbStore.spec.ts +0 -103
  40. package/tests/utils/IndexedDbVirtualFs.spec.ts +0 -171
  41. package/tests/utils/download.spec.ts +0 -66
  42. package/tests/utils/fetch.spec.ts +0 -154
@@ -1,21 +1,21 @@
1
- function openFileDialog(options) {
2
- return new Promise((resolve) => {
3
- const input = document.createElement("input");
4
- input.type = "file";
5
- input.multiple = (options == null ? void 0 : options.multiple) ?? false;
6
- if ((options == null ? void 0 : options.accept) != null) {
7
- input.accept = options.accept;
8
- }
9
- input.onchange = () => {
10
- resolve(input.files != null && input.files.length > 0 ? [...input.files] : void 0);
11
- };
12
- input.addEventListener("cancel", () => {
13
- resolve(void 0);
1
+ /**
2
+ * 프로그래밍 방식으로 파일 선택 대화상자 열기
3
+ */
4
+ export function openFileDialog(options) {
5
+ return new Promise((resolve) => {
6
+ const input = document.createElement("input");
7
+ input.type = "file";
8
+ input.multiple = options?.multiple ?? false;
9
+ if (options?.accept != null) {
10
+ input.accept = options.accept;
11
+ }
12
+ input.onchange = () => {
13
+ resolve(input.files != null && input.files.length > 0 ? [...input.files] : undefined);
14
+ };
15
+ input.addEventListener("cancel", () => {
16
+ resolve(undefined);
17
+ });
18
+ input.click();
14
19
  });
15
- input.click();
16
- });
17
20
  }
18
- export {
19
- openFileDialog
20
- };
21
- //# sourceMappingURL=file-dialog.js.map
21
+ //# sourceMappingURL=file-dialog.js.map
@@ -1,6 +1 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/utils/file-dialog.ts"],
4
- "mappings": "AAGO,SAAS,eAAe,SAGC;AAC9B,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,SAAS,cAAc,OAAO;AAC5C,UAAM,OAAO;AACb,UAAM,YAAW,mCAAS,aAAY;AACtC,SAAI,mCAAS,WAAU,MAAM;AAC3B,YAAM,SAAS,QAAQ;AAAA,IACzB;AACA,UAAM,WAAW,MAAM;AACrB,cAAQ,MAAM,SAAS,QAAQ,MAAM,MAAM,SAAS,IAAI,CAAC,GAAG,MAAM,KAAK,IAAI,MAAS;AAAA,IACtF;AACA,UAAM,iBAAiB,UAAU,MAAM;AACrC,cAAQ,MAAS;AAAA,IACnB,CAAC;AACD,UAAM,MAAM;AAAA,EACd,CAAC;AACH;",
5
- "names": []
6
- }
1
+ {"version":3,"file":"file-dialog.js","sourceRoot":"","sources":["..\\..\\src\\utils\\file-dialog.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAG9B;IACC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,GAAG,MAAM,CAAC;QACpB,KAAK,CAAC,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC;QAC5C,IAAI,OAAO,EAAE,MAAM,IAAI,IAAI,EAAE,CAAC;YAC5B,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAChC,CAAC;QACD,KAAK,CAAC,QAAQ,GAAG,GAAG,EAAE;YACpB,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACxF,CAAC,CAAC;QACF,KAAK,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,EAAE;YACpC,OAAO,CAAC,SAAS,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@simplysm/core-browser",
3
- "version": "13.0.100",
4
- "description": "Simplysm package - Core module (browser)",
5
- "author": "simplysm",
3
+ "version": "14.0.1",
4
+ "description": "심플리즘 패키지 - 코어 (browser)",
5
+ "author": "심플리즘",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
@@ -14,19 +14,16 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
- "docs",
18
- "src",
19
- "tests"
17
+ "src"
20
18
  ],
21
19
  "sideEffects": [
20
+ "./src/extensions/element-ext.ts",
21
+ "./src/extensions/html-element-ext.ts",
22
22
  "./dist/extensions/element-ext.js",
23
23
  "./dist/extensions/html-element-ext.js"
24
24
  ],
25
25
  "dependencies": {
26
26
  "tabbable": "^6.4.0",
27
- "@simplysm/core-common": "13.0.100"
28
- },
29
- "devDependencies": {
30
- "happy-dom": "^20.8.4"
27
+ "@simplysm/core-common": "14.0.1"
31
28
  }
32
29
  }
@@ -2,82 +2,82 @@ import { isFocusable } from "tabbable";
2
2
  import { TimeoutError } from "@simplysm/core-common";
3
3
 
4
4
  /**
5
- * Element bounds information type
5
+ * 요소 경계 정보 타입
6
6
  */
7
7
  export interface ElementBounds {
8
- /** Element to be measured */
8
+ /** 측정 대상 요소 */
9
9
  target: Element;
10
- /** Top position relative to viewport */
10
+ /** 뷰포트 기준 상단 위치 */
11
11
  top: number;
12
- /** Left position relative to viewport */
12
+ /** 뷰포트 기준 좌측 위치 */
13
13
  left: number;
14
- /** Element width */
14
+ /** 요소 너비 */
15
15
  width: number;
16
- /** Element height */
16
+ /** 요소 높이 */
17
17
  height: number;
18
18
  }
19
19
 
20
20
  declare global {
21
21
  interface Element {
22
22
  /**
23
- * Find all child elements matching selector
23
+ * 선택자와 일치하는 모든 하위 요소 검색
24
24
  *
25
- * @param selector - CSS selector
26
- * @returns Array of matching elements (empty selector returns empty array)
25
+ * @param selector - CSS 선택자
26
+ * @returns 일치하는 요소 배열 ( 선택자는 배열 반환)
27
27
  */
28
28
  findAll<TEl extends Element = Element>(selector: string): TEl[];
29
29
 
30
30
  /**
31
- * Find first element matching selector
31
+ * 선택자와 일치하는 번째 요소 검색
32
32
  *
33
- * @param selector - CSS selector
34
- * @returns First matching element or undefined (empty selector returns undefined)
33
+ * @param selector - CSS 선택자
34
+ * @returns 번째 일치 요소 또는 undefined ( 선택자는 undefined 반환)
35
35
  */
36
36
  findFirst<TEl extends Element = Element>(selector: string): TEl | undefined;
37
37
 
38
38
  /**
39
- * Insert element as first child
39
+ * 요소를 번째 자식으로 삽입
40
40
  *
41
- * @param child - Child element to insert
42
- * @returns Inserted child element
41
+ * @param child - 삽입할 자식 요소
42
+ * @returns 삽입된 자식 요소
43
43
  */
44
44
  prependChild<TEl extends Element>(child: TEl): TEl;
45
45
 
46
46
  /**
47
- * Get all parent elements (in order of proximity)
47
+ * 모든 부모 요소 조회 (가까운 순서)
48
48
  *
49
- * @returns Array of parent elements (from closest to farthest)
49
+ * @returns 부모 요소 배열 (가장 가까운 것부터 먼 것 순)
50
50
  */
51
51
  getParents(): Element[];
52
52
 
53
53
  /**
54
- * Find first focusable parent element (using tabbable)
54
+ * 번째 포커스 가능한 부모 요소 검색 (tabbable 사용)
55
55
  *
56
- * @returns First focusable parent element or undefined
56
+ * @returns 번째 포커스 가능한 부모 요소 또는 undefined
57
57
  */
58
58
  findFocusableParent(): HTMLElement | undefined;
59
59
 
60
60
  /**
61
- * Find first focusable child element (using tabbable)
61
+ * 번째 포커스 가능한 자식 요소 검색 (tabbable 사용)
62
62
  *
63
- * @returns First focusable child element or undefined
63
+ * @returns 번째 포커스 가능한 자식 요소 또는 undefined
64
64
  */
65
65
  findFirstFocusableChild(): HTMLElement | undefined;
66
66
 
67
67
  /**
68
- * Check if element is an offset parent (position: relative/absolute/fixed/sticky)
68
+ * 요소가 offset parent인지 확인 (position: relative/absolute/fixed/sticky)
69
69
  *
70
- * @returns true if position property is one of relative, absolute, fixed, or sticky
70
+ * @returns position 속성이 relative, absolute, fixed, sticky 중 하나이면 true
71
71
  */
72
72
  isOffsetElement(): boolean;
73
73
 
74
74
  /**
75
- * Check if element is visible on screen
75
+ * 요소가 화면에 보이는지 확인
76
76
  *
77
77
  * @remarks
78
- * Checks existence of clientRects, visibility: hidden, and opacity: 0.
78
+ * clientRects 존재 여부, visibility: hidden, opacity: 0을 확인합니다.
79
79
  *
80
- * @returns true if element is visible on screen
80
+ * @returns 요소가 화면에 보이면 true
81
81
  */
82
82
  isVisible(): boolean;
83
83
  }
@@ -144,13 +144,13 @@ Element.prototype.isVisible = function (): boolean {
144
144
  };
145
145
 
146
146
  // ============================================================================
147
- // Static functions (for event handlers or multiple elements)
147
+ // 정적 함수 (이벤트 핸들러 또는 다중 요소용)
148
148
  // ============================================================================
149
149
 
150
150
  /**
151
- * Copy element content to clipboard (use with copy event handler)
151
+ * 요소 내용을 클립보드에 복사 (copy 이벤트 핸들러와 함께 사용)
152
152
  *
153
- * @param event - copy event object
153
+ * @param event - copy 이벤트 객체
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
- * Paste clipboard content to element (use with paste event handler)
170
+ * 클립보드 내용을 요소에 붙여넣기 (paste 이벤트 핸들러와 함께 사용)
171
171
  *
172
172
  * @remarks
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.
173
+ * 대상 요소 내의 첫 번째 input/textarea 찾아 전체 값을 클립보드 내용으로 교체합니다.
174
+ * 커서 위치나 선택 영역은 고려하지 않습니다.
175
175
  *
176
- * @param event - paste event object
176
+ * @param event - paste 이벤트 객체
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
- * Get bounds information for elements using IntersectionObserver
194
+ * IntersectionObserver를 사용하여 요소의 경계 정보 조회
195
195
  *
196
- * @param els - Array of target elements
197
- * @param timeout - Timeout in milliseconds (default: 5000)
198
- * @throws {TimeoutError} If no response within timeout duration
196
+ * @param els - 대상 요소 배열
197
+ * @param timeout - 타임아웃 밀리초 (기본값: 5000)
198
+ * @throws {TimeoutError} 타임아웃 시간 내에 응답이 없는 경우
199
199
  */
200
200
  export async function getBounds(els: Element[], timeout: number = 5000): Promise<ElementBounds[]> {
201
- // Index map to remove duplicates and sort results in input order
201
+ // 중복 제거 입력 순서대로 결과 정렬을 위한 인덱스
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
- // Set to track remaining elements
207
+ // 남은 요소를 추적하기 위한 Set
208
208
  const remaining = new Set(indexMap.keys());
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 (remaining.size === 0) {
233
233
  observer?.disconnect();
234
- // Sort in input order
234
+ // 입력 순서대로 정렬
235
235
  resolve(
236
236
  results.sort((a, b) => indexMap.get(a.target)! - indexMap.get(b.target)!),
237
237
  );
@@ -3,44 +3,44 @@ import { ArgumentError } from "@simplysm/core-common";
3
3
  declare global {
4
4
  interface HTMLElement {
5
5
  /**
6
- * Force repaint (triggers reflow)
6
+ * 강제 리페인트 (reflow 트리거)
7
7
  */
8
8
  repaint(): void;
9
9
 
10
10
  /**
11
- * Calculate relative position based on parent element (for CSS positioning)
11
+ * 부모 요소 기준 상대 위치 계산 (CSS 포지셔닝용)
12
12
  *
13
13
  * @remarks
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.
14
+ * 부모 요소 기준으로 요소 위치를 계산하며, CSS `top`/`left` 속성에 바로 사용할 수 있는
15
+ * `window.scrollX/Y`를 포함한 문서 기반 좌표를 반환합니다.
16
16
  *
17
- * Common use cases:
18
- * - Position dropdowns, popups after appending to `document.body`
19
- * - Works correctly on scrolled pages
17
+ * 주요 사용 사례:
18
+ * - `document.body`에 append한 드롭다운, 팝업 위치 지정
19
+ * - 스크롤된 페이지에서도 정상 동작
20
20
  *
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
21
+ * 계산에 포함되는 요소:
22
+ * - 뷰포트 기준 위치 (getBoundingClientRect)
23
+ * - 문서 스크롤 위치 (window.scrollX/Y)
24
+ * - 부모 요소 내부 스크롤 (parentEl.scrollTop/Left)
25
+ * - 중간 요소의 border 두께
26
+ * - CSS transform 변환
27
27
  *
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
28
+ * @param parent - 기준이 되는 부모 요소 또는 선택자 (예: document.body, ".container")
29
+ * @returns CSS top/left 속성에 사용 가능한 좌표
30
+ * @throws {ArgumentError} 부모 요소를 찾을 없는 경우
31
31
  */
32
32
  getRelativeOffset(parent: HTMLElement | string): { top: number; left: number };
33
33
 
34
34
  /**
35
- * Scroll to make target visible if hidden by offset area (e.g., fixed header/column)
35
+ * offset 영역(예: 고정 헤더/컬럼)에 가려진 경우 대상이 보이도록 스크롤
36
36
  *
37
37
  * @remarks
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.
38
+ * 대상이 스크롤 영역의 상단/좌측 경계를 벗어나는 경우만 처리합니다.
39
+ * 하단/우측 방향 스크롤이 필요한 경우는 브라우저의 기본 포커스 스크롤 동작에 의존합니다.
40
+ * 주로 고정 헤더나 컬럼이 있는 테이블의 포커스 이벤트에서 사용됩니다.
41
41
  *
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)
42
+ * @param target - 컨테이너 대상 위치 (offsetTop, offsetLeft)
43
+ * @param offset - 가려지면 되는 영역의 크기 (예: 고정 헤더 높이, 고정 컬럼 너비)
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
- // Accessing offsetHeight triggers forced synchronous layout in browser,
54
- // causing style changes in current layout to be applied immediately and triggering repaint.
53
+ // offsetHeight 접근하면 브라우저에서 강제 동기 레이아웃이 트리거되어,
54
+ // 현재 레이아웃의 스타일 변경이 즉시 적용되고 리페인트가 발생합니다.
55
55
  void this.offsetHeight;
56
56
  };
57
57
 
package/src/index.ts CHANGED
@@ -1,10 +1,10 @@
1
- // core-browser: Browser-only utilities
1
+ // core-browser: 브라우저 전용 유틸리티
2
2
 
3
- // extensions (side-effect)
3
+ // 확장 (사이드 이펙트)
4
4
  import "./extensions/element-ext";
5
5
  import "./extensions/html-element-ext";
6
6
 
7
- // re-exports
7
+ // 재내보내기
8
8
  export * from "./extensions/element-ext";
9
9
  export * from "./extensions/html-element-ext";
10
10
  export * from "./utils/download";
@@ -53,7 +53,7 @@ export class IndexedDbStore {
53
53
  };
54
54
  req.onblocked = () => {
55
55
  this._opening = undefined;
56
- reject(new Error("Database blocked by another connection"));
56
+ reject(new Error("다른 연결에 의해 데이터베이스가 차단되었습니다"));
57
57
  };
58
58
  });
59
59
 
@@ -69,7 +69,7 @@ export class IndexedDbStore {
69
69
  const tx = db.transaction(storeName, mode);
70
70
  const store = tx.objectStore(storeName);
71
71
 
72
- // Await fn result first
72
+ // 먼저 fn 결과를 대기
73
73
  let result: TResult;
74
74
  let fnError: unknown;
75
75
  let hasFnError = false;
@@ -81,7 +81,7 @@ export class IndexedDbStore {
81
81
  tx.abort();
82
82
  }
83
83
 
84
- // Wait for transaction completion
84
+ // 트랜잭션 완료 대기
85
85
  return new Promise<TResult>((resolve, reject) => {
86
86
  if (hasFnError) {
87
87
  tx.onabort = () => {
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Download Blob as file
2
+ * Blob 파일로 다운로드
3
3
  *
4
- * @param blob - Blob object to download
5
- * @param fileName - File name to save as
4
+ * @param blob - 다운로드할 Blob 객체
5
+ * @param fileName - 저장할 파일명
6
6
  */
7
7
  export function downloadBlob(blob: Blob, fileName: string): void {
8
8
  const url = URL.createObjectURL(blob);
@@ -6,7 +6,7 @@ export interface DownloadProgress {
6
6
  }
7
7
 
8
8
  /**
9
- * Download binary data from URL (with progress callback support)
9
+ * URL에서 바이너리 데이터 다운로드 (진행 콜백 지원)
10
10
  */
11
11
  export async function fetchUrlBytes(
12
12
  url: string,
@@ -14,17 +14,17 @@ export async function fetchUrlBytes(
14
14
  ): Promise<Uint8Array> {
15
15
  const response = await fetch(url);
16
16
  if (!response.ok) {
17
- throw new Error(`Download failed: ${response.status} ${response.statusText}`);
17
+ throw new Error(`다운로드 실패: ${response.status} ${response.statusText}`);
18
18
  }
19
19
 
20
20
  const contentLength = Number(response.headers.get("Content-Length") ?? 0);
21
21
  const reader = response.body?.getReader();
22
22
  if (!reader) {
23
- throw new Error("Response body is not readable");
23
+ throw new Error("응답 본문을 읽을 없습니다");
24
24
  }
25
25
 
26
26
  try {
27
- // If Content-Length is known, pre-allocate for memory efficiency
27
+ // Content-Length 있는 경우, 메모리 효율을 위해 사전 할당
28
28
  if (contentLength > 0) {
29
29
  const result = new Uint8Array(contentLength);
30
30
  let receivedLength = 0;
@@ -33,15 +33,27 @@ export async function fetchUrlBytes(
33
33
  const { done, value } = await reader.read();
34
34
  if (done) break;
35
35
 
36
+ if (receivedLength + value.length > contentLength) {
37
+ throw new Error(
38
+ `수신 데이터가 Content-Length를 초과했습니다 (Content-Length: ${contentLength}, 수신: ${receivedLength + value.length}+)`,
39
+ );
40
+ }
41
+
36
42
  result.set(value, receivedLength);
37
43
  receivedLength += value.length;
38
44
  options?.onProgress?.({ receivedLength, contentLength });
39
45
  }
40
46
 
47
+ if (receivedLength < contentLength) {
48
+ throw new Error(
49
+ `수신 데이터가 Content-Length보다 부족합니다 (Content-Length: ${contentLength}, 수신: ${receivedLength})`,
50
+ );
51
+ }
52
+
41
53
  return result;
42
54
  }
43
55
 
44
- // If Content-Length is unknown, collect chunks then merge (chunked encoding)
56
+ // Content-Length 없는 경우, 청크를 수집 후 병합 (chunked encoding)
45
57
  const chunks: Uint8Array[] = [];
46
58
 
47
59
  while (true) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Programmatically open file selection dialog
2
+ * 프로그래밍 방식으로 파일 선택 대화상자 열기
3
3
  */
4
4
  export function openFileDialog(options?: {
5
5
  accept?: string;
package/README.md DELETED
@@ -1,106 +0,0 @@
1
- # @simplysm/core-browser
2
-
3
- Simplysm package - Core module (browser). Browser-only utilities including DOM extensions, file downloads, IndexedDB storage, and virtual file system.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @simplysm/core-browser
9
- ```
10
-
11
- ## Side-Effect Imports
12
-
13
- This package includes side-effect imports that augment global prototypes when the module is loaded:
14
-
15
- - `Element.prototype` -- Adds `findAll`, `findFirst`, `prependChild`, `getParents`, `findFocusableParent`, `findFirstFocusableChild`, `isOffsetElement`, `isVisible`.
16
- - `HTMLElement.prototype` -- Adds `repaint`, `getRelativeOffset`, `scrollIntoViewIfNeeded`.
17
-
18
- These side effects run automatically when you import from `@simplysm/core-browser`.
19
-
20
- ## API Overview
21
-
22
- ### Element Extensions
23
-
24
- | API | Type | Description |
25
- |-----|------|-------------|
26
- | `ElementBounds` | interface | Element bounds info (`target`, `top`, `left`, `width`, `height`) |
27
- | `Element.findAll` | prototype method | Find all child elements matching a CSS selector |
28
- | `Element.findFirst` | prototype method | Find first element matching a CSS selector |
29
- | `Element.prependChild` | prototype method | Insert element as first child |
30
- | `Element.getParents` | prototype method | Get all parent elements (closest to farthest) |
31
- | `Element.findFocusableParent` | prototype method | Find first focusable parent element |
32
- | `Element.findFirstFocusableChild` | prototype method | Find first focusable child element |
33
- | `Element.isOffsetElement` | prototype method | Check if element has offset positioning |
34
- | `Element.isVisible` | prototype method | Check if element is visible on screen |
35
- | `copyElement` | function | Copy element content to clipboard via ClipboardEvent |
36
- | `pasteToElement` | function | Paste clipboard content to element via ClipboardEvent |
37
- | `getBounds` | function | Get bounds for multiple elements using IntersectionObserver |
38
-
39
- -> See [docs/element-extensions.md](./docs/element-extensions.md) for details.
40
-
41
- ### HTMLElement Extensions
42
-
43
- | API | Type | Description |
44
- |-----|------|-------------|
45
- | `HTMLElement.repaint` | prototype method | Force repaint (triggers reflow) |
46
- | `HTMLElement.getRelativeOffset` | prototype method | Calculate position relative to a parent element |
47
- | `HTMLElement.scrollIntoViewIfNeeded` | prototype method | Scroll to make target visible if obscured |
48
-
49
- -> See [docs/html-element-extensions.md](./docs/html-element-extensions.md) for details.
50
-
51
- ### Utilities
52
-
53
- | API | Type | Description |
54
- |-----|------|-------------|
55
- | `downloadBlob` | function | Download a Blob as a file |
56
- | `DownloadProgress` | interface | Download progress info (`receivedLength`, `contentLength`) |
57
- | `fetchUrlBytes` | function | Download binary data from URL with progress callback |
58
- | `openFileDialog` | function | Programmatically open file selection dialog |
59
-
60
- -> See [docs/utilities.md](./docs/utilities.md) for details.
61
-
62
- ### Classes
63
-
64
- | API | Type | Description |
65
- |-----|------|-------------|
66
- | `StoreConfig` | interface | IndexedDB store configuration (`name`, `keyPath`) |
67
- | `IndexedDbStore` | class | IndexedDB wrapper for key-value storage |
68
- | `VirtualFsEntry` | interface | Virtual file system entry (`kind`, `dataBase64`) |
69
- | `IndexedDbVirtualFs` | class | IndexedDB-backed virtual file system |
70
-
71
- -> See [docs/classes.md](./docs/classes.md) for details.
72
-
73
- ## Usage Examples
74
-
75
- ### Download a file
76
-
77
- ```typescript
78
- import { downloadBlob } from "@simplysm/core-browser";
79
-
80
- const blob = new Blob(["Hello"], { type: "text/plain" });
81
- downloadBlob(blob, "hello.txt");
82
- ```
83
-
84
- ### Open file dialog and read files
85
-
86
- ```typescript
87
- import { openFileDialog } from "@simplysm/core-browser";
88
-
89
- const files = await openFileDialog({ accept: ".csv", multiple: true });
90
- if (files) {
91
- for (const file of files) {
92
- const text = await file.text();
93
- }
94
- }
95
- ```
96
-
97
- ### Use IndexedDB store
98
-
99
- ```typescript
100
- import { IndexedDbStore } from "@simplysm/core-browser";
101
-
102
- const store = new IndexedDbStore("myApp", 1, [{ name: "settings", keyPath: "key" }]);
103
- await store.put("settings", { key: "theme", value: "dark" });
104
- const item = await store.get<{ key: string; value: string }>("settings", "theme");
105
- store.close();
106
- ```