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 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/your-username/snap-records/issues).
12
- - If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/your-username/snap-records/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.
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](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
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 (up to 3 attempts by default).
36
- - **Caching Support**: Uses IndexedDB via Dexie for caching server responses when `useCache` is enabled, with a default 8-hour expiry and cleanup on destroy if `destroyOnUnload` is enabled.
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**: Implements ARIA attributes (`aria-sort`, `aria-selected`, `aria-label`), keyboard navigation, and screen reader announcements for inclusive experiences.
39
- - **State Persistence**: Persists UI state (column order, widths, filters, page, etc.) in `localStorage` when `persistState` is enabled.
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
- If using a bundler (Vite, Webpack, etc.):
58
+ If using a bundler (Vite, Webpack, etc.):
59
+
57
60
  ```typescript
58
- import 'snap-records/dist/snap-records.css';
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 20 or higher)
94
- - TypeScript (version 5 or higher)
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
- SnapRecords relies on the following runtime dependencies, which must be installed in your project:
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
- 3. **Add Translation Files**: Place translation JSON files (e.g., `en_US.json`, `pt_PT.json`, `es_ES.json`) in the `/lang` directory within your application's public directory:
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.gotoPage(2);
202
+ api.setCurrentPage(2);
245
203
  api.setTheme('light');
246
- api.setRenderMode(RenderType.MOBILE_CARDS);
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. Key options include (see [config.md](https://github.com/lbassuncao/SnapRecords/blob/main/docs/CONFIG.md) for full details):
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): Updates browser URL with state. Default: `false`.
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
- The `SnapApi` class provides methods for interacting with the component:
276
-
277
- - `search(filters: Record<string, string>, merge?: boolean): void` - Applies filters and reloads data.
278
- - `updateParams(params: Partial<Pick<SnapRecordsState<T>, 'currentPage' | 'rowsPerPage' | 'filters' | 'sortConditions'>>): void` - Updates multiple parameters.
279
- - `reset(): void` - Clears filters, sorting, and state.
280
- - `refresh(): void` - Reloads current data view.
281
- - `gotoPage(page: number): void` - Navigates to a page.
282
- - `setTheme(theme: 'light' | 'dark' | 'default'): void` - Sets the theme.
283
- - `setRenderMode(mode: RenderType): void` - Changes rendering mode.
284
- - `setRowsPerPage(newRowsPerPage: RowsPerPage): void` - Sets rows per page.
285
- - `setLanguage(newLanguage: string): Promise<void>` - Sets UI language.
286
- - `getData(): ReadonlyArray<T>` - Returns current data.
287
- - `getTotals(): { totalRecords: number }` - Returns total records.
288
- - `getSelectedRows(): T[]` - Returns selected rows.
289
- - `clearSelection(): void` - Clears row selections.
290
- - `destroy(): void` - Destroys the instance, clearing elements and cache.
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.gotoPage(2);
298
- api.setRenderMode(RenderType.LIST);
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**: Supports `aria-sort`, `aria-selected`, `aria-label` for table, list, and card modes.
328
- - **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)).
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, filters, sort conditions.
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 `/lang` JSON files (e.g., `en_US.json`) via `TranslationManager`. Add new languages by creating JSON files following the `Translation` interface:
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: {total}",
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
- "retry": "Retry",
363
- "pagination": {
364
- "showingRecords": "Showing {start} to {end} of {total} records"
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
- "columnResizeHandle": "Resize column",
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
- "loadMore": "Load more"
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` (for `tsc --noEmit` and `vite build`) and `npm run build:css` (for SCSS compilation with source maps).
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 in `/lang`:
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`.