@simplysm/core-browser 13.0.72 → 13.0.74
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 +451 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,29 +2,465 @@
|
|
|
2
2
|
|
|
3
3
|
Simplysm package - Core module (browser)
|
|
4
4
|
|
|
5
|
+
Browser-only utilities including DOM element extensions, file download helpers, fetch utilities, and file dialog helpers.
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
9
|
+
```bash
|
|
7
10
|
pnpm add @simplysm/core-browser
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import the package to activate side-effect extensions on `Element` and `HTMLElement`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import "@simplysm/core-browser";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Element Extensions
|
|
22
|
+
|
|
23
|
+
Extends the global `Element` interface with utility methods. These are activated as side effects when the package is imported.
|
|
24
|
+
|
|
25
|
+
### `element.findAll<T>(selector)`
|
|
26
|
+
|
|
27
|
+
Finds all child elements matching a CSS selector.
|
|
28
|
+
|
|
29
|
+
- Returns an empty array if the selector is empty.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import "@simplysm/core-browser";
|
|
33
|
+
|
|
34
|
+
const items = containerEl.findAll<HTMLLIElement>("li.active");
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Signature:**
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
findAll<T extends Element = Element>(selector: string): T[]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### `element.findFirst<T>(selector)`
|
|
46
|
+
|
|
47
|
+
Finds the first child element matching a CSS selector.
|
|
48
|
+
|
|
49
|
+
- Returns `undefined` if the selector is empty or no match is found.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import "@simplysm/core-browser";
|
|
53
|
+
|
|
54
|
+
const input = formEl.findFirst<HTMLInputElement>("input[name='email']");
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Signature:**
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
findFirst<T extends Element = Element>(selector: string): T | undefined
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### `element.prependChild<T>(child)`
|
|
66
|
+
|
|
67
|
+
Inserts a child element as the first child of the element.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import "@simplysm/core-browser";
|
|
71
|
+
|
|
72
|
+
const newEl = document.createElement("div");
|
|
73
|
+
containerEl.prependChild(newEl);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Signature:**
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
prependChild<T extends Element>(child: T): T
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### `element.getParents()`
|
|
85
|
+
|
|
86
|
+
Returns all ancestor elements in order from closest to farthest.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import "@simplysm/core-browser";
|
|
90
|
+
|
|
91
|
+
const parents = someEl.getParents();
|
|
92
|
+
// parents[0] is the immediate parent, parents[n-1] is the farthest ancestor
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Signature:**
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
getParents(): Element[]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### `element.findFocusableParent()`
|
|
104
|
+
|
|
105
|
+
Finds the first focusable ancestor element using the `tabbable` library's focusability check.
|
|
106
|
+
|
|
107
|
+
- Returns `undefined` if no focusable parent is found.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import "@simplysm/core-browser";
|
|
111
|
+
|
|
112
|
+
const focusableParent = someEl.findFocusableParent();
|
|
113
|
+
focusableParent?.focus();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Signature:**
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
findFocusableParent(): HTMLElement | undefined
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `element.findFirstFocusableChild()`
|
|
125
|
+
|
|
126
|
+
Finds the first focusable descendant element using a `TreeWalker` and the `tabbable` library's focusability check.
|
|
127
|
+
|
|
128
|
+
- Returns `undefined` if no focusable child is found.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import "@simplysm/core-browser";
|
|
132
|
+
|
|
133
|
+
const firstFocusable = dialogEl.findFirstFocusableChild();
|
|
134
|
+
firstFocusable?.focus();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Signature:**
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
findFirstFocusableChild(): HTMLElement | undefined
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### `element.isOffsetElement()`
|
|
146
|
+
|
|
147
|
+
Checks whether the element has a CSS `position` of `relative`, `absolute`, `fixed`, or `sticky`.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import "@simplysm/core-browser";
|
|
151
|
+
|
|
152
|
+
if (someEl.isOffsetElement()) {
|
|
153
|
+
// element is a positioning context
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Signature:**
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
isOffsetElement(): boolean
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
### `element.isVisible()`
|
|
166
|
+
|
|
167
|
+
Checks whether the element is visible on screen.
|
|
168
|
+
|
|
169
|
+
Considers:
|
|
170
|
+
- Presence of client rects (`getClientRects().length > 0`)
|
|
171
|
+
- `visibility: hidden`
|
|
172
|
+
- `opacity: 0`
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import "@simplysm/core-browser";
|
|
176
|
+
|
|
177
|
+
if (someEl.isVisible()) {
|
|
178
|
+
// element is visible
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Signature:**
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
isVisible(): boolean
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## HTMLElement Extensions
|
|
191
|
+
|
|
192
|
+
Extends the global `HTMLElement` interface with utility methods. These are activated as side effects when the package is imported.
|
|
193
|
+
|
|
194
|
+
### `htmlElement.repaint()`
|
|
195
|
+
|
|
196
|
+
Forces a browser repaint by triggering a synchronous reflow.
|
|
197
|
+
|
|
198
|
+
Accessing `offsetHeight` forces the browser to flush pending style changes immediately.
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import "@simplysm/core-browser";
|
|
202
|
+
|
|
203
|
+
el.classList.add("animate");
|
|
204
|
+
el.repaint(); // flush styles
|
|
205
|
+
el.classList.add("animate-active");
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Signature:**
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
repaint(): void
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### `htmlElement.getRelativeOffset(parent)`
|
|
217
|
+
|
|
218
|
+
Calculates the element's position relative to a parent element, returning coordinates suitable for use directly in CSS `top`/`left` properties.
|
|
219
|
+
|
|
220
|
+
Accounts for:
|
|
221
|
+
- Viewport-relative position (`getBoundingClientRect`)
|
|
222
|
+
- Document scroll position (`window.scrollX/Y`)
|
|
223
|
+
- Parent element internal scroll (`scrollTop/Left`)
|
|
224
|
+
- Border widths of intermediate elements
|
|
225
|
+
- CSS `transform` on element and parent
|
|
226
|
+
|
|
227
|
+
Typical use: positioning dropdowns or popups appended to `document.body`.
|
|
228
|
+
|
|
229
|
+
- `parent` — a reference `HTMLElement` or a CSS selector string (resolved via `closest`)
|
|
230
|
+
- Throws `ArgumentError` if the parent element cannot be found.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import "@simplysm/core-browser";
|
|
234
|
+
|
|
235
|
+
const popup = document.createElement("div");
|
|
236
|
+
document.body.appendChild(popup);
|
|
237
|
+
const { top, left } = triggerEl.getRelativeOffset(document.body);
|
|
238
|
+
popup.style.top = `${top}px`;
|
|
239
|
+
popup.style.left = `${left}px`;
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Signature:**
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
getRelativeOffset(parent: HTMLElement | string): { top: number; left: number }
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### `htmlElement.scrollIntoViewIfNeeded(target, offset?)`
|
|
251
|
+
|
|
252
|
+
Scrolls the element so that a target position becomes visible, but only if the target is hidden behind an offset area (e.g., a fixed header or fixed column).
|
|
253
|
+
|
|
254
|
+
- Only handles cases where the target is obscured from the top or left. Downward/rightward scrolling relies on the browser's default focus behavior.
|
|
255
|
+
- Typically used with focus events on tables with fixed headers or columns.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import "@simplysm/core-browser";
|
|
259
|
+
|
|
260
|
+
tableEl.scrollIntoViewIfNeeded(
|
|
261
|
+
{ top: row.offsetTop, left: row.offsetLeft },
|
|
262
|
+
{ top: 48, left: 120 }, // fixed header height and fixed column width
|
|
263
|
+
);
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Signature:**
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
scrollIntoViewIfNeeded(
|
|
270
|
+
target: { top: number; left: number },
|
|
271
|
+
offset?: { top: number; left: number },
|
|
272
|
+
): void
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Static Element Utilities
|
|
278
|
+
|
|
279
|
+
Standalone functions exported from `element-ext`.
|
|
280
|
+
|
|
281
|
+
### `copyElement(event)`
|
|
282
|
+
|
|
283
|
+
Copies the value of the first `input` or `textarea` inside the event target element to the clipboard.
|
|
284
|
+
|
|
285
|
+
Intended for use as a `copy` event handler.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
import { copyElement } from "@simplysm/core-browser";
|
|
289
|
+
|
|
290
|
+
someEl.addEventListener("copy", copyElement);
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Signature:**
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
function copyElement(event: ClipboardEvent): void
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
### `pasteToElement(event)`
|
|
302
|
+
|
|
303
|
+
Pastes plain-text clipboard content into the first `input` or `textarea` inside the event target element, replacing its entire value.
|
|
304
|
+
|
|
305
|
+
Intended for use as a `paste` event handler. Does not consider cursor position or selection.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import { pasteToElement } from "@simplysm/core-browser";
|
|
309
|
+
|
|
310
|
+
someEl.addEventListener("paste", pasteToElement);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Signature:**
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
function pasteToElement(event: ClipboardEvent): void
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### `getBounds(els, timeout?)`
|
|
322
|
+
|
|
323
|
+
Retrieves bounding box information for multiple elements using `IntersectionObserver`. Results are returned in the same order as the input array.
|
|
324
|
+
|
|
325
|
+
- Deduplicates elements internally.
|
|
326
|
+
- Returns an empty array if `els` is empty.
|
|
327
|
+
- Throws `TimeoutError` if results are not received within `timeout` milliseconds (default: `5000`).
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { getBounds } from "@simplysm/core-browser";
|
|
331
|
+
|
|
332
|
+
const bounds = await getBounds([el1, el2]);
|
|
333
|
+
for (const { target, top, left, width, height } of bounds) {
|
|
334
|
+
console.log(target, top, left, width, height);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Signature:**
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
function getBounds(els: Element[], timeout?: number): Promise<ElementBounds[]>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Download Utilities
|
|
347
|
+
|
|
348
|
+
### `downloadBlob(blob, fileName)`
|
|
349
|
+
|
|
350
|
+
Triggers a file download in the browser for a given `Blob` object.
|
|
351
|
+
|
|
352
|
+
Creates a temporary object URL, simulates an anchor click, and revokes the URL after 1 second.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { downloadBlob } from "@simplysm/core-browser";
|
|
356
|
+
|
|
357
|
+
const blob = new Blob(["hello"], { type: "text/plain" });
|
|
358
|
+
downloadBlob(blob, "hello.txt");
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Signature:**
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
function downloadBlob(blob: Blob, fileName: string): void
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Fetch Utilities
|
|
370
|
+
|
|
371
|
+
### `fetchUrlBytes(url, options?)`
|
|
372
|
+
|
|
373
|
+
Downloads binary data from a URL and returns it as a `Uint8Array`, with optional progress reporting.
|
|
374
|
+
|
|
375
|
+
- When `Content-Length` is known, pre-allocates memory for efficiency.
|
|
376
|
+
- When `Content-Length` is unknown (chunked transfer encoding), collects chunks then merges.
|
|
377
|
+
- Throws an `Error` if the response is not OK or the body is not readable.
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
import { fetchUrlBytes } from "@simplysm/core-browser";
|
|
381
|
+
|
|
382
|
+
const bytes = await fetchUrlBytes("https://example.com/file.bin", {
|
|
383
|
+
onProgress: ({ receivedLength, contentLength }) => {
|
|
384
|
+
console.log(`${receivedLength} / ${contentLength}`);
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Signature:**
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
function fetchUrlBytes(
|
|
393
|
+
url: string,
|
|
394
|
+
options?: { onProgress?: (progress: DownloadProgress) => void },
|
|
395
|
+
): Promise<Uint8Array>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## File Dialog Utilities
|
|
401
|
+
|
|
402
|
+
### `openFileDialog(options?)`
|
|
403
|
+
|
|
404
|
+
Programmatically opens a browser file selection dialog and returns the selected files.
|
|
405
|
+
|
|
406
|
+
- Returns `undefined` if the user cancels or selects no files.
|
|
407
|
+
- `options.accept` — MIME type or file extension filter (e.g., `"image/*"`, `".pdf"`)
|
|
408
|
+
- `options.multiple` — allow selecting multiple files (default: `false`)
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { openFileDialog } from "@simplysm/core-browser";
|
|
412
|
+
|
|
413
|
+
const files = await openFileDialog({ accept: ".csv", multiple: true });
|
|
414
|
+
if (files) {
|
|
415
|
+
for (const file of files) {
|
|
416
|
+
console.log(file.name);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**Signature:**
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
function openFileDialog(options?: {
|
|
425
|
+
accept?: string;
|
|
426
|
+
multiple?: boolean;
|
|
427
|
+
}): Promise<File[] | undefined>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
8
431
|
|
|
9
|
-
|
|
432
|
+
## Types
|
|
10
433
|
|
|
11
|
-
|
|
434
|
+
### `ElementBounds`
|
|
12
435
|
|
|
13
|
-
|
|
436
|
+
Bounding box information for an element, returned by `getBounds`.
|
|
14
437
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
438
|
+
```ts
|
|
439
|
+
interface ElementBounds {
|
|
440
|
+
/** Element that was measured */
|
|
441
|
+
target: Element;
|
|
442
|
+
/** Top position relative to the viewport */
|
|
443
|
+
top: number;
|
|
444
|
+
/** Left position relative to the viewport */
|
|
445
|
+
left: number;
|
|
446
|
+
/** Element width */
|
|
447
|
+
width: number;
|
|
448
|
+
/** Element height */
|
|
449
|
+
height: number;
|
|
450
|
+
}
|
|
451
|
+
```
|
|
19
452
|
|
|
20
|
-
|
|
453
|
+
---
|
|
21
454
|
|
|
22
|
-
|
|
23
|
-
|--------|---------|-------------|------|
|
|
24
|
-
| `src/utils/download.ts` | `downloadBlob` | Trigger browser file download from a Blob | `download.spec.ts` |
|
|
25
|
-
| `src/utils/fetch.ts` | `DownloadProgress`, `fetchUrlBytes` | Fetch URL as Uint8Array with progress callback | - |
|
|
26
|
-
| `src/utils/file-dialog.ts` | `openFileDialog` | Open native file selection dialog programmatically | - |
|
|
455
|
+
### `DownloadProgress`
|
|
27
456
|
|
|
28
|
-
|
|
457
|
+
Progress information passed to the `onProgress` callback of `fetchUrlBytes`.
|
|
29
458
|
|
|
30
|
-
|
|
459
|
+
```ts
|
|
460
|
+
interface DownloadProgress {
|
|
461
|
+
/** Number of bytes received so far */
|
|
462
|
+
receivedLength: number;
|
|
463
|
+
/** Total content length in bytes (0 if unknown) */
|
|
464
|
+
contentLength: number;
|
|
465
|
+
}
|
|
466
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/core-browser",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.74",
|
|
4
4
|
"description": "Simplysm package - Core module (browser)",
|
|
5
5
|
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"tabbable": "^6.4.0",
|
|
26
|
-
"@simplysm/core-common": "13.0.
|
|
26
|
+
"@simplysm/core-common": "13.0.74"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"happy-dom": "^20.7.0"
|