snap-records 1.1.12 → 1.20.0
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/.nvmrc +1 -0
- package/CONTRIBUTING.md +5 -5
- package/README.md +78 -117
- package/RELEASES.md +212 -0
- package/dist/index.d.ts +89 -38
- package/dist/lang/en_US.json +1 -0
- package/dist/lang/es_ES.json +1 -0
- package/dist/lang/pt_PT.json +2 -1
- package/dist/snap-records.css +1 -1
- package/dist/snap-records.css.map +1 -1
- package/dist/snap-records.es.js +2 -7366
- package/dist/snap-records.es.js.map +1 -1
- package/dist/snap-records.umd.cjs +2 -0
- package/dist/snap-records.umd.cjs.map +1 -0
- package/package.json +45 -30
- package/dist/snap-records.umd.js +0 -2
- package/dist/snap-records.umd.js.map +0 -1
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
26.7.0
|
package/CONTRIBUTING.md
CHANGED
|
@@ -8,8 +8,8 @@ First off, thank you for considering contributing to SnapRecords! It's people li
|
|
|
8
8
|
|
|
9
9
|
### Reporting Bugs
|
|
10
10
|
|
|
11
|
-
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/
|
|
12
|
-
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/
|
|
11
|
+
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/lbassuncao/SnapRecords/issues).
|
|
12
|
+
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/lbassuncao/SnapRecords/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
|
|
13
13
|
|
|
14
14
|
### Suggesting Enhancements
|
|
15
15
|
|
|
@@ -20,12 +20,12 @@ First off, thank you for considering contributing to SnapRecords! It's people li
|
|
|
20
20
|
1. Fork the repo and create your branch from `main`.
|
|
21
21
|
2. If you've added code that should be tested, add tests.
|
|
22
22
|
3. If you've changed APIs, update the documentation.
|
|
23
|
-
4. Ensure the test suite passes (`npm test`).
|
|
24
|
-
5. Make sure your code lints (`npm run lint`).
|
|
23
|
+
4. Ensure the test suite passes (`npm test`) and the library builds (`npm run build`).
|
|
24
|
+
5. Make sure your code lints (`npm run lint`) and formatting checks pass (`npm run format:check`).
|
|
25
25
|
6. Issue that pull request!
|
|
26
26
|
|
|
27
27
|
We will review your pull request as soon as possible.
|
|
28
28
|
|
|
29
29
|
## Code of Conduct
|
|
30
30
|
|
|
31
|
-
This project and everyone participating in it is governed by the [SnapRecords Code of Conduct](
|
|
31
|
+
This project and everyone participating in it is governed by the [SnapRecords Code of Conduct](./docs/COC.md). By participating, you are expected to uphold this code.
|
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
<br>
|
|
14
14
|
|
|
15
15
|
<p align="center" style="font-size: 1.15rem">
|
|
16
|
+
<strong><a href="https://github.com/lbassuncao/SnapRecords/blob/main/RELEASES.md">Releases</a></strong> |
|
|
16
17
|
<strong><a href="https://github.com/lbassuncao/SnapRecords/blob/main/docs/CONFIG.md">Configuration</a></strong> |
|
|
17
18
|
<strong><a href="https://github.com/lbassuncao/SnapRecords/blob/main/docs/BUILD.md">Build Guide</a></strong> |
|
|
18
19
|
<strong><a href="https://github.com/lbassuncao/SnapRecords/blob/main/docs/KEYBOARD.md">Keyboard Navigation</a></strong> |
|
|
@@ -32,11 +33,11 @@ It supports server-side pagination, sorting, filtering, caching, multiple render
|
|
|
32
33
|
## Key Strengths
|
|
33
34
|
|
|
34
35
|
- **Multiple Rendering Modes**: Supports table (`TABLE`), list (`LIST`), and mobile-friendly card (`MOBILE_CARDS`) views, adapting to various devices and use cases.
|
|
35
|
-
- **Server-Side Data Handling**: Integrates with APIs for pagination, filtering, and sorting, with a 250ms debounce delay and retry mechanism (
|
|
36
|
-
- **Caching Support**: Uses IndexedDB via Dexie for caching server responses when `useCache` is enabled, with a default 8-hour expiry
|
|
36
|
+
- **Server-Side Data Handling**: Integrates with APIs for pagination, filtering, and sorting, with a 250ms debounce delay and retry mechanism (3 retries by default).
|
|
37
|
+
- **Caching Support**: Uses IndexedDB via Dexie for caching server responses when `useCache` is enabled, with a default 8-hour expiry. `destroy()` closes the database connection; it does not delete cached responses.
|
|
37
38
|
- **Interactive Features**: Includes column resizing, drag-and-drop column reordering, and row selection with keyboard navigation (ArrowUp/Down, Enter/Space, PageUp/Down).
|
|
38
|
-
- **Accessibility**:
|
|
39
|
-
- **State Persistence**: Persists UI state (column order, widths,
|
|
39
|
+
- **Accessibility**: ARIA sort labels and pagination names in all modes; `aria-selected` only on table rows (`role="row"`). Keyboard navigation and screen reader announcements.
|
|
40
|
+
- **State Persistence**: Persists UI state (column order, widths, `filtering`, page, etc.) in `localStorage` when `persistState` is enabled.
|
|
40
41
|
- **Customizable Styling**: Provides built-in `light` and `dark` themes, plus a `default` theme that inherits styles from the host page via CSS Custom Properties (`--sr-...`). This allows for seamless integration with any design system.
|
|
41
42
|
- **Type Safety**: Written in TypeScript with generic typing for type-safe data and configuration.
|
|
42
43
|
- **Extensibility**: Offers lifecycle hooks (`preDataLoad`, `postDataLoad`, `preRender`, `postRender`, `selectionChanged`) and customizable renderer, event, state, URL, and cache managers.
|
|
@@ -48,21 +49,26 @@ It supports server-side pagination, sorting, filtering, caching, multiple render
|
|
|
48
49
|
To quickly set up SnapRecords:
|
|
49
50
|
|
|
50
51
|
1. **Install via NPM**:
|
|
52
|
+
|
|
51
53
|
```bash
|
|
52
54
|
npm install snap-records
|
|
53
55
|
```
|
|
54
56
|
|
|
55
57
|
2. **Include Styles**:
|
|
56
|
-
|
|
58
|
+
If using a bundler (Vite, Webpack, etc.):
|
|
59
|
+
|
|
57
60
|
```typescript
|
|
58
|
-
import 'snap-records/
|
|
61
|
+
import 'snap-records/style.css';
|
|
59
62
|
```
|
|
63
|
+
|
|
60
64
|
Or via HTML:
|
|
65
|
+
|
|
61
66
|
```html
|
|
62
67
|
<link rel="stylesheet" href="/node_modules/snap-records/dist/snap-records.css" />
|
|
63
68
|
```
|
|
64
69
|
|
|
65
70
|
3. **Create a container**:
|
|
71
|
+
|
|
66
72
|
```html
|
|
67
73
|
<div id="table-container"></div>
|
|
68
74
|
```
|
|
@@ -90,63 +96,15 @@ npm install snap-records
|
|
|
90
96
|
|
|
91
97
|
### Prerequisites
|
|
92
98
|
|
|
93
|
-
- Node.js (version
|
|
94
|
-
- TypeScript (version
|
|
99
|
+
- Node.js (version 26.7.0 or higher; see `.nvmrc`)
|
|
100
|
+
- TypeScript (version 6 or higher)
|
|
95
101
|
- A modern browser supporting IndexedDB for caching
|
|
96
102
|
|
|
97
103
|
### Dependencies
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- `dexie` (^4.0.11): For IndexedDB caching of server responses.
|
|
102
|
-
- `immer` (^10.1.1): For immutable state management.
|
|
103
|
-
- `lru-cache` (^11.1.0): For efficient caching of formatted cell values.
|
|
104
|
-
|
|
105
|
-
Install them with:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
npm install dexie immer lru-cache
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Steps
|
|
112
|
-
|
|
113
|
-
1. **Install Dependencies**:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
npm install dexie immer lru-cache
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
2. **Add SnapRecords**: Copy the source files (`SnapRecords.ts`, `SnapApi.ts`, `SnapRenderer.ts`, `EventManager.ts`, `SnapRecordsDB.ts`, `Translations.ts`, `SnapOptions.ts`, `SnapTypes.ts`, `Configuration.ts`, `StateManager.ts`, `UrlManager.ts`, `CacheManager.ts`, `utils.ts`, and `scss/SnapRecords.scss`) into your project.
|
|
105
|
+
`dexie`, `immer`, and `lru-cache` are runtime dependencies of `snap-records` and are installed automatically with the package.
|
|
120
106
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
public/
|
|
125
|
-
└── lang/
|
|
126
|
-
├── en_US.json
|
|
127
|
-
├── pt_PT.json
|
|
128
|
-
└── es_ES.json
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
4. **Include Styles**: Compile the SCSS file to CSS and include it in your application:
|
|
132
|
-
|
|
133
|
-
````bash
|
|
134
|
-
sass src/scss/SnapRecords.scss dist/snap-records.css --style=compressed --source-map
|
|
135
|
-
```html
|
|
136
|
-
<link rel="stylesheet" href="/path/to/snap-records.css">
|
|
137
|
-
````
|
|
138
|
-
|
|
139
|
-
5. **Import and Initialize**: Import SnapRecords and initialize it:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
import { SnapRecords, RenderType, RowsPerPage } from './SnapRecords';
|
|
143
|
-
|
|
144
|
-
const snapRecords = new SnapRecords('table-container', {
|
|
145
|
-
url: 'https://api.example.com/data',
|
|
146
|
-
columns: ['id', 'name', 'email'],
|
|
147
|
-
rowsPerPage: RowsPerPage.DEFAULT,
|
|
148
|
-
});
|
|
149
|
-
```
|
|
107
|
+
Translations ship in `snap-records/lang/*`. Serve them from a public `/lang` path, or set `langPath` to wherever you host the JSON files.
|
|
150
108
|
|
|
151
109
|
## Usage Examples
|
|
152
110
|
|
|
@@ -241,24 +199,28 @@ const snapRecords = new SnapRecords('table-container', {
|
|
|
241
199
|
|
|
242
200
|
const api = snapRecords.getApi();
|
|
243
201
|
api.search({ status: 'active' }, true);
|
|
244
|
-
api.
|
|
202
|
+
api.setCurrentPage(2);
|
|
245
203
|
api.setTheme('light');
|
|
246
|
-
api.
|
|
204
|
+
api.setFormat(RenderType.MOBILE_CARDS);
|
|
247
205
|
```
|
|
248
206
|
|
|
249
207
|
## Configuration Options
|
|
250
208
|
|
|
251
|
-
The `SnapRecordsOptions<T>` interface defines all configuration options.
|
|
209
|
+
The `SnapRecordsOptions<T>` interface defines all configuration options. See [CONFIG.md](https://github.com/lbassuncao/SnapRecords/blob/main/docs/CONFIG.md) for full details.
|
|
252
210
|
|
|
253
211
|
- `url` (string, required): API URL for data fetching.
|
|
254
212
|
- `columns` (string[], required): Column keys to display.
|
|
255
213
|
- `columnTitles` (string[]): Custom header titles.
|
|
256
214
|
- `columnFormatters` ({ [key: string]: (value, row) => string }): Custom cell formatters, cached with `lru-cache`.
|
|
257
|
-
- `format` (RenderType): Rendering mode (`TABLE`, `LIST`, `MOBILE_CARDS`). Default: `TABLE`.
|
|
215
|
+
- `format` (RenderType): Rendering mode (`TABLE`, `LIST`, `MOBILE_CARDS`). Default: `TABLE`. Change at runtime with `setFormat()`.
|
|
258
216
|
- `rowsPerPage` (RowsPerPage): Rows per page (10, 20, 50, 100, 250, 500, 1000). Default: 10.
|
|
217
|
+
- `filtering` (`Record<string, string>`): Initial filters. Sent to the server as `filtering[key]`. Update later with `search()` or `updateParams()`.
|
|
218
|
+
- `sorting` (`SortCondition[]`): Initial sort. Sent to the server as `sorting[column]`.
|
|
259
219
|
- `useCache` (boolean): Enables IndexedDB caching. Default: `false`.
|
|
260
|
-
- `usePushState` (boolean):
|
|
220
|
+
- `usePushState` (boolean): Syncs page/filters/sort to the browser URL via `StateManager`, merging into the existing query string. Default: `false`.
|
|
261
221
|
- `language` (string): UI language. Default: `en_US`.
|
|
222
|
+
- `langPath` (string): Directory for translation JSON files. Default: `/lang`.
|
|
223
|
+
- `debounceDelay` (number): Delay in ms before reloading data. Default: `250`.
|
|
262
224
|
- `headerCellClasses` (string[]): Header CSS classes, with `no-sorting` to disable sorting.
|
|
263
225
|
- `selectable` (boolean): Enables row selection. Default: `false`.
|
|
264
226
|
- `draggableColumns` (boolean): Enables column drag-and-drop. Default: `false`.
|
|
@@ -267,35 +229,39 @@ The `SnapRecordsOptions<T>` interface defines all configuration options. Key opt
|
|
|
267
229
|
- `debug` (boolean): Enables debug logs. Default: `false`.
|
|
268
230
|
- `lazyLoadMedia` (boolean): Enables lazy loading for images. Default: `false`.
|
|
269
231
|
- `formatCacheSize` (number): Sets the maximum size of the LRU format cache. Default: 500.
|
|
232
|
+
- `preloadNextPage` (boolean): Prefetches the next page. Default: `false`.
|
|
270
233
|
- `lifecycleHooks` (LifecycleHooks<T>): Callbacks for lifecycle events.
|
|
271
234
|
- `prevButton`, `nextButton`: Customizes pagination buttons with text, HTML, or templates.
|
|
272
235
|
|
|
236
|
+
The constructor accepts a container element id (`string`) or an `HTMLElement`.
|
|
237
|
+
|
|
273
238
|
## API Methods
|
|
274
239
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
- `search(
|
|
278
|
-
- `updateParams(params: Partial<Pick<SnapRecordsState<T>, 'currentPage' | 'rowsPerPage' | '
|
|
279
|
-
- `reset(): void`
|
|
280
|
-
- `refresh(): void`
|
|
281
|
-
- `
|
|
282
|
-
- `setTheme(theme: 'light' | 'dark' | 'default'
|
|
283
|
-
- `
|
|
284
|
-
- `setRowsPerPage(newRowsPerPage: RowsPerPage): void`
|
|
285
|
-
- `setLanguage(newLanguage: string): Promise<void>`
|
|
286
|
-
- `getData(): ReadonlyArray<T>`
|
|
287
|
-
- `getTotals(): { totalRecords: number }`
|
|
288
|
-
- `getSelectedRows(): T[]`
|
|
289
|
-
- `clearSelection(): void`
|
|
290
|
-
- `
|
|
240
|
+
Use `snapRecords.getApi()` for the public `ISnapApi` surface. Method names match the implementation:
|
|
241
|
+
|
|
242
|
+
- `search(filtering: Record<string, string>, merge?: boolean): void` — Applies `filtering` and reloads data.
|
|
243
|
+
- `updateParams(params: Partial<Pick<SnapRecordsState<T>, 'currentPage' | 'rowsPerPage' | 'filtering' | 'sorting'>>): void` — Updates those state fields and reloads.
|
|
244
|
+
- `reset(): void` — Restores constructor `filtering`, sorting, rows per page, and column layout.
|
|
245
|
+
- `refresh(): void` — Reloads the current data view.
|
|
246
|
+
- `setCurrentPage(page: number): void` — Navigates to a page.
|
|
247
|
+
- `setTheme(theme: SnapTheme): void` — Sets `'light' | 'dark' | 'default'`.
|
|
248
|
+
- `setFormat(mode: RenderType): void` — Sets the `format` used for rendering.
|
|
249
|
+
- `setRowsPerPage(newRowsPerPage: RowsPerPage): void` — Sets rows per page.
|
|
250
|
+
- `setLanguage(newLanguage: string): Promise<void>` — Sets UI language.
|
|
251
|
+
- `getData(): ReadonlyArray<T>` — Returns current data.
|
|
252
|
+
- `getTotals(): { totalRecords: number }` — Returns total records.
|
|
253
|
+
- `getSelectedRows(): T[]` — Returns selected rows.
|
|
254
|
+
- `clearSelection(): void` — Clears row selections.
|
|
255
|
+
- `isDestroyed` — `true` after `destroy()`.
|
|
256
|
+
- `destroy(): void` — Removes listeners, clears the container, and closes the cache database connection. Does not wipe cached API responses.
|
|
291
257
|
|
|
292
258
|
Example:
|
|
293
259
|
|
|
294
260
|
```typescript
|
|
295
261
|
const api = snapRecords.getApi();
|
|
296
262
|
api.search({ status: 'active' }, true);
|
|
297
|
-
api.
|
|
298
|
-
api.
|
|
263
|
+
api.setCurrentPage(2);
|
|
264
|
+
api.setFormat(RenderType.LIST);
|
|
299
265
|
api.clearSelection();
|
|
300
266
|
api.destroy();
|
|
301
267
|
```
|
|
@@ -324,15 +290,15 @@ Override styles in your CSS as needed.
|
|
|
324
290
|
|
|
325
291
|
SnapRecords prioritizes accessibility:
|
|
326
292
|
|
|
327
|
-
- **ARIA Attributes**:
|
|
328
|
-
- **Keyboard Navigation**: ArrowUp/Down for row navigation, Enter/Space for selection, PageUp/Down for pagination (see [
|
|
293
|
+
- **ARIA Attributes**: `aria-sort` and `aria-label` in all modes. `aria-selected` only on table rows (`role="row"`).
|
|
294
|
+
- **Keyboard Navigation**: ArrowUp/Down for row navigation, Enter/Space for selection, PageUp/Down for pagination (see [KEYBOARD.md](https://github.com/lbassuncao/SnapRecords/blob/main/docs/KEYBOARD.md)).
|
|
329
295
|
- **Screen Reader Support**: Announces updates (e.g., row selection, mode changes) via ARIA live regions.
|
|
330
296
|
|
|
331
297
|
## State Management
|
|
332
298
|
|
|
333
299
|
The `SnapRecordsState` interface manages state, including:
|
|
334
300
|
|
|
335
|
-
- Current page, rows per page,
|
|
301
|
+
- Current page, rows per page, `filtering`, `sorting`.
|
|
336
302
|
- Column order, widths, titles.
|
|
337
303
|
- Data, total records, format, language, theme.
|
|
338
304
|
|
|
@@ -340,40 +306,41 @@ State is persisted to `localStorage` when `persistState` is `true`, managed by `
|
|
|
340
306
|
|
|
341
307
|
## Internationalization
|
|
342
308
|
|
|
343
|
-
Translations are loaded from
|
|
309
|
+
Translations are loaded from `{langPath}/{language}.json` (default `/lang/en_US.json`) by `TranslationManager` in `Translations.ts`. Copy files from `node_modules/snap-records/lang/` into your public directory, or set `langPath`. New files must follow the `Translation` interface, for example:
|
|
344
310
|
|
|
345
311
|
```json
|
|
346
312
|
{
|
|
347
|
-
"errors": {
|
|
348
|
-
"generic": "An error occurred.",
|
|
349
|
-
"invalidConfig": "Invalid configuration: {reason}",
|
|
350
|
-
"containerNotFound": "Container with ID {id} not found.",
|
|
351
|
-
"dataLoadingFailed": "Failed to load data: {error}",
|
|
352
|
-
"renderFailed": "Failed to render: {error}"
|
|
353
|
-
},
|
|
354
313
|
"loading": "Loading...",
|
|
355
314
|
"totalRecords": "Total records: {total}",
|
|
356
|
-
"filteredRecords": "Filtered records: {
|
|
357
|
-
"errorTitle": "Error",
|
|
358
|
-
"errorMessage": "An unexpected error occurred.",
|
|
359
|
-
"noDataAvailable": "No data available.",
|
|
315
|
+
"filteredRecords": "Filtered records: {filtered}",
|
|
360
316
|
"previous": "Previous",
|
|
361
317
|
"next": "Next",
|
|
362
|
-
"
|
|
363
|
-
"
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
"currentPage": "Page {page}",
|
|
367
|
-
"jumpToPage": "Jump to page",
|
|
368
|
-
"pageNavigation": "Page navigation",
|
|
318
|
+
"errorTitle": "Error",
|
|
319
|
+
"errorMessage": "An error occurred.",
|
|
320
|
+
"noDataAvailable": "No data available.",
|
|
321
|
+
"columnResizeHandle": "Resize column",
|
|
369
322
|
"sortAscending": "Sort ascending",
|
|
370
323
|
"sortDescending": "Sort descending",
|
|
371
324
|
"removeSort": "Remove sort",
|
|
372
325
|
"rowSelected": "Row selected",
|
|
373
326
|
"rowDeselected": "Row deselected",
|
|
374
|
-
"
|
|
327
|
+
"currentPage": "Current page: {page}",
|
|
328
|
+
"pageNavigation": "Page navigation",
|
|
329
|
+
"loadMore": "Load More",
|
|
330
|
+
"jumpToPage": "Jump to page",
|
|
331
|
+
"retry": "Retry",
|
|
375
332
|
"dragColumn": "Drag column {col}",
|
|
376
|
-
"
|
|
333
|
+
"rowsPerPageChanged": "Rows per page changed to {count}",
|
|
334
|
+
"errors": {
|
|
335
|
+
"containerNotFound": "Container not found.",
|
|
336
|
+
"invalidConfig": "Invalid configuration.",
|
|
337
|
+
"dataLoadingFailed": "Failed to load data: {error}",
|
|
338
|
+
"renderFailed": "Failed to render: {error}",
|
|
339
|
+
"generic": "An error occurred."
|
|
340
|
+
},
|
|
341
|
+
"pagination": {
|
|
342
|
+
"showingRecords": "Showing {start} to {end} of {total} records"
|
|
343
|
+
}
|
|
377
344
|
}
|
|
378
345
|
```
|
|
379
346
|
|
|
@@ -424,7 +391,7 @@ Compile TypeScript and SCSS:
|
|
|
424
391
|
npm run build
|
|
425
392
|
```
|
|
426
393
|
|
|
427
|
-
This runs `npm run build:js` (
|
|
394
|
+
This runs `npm run build:js` (`vite build`), `npm run build:css` (Sass), and copies `src/lang/*.json` into `dist/lang/`.
|
|
428
395
|
|
|
429
396
|
### Testing
|
|
430
397
|
|
|
@@ -436,20 +403,14 @@ npm test
|
|
|
436
403
|
|
|
437
404
|
### Extending
|
|
438
405
|
|
|
439
|
-
Add custom translations by creating a JSON file
|
|
440
|
-
|
|
441
|
-
```json
|
|
442
|
-
{
|
|
443
|
-
"loading": "Chargement...",
|
|
444
|
-
"errors": {
|
|
445
|
-
"generic": "Une erreur est survenue."
|
|
446
|
-
// ...
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
```
|
|
406
|
+
Add custom translations by creating a JSON file that matches `src/lang/en_US.json` and serving it from `langPath`.
|
|
450
407
|
|
|
451
408
|
Customize rendering or event handling by providing custom `renderer`, `eventManager`, `stateManager`, `urlManager`, or `cacheManager` in the options.
|
|
452
409
|
|
|
410
|
+
### Framework Wrappers
|
|
411
|
+
|
|
412
|
+
React, Vue, Svelte, and Angular wrapper components live in [`wrappers/`](https://github.com/lbassuncao/SnapRecords/tree/main/wrappers) on GitHub (`SnapRecordsReact.tsx`, `SnapRecordsVue.vue`, `SnapRecords.svelte`, `snap-records.component.ts`). They are **not** published in the `snap-records` npm package and are not importable from it (there is no `snap-records/wrappers/*` export) — each one needs to be compiled by your own app's toolchain (JSX, SFC, Angular CLI, etc.), so copy the file for your framework straight into your project's source tree and adjust the import path to `snap-records`. See [CONFIG.md](https://github.com/lbassuncao/SnapRecords/blob/main/docs/CONFIG.md#public-api-getapi) for what they sync automatically, and [RELEASES.md](https://github.com/lbassuncao/SnapRecords/blob/main/RELEASES.md#framework-wrappers) for their exact behavior.
|
|
413
|
+
|
|
453
414
|
## Additional Notes
|
|
454
415
|
|
|
455
416
|
- **Data Requirement: Unique `id` Field**: The data returned from the server must include a unique `id` field for each row, as required by the `Identifiable` interface in `SnapTypes.ts`. This `id` (string or number) is used by the plugin’s diffing mechanism to efficiently track and reconcile rows during rendering. The diffing process, implemented in `SnapRenderer.ts` (e.g., `#reconcileItems`), relies on this unique identifier to map existing DOM elements to data rows, ensuring accurate updates and preventing duplication or loss of data. For example, a server response should look like:
|
package/RELEASES.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Releases
|
|
2
|
+
|
|
3
|
+
## 1.20.0
|
|
4
|
+
|
|
5
|
+
Breaking release. State, public API, HTTP query strings, and `usePushState` now share one vocabulary.
|
|
6
|
+
|
|
7
|
+
Upgrade from `1.1.x` by renaming the symbols below. There are no compatibility aliases.
|
|
8
|
+
|
|
9
|
+
### Public API methods
|
|
10
|
+
|
|
11
|
+
| 1.1.x | 1.20.0 |
|
|
12
|
+
| ------------------------------------------- | ------------------------------------------ |
|
|
13
|
+
| `api.gotoPage(n)` | `api.setCurrentPage(n)` |
|
|
14
|
+
| `api.setRenderMode(mode)` | `api.setFormat(mode)` |
|
|
15
|
+
| `search(filters, merge?)` | `search(filtering, merge?)` |
|
|
16
|
+
| `updateParams({ filters, sortConditions })` | `updateParams({ filtering, sorting })` |
|
|
17
|
+
| `setTheme('light' \| 'dark')` | `setTheme('light' \| 'dark' \| 'default')` |
|
|
18
|
+
|
|
19
|
+
`setTheme('default')` now works in the implementation. In 1.x the type allowed `'default'` but the method rejected it.
|
|
20
|
+
|
|
21
|
+
`ISnapApi` also exposes `isDestroyed` (`true` after `destroy()`). Invalid `setTheme` / `setFormat` values are ignored.
|
|
22
|
+
|
|
23
|
+
### State, options, and `localStorage`
|
|
24
|
+
|
|
25
|
+
`SnapRecordsState`, `SnapRecordsOptions`, `updateParams()`, `preDataLoad`, and persisted state (`persistState`) renamed:
|
|
26
|
+
|
|
27
|
+
| 1.1.x | 1.20.0 |
|
|
28
|
+
| ---------------- | ----------- |
|
|
29
|
+
| `filters` | `filtering` |
|
|
30
|
+
| `sortConditions` | `sorting` |
|
|
31
|
+
|
|
32
|
+
`SortCondition` is unchanged: `[column, OrderDirection]`.
|
|
33
|
+
|
|
34
|
+
Saved `localStorage` payloads from 1.1.x (`filters`, `sortConditions`) are not migrated. Clear old keys or rewrite them before enabling `persistState` on 1.20.0.
|
|
35
|
+
|
|
36
|
+
### HTTP query and `usePushState`
|
|
37
|
+
|
|
38
|
+
`filtering[key]` and `sorting[column]` are unchanged. Pagination query keys are not:
|
|
39
|
+
|
|
40
|
+
| 1.1.x | 1.20.0 |
|
|
41
|
+
| ----------------- | ------------------------------------------------------------------ |
|
|
42
|
+
| `page` | `currentPage` |
|
|
43
|
+
| `perPage` | `rowsPerPage` |
|
|
44
|
+
| `offset` | `offset` (unchanged, derived as `(currentPage - 1) * rowsPerPage`) |
|
|
45
|
+
| `filtering[key]` | `filtering[key]` |
|
|
46
|
+
| `sorting[column]` | `sorting[column]` |
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
/api/data?currentPage=2&rowsPerPage=10&offset=10&filtering[status]=active&sorting[name]=ASC
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Backends that read `page` or `perPage` must switch to `currentPage` and `rowsPerPage`.
|
|
55
|
+
|
|
56
|
+
Empty filter values are no longer written to the query string.
|
|
57
|
+
|
|
58
|
+
The API request URL and the browser URL (`usePushState`) are built from the same helper, so they use the same keys. `usePushState` merges those keys into the existing query string and listens for `popstate`.
|
|
59
|
+
|
|
60
|
+
### `preDataLoad` / `ServerRequestParams`
|
|
61
|
+
|
|
62
|
+
| 1.1.x | 1.20.0 |
|
|
63
|
+
| ------------------------------------------- | ---------------------------------------------------- |
|
|
64
|
+
| `page: number` | `currentPage: number` |
|
|
65
|
+
| `perPage: number` | `rowsPerPage: number` |
|
|
66
|
+
| `filtering?: Record<string, string>` | `filtering: Record<string, string>` (always present) |
|
|
67
|
+
| `sorting?: Record<string, 'ASC' \| 'DESC'>` | `sorting: SortCondition[]` (always present) |
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
lifecycleHooks: {
|
|
71
|
+
preDataLoad: (params) => {
|
|
72
|
+
// 1.x: params.page, params.perPage, params.sorting?.name
|
|
73
|
+
// 1.20.0:
|
|
74
|
+
console.log(params.currentPage, params.rowsPerPage, params.sorting);
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Constructor
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
new SnapRecords(container: string | HTMLElement, options)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
A container id still works. Passing an `HTMLElement` is new and required by the framework wrappers.
|
|
86
|
+
|
|
87
|
+
If the element has no `id`, SnapRecords assigns one (used as the `persistState` storage key).
|
|
88
|
+
|
|
89
|
+
### Removed from `SnapRecords`
|
|
90
|
+
|
|
91
|
+
These were public on the instance in 1.x and are now private renderer details:
|
|
92
|
+
|
|
93
|
+
- `createTableRow`
|
|
94
|
+
- `updateRow`
|
|
95
|
+
- `createListItem`
|
|
96
|
+
- `updateListItem`
|
|
97
|
+
- `createMobileCard`
|
|
98
|
+
- `updateMobileCard`
|
|
99
|
+
|
|
100
|
+
### Instance properties
|
|
101
|
+
|
|
102
|
+
| 1.1.x | 1.20.0 |
|
|
103
|
+
| ------------------------ | ----------------- |
|
|
104
|
+
| `preloadNextPageEnabled` | `preloadNextPage` |
|
|
105
|
+
|
|
106
|
+
The option name was already `preloadNextPage`.
|
|
107
|
+
|
|
108
|
+
Each instance no longer attaches `window` `error` / `unhandledrejection` listeners.
|
|
109
|
+
|
|
110
|
+
`destroy()` still tears down events, DOM, translations, and the IndexedDB connection. It also clears the in-memory format cache and row selection. It does **not** wipe cached API responses in IndexedDB.
|
|
111
|
+
|
|
112
|
+
### Pagination CSS config
|
|
113
|
+
|
|
114
|
+
Current-page number buttons used `classNames.disabled: 'snap-active'` in 1.x. That field is now `active`:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
config.pagination.numberButton.classNames.active; // 'snap-active'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`ButtonConfig.classNames.disabled` is optional. Prev/next buttons still use `disabled`.
|
|
121
|
+
|
|
122
|
+
Custom `renderer` / pagination code that read `classNames.disabled` on number buttons must use `active`.
|
|
123
|
+
|
|
124
|
+
### Package exports
|
|
125
|
+
|
|
126
|
+
`snap-records` now exports `ISnapApi`, `SnapTheme`, `SortCondition`, `SnapRecordsState`, and `PersistedState`. Wrappers that imported `ISnapApi` from the package type-check against the public entry.
|
|
127
|
+
|
|
128
|
+
### Framework wrappers
|
|
129
|
+
|
|
130
|
+
React, Vue, Svelte, and Angular wrappers pass the container element (not a random id), call `destroy()` on unmount, sync `theme`, `language`, `format`, `filtering`, `sorting`, and `rowsPerPage` through `ISnapApi` (each field watched separately — not the whole `options` object), and skip prop sync when `isDestroyed` is true. The Vue wrapper clears its instance ref after `destroy()` and guards theme/language/format watchers when the instance is not mounted yet. Pagination (`currentPage`) is runtime-only via `ISnapApi`, not reactive `options`.
|
|
131
|
+
|
|
132
|
+
### Bug fixes (from production use)
|
|
133
|
+
|
|
134
|
+
- `headerCellClasses` (including `no-sorting`) is read from state. In 1.x the option was stored but never applied to headers.
|
|
135
|
+
- `columnWidths` stays a `Map`. Production calls Immer `enableMapSet()` (tests used to hide the crash by calling it only in the test setup).
|
|
136
|
+
- Constructing a second instance on the same container destroys the previous one (bfcache / `pageshow` re-init).
|
|
137
|
+
- `destroy()` is idempotent, cancels pending loads, and aborts in-flight `fetch`. It closes IndexedDB; it does not wipe the cache.
|
|
138
|
+
- `search()` / `updateParams({ filtering })` drop empty values and reset `currentPage` to 1. Filter object key order does not trigger a reload.
|
|
139
|
+
- `updateParams()` ignores `undefined` fields and does not reload when nothing changed.
|
|
140
|
+
- `setCurrentPage()` / `updateParams({ currentPage })` clamp to the page range. After a load, an out-of-range page redirects.
|
|
141
|
+
- `rowsPerPage` is clamped to 1–1000 in the constructor, setters, URL, and `localStorage`.
|
|
142
|
+
- `reset()` restores constructor `filtering`, `sorting`, `rowsPerPage`, columns, titles, and header classes.
|
|
143
|
+
- Cached responses run `preDataLoad` / `postDataLoad`.
|
|
144
|
+
- A throw in a lifecycle hook is logged and does not abort render or get retried as a network error.
|
|
145
|
+
- HTTP 4xx and invalid JSON are not retried. 5xx and network errors still are.
|
|
146
|
+
- If the API omits `totalRecords`, the received row count is used (the table no longer shows rows with “No data available”).
|
|
147
|
+
- Clicks on buttons, links, and form controls inside a row no longer toggle selection.
|
|
148
|
+
- Row selection highlighting is reapplied after `setFormat()` and other re-renders.
|
|
149
|
+
- Unformatted cell values are escaped as text (`<`, `&`). Formatter HTML is still sanitized. `lazyLoadMedia` does not overwrite an existing `loading` attribute.
|
|
150
|
+
- Prev/next pagination defaults (`«` / `»`) keep `aria-hidden` on the glyph and set `aria-label` from translations. Sort, resize, and drag use `sortAscending` / `sortDescending` / `removeSort`, `columnResizeHandle`, and `dragColumn`.
|
|
151
|
+
- Keyboard handling ignores `input`, `textarea`, `select`, `button`, `a`, and `[contenteditable]:not([contenteditable="false"])`. `Home` / `End` move the current row (they do not call `reset()`). `ArrowUp` is ignored when no row is current. `.snap-current-row` is styled in table, list, and card modes.
|
|
152
|
+
- The table wrapper class is `snap-table-responsive` (the CSS never matched Bootstrap’s `table-responsive`).
|
|
153
|
+
- `loadData()` aborts an in-flight next-page preload so a filter change cannot recache the previous query.
|
|
154
|
+
- Column resize uses the SnapRecords handle only (`resize: horizontal` on `th` was ignored by state/persist).
|
|
155
|
+
- `cursor: pointer` on rows/cards applies only when `selectable` is on (`.snap-selectable`).
|
|
156
|
+
- Sort headers are `<button type="button">` (not `<a href="#">`), so middle-click / Ctrl+click does not navigate away.
|
|
157
|
+
- Translation fetch retries abort immediately on `destroy()` instead of waiting out the retry delay.
|
|
158
|
+
- `aria-selected` is set only on `role="row"` (table). List items and cards keep `.snap-selected` (ARIA does not allow `aria-selected` on `listitem` / `rowgroup`).
|
|
159
|
+
- `retryAttempts` is clamped to 0–10 and `debounceDelay` to 0–10000ms.
|
|
160
|
+
- Resize and drag listeners are no longer duplicated on every render.
|
|
161
|
+
- `showError()` no longer hides the table forever; `render()` / `showLoading()` restore content.
|
|
162
|
+
- Format cache keys are `JSON.stringify([rowIndex, row.id, column])` so duplicate row ids on the same page do not share formatted values.
|
|
163
|
+
- Row reconciliation keys DOM nodes by `` `${id}:${index}` `` so duplicate ids on one page render and update correctly.
|
|
164
|
+
- When the API sends `totalRecords: 0` but returns rows, the footer uses the row count instead of “No data available”.
|
|
165
|
+
- When the dataset becomes empty, `currentPage` resets to 1 instead of leaving pagination on a stale page.
|
|
166
|
+
- On `popstate` to a URL with no snap params, `persistState` reloads filtering/sorting from `localStorage` instead of constructor defaults.
|
|
167
|
+
- `destroy()` during an in-flight load no longer re-renders or rebinds event handlers after IndexedDB caching completes.
|
|
168
|
+
- `setState()` / URL persistence / `localStorage` writes are skipped after `destroy()`.
|
|
169
|
+
- `setupAllHandlers()` is skipped when the instance is destroyed.
|
|
170
|
+
- `showError()` falls back to bundled English when translations are not loaded yet.
|
|
171
|
+
- `buildUrl()` fallback strips existing snap query keys before appending new ones.
|
|
172
|
+
- Language codes are sanitized to `[a-zA-Z0-9_-]` in state and when fetching `{lang}.json` (`../en_US` → `en_US`).
|
|
173
|
+
- `offset` in the URL is derived. A query that only has `offset` is not treated as snap state and does not wipe `persistState` filters.
|
|
174
|
+
- `loadFromURL()` updates `filtering` / `sorting` only when those keys appear in the URL. Pagination-only snap queries no longer reset them to `{}` / `[]`.
|
|
175
|
+
- `loadFromURL()` updates `currentPage` only when `currentPage` appears in the URL. Sorting- or filtering-only snap queries no longer reset the page to 1.
|
|
176
|
+
- On `popstate`, absent `filtering` / `sorting` keys reset those fields to `{}` / `[]` so browser back/forward matches the URL. First load still keeps absent keys from `persistState` or constructor options.
|
|
177
|
+
- `End` with no rows leaves the current row index at `-1` (same as `Home`).
|
|
178
|
+
- Column resize no longer writes `persistState` after `destroy()`.
|
|
179
|
+
- `showLoading()` recovers when `isLoading` is true but the overlay was removed (for example after an aborted fetch or a re-render during load).
|
|
180
|
+
- `hideLoading()` removes stray overlays when `isLoading` was already cleared.
|
|
181
|
+
- `langPath` trailing slashes are stripped (`/lang/` → `/lang/pt_PT.json`). Loaded JSON is merged with bundled `en_US`. Non-string fields in that JSON are ignored.
|
|
182
|
+
- `usePushState` merges snap keys into the existing query. An empty URL on first load does not wipe `persistState`. `popstate` reloads from the URL.
|
|
183
|
+
- Constructor `filtering` / `sorting` are compacted and normalized before `reset()` or an empty `popstate` restore them.
|
|
184
|
+
- `persistState` `currentPage` is truncated to an integer.
|
|
185
|
+
- Pagination totals insert `{start}` / `{end}` / `{total}` as text. Markup in `showingRecords` is not parsed as HTML.
|
|
186
|
+
- `javascript:` / `vbscript:` URLs that use whitespace or control characters are stripped from formatter HTML.
|
|
187
|
+
- Data-load retries wait between attempts (aborted by `destroy()`), matching translation retries.
|
|
188
|
+
- Next-page preload is skipped if the query changed while the cache lookup was in flight.
|
|
189
|
+
- Next-page preload is skipped when `useCache` is `false` (prefetch only writes to IndexedDB).
|
|
190
|
+
- `persistState` writes are flushed on `destroy()` even when the debounce timer has not fired.
|
|
191
|
+
- Duplicate `sorting` entries for the same column collapse to the last direction.
|
|
192
|
+
- `destroy()` removes `tabindex`, theme classes, and the container shell classes from the host element.
|
|
193
|
+
- Invalid persisted `columnWidths` (non-finite or non-positive) are ignored.
|
|
194
|
+
- IndexedDB cache clear on filter change is awaited so a new page cannot be written and then wiped.
|
|
195
|
+
- IndexedDB reads and writes wait for the database connection to open before running.
|
|
196
|
+
- `search()` / `updateParams({ filtering })` treat a non-object as `{}` instead of throwing.
|
|
197
|
+
- The error Retry button is `type="button"` so it does not submit a parent form.
|
|
198
|
+
- `destroy()` restores `position` on the host element when SnapRecords had set it to `relative`.
|
|
199
|
+
- `persistState` restores saved `headerCellClasses` when the array length matches the column count.
|
|
200
|
+
- Corrupted `localStorage` `filtering` arrays are ignored instead of becoming numeric keys.
|
|
201
|
+
- Constructor `filtering` must be a plain object; arrays log a warning and fall back to `{}`.
|
|
202
|
+
- `popstate` re-renders sort/pagination chrome before the debounced fetch completes.
|
|
203
|
+
- Column sort clears the format cache before reload.
|
|
204
|
+
- `reset()` clamps `rowsPerPage` through the same sanitizer as the constructor.
|
|
205
|
+
- `headerCellClasses` length is validated against `columns` in the constructor (`Configuration.validateHeaderCellClasses`). A mismatched, non-empty array now logs a warning and falls back to `[]` instead of silently misaligning header classes across columns after drag-and-drop reorder (`reorderColumns()`) or a `persistState` column-order restore.
|
|
206
|
+
- Removed unused `config.classes` entries (`paginationCell`, `tableOverlay`, `listOverlay`, `cardsOverlay`) that were never applied to the DOM or styled in CSS.
|
|
207
|
+
- `tsconfig.json` / `tsconfig.test.json` use `moduleResolution: "bundler"` instead of `"node"`. TypeScript 6.0.3 (the pinned devDependency) treats `"node"` as a hard error (`TS5107`), so `tsc --noEmit` against either config failed outright even though `vite build` and `ts-jest` masked it by compiling through their own, more lenient paths.
|
|
208
|
+
- **CommonJS `require('snap-records')` returned an empty module.** The UMD build was emitted as `dist/snap-records.umd.js`, but `package.json` has `"type": "module"`, so Node loaded that plain `.js` file as ESM and silently produced a namespace object with none of the UMD bundle's exports (`SnapRecords` was `undefined`). The UMD output is now `dist/snap-records.umd.cjs` (`.cjs` is always loaded as CommonJS regardless of `"type"`), and `package.json`'s `main` and `exports["."].require` were updated to match. Verified both `require()` and `import` resolve `SnapRecords` correctly against the built package.
|
|
209
|
+
|
|
210
|
+
### Translations
|
|
211
|
+
|
|
212
|
+
`pt_PT` pagination string is now `A mostrar {start} a {end} de {total} registos`. Custom copies of that file should match `src/lang/en_US.json`.
|