@simplysm/core-browser 14.0.51 → 14.0.53

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 (2) hide show
  1. package/package.json +2 -2
  2. package/README.md +0 -306
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/core-browser",
3
- "version": "14.0.51",
3
+ "version": "14.0.53",
4
4
  "description": "심플리즘 패키지 - 코어 (browser)",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -27,6 +27,6 @@
27
27
  "dependencies": {
28
28
  "sanitize-filename": "^1.6.4",
29
29
  "tabbable": "^6.4.0",
30
- "@simplysm/core-common": "14.0.51"
30
+ "@simplysm/core-common": "14.0.53"
31
31
  }
32
32
  }
package/README.md DELETED
@@ -1,306 +0,0 @@
1
- # @simplysm/core-browser
2
-
3
- 브라우저 전용 유틸리티 패키지. DOM 프로토타입 확장, 파일 다운로드/업로드, HTTP fetch, IndexedDB 추상화를 제공한다.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @simplysm/core-browser
9
- ```
10
-
11
- ## API Overview
12
-
13
- ### Extensions
14
-
15
- 사이드 이펙트 모듈로, 패키지를 임포트하면 자동으로 `Element`와 `HTMLElement` 프로토타입에 메서드가 추가된다.
16
-
17
- | API | Type | Description |
18
- |-----|------|-------------|
19
- | `ElementBounds` | interface | 요소 경계 정보 (target, top, left, width, height) |
20
- | `copyElement` | function | copy 이벤트 핸들러에서 요소 내 input/textarea 값을 클립보드에 복사 |
21
- | `pasteToElement` | function | paste 이벤트 핸들러에서 클립보드 텍스트를 요소 내 input/textarea에 붙여넣기 |
22
- | `getBounds` | function | IntersectionObserver로 여러 요소의 경계 정보를 비동기 조회 |
23
- | `Element.prototype.findAll` | prototype extension | 선택자와 일치하는 모든 하위 요소 검색 |
24
- | `Element.prototype.findFirst` | prototype extension | 선택자와 일치하는 첫 번째 요소 검색 |
25
- | `Element.prototype.prependChild` | prototype extension | 요소를 첫 번째 자식으로 삽입 |
26
- | `Element.prototype.getParents` | prototype extension | 모든 부모 요소를 가까운 순서로 조회 |
27
- | `Element.prototype.findTabbableParent` | prototype extension | 첫 번째 탭 이동 가능한 부모 요소 검색 (tabbable 사용) |
28
- | `Element.prototype.findFirstTabbableChild` | prototype extension | 첫 번째 탭 이동 가능한 자식 요소 검색 (tabbable 사용) |
29
- | `Element.prototype.isOffsetElement` | prototype extension | position이 relative/absolute/fixed/sticky인지 확인 |
30
- | `Element.prototype.isVisible` | prototype extension | 요소가 화면에 보이는지 확인 (clientRects, visibility, opacity) |
31
- | `HTMLElement.prototype.repaint` | prototype extension | 강제 리페인트 트리거 (offsetHeight 접근) |
32
- | `HTMLElement.prototype.getRelativeOffset` | prototype extension | 부모 요소 기준 상대 위치 계산 (CSS top/left 용) |
33
- | `HTMLElement.prototype.scrollIntoViewIfNeeded` | prototype extension | offset 영역에 가려진 경우 대상이 보이도록 스크롤 |
34
-
35
- #### `ElementBounds`
36
-
37
- ```typescript
38
- export interface ElementBounds {
39
- target: Element;
40
- top: number;
41
- left: number;
42
- width: number;
43
- height: number;
44
- }
45
- ```
46
-
47
- | Field | Type | Description |
48
- |-------|------|-------------|
49
- | `target` | `Element` | 측정 대상 요소 |
50
- | `top` | `number` | 뷰포트 기준 상단 위치 |
51
- | `left` | `number` | 뷰포트 기준 좌측 위치 |
52
- | `width` | `number` | 요소 너비 |
53
- | `height` | `number` | 요소 높이 |
54
-
55
- #### `copyElement`
56
-
57
- ```typescript
58
- export function copyElement(event: ClipboardEvent): void
59
- ```
60
-
61
- copy 이벤트 핸들러와 함께 사용한다. 이벤트 타겟 요소 내의 첫 번째 `input` 또는 `textarea`를 찾아 그 값을 클립보드에 설정한다. 해당 요소가 없으면 아무 동작도 하지 않는다.
62
-
63
- #### `pasteToElement`
64
-
65
- ```typescript
66
- export function pasteToElement(event: ClipboardEvent): void
67
- ```
68
-
69
- paste 이벤트 핸들러와 함께 사용한다. 이벤트 타겟 요소 내의 첫 번째 `input` 또는 `textarea`를 찾아 값 전체를 클립보드 텍스트로 교체한다. 커서 위치나 선택 영역은 고려하지 않는다. `input` 이벤트를 dispatch한다.
70
-
71
- #### `getBounds`
72
-
73
- ```typescript
74
- export async function getBounds(els: Element[], timeout?: number): Promise<ElementBounds[]>
75
- ```
76
-
77
- IntersectionObserver를 사용하여 여러 요소의 경계 정보를 비동기로 조회한다. 중복 요소는 자동 제거되며, 입력 순서대로 결과가 정렬된다. `timeout` 기본값은 5000ms이며, 시간 내에 응답이 없으면 `TimeoutError`를 던진다.
78
-
79
- #### Element Prototype Extensions
80
-
81
- `Element.prototype`에 추가되는 메서드들:
82
-
83
- ```typescript
84
- // 선택자와 일치하는 모든 하위 요소 검색 (빈 선택자 -> 빈 배열)
85
- element.findAll<TEl extends Element = Element>(selector: string): TEl[]
86
-
87
- // 선택자와 일치하는 첫 번째 요소 (빈 선택자 -> undefined)
88
- element.findFirst<TEl extends Element = Element>(selector: string): TEl | undefined
89
-
90
- // 요소를 첫 번째 자식으로 삽입
91
- element.prependChild<TEl extends Element>(child: TEl): TEl
92
-
93
- // 모든 부모 요소 배열 (가까운 순서)
94
- element.getParents(): Element[]
95
-
96
- // 첫 번째 탭 이동 가능한 부모 요소 (tabbable 라이브러리 사용)
97
- element.findTabbableParent(): HTMLElement | undefined
98
-
99
- // 첫 번째 탭 이동 가능한 자식 요소 (tabbable 라이브러리 사용)
100
- element.findFirstTabbableChild(): HTMLElement | undefined
101
-
102
- // position이 relative/absolute/fixed/sticky인지 확인
103
- element.isOffsetElement(): boolean
104
-
105
- // 화면에 보이는지 확인 (clientRects, visibility, opacity)
106
- element.isVisible(): boolean
107
- ```
108
-
109
- #### HTMLElement Prototype Extensions
110
-
111
- `HTMLElement.prototype`에 추가되는 메서드들:
112
-
113
- ```typescript
114
- // 강제 리페인트 (offsetHeight 접근으로 reflow 트리거)
115
- htmlElement.repaint(): void
116
-
117
- // 부모 요소 기준 상대 위치 계산 (CSS top/left에 사용 가능)
118
- // parent: HTMLElement 또는 CSS 선택자 문자열
119
- // border 두께, CSS transform 변환 포함
120
- // 부모를 찾을 수 없으면 ArgumentError throw
121
- htmlElement.getRelativeOffset(parent: HTMLElement | string): { top: number; left: number }
122
-
123
- // offset 영역에 가려진 경우 스크롤 조정
124
- // target: 컨테이너 내 대상 위치 (offsetTop, offsetLeft)
125
- // offset: 가려지면 안 되는 영역 크기 (기본값 { top: 0, left: 0 })
126
- // 상단/좌측 방향만 처리, 하단/우측은 브라우저 기본 동작에 의존
127
- htmlElement.scrollIntoViewIfNeeded(
128
- target: { top: number; left: number },
129
- offset?: { top: number; left: number }
130
- ): void
131
- ```
132
-
133
- ### Utils
134
-
135
- | API | Type | Description |
136
- |-----|------|-------------|
137
- | `downloadBlob` | function | Blob을 파일로 다운로드 (링크 클릭 방식) |
138
- | `DownloadProgress` | interface | 다운로드 진행 정보 (receivedLength, contentLength) |
139
- | `fetchUrlBytes` | function | URL에서 Uint8Array 다운로드 (진행 콜백 지원) |
140
- | `openFileDialog` | function | 파일 선택 대화상자를 프로그래밍 방식으로 열기 |
141
- | `StoreConfig` | interface | IndexedDbStore 스토어 설정 (name, keyPath) |
142
- | `IndexedDbStore` | class | IndexedDB를 Promise 기반으로 래핑한 저수준 CRUD 클래스 |
143
- | `VirtualFsEntry` | interface | 가상 파일시스템 엔트리 (kind, dataBase64) |
144
- | `IndexedDbVirtualFs` | class | IndexedDB 기반 경로 기반 가상 파일시스템 |
145
-
146
- #### `downloadBlob`
147
-
148
- ```typescript
149
- export function downloadBlob(blob: Blob, fileName: string): void
150
- ```
151
-
152
- Blob을 파일로 다운로드한다. `<a>` 태그를 생성하여 클릭하는 방식이다. Object URL은 1초 후 자동 해제된다. `fileName`의 파일시스템 금지 문자(`/ \ : * ? " < > |`), 제어문자, Windows 예약어(CON, PRN 등)는 자동 제거되며, 제거 후 빈 문자열이 되면 `download`로 대체된다.
153
-
154
- #### `DownloadProgress`
155
-
156
- ```typescript
157
- export interface DownloadProgress {
158
- receivedLength: number;
159
- contentLength: number;
160
- }
161
- ```
162
-
163
- | Field | Type | Description |
164
- |-------|------|-------------|
165
- | `receivedLength` | `number` | 현재까지 수신한 바이트 수 |
166
- | `contentLength` | `number` | 전체 콘텐츠 길이 (Content-Length 헤더 값) |
167
-
168
- #### `fetchUrlBytes`
169
-
170
- ```typescript
171
- export async function fetchUrlBytes(
172
- url: string,
173
- options?: { onProgress?: (progress: DownloadProgress) => void },
174
- ): Promise<Uint8Array>
175
- ```
176
-
177
- URL에서 바이너리 데이터를 `Uint8Array`로 다운로드한다. `Content-Length` 헤더가 있으면 사전 할당으로 메모리 효율을 높이고, 없으면 청크 수집 후 `bytes.concat`으로 병합한다. 진행 콜백은 `options.onProgress`로 수신한다. 응답이 실패하면 Error를 던진다.
178
-
179
- #### `openFileDialog`
180
-
181
- ```typescript
182
- export function openFileDialog(options?: {
183
- accept?: string;
184
- multiple?: boolean;
185
- }): Promise<File[] | undefined>
186
- ```
187
-
188
- 프로그래밍 방식으로 파일 선택 대화상자를 연다. `accept`로 파일 형식 필터, `multiple`로 다중 선택 여부를 지정한다. 파일 선택 시 `File[]` 반환, 취소 시 `undefined` 반환.
189
-
190
- #### `StoreConfig`
191
-
192
- ```typescript
193
- export interface StoreConfig {
194
- name: string;
195
- keyPath: string;
196
- }
197
- ```
198
-
199
- | Field | Type | Description |
200
- |-------|------|-------------|
201
- | `name` | `string` | object store 이름 |
202
- | `keyPath` | `string` | 기본 키 경로 |
203
-
204
- #### `IndexedDbStore`
205
-
206
- ```typescript
207
- export class IndexedDbStore {
208
- constructor(dbName: string, dbVersion: number, storeConfigs: StoreConfig[])
209
-
210
- open(): Promise<IDBDatabase>
211
- withStore<TResult>(storeName: string, mode: IDBTransactionMode, fn: (store: IDBObjectStore) => Promise<TResult>): Promise<TResult>
212
- get<TValue>(storeName: string, key: IDBValidKey): Promise<TValue | undefined>
213
- put(storeName: string, value: unknown): Promise<void>
214
- delete(storeName: string, key: IDBValidKey): Promise<void>
215
- getAll<TItem>(storeName: string): Promise<TItem[]>
216
- close(): void
217
- }
218
- ```
219
-
220
- IndexedDB를 Promise 기반으로 래핑한 저수준 클래스. 생성자에서 DB 이름, 버전, 스토어 설정을 받는다.
221
-
222
- | Method | Description |
223
- |--------|-------------|
224
- | `open()` | DB 연결을 열고 `IDBDatabase`를 반환. 중복 호출에 안전 (이미 열려 있으면 기존 인스턴스 반환) |
225
- | `withStore(storeName, mode, fn)` | 지정 스토어에서 트랜잭션을 열고 `fn`을 실행. fn이 에러를 던지면 트랜잭션을 abort한다 |
226
- | `get(storeName, key)` | 키로 단일 항목 조회. 없으면 `undefined` |
227
- | `put(storeName, value)` | 항목 추가/갱신 (keyPath에 해당하는 필드가 value에 포함되어야 함) |
228
- | `delete(storeName, key)` | 키로 항목 삭제 |
229
- | `getAll(storeName)` | 스토어의 모든 항목 조회 |
230
- | `close()` | DB 연결 닫기 |
231
-
232
- #### `VirtualFsEntry`
233
-
234
- ```typescript
235
- export interface VirtualFsEntry {
236
- kind: "file" | "dir";
237
- dataBase64?: string;
238
- }
239
- ```
240
-
241
- | Field | Type | Description |
242
- |-------|------|-------------|
243
- | `kind` | `"file" \| "dir"` | 엔트리 종류 (파일 또는 디렉토리) |
244
- | `dataBase64` | `string \| undefined` | 파일 데이터 (Base64 인코딩, 디렉토리인 경우 없음) |
245
-
246
- #### `IndexedDbVirtualFs`
247
-
248
- ```typescript
249
- export class IndexedDbVirtualFs {
250
- constructor(db: IndexedDbStore, storeName: string, keyField: string)
251
-
252
- getEntry(fullKey: string): Promise<VirtualFsEntry | undefined>
253
- putEntry(fullKey: string, kind: "file" | "dir", dataBase64?: string): Promise<void>
254
- deleteByPrefix(keyPrefix: string): Promise<boolean>
255
- listChildren(prefix: string): Promise<{ name: string; isDirectory: boolean }[]>
256
- ensureDir(fullKeyBuilder: (path: string) => string, dirPath: string): Promise<void>
257
- }
258
- ```
259
-
260
- `IndexedDbStore` 위에 경로 기반 가상 파일시스템을 구현하는 클래스. 키는 `/path/to/file` 형태의 문자열.
261
-
262
- | Method | Description |
263
- |--------|-------------|
264
- | `getEntry(fullKey)` | 경로에 해당하는 엔트리 조회. 없으면 `undefined` |
265
- | `putEntry(fullKey, kind, dataBase64?)` | 엔트리 추가/갱신. `kind`는 `"file"` 또는 `"dir"` |
266
- | `deleteByPrefix(keyPrefix)` | 접두사와 일치하는 모든 엔트리 삭제. 삭제된 항목이 있으면 `true` |
267
- | `listChildren(prefix)` | 접두사 바로 아래 자식 목록 반환. 이름과 디렉토리 여부 포함 |
268
- | `ensureDir(fullKeyBuilder, dirPath)` | 경로의 모든 중간 디렉토리를 재귀적으로 생성. 이미 있으면 건너뜀 |
269
-
270
- ## Usage Examples
271
-
272
- ### Blob 파일 다운로드
273
-
274
- ```typescript
275
- import { downloadBlob } from "@simplysm/core-browser";
276
-
277
- const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
278
- const blob = new Blob([data], { type: "application/octet-stream" });
279
- downloadBlob(blob, "output.bin");
280
- ```
281
-
282
- ### IndexedDB CRUD
283
-
284
- ```typescript
285
- import { IndexedDbStore } from "@simplysm/core-browser";
286
-
287
- const store = new IndexedDbStore("myDb", 1, [{ name: "items", keyPath: "id" }]);
288
-
289
- await store.put("items", { id: "key1", value: "hello" });
290
- const item = await store.get<{ id: string; value: string }>("items", "key1");
291
- const all = await store.getAll<{ id: string; value: string }>("items");
292
- await store.delete("items", "key1");
293
- store.close();
294
- ```
295
-
296
- ### DOM 프로토타입 확장 사용
297
-
298
- ```typescript
299
- import "@simplysm/core-browser";
300
-
301
- const container = document.querySelector(".container")!;
302
- const buttons = container.findAll<HTMLButtonElement>("button");
303
- const firstInput = container.findFirst<HTMLInputElement>("input[type=text]");
304
- const parents = container.getParents();
305
- const focusable = container.findFirstTabbableChild();
306
- ```