@simplysm/core-browser 14.0.4 → 14.0.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @simplysm/core-browser
2
2
 
3
- Core browser utilities -- DOM extensions, file operations, IndexedDB wrappers.
3
+ Browser-specific core utilities for the Simplysm framework. Provides DOM element extensions, download/fetch helpers, file dialog, and IndexedDB abstractions.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,217 +8,144 @@ Core browser utilities -- DOM extensions, file operations, IndexedDB wrappers.
8
8
  npm install @simplysm/core-browser
9
9
  ```
10
10
 
11
- ## API
11
+ ## API Overview
12
12
 
13
- ### Extensions
13
+ ### Element Extensions
14
14
 
15
- | Export | Kind | Description |
16
- |--------|------|-------------|
17
- | `ElementBounds` | interface | Element bounding rectangle data |
18
- | Element prototype extensions | side-effect import | DOM query/traversal helpers added to `Element.prototype` |
19
- | HTMLElement prototype extensions | side-effect import | Repaint, relative offset, scroll helpers added to `HTMLElement.prototype` |
20
- | `copyElement` | function | Copy element content to clipboard |
21
- | `pasteToElement` | function | Paste clipboard content to element |
22
- | `getBounds` | async function | Get element bounds via IntersectionObserver |
15
+ | API | Type | Description |
16
+ |-----|------|-------------|
17
+ | `ElementBounds` | interface | Bounding rect info for an element |
18
+ | `Element.prototype.findAll` | method | Find all descendant elements matching a CSS selector |
19
+ | `Element.prototype.findFirst` | method | Find the first descendant element matching a CSS selector |
20
+ | `Element.prototype.prependChild` | method | Insert a child as the first element child |
21
+ | `Element.prototype.getParents` | method | Get all ancestor elements (nearest first) |
22
+ | `Element.prototype.findFocusableParent` | method | Find the nearest focusable ancestor |
23
+ | `Element.prototype.findFirstFocusableChild` | method | Find the first focusable descendant |
24
+ | `Element.prototype.isOffsetElement` | method | Check if element is an offset parent |
25
+ | `Element.prototype.isVisible` | method | Check if element is visible on screen |
26
+ | `copyElement` | function | Copy element content to clipboard via ClipboardEvent |
27
+ | `pasteToElement` | function | Paste clipboard content into element via ClipboardEvent |
28
+ | `getBounds` | function | Get bounding info for multiple elements using IntersectionObserver |
23
29
 
24
- ### Utils
30
+ > See [docs/element-extensions.md](./docs/element-extensions.md) for details.
25
31
 
26
- | Export | Kind | Description |
27
- |--------|------|-------------|
28
- | `downloadBlob` | function | Download a Blob as a file |
29
- | `DownloadProgress` | interface | Progress info for byte fetching |
30
- | `fetchUrlBytes` | async function | Fetch URL content as `Uint8Array` with progress |
31
- | `openFileDialog` | function | Open a native file picker dialog |
32
- | `StoreConfig` | interface | IndexedDB store configuration |
33
- | `IndexedDbStore` | class | IndexedDB key-value store wrapper |
34
- | `VirtualFsEntry` | interface | Virtual filesystem entry descriptor |
35
- | `IndexedDbVirtualFs` | class | Virtual filesystem backed by IndexedDB |
36
-
37
- ## Extensions
38
-
39
- ### ElementBounds (interface)
40
-
41
- ```ts
42
- interface ElementBounds {
43
- target: Element;
44
- top: number;
45
- left: number;
46
- width: number;
47
- height: number;
48
- }
49
- ```
50
-
51
- ### Element prototype extensions (side-effect import)
52
-
53
- Import `@simplysm/core-browser` to activate these extensions on `Element.prototype`.
32
+ ### HTML Element Extensions
54
33
 
55
- | Method | Signature | Description |
56
- |--------|-----------|-------------|
57
- | `findAll` | `findAll<TEl>(selector: string): TEl[]` | Find all matching descendants |
58
- | `findFirst` | `findFirst<TEl>(selector: string): TEl \| undefined` | Find first matching descendant |
59
- | `prependChild` | `prependChild<TEl>(child: TEl): TEl` | Prepend a child element |
60
- | `getParents` | `getParents(): Element[]` | Get all ancestor elements |
61
- | `findFocusableParent` | `findFocusableParent(): HTMLElement \| undefined` | Find nearest focusable ancestor |
62
- | `findFirstFocusableChild` | `findFirstFocusableChild(): HTMLElement \| undefined` | Find first focusable descendant |
63
- | `isOffsetElement` | `isOffsetElement(): boolean` | Check if element is an offset parent |
64
- | `isVisible` | `isVisible(): boolean` | Check if element is visible |
34
+ | API | Type | Description |
35
+ |-----|------|-------------|
36
+ | `HTMLElement.prototype.repaint` | method | Force a synchronous repaint |
37
+ | `HTMLElement.prototype.getRelativeOffset` | method | Calculate position relative to a parent element |
38
+ | `HTMLElement.prototype.scrollIntoViewIfNeeded` | method | Scroll container so target is not hidden by offset areas |
65
39
 
66
- ### HTMLElement prototype extensions (side-effect import)
40
+ > See [docs/html-element-extensions.md](./docs/html-element-extensions.md) for details.
67
41
 
68
- | Method | Signature | Description |
69
- |--------|-----------|-------------|
70
- | `repaint` | `repaint(): void` | Force a repaint of the element |
71
- | `getRelativeOffset` | `getRelativeOffset(parent: HTMLElement \| string): { top: number; left: number }` | Get position relative to a parent |
72
- | `scrollIntoViewIfNeeded` | `scrollIntoViewIfNeeded(target: { top: number; left: number }, offset?: { top: number; left: number }): void` | Scroll to make target position visible |
42
+ ### Download
73
43
 
74
- ### copyElement
75
-
76
- ```ts
77
- function copyElement(event: ClipboardEvent): void
78
- ```
79
-
80
- Copy the content of the focused element to the clipboard via a `ClipboardEvent`.
44
+ | API | Type | Description |
45
+ |-----|------|-------------|
46
+ | `downloadBlob` | function | Download a Blob as a file |
81
47
 
82
- ### pasteToElement
48
+ > See [docs/download.md](./docs/download.md) for details.
83
49
 
84
- ```ts
85
- function pasteToElement(event: ClipboardEvent): void
86
- ```
50
+ ### Fetch
87
51
 
88
- Paste clipboard content into the focused element via a `ClipboardEvent`.
52
+ | API | Type | Description |
53
+ |-----|------|-------------|
54
+ | `DownloadProgress` | interface | Progress info for fetch downloads |
55
+ | `fetchUrlBytes` | function | Download binary data from a URL with progress callback |
89
56
 
90
- ### getBounds
57
+ > See [docs/fetch.md](./docs/fetch.md) for details.
91
58
 
92
- ```ts
93
- async function getBounds(els: Element[], timeout?: number): Promise<ElementBounds[]>
94
- ```
59
+ ### File Dialog
95
60
 
96
- Get bounding rectangles for multiple elements using `IntersectionObserver`. An optional `timeout` (ms) limits the observation duration.
61
+ | API | Type | Description |
62
+ |-----|------|-------------|
63
+ | `openFileDialog` | function | Programmatically open a file picker dialog |
97
64
 
98
- ## Utils
65
+ > See [docs/file-dialog.md](./docs/file-dialog.md) for details.
99
66
 
100
- ### downloadBlob
67
+ ### IndexedDB Store
101
68
 
102
- ```ts
103
- function downloadBlob(blob: Blob, fileName: string): void
104
- ```
69
+ | API | Type | Description |
70
+ |-----|------|-------------|
71
+ | `StoreConfig` | interface | Configuration for an IndexedDB object store |
72
+ | `IndexedDbStore` | class | Generic IndexedDB wrapper with CRUD operations |
105
73
 
106
- Trigger a browser download for the given `Blob` with the specified file name.
74
+ > See [docs/indexed-db-store.md](./docs/indexed-db-store.md) for details.
107
75
 
108
- ### DownloadProgress (interface)
76
+ ### IndexedDB Virtual FS
109
77
 
110
- ```ts
111
- interface DownloadProgress {
112
- receivedLength: number;
113
- contentLength: number;
114
- }
115
- ```
78
+ | API | Type | Description |
79
+ |-----|------|-------------|
80
+ | `VirtualFsEntry` | interface | Entry representing a file or directory in the virtual FS |
81
+ | `IndexedDbVirtualFs` | class | Virtual file system backed by IndexedDB |
116
82
 
117
- ### fetchUrlBytes
83
+ > See [docs/indexed-db-virtual-fs.md](./docs/indexed-db-virtual-fs.md) for details.
118
84
 
119
- ```ts
120
- async function fetchUrlBytes(
121
- url: string,
122
- options?: { onProgress?: (progress: DownloadProgress) => void },
123
- ): Promise<Uint8Array>
124
- ```
85
+ ## Usage Examples
125
86
 
126
- Fetch URL content as a `Uint8Array`. Optionally reports download progress via `onProgress`.
87
+ ### Copy/Paste with ClipboardEvent
127
88
 
128
- ### openFileDialog
89
+ ```typescript
90
+ import { copyElement, pasteToElement } from "@simplysm/core-browser";
129
91
 
130
- ```ts
131
- function openFileDialog(options?: {
132
- accept?: string;
133
- multiple?: boolean;
134
- }): Promise<File[] | undefined>
92
+ document.addEventListener("copy", (event) => copyElement(event));
93
+ document.addEventListener("paste", (event) => pasteToElement(event));
135
94
  ```
136
95
 
137
- Open a native file picker dialog. Returns selected files, or `undefined` if cancelled.
96
+ ### Download a Blob
138
97
 
139
- ### StoreConfig (interface)
98
+ ```typescript
99
+ import { downloadBlob } from "@simplysm/core-browser";
140
100
 
141
- ```ts
142
- interface StoreConfig {
143
- name: string;
144
- keyPath: string;
145
- }
101
+ const blob = new Blob(["hello"], { type: "text/plain" });
102
+ downloadBlob(blob, "hello.txt");
146
103
  ```
147
104
 
148
- ### IndexedDbStore (class)
105
+ ### Fetch binary data with progress
149
106
 
150
- IndexedDB wrapper providing simple key-value operations.
107
+ ```typescript
108
+ import { fetchUrlBytes } from "@simplysm/core-browser";
151
109
 
152
- **Constructor:**
153
-
154
- ```ts
155
- constructor(dbName: string, dbVersion: number, storeConfigs: StoreConfig[])
110
+ const data = await fetchUrlBytes("/api/file.bin", {
111
+ onProgress: ({ receivedLength, contentLength }) => {
112
+ console.log(`${receivedLength} / ${contentLength}`);
113
+ },
114
+ });
156
115
  ```
157
116
 
158
- **Methods:**
117
+ ### Open a file dialog
159
118
 
160
- | Method | Signature | Description |
161
- |--------|-----------|-------------|
162
- | `open` | `open(): Promise<IDBDatabase>` | Open the database connection |
163
- | `withStore` | `withStore<T>(storeName: string, mode: IDBTransactionMode, fn: (store: IDBObjectStore) => IDBRequest<T>): Promise<T>` | Execute a single-store transaction |
164
- | `get` | `get<T>(storeName: string, key: IDBValidKey): Promise<T \| undefined>` | Get a value by key |
165
- | `put` | `put(storeName: string, value: unknown): Promise<void>` | Insert or update a value |
166
- | `delete` | `delete(storeName: string, key: IDBValidKey): Promise<void>` | Delete a value by key |
167
- | `getAll` | `getAll<T>(storeName: string): Promise<T[]>` | Get all values in a store |
168
- | `close` | `close(): void` | Close the database connection |
119
+ ```typescript
120
+ import { openFileDialog } from "@simplysm/core-browser";
169
121
 
170
- ### VirtualFsEntry (interface)
171
-
172
- ```ts
173
- interface VirtualFsEntry {
174
- kind: "file" | "dir";
175
- dataBase64?: string;
122
+ const files = await openFileDialog({ accept: ".csv", multiple: true });
123
+ if (files) {
124
+ for (const file of files) {
125
+ console.log(file.name);
126
+ }
176
127
  }
177
128
  ```
178
129
 
179
- ### IndexedDbVirtualFs (class)
180
-
181
- Virtual filesystem backed by an `IndexedDbStore`. Stores files and directories as entries in IndexedDB.
182
-
183
- **Constructor:**
184
-
185
- ```ts
186
- constructor(db: IndexedDbStore, storeName: string, keyField: string)
187
- ```
188
-
189
- **Methods:**
190
-
191
- | Method | Signature | Description |
192
- |--------|-----------|-------------|
193
- | `getEntry` | `getEntry(fullKey: string): Promise<VirtualFsEntry \| undefined>` | Get a filesystem entry by key |
194
- | `putEntry` | `putEntry(fullKey: string, kind: "file" \| "dir", dataBase64?: string): Promise<void>` | Create or update a filesystem entry |
195
- | `deleteByPrefix` | `deleteByPrefix(keyPrefix: string): Promise<boolean>` | Delete all entries matching a key prefix |
196
- | `listChildren` | `listChildren(prefix: string): Promise<{ name: string; isDirectory: boolean }[]>` | List immediate children of a directory |
197
- | `ensureDir` | `ensureDir(fullKeyBuilder: (dir: string) => string, dirPath: string): Promise<void>` | Recursively ensure a directory path exists |
198
-
199
- ## Usage
200
-
201
- ```ts
202
- import "@simplysm/core-browser"; // activate prototype extensions
130
+ ### IndexedDB Store
203
131
 
204
- // DOM extensions
205
- const el = document.querySelector(".my-element")!;
206
- const parents = el.getParents();
207
- const child = el.findFirst<HTMLDivElement>(".child");
208
-
209
- // File download
210
- import { downloadBlob, fetchUrlBytes } from "@simplysm/core-browser";
211
-
212
- const bytes = await fetchUrlBytes("/api/data.bin", {
213
- onProgress: (p) => console.log(`${p.receivedLength}/${p.contentLength}`),
214
- });
215
- downloadBlob(new Blob([bytes]), "data.bin");
216
-
217
- // IndexedDB store
132
+ ```typescript
218
133
  import { IndexedDbStore } from "@simplysm/core-browser";
219
134
 
220
135
  const store = new IndexedDbStore("myDb", 1, [{ name: "items", keyPath: "id" }]);
221
136
  await store.put("items", { id: "1", value: "hello" });
222
- const item = await store.get("items", "1");
137
+ const item = await store.get<{ id: string; value: string }>("items", "1");
223
138
  store.close();
224
139
  ```
140
+
141
+ ### Get element bounds
142
+
143
+ ```typescript
144
+ import { getBounds } from "@simplysm/core-browser";
145
+
146
+ const elements = document.querySelectorAll(".card");
147
+ const bounds = await getBounds([...elements]);
148
+ for (const b of bounds) {
149
+ console.log(`${b.target.tagName}: ${b.top}, ${b.left}, ${b.width}x${b.height}`);
150
+ }
151
+ ```
@@ -0,0 +1,16 @@
1
+ # Download
2
+
3
+ Utility for triggering file downloads in the browser.
4
+
5
+ ## `downloadBlob`
6
+
7
+ Download a `Blob` as a file by creating a temporary object URL and clicking a hidden anchor element. The object URL is revoked after 1 second.
8
+
9
+ ```typescript
10
+ function downloadBlob(blob: Blob, fileName: string): void
11
+ ```
12
+
13
+ | Parameter | Type | Description |
14
+ |-----------|------|-------------|
15
+ | `blob` | `Blob` | The Blob to download |
16
+ | `fileName` | `string` | File name for the download |
@@ -0,0 +1,158 @@
1
+ # Element Extensions
2
+
3
+ Side-effect import that extends the global `Element` prototype with DOM utility methods, plus standalone clipboard and bounds functions.
4
+
5
+ ## `ElementBounds`
6
+
7
+ Bounding rectangle information for an element, returned by `getBounds`.
8
+
9
+ ```typescript
10
+ interface ElementBounds {
11
+ target: Element;
12
+ top: number;
13
+ left: number;
14
+ width: number;
15
+ height: number;
16
+ }
17
+ ```
18
+
19
+ | Field | Type | Description |
20
+ |-------|------|-------------|
21
+ | `target` | `Element` | The measured element |
22
+ | `top` | `number` | Top position relative to viewport |
23
+ | `left` | `number` | Left position relative to viewport |
24
+ | `width` | `number` | Element width |
25
+ | `height` | `number` | Element height |
26
+
27
+ ## `Element.prototype.findAll`
28
+
29
+ Find all descendant elements matching a CSS selector.
30
+
31
+ ```typescript
32
+ findAll<TEl extends Element = Element>(selector: string): TEl[]
33
+ ```
34
+
35
+ | Parameter | Type | Description |
36
+ |-----------|------|-------------|
37
+ | `selector` | `string` | CSS selector. Returns empty array if empty string. |
38
+
39
+ **Returns:** `TEl[]` -- Array of matching elements.
40
+
41
+ ## `Element.prototype.findFirst`
42
+
43
+ Find the first descendant element matching a CSS selector.
44
+
45
+ ```typescript
46
+ findFirst<TEl extends Element = Element>(selector: string): TEl | undefined
47
+ ```
48
+
49
+ | Parameter | Type | Description |
50
+ |-----------|------|-------------|
51
+ | `selector` | `string` | CSS selector. Returns `undefined` if empty string. |
52
+
53
+ **Returns:** `TEl | undefined` -- First matching element or `undefined`.
54
+
55
+ ## `Element.prototype.prependChild`
56
+
57
+ Insert a child element as the first child.
58
+
59
+ ```typescript
60
+ prependChild<TEl extends Element>(child: TEl): TEl
61
+ ```
62
+
63
+ | Parameter | Type | Description |
64
+ |-----------|------|-------------|
65
+ | `child` | `TEl` | Element to insert |
66
+
67
+ **Returns:** `TEl` -- The inserted child element.
68
+
69
+ ## `Element.prototype.getParents`
70
+
71
+ Get all ancestor elements, ordered from nearest to farthest.
72
+
73
+ ```typescript
74
+ getParents(): Element[]
75
+ ```
76
+
77
+ **Returns:** `Element[]` -- Array of parent elements (nearest first).
78
+
79
+ ## `Element.prototype.findFocusableParent`
80
+
81
+ Find the nearest focusable ancestor element (uses `tabbable` library).
82
+
83
+ ```typescript
84
+ findFocusableParent(): HTMLElement | undefined
85
+ ```
86
+
87
+ **Returns:** `HTMLElement | undefined` -- First focusable parent or `undefined`.
88
+
89
+ ## `Element.prototype.findFirstFocusableChild`
90
+
91
+ Find the first focusable descendant element (uses `tabbable` library).
92
+
93
+ ```typescript
94
+ findFirstFocusableChild(): HTMLElement | undefined
95
+ ```
96
+
97
+ **Returns:** `HTMLElement | undefined` -- First focusable child or `undefined`.
98
+
99
+ ## `Element.prototype.isOffsetElement`
100
+
101
+ Check whether the element is an offset parent (`position: relative | absolute | fixed | sticky`).
102
+
103
+ ```typescript
104
+ isOffsetElement(): boolean
105
+ ```
106
+
107
+ **Returns:** `boolean` -- `true` if element has a positioning CSS property.
108
+
109
+ ## `Element.prototype.isVisible`
110
+
111
+ Check whether the element is visible on screen. Checks `getClientRects()`, `visibility`, and `opacity`.
112
+
113
+ ```typescript
114
+ isVisible(): boolean
115
+ ```
116
+
117
+ **Returns:** `boolean` -- `true` if the element is visible.
118
+
119
+ ## `copyElement`
120
+
121
+ Copy element content to the clipboard. Intended for use as a `copy` event handler. Finds the first `input` or `textarea` within the event target and copies its value.
122
+
123
+ ```typescript
124
+ function copyElement(event: ClipboardEvent): void
125
+ ```
126
+
127
+ | Parameter | Type | Description |
128
+ |-----------|------|-------------|
129
+ | `event` | `ClipboardEvent` | The copy event object |
130
+
131
+ ## `pasteToElement`
132
+
133
+ Paste clipboard content into an element. Intended for use as a `paste` event handler. Finds the first `input` or `textarea` within the event target and replaces its entire value with the clipboard text.
134
+
135
+ ```typescript
136
+ function pasteToElement(event: ClipboardEvent): void
137
+ ```
138
+
139
+ | Parameter | Type | Description |
140
+ |-----------|------|-------------|
141
+ | `event` | `ClipboardEvent` | The paste event object |
142
+
143
+ ## `getBounds`
144
+
145
+ Get bounding rectangle information for multiple elements using `IntersectionObserver`. Results are returned in the same order as the input array. Duplicate elements are deduplicated.
146
+
147
+ ```typescript
148
+ async function getBounds(els: Element[], timeout?: number): Promise<ElementBounds[]>
149
+ ```
150
+
151
+ | Parameter | Type | Default | Description |
152
+ |-----------|------|---------|-------------|
153
+ | `els` | `Element[]` | | Target elements |
154
+ | `timeout` | `number` | `5000` | Timeout in milliseconds |
155
+
156
+ **Returns:** `Promise<ElementBounds[]>` -- Bounding info sorted by input order.
157
+
158
+ **Throws:** `TimeoutError` if the observer does not respond within the timeout.
package/docs/fetch.md ADDED
@@ -0,0 +1,39 @@
1
+ # Fetch
2
+
3
+ Utility for downloading binary data from a URL with optional progress tracking.
4
+
5
+ ## `DownloadProgress`
6
+
7
+ Progress information emitted during a fetch download.
8
+
9
+ ```typescript
10
+ interface DownloadProgress {
11
+ receivedLength: number;
12
+ contentLength: number;
13
+ }
14
+ ```
15
+
16
+ | Field | Type | Description |
17
+ |-------|------|-------------|
18
+ | `receivedLength` | `number` | Bytes received so far |
19
+ | `contentLength` | `number` | Total content length from the `Content-Length` header |
20
+
21
+ ## `fetchUrlBytes`
22
+
23
+ Download binary data from a URL. Supports progress callbacks via `ReadableStream` reader. When the `Content-Length` header is available, the result buffer is pre-allocated for efficiency. When unavailable (chunked encoding), chunks are collected and concatenated.
24
+
25
+ ```typescript
26
+ async function fetchUrlBytes(
27
+ url: string,
28
+ options?: { onProgress?: (progress: DownloadProgress) => void },
29
+ ): Promise<Uint8Array>
30
+ ```
31
+
32
+ | Parameter | Type | Description |
33
+ |-----------|------|-------------|
34
+ | `url` | `string` | URL to fetch |
35
+ | `options.onProgress` | `(progress: DownloadProgress) => void` | Optional progress callback |
36
+
37
+ **Returns:** `Promise<Uint8Array>` -- The downloaded binary data.
38
+
39
+ **Throws:** `Error` if the response is not OK or the body cannot be read.
@@ -0,0 +1,21 @@
1
+ # File Dialog
2
+
3
+ Utility for programmatically opening a native file picker dialog.
4
+
5
+ ## `openFileDialog`
6
+
7
+ Open a file selection dialog by creating and clicking a hidden `<input type="file">` element. Returns the selected files or `undefined` if the dialog is cancelled.
8
+
9
+ ```typescript
10
+ function openFileDialog(options?: {
11
+ accept?: string;
12
+ multiple?: boolean;
13
+ }): Promise<File[] | undefined>
14
+ ```
15
+
16
+ | Parameter | Type | Default | Description |
17
+ |-----------|------|---------|-------------|
18
+ | `options.accept` | `string` | `undefined` | File type filter (e.g., `".csv"`, `"image/*"`) |
19
+ | `options.multiple` | `boolean` | `false` | Allow selecting multiple files |
20
+
21
+ **Returns:** `Promise<File[] | undefined>` -- Selected files array, or `undefined` if cancelled or no files selected.
@@ -0,0 +1,50 @@
1
+ # HTML Element Extensions
2
+
3
+ Side-effect import that extends the global `HTMLElement` prototype with layout and scroll utility methods.
4
+
5
+ ## `HTMLElement.prototype.repaint`
6
+
7
+ Force a synchronous repaint by triggering a reflow (reads `offsetHeight`).
8
+
9
+ ```typescript
10
+ repaint(): void
11
+ ```
12
+
13
+ ## `HTMLElement.prototype.getRelativeOffset`
14
+
15
+ Calculate the element's position relative to a parent element. Returns document-based coordinates (including `window.scrollX/Y`) suitable for CSS `top`/`left` properties.
16
+
17
+ The calculation accounts for:
18
+ - Viewport position (`getBoundingClientRect`)
19
+ - Document scroll (`window.scrollX/Y`)
20
+ - Parent internal scroll (`parentEl.scrollTop/Left`)
21
+ - Intermediate element border widths
22
+ - CSS `transform`
23
+
24
+ ```typescript
25
+ getRelativeOffset(parent: HTMLElement | string): { top: number; left: number }
26
+ ```
27
+
28
+ | Parameter | Type | Description |
29
+ |-----------|------|-------------|
30
+ | `parent` | `HTMLElement \| string` | Parent element or CSS selector to match via `closest()` |
31
+
32
+ **Returns:** `{ top: number; left: number }` -- Coordinates usable for CSS `top`/`left`.
33
+
34
+ **Throws:** `ArgumentError` if the parent element cannot be found.
35
+
36
+ ## `HTMLElement.prototype.scrollIntoViewIfNeeded`
37
+
38
+ Scroll the container so that the target position is not obscured by fixed offset areas (e.g., sticky headers or fixed columns). Only handles cases where the target is above or to the left of the visible area; downward/rightward scrolling relies on the browser's default focus scroll behavior.
39
+
40
+ ```typescript
41
+ scrollIntoViewIfNeeded(
42
+ target: { top: number; left: number },
43
+ offset?: { top: number; left: number },
44
+ ): void
45
+ ```
46
+
47
+ | Parameter | Type | Default | Description |
48
+ |-----------|------|---------|-------------|
49
+ | `target` | `{ top: number; left: number }` | | Target position within the container (`offsetTop`, `offsetLeft`) |
50
+ | `offset` | `{ top: number; left: number }` | `{ top: 0, left: 0 }` | Size of the offset area that must not obscure the target (e.g., fixed header height) |
@@ -0,0 +1,103 @@
1
+ # IndexedDB Store
2
+
3
+ Generic IndexedDB wrapper providing typed CRUD operations and transaction management.
4
+
5
+ ## `StoreConfig`
6
+
7
+ Configuration for an IndexedDB object store, used when opening the database.
8
+
9
+ ```typescript
10
+ interface StoreConfig {
11
+ name: string;
12
+ keyPath: string;
13
+ }
14
+ ```
15
+
16
+ | Field | Type | Description |
17
+ |-------|------|-------------|
18
+ | `name` | `string` | Object store name |
19
+ | `keyPath` | `string` | Key path for the object store |
20
+
21
+ ## `IndexedDbStore`
22
+
23
+ A wrapper around the IndexedDB API with automatic database opening, version upgrade handling, and typed CRUD methods.
24
+
25
+ ```typescript
26
+ class IndexedDbStore {
27
+ constructor(dbName: string, dbVersion: number, storeConfigs: StoreConfig[]);
28
+ }
29
+ ```
30
+
31
+ | Constructor Parameter | Type | Description |
32
+ |----------------------|------|-------------|
33
+ | `dbName` | `string` | Database name |
34
+ | `dbVersion` | `number` | Database version (triggers `onupgradeneeded` when increased) |
35
+ | `storeConfigs` | `StoreConfig[]` | Object store configurations to create on upgrade |
36
+
37
+ ### Methods
38
+
39
+ #### `open`
40
+
41
+ Open the database (creates stores on version upgrade). Returns the existing connection if already open. Concurrent calls return the same pending promise.
42
+
43
+ ```typescript
44
+ async open(): Promise<IDBDatabase>
45
+ ```
46
+
47
+ #### `withStore`
48
+
49
+ Execute a function within a transaction on a specific store. Handles transaction commit/abort automatically.
50
+
51
+ ```typescript
52
+ async withStore<TResult>(
53
+ storeName: string,
54
+ mode: IDBTransactionMode,
55
+ fn: (store: IDBObjectStore) => Promise<TResult>,
56
+ ): Promise<TResult>
57
+ ```
58
+
59
+ | Parameter | Type | Description |
60
+ |-----------|------|-------------|
61
+ | `storeName` | `string` | Target object store name |
62
+ | `mode` | `IDBTransactionMode` | `"readonly"` or `"readwrite"` |
63
+ | `fn` | `(store: IDBObjectStore) => Promise<TResult>` | Function to execute within the transaction |
64
+
65
+ #### `get`
66
+
67
+ Get a single value by key.
68
+
69
+ ```typescript
70
+ async get<TValue>(storeName: string, key: IDBValidKey): Promise<TValue | undefined>
71
+ ```
72
+
73
+ #### `put`
74
+
75
+ Insert or update a value.
76
+
77
+ ```typescript
78
+ async put(storeName: string, value: unknown): Promise<void>
79
+ ```
80
+
81
+ #### `delete`
82
+
83
+ Delete a value by key.
84
+
85
+ ```typescript
86
+ async delete(storeName: string, key: IDBValidKey): Promise<void>
87
+ ```
88
+
89
+ #### `getAll`
90
+
91
+ Get all values from a store.
92
+
93
+ ```typescript
94
+ async getAll<TItem>(storeName: string): Promise<TItem[]>
95
+ ```
96
+
97
+ #### `close`
98
+
99
+ Close the database connection.
100
+
101
+ ```typescript
102
+ close(): void
103
+ ```
@@ -0,0 +1,107 @@
1
+ # IndexedDB Virtual FS
2
+
3
+ A virtual file system abstraction backed by IndexedDB, built on top of `IndexedDbStore`.
4
+
5
+ ## `VirtualFsEntry`
6
+
7
+ Represents a file or directory entry in the virtual file system.
8
+
9
+ ```typescript
10
+ interface VirtualFsEntry {
11
+ kind: "file" | "dir";
12
+ dataBase64?: string;
13
+ }
14
+ ```
15
+
16
+ | Field | Type | Description |
17
+ |-------|------|-------------|
18
+ | `kind` | `"file" \| "dir"` | Entry type |
19
+ | `dataBase64` | `string \| undefined` | Base64-encoded file data (only for files) |
20
+
21
+ ## `IndexedDbVirtualFs`
22
+
23
+ Virtual file system that stores entries in an `IndexedDbStore`. Supports hierarchical path-based operations including directory creation, prefix-based deletion, and child listing.
24
+
25
+ ```typescript
26
+ class IndexedDbVirtualFs {
27
+ constructor(db: IndexedDbStore, storeName: string, keyField: string);
28
+ }
29
+ ```
30
+
31
+ | Constructor Parameter | Type | Description |
32
+ |----------------------|------|-------------|
33
+ | `db` | `IndexedDbStore` | The IndexedDB store instance |
34
+ | `storeName` | `string` | Object store name to use |
35
+ | `keyField` | `string` | Field name used as the key in the store |
36
+
37
+ ### Methods
38
+
39
+ #### `getEntry`
40
+
41
+ Get a virtual FS entry by its full key.
42
+
43
+ ```typescript
44
+ async getEntry(fullKey: string): Promise<VirtualFsEntry | undefined>
45
+ ```
46
+
47
+ | Parameter | Type | Description |
48
+ |-----------|------|-------------|
49
+ | `fullKey` | `string` | Full key identifying the entry |
50
+
51
+ #### `putEntry`
52
+
53
+ Insert or update a virtual FS entry.
54
+
55
+ ```typescript
56
+ async putEntry(fullKey: string, kind: "file" | "dir", dataBase64?: string): Promise<void>
57
+ ```
58
+
59
+ | Parameter | Type | Description |
60
+ |-----------|------|-------------|
61
+ | `fullKey` | `string` | Full key for the entry |
62
+ | `kind` | `"file" \| "dir"` | Entry type |
63
+ | `dataBase64` | `string` | Optional base64-encoded data (for files) |
64
+
65
+ #### `deleteByPrefix`
66
+
67
+ Delete all entries whose key matches the given prefix or starts with `prefix + "/"`.
68
+
69
+ ```typescript
70
+ async deleteByPrefix(keyPrefix: string): Promise<boolean>
71
+ ```
72
+
73
+ | Parameter | Type | Description |
74
+ |-----------|------|-------------|
75
+ | `keyPrefix` | `string` | Key prefix to match |
76
+
77
+ **Returns:** `Promise<boolean>` -- `true` if at least one entry was deleted.
78
+
79
+ #### `listChildren`
80
+
81
+ List immediate children under a given prefix. Returns each child's name and whether it is a directory.
82
+
83
+ ```typescript
84
+ async listChildren(prefix: string): Promise<{ name: string; isDirectory: boolean }[]>
85
+ ```
86
+
87
+ | Parameter | Type | Description |
88
+ |-----------|------|-------------|
89
+ | `prefix` | `string` | Parent path prefix (e.g., `"/root/dir/"`) |
90
+
91
+ **Returns:** `Promise<{ name: string; isDirectory: boolean }[]>` -- Array of child entries.
92
+
93
+ #### `ensureDir`
94
+
95
+ Ensure all directories along a path exist, creating missing ones.
96
+
97
+ ```typescript
98
+ async ensureDir(
99
+ fullKeyBuilder: (path: string) => string,
100
+ dirPath: string,
101
+ ): Promise<void>
102
+ ```
103
+
104
+ | Parameter | Type | Description |
105
+ |-----------|------|-------------|
106
+ | `fullKeyBuilder` | `(path: string) => string` | Function to build the full key from a path segment |
107
+ | `dirPath` | `string` | Directory path to ensure (e.g., `"/a/b/c"`) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/core-browser",
3
- "version": "14.0.4",
3
+ "version": "14.0.6",
4
4
  "description": "심플리즘 패키지 - 코어 (browser)",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -14,7 +14,8 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
- "src"
17
+ "src",
18
+ "docs"
18
19
  ],
19
20
  "sideEffects": [
20
21
  "./src/extensions/element-ext.ts",
@@ -24,6 +25,6 @@
24
25
  ],
25
26
  "dependencies": {
26
27
  "tabbable": "^6.4.0",
27
- "@simplysm/core-common": "14.0.4"
28
+ "@simplysm/core-common": "14.0.6"
28
29
  }
29
30
  }