@simplysm/core-browser 13.0.97 → 13.0.99

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 ADDED
@@ -0,0 +1,106 @@
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
+ ```
@@ -0,0 +1,184 @@
1
+ # Classes
2
+
3
+ IndexedDB-based storage classes for browser-side data persistence.
4
+
5
+ ```typescript
6
+ import { IndexedDbStore, IndexedDbVirtualFs } from "@simplysm/core-browser";
7
+ ```
8
+
9
+ ## `StoreConfig`
10
+
11
+ Configuration for an IndexedDB object store.
12
+
13
+ ```typescript
14
+ interface StoreConfig {
15
+ name: string;
16
+ keyPath: string;
17
+ }
18
+ ```
19
+
20
+ | Field | Type | Description |
21
+ |-------|------|-------------|
22
+ | `name` | `string` | Object store name |
23
+ | `keyPath` | `string` | Key path for the object store |
24
+
25
+ ## `IndexedDbStore`
26
+
27
+ IndexedDB wrapper that manages database connections, schema upgrades, and transactional CRUD operations. Handles version changes and connection blocking gracefully.
28
+
29
+ ### Constructor
30
+
31
+ ```typescript
32
+ constructor(dbName: string, dbVersion: number, storeConfigs: StoreConfig[])
33
+ ```
34
+
35
+ **Parameters:**
36
+ - `dbName` -- Database name
37
+ - `dbVersion` -- Database version (triggers upgrade when increased)
38
+ - `storeConfigs` -- Array of object store configurations
39
+
40
+ ### Methods
41
+
42
+ #### `open`
43
+
44
+ Open or reuse the database connection. Creates object stores defined in `storeConfigs` during upgrade. Handles `onversionchange` and `onclose` events to clear internal references.
45
+
46
+ ```typescript
47
+ async open(): Promise<IDBDatabase>;
48
+ ```
49
+
50
+ #### `withStore`
51
+
52
+ Execute a function within an IndexedDB transaction. Handles transaction completion and error propagation.
53
+
54
+ ```typescript
55
+ async withStore<TResult>(
56
+ storeName: string,
57
+ mode: IDBTransactionMode,
58
+ fn: (store: IDBObjectStore) => Promise<TResult>,
59
+ ): Promise<TResult>;
60
+ ```
61
+
62
+ #### `get`
63
+
64
+ Get a value by key from a store.
65
+
66
+ ```typescript
67
+ async get<TValue>(storeName: string, key: IDBValidKey): Promise<TValue | undefined>;
68
+ ```
69
+
70
+ #### `put`
71
+
72
+ Put a value into a store (insert or update).
73
+
74
+ ```typescript
75
+ async put(storeName: string, value: unknown): Promise<void>;
76
+ ```
77
+
78
+ #### `delete`
79
+
80
+ Delete a value by key from a store.
81
+
82
+ ```typescript
83
+ async delete(storeName: string, key: IDBValidKey): Promise<void>;
84
+ ```
85
+
86
+ #### `getAll`
87
+
88
+ Get all values from a store.
89
+
90
+ ```typescript
91
+ async getAll<TItem>(storeName: string): Promise<TItem[]>;
92
+ ```
93
+
94
+ #### `close`
95
+
96
+ Close the database connection and clear internal references.
97
+
98
+ ```typescript
99
+ close(): void;
100
+ ```
101
+
102
+ ---
103
+
104
+ ## `VirtualFsEntry`
105
+
106
+ Entry in the virtual file system.
107
+
108
+ ```typescript
109
+ interface VirtualFsEntry {
110
+ kind: "file" | "dir";
111
+ dataBase64?: string;
112
+ }
113
+ ```
114
+
115
+ | Field | Type | Description |
116
+ |-------|------|-------------|
117
+ | `kind` | `"file" \| "dir"` | Entry type |
118
+ | `dataBase64` | `string \| undefined` | Base64-encoded file data (files only) |
119
+
120
+ ## `IndexedDbVirtualFs`
121
+
122
+ Virtual file system backed by IndexedDB. Stores files and directories as key-value entries with hierarchical path support. Built on top of `IndexedDbStore`.
123
+
124
+ ### Constructor
125
+
126
+ ```typescript
127
+ constructor(db: IndexedDbStore, storeName: string, keyField: string)
128
+ ```
129
+
130
+ **Parameters:**
131
+ - `db` -- `IndexedDbStore` instance
132
+ - `storeName` -- Name of the object store to use
133
+ - `keyField` -- Key field name for entries
134
+
135
+ ### Methods
136
+
137
+ #### `getEntry`
138
+
139
+ Get a virtual file system entry by its full key.
140
+
141
+ ```typescript
142
+ async getEntry(fullKey: string): Promise<VirtualFsEntry | undefined>;
143
+ ```
144
+
145
+ #### `putEntry`
146
+
147
+ Put an entry into the virtual file system.
148
+
149
+ ```typescript
150
+ async putEntry(fullKey: string, kind: "file" | "dir", dataBase64?: string): Promise<void>;
151
+ ```
152
+
153
+ #### `deleteByPrefix`
154
+
155
+ Delete all entries matching a key prefix (the entry with the exact prefix key and all children).
156
+
157
+ ```typescript
158
+ async deleteByPrefix(keyPrefix: string): Promise<boolean>;
159
+ ```
160
+
161
+ **Returns:** `true` if any entries were deleted.
162
+
163
+ #### `listChildren`
164
+
165
+ List immediate children under a prefix path.
166
+
167
+ ```typescript
168
+ async listChildren(prefix: string): Promise<{ name: string; isDirectory: boolean }[]>;
169
+ ```
170
+
171
+ #### `ensureDir`
172
+
173
+ Ensure a directory path exists by creating all missing parent directories.
174
+
175
+ ```typescript
176
+ async ensureDir(
177
+ fullKeyBuilder: (path: string) => string,
178
+ dirPath: string,
179
+ ): Promise<void>;
180
+ ```
181
+
182
+ **Parameters:**
183
+ - `fullKeyBuilder` -- Function to convert a path segment to a full key
184
+ - `dirPath` -- Directory path to ensure (e.g., `"/a/b/c"`)
@@ -0,0 +1,134 @@
1
+ # Element Extensions
2
+
3
+ Extensions added to the `Element.prototype` via side-effect import. These methods are available on all `Element` instances when `@simplysm/core-browser` is imported.
4
+
5
+ ```typescript
6
+ import "@simplysm/core-browser";
7
+ ```
8
+
9
+ ## `ElementBounds`
10
+
11
+ Element bounds information type returned by `getBounds`.
12
+
13
+ ```typescript
14
+ interface ElementBounds {
15
+ /** Element to be measured */
16
+ target: Element;
17
+ /** Top position relative to viewport */
18
+ top: number;
19
+ /** Left position relative to viewport */
20
+ left: number;
21
+ /** Element width */
22
+ width: number;
23
+ /** Element height */
24
+ height: number;
25
+ }
26
+ ```
27
+
28
+ | Field | Type | Description |
29
+ |-------|------|-------------|
30
+ | `target` | `Element` | Element to be measured |
31
+ | `top` | `number` | Top position relative to viewport |
32
+ | `left` | `number` | Left position relative to viewport |
33
+ | `width` | `number` | Element width |
34
+ | `height` | `number` | Element height |
35
+
36
+ ## `Element.findAll`
37
+
38
+ Find all child elements matching a CSS selector.
39
+
40
+ ```typescript
41
+ findAll<TEl extends Element = Element>(selector: string): TEl[];
42
+ ```
43
+
44
+ Returns an empty array if the selector is empty.
45
+
46
+ ## `Element.findFirst`
47
+
48
+ Find first element matching a CSS selector.
49
+
50
+ ```typescript
51
+ findFirst<TEl extends Element = Element>(selector: string): TEl | undefined;
52
+ ```
53
+
54
+ Returns `undefined` if the selector is empty or no match is found.
55
+
56
+ ## `Element.prependChild`
57
+
58
+ Insert element as first child.
59
+
60
+ ```typescript
61
+ prependChild<TEl extends Element>(child: TEl): TEl;
62
+ ```
63
+
64
+ ## `Element.getParents`
65
+
66
+ Get all parent elements in order of proximity (from closest to farthest).
67
+
68
+ ```typescript
69
+ getParents(): Element[];
70
+ ```
71
+
72
+ ## `Element.findFocusableParent`
73
+
74
+ Find first focusable parent element (using tabbable library).
75
+
76
+ ```typescript
77
+ findFocusableParent(): HTMLElement | undefined;
78
+ ```
79
+
80
+ ## `Element.findFirstFocusableChild`
81
+
82
+ Find first focusable child element (using tabbable library). Uses a TreeWalker for traversal.
83
+
84
+ ```typescript
85
+ findFirstFocusableChild(): HTMLElement | undefined;
86
+ ```
87
+
88
+ ## `Element.isOffsetElement`
89
+
90
+ Check if element is an offset parent (`position: relative | absolute | fixed | sticky`).
91
+
92
+ ```typescript
93
+ isOffsetElement(): boolean;
94
+ ```
95
+
96
+ ## `Element.isVisible`
97
+
98
+ Check if element is visible on screen. Checks existence of clientRects, `visibility: hidden`, and `opacity: 0`.
99
+
100
+ ```typescript
101
+ isVisible(): boolean;
102
+ ```
103
+
104
+ ## `copyElement`
105
+
106
+ Copy element content to clipboard. Use as a copy event handler. Finds the first `input`/`textarea` within the target element and copies its value.
107
+
108
+ ```typescript
109
+ function copyElement(event: ClipboardEvent): void;
110
+ ```
111
+
112
+ ## `pasteToElement`
113
+
114
+ Paste clipboard content to element. Use as a paste event handler. Finds the first `input`/`textarea` within the target element and replaces its entire value with clipboard content. Does not consider cursor position or selection.
115
+
116
+ ```typescript
117
+ function pasteToElement(event: ClipboardEvent): void;
118
+ ```
119
+
120
+ ## `getBounds`
121
+
122
+ Get bounds information for multiple elements using IntersectionObserver.
123
+
124
+ ```typescript
125
+ async function getBounds(els: Element[], timeout?: number): Promise<ElementBounds[]>;
126
+ ```
127
+
128
+ **Parameters:**
129
+ - `els` -- Array of target elements
130
+ - `timeout` -- Timeout in milliseconds (default: `5000`)
131
+
132
+ **Throws:** `TimeoutError` if no response within the timeout duration.
133
+
134
+ Results are returned sorted in input order, with duplicates removed.
@@ -0,0 +1,56 @@
1
+ # HTMLElement Extensions
2
+
3
+ Extensions added to the `HTMLElement.prototype` via side-effect import. These methods are available on all `HTMLElement` instances when `@simplysm/core-browser` is imported.
4
+
5
+ ```typescript
6
+ import "@simplysm/core-browser";
7
+ ```
8
+
9
+ ## `HTMLElement.repaint`
10
+
11
+ Force repaint by triggering reflow. Internally accesses `offsetHeight` which triggers forced synchronous layout in the browser.
12
+
13
+ ```typescript
14
+ repaint(): void;
15
+ ```
16
+
17
+ ## `HTMLElement.getRelativeOffset`
18
+
19
+ Calculate relative position based on a parent element for CSS positioning.
20
+
21
+ ```typescript
22
+ getRelativeOffset(parent: HTMLElement | string): { top: number; left: number };
23
+ ```
24
+
25
+ Returns document-based coordinates including `window.scrollX/Y` that can be directly used in CSS `top`/`left` properties.
26
+
27
+ **Parameters:**
28
+ - `parent` -- Parent element or CSS selector to use as reference (e.g., `document.body`, `".container"`)
29
+
30
+ **Throws:** `ArgumentError` if parent element cannot be found.
31
+
32
+ **Factors included in calculation:**
33
+ - Viewport-relative position (`getBoundingClientRect`)
34
+ - Document scroll position (`window.scrollX/Y`)
35
+ - Parent element internal scroll (`parentEl.scrollTop/Left`)
36
+ - Border thickness of intermediate elements
37
+ - CSS transform transformations
38
+
39
+ **Common use cases:**
40
+ - Position dropdowns, popups after appending to `document.body`
41
+ - Works correctly on scrolled pages
42
+
43
+ ## `HTMLElement.scrollIntoViewIfNeeded`
44
+
45
+ Scroll to make target visible if hidden by offset area (e.g., fixed header/column). Only handles cases where target extends beyond top/left boundaries of the scroll area. For downward/rightward scrolling, relies on browser's default focus scroll behavior. Typically used with focus events on tables with fixed headers or columns.
46
+
47
+ ```typescript
48
+ scrollIntoViewIfNeeded(
49
+ target: { top: number; left: number },
50
+ offset?: { top: number; left: number },
51
+ ): void;
52
+ ```
53
+
54
+ **Parameters:**
55
+ - `target` -- Target position within container (`offsetTop`, `offsetLeft`)
56
+ - `offset` -- Size of area that must not be obscured (e.g., fixed header height, fixed column width). Defaults to `{ top: 0, left: 0 }`.
@@ -0,0 +1,71 @@
1
+ # Utilities
2
+
3
+ Standalone utility functions for browser-side operations.
4
+
5
+ ```typescript
6
+ import { downloadBlob, fetchUrlBytes, openFileDialog } from "@simplysm/core-browser";
7
+ ```
8
+
9
+ ## `downloadBlob`
10
+
11
+ Download a Blob as a file by creating a temporary object URL and clicking a hidden link.
12
+
13
+ ```typescript
14
+ function downloadBlob(blob: Blob, fileName: string): void;
15
+ ```
16
+
17
+ **Parameters:**
18
+ - `blob` -- Blob object to download
19
+ - `fileName` -- File name to save as
20
+
21
+ ## `DownloadProgress`
22
+
23
+ Download progress information used by `fetchUrlBytes`.
24
+
25
+ ```typescript
26
+ interface DownloadProgress {
27
+ receivedLength: number;
28
+ contentLength: number;
29
+ }
30
+ ```
31
+
32
+ | Field | Type | Description |
33
+ |-------|------|-------------|
34
+ | `receivedLength` | `number` | Number of bytes received so far |
35
+ | `contentLength` | `number` | Total content length from `Content-Length` header |
36
+
37
+ ## `fetchUrlBytes`
38
+
39
+ Download binary data from a URL with optional progress callback support.
40
+
41
+ ```typescript
42
+ async function fetchUrlBytes(
43
+ url: string,
44
+ options?: { onProgress?: (progress: DownloadProgress) => void },
45
+ ): Promise<Uint8Array>;
46
+ ```
47
+
48
+ **Parameters:**
49
+ - `url` -- URL to download from
50
+ - `options.onProgress` -- Progress callback function
51
+
52
+ When `Content-Length` is known, pre-allocates memory for efficiency. For chunked encoding (unknown length), collects chunks and merges them.
53
+
54
+ **Throws:** `Error` if the download fails or the response body is not readable.
55
+
56
+ ## `openFileDialog`
57
+
58
+ Programmatically open a file selection dialog.
59
+
60
+ ```typescript
61
+ function openFileDialog(options?: {
62
+ accept?: string;
63
+ multiple?: boolean;
64
+ }): Promise<File[] | undefined>;
65
+ ```
66
+
67
+ **Parameters:**
68
+ - `options.accept` -- File type filter (e.g., `".csv"`, `"image/*"`)
69
+ - `options.multiple` -- Allow multiple file selection (default: `false`)
70
+
71
+ **Returns:** Array of selected files, or `undefined` if the dialog is cancelled.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/core-browser",
3
- "version": "13.0.97",
3
+ "version": "13.0.99",
4
4
  "description": "Simplysm package - Core module (browser)",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "tabbable": "^6.4.0",
27
- "@simplysm/core-common": "13.0.97"
27
+ "@simplysm/core-common": "13.0.99"
28
28
  },
29
29
  "devDependencies": {
30
30
  "happy-dom": "^20.8.4"