snap-records 1.1.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/README.md ADDED
@@ -0,0 +1,471 @@
1
+ # SnapRecords
2
+
3
+ <p align="center">
4
+ <img src="https://github.com/lbassuncao/SnapRecords/blob/main/docs/SnapRecords.png?raw=true" alt="SnapRecords Logo" width="256">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/snap-records"><img src="https://img.shields.io/npm/v/snap-records.svg" alt="NPM Version"></a>
9
+ <a href="./docs/LICENSE.txt"><img src="https://img.shields.io/npm/l/snap-records.svg" alt="License"></a>
10
+ <a href="https://github.com/lbassuncao/SnapRecords/actions/workflows/ci.yml"><img src="https://github.com/lbassuncao/SnapRecords/actions/workflows/ci.yml/badge.svg" alt="Build Status"></a>
11
+ </p>
12
+
13
+ <br>
14
+
15
+ <p align="center" style="font-size: 1.15rem">
16
+ <strong><a href="./docs/CONFIG.md">Configuration</a></strong> |
17
+ <strong><a href="./docs/BUILD.md">Build Guide</a></strong> |
18
+ <strong><a href="./docs/KEYBOARD.md">Keyboard Navigation</a></strong> |
19
+ <strong><a href="./CONTRIBUTING.md">Contributing</a></strong> |
20
+ <strong><a href="./docs/LICENSE.txt">License</a></strong> |
21
+ <strong><a href="./docs/COC.md">Code of Conduct</a></strong>
22
+ </p>
23
+
24
+ <br>
25
+
26
+ **SnapRecords** is a powerful, flexible TypeScript-based data grid component for displaying, managing, and interacting with tabular data in web applications.
27
+
28
+ Inspired by [jQuery Dynatable](https://github.com/alfajango/jquery-dynatable), it modernizes the concept with type safety, enhanced features, and performance optimizations.
29
+
30
+ It supports server-side pagination, sorting, filtering, caching, multiple rendering modes, and accessibility, making it ideal for both simple and complex data-driven interfaces.
31
+
32
+ ## Key Strengths
33
+
34
+ - **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.
37
+ - **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.
40
+ - **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
+ - **Type Safety**: Written in TypeScript with generic typing for type-safe data and configuration.
42
+ - **Extensibility**: Offers lifecycle hooks (`preDataLoad`, `postDataLoad`, `preRender`, `postRender`, `selectionChanged`) and customizable renderer, event, state, URL, and cache managers.
43
+ - **Internationalization**: Supports multiple languages via JSON files in the `/lang` directory, managed by `TranslationManager`.
44
+ - **Performance Optimizations**: Includes lazy loading of media, preloading of next-page data, and efficient format caching with `lru-cache` (controlled by `formatCacheSize`).
45
+
46
+ ## Getting Started
47
+
48
+ To quickly set up SnapRecords:
49
+
50
+ 1. Install dependencies:
51
+ ```bash
52
+ npm install dexie immer lru-cache
53
+ ```
54
+ 2. Include the compiled CSS:
55
+ ```html
56
+ <link rel="stylesheet" href="/path/to/snap-records.css" />
57
+ ```
58
+ 3. Create a container:
59
+ ```html
60
+ <div id="table-container"></div>
61
+ ```
62
+ 4. Initialize SnapRecords:
63
+
64
+ ```typescript
65
+ import { SnapRecords, RowsPerPage } from './SnapRecords';
66
+
67
+ new SnapRecords('table-container', {
68
+ url: '[https://api.example.com/data](https://api.example.com/data)',
69
+ columns: ['id', 'name'],
70
+ rowsPerPage: RowsPerPage.DEFAULT,
71
+ // No theme specified, so it uses 'default' and inherits host page styles
72
+ });
73
+ ```
74
+
75
+ ## Installation
76
+
77
+ ### Prerequisites
78
+
79
+ - Node.js (version 20 or higher)
80
+ - TypeScript (version 5 or higher)
81
+ - A modern browser supporting IndexedDB for caching
82
+
83
+ ### Dependencies
84
+
85
+ SnapRecords relies on the following runtime dependencies, which must be installed in your project:
86
+
87
+ - `dexie` (^4.0.11): For IndexedDB caching of server responses.
88
+ - `immer` (^10.1.1): For immutable state management.
89
+ - `lru-cache` (^11.1.0): For efficient caching of formatted cell values.
90
+
91
+ Install them with:
92
+
93
+ ```bash
94
+ npm install dexie immer lru-cache
95
+ ```
96
+
97
+ ### Steps
98
+
99
+ 1. **Install Dependencies**:
100
+
101
+ ```bash
102
+ npm install dexie immer lru-cache
103
+ ```
104
+
105
+ 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.
106
+
107
+ 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:
108
+
109
+ ```
110
+ public/
111
+ └── lang/
112
+ ├── en_US.json
113
+ ├── pt_PT.json
114
+ └── es_ES.json
115
+ ```
116
+
117
+ 4. **Include Styles**: Compile the SCSS file to CSS and include it in your application:
118
+
119
+ ````bash
120
+ sass src/scss/SnapRecords.scss dist/snap-records.css --style=compressed --source-map
121
+ ```html
122
+ <link rel="stylesheet" href="/path/to/snap-records.css">
123
+ ````
124
+
125
+ 5. **Import and Initialize**: Import SnapRecords and initialize it:
126
+
127
+ ```typescript
128
+ import { SnapRecords, RenderType, RowsPerPage } from './SnapRecords';
129
+
130
+ const snapRecords = new SnapRecords('table-container', {
131
+ url: 'https://api.example.com/data',
132
+ columns: ['id', 'name', 'email'],
133
+ rowsPerPage: RowsPerPage.DEFAULT,
134
+ });
135
+ ```
136
+
137
+ ## Usage Examples
138
+
139
+ ### Basic Example
140
+
141
+ A minimal setup with a table displaying user data:
142
+
143
+ ```typescript
144
+ import { SnapRecords, RowsPerPage } from './SnapRecords';
145
+
146
+ const snapRecords = new SnapRecords('table-container', {
147
+ url: '/api/users',
148
+ columns: ['id', 'name', 'email'],
149
+ columnTitles: ['ID', 'Name', 'Email'],
150
+ rowsPerPage: RowsPerPage.TWENTY,
151
+ });
152
+ ```
153
+
154
+ ### Example with Row Selection and Custom Formatting
155
+
156
+ Enabling row selection, custom formatting, and disabling sorting on a column:
157
+
158
+ ```typescript
159
+ import { SnapRecords, RenderType, RowsPerPage } from './SnapRecords';
160
+
161
+ const snapRecords = new SnapRecords('table-container', {
162
+ url: '/api/users',
163
+ columns: ['id', 'name', 'status', 'notes'],
164
+ columnTitles: ['ID', 'Name', 'Status', 'Notes'],
165
+ columnFormatters: {
166
+ status: (value) => `<span class="${value}">${String(value).toUpperCase()}</span>`,
167
+ },
168
+ rowsPerPage: RowsPerPage.FIFTY,
169
+ selectable: true,
170
+ headerCellClasses: ['col-id', 'col-name no-sorting', 'col-status', 'col-notes'],
171
+ });
172
+
173
+ const api = snapRecords.getApi();
174
+ console.log(api.getSelectedRows());
175
+ ```
176
+
177
+ ### Complete Configuration Example
178
+
179
+ Using all available options:
180
+
181
+ ```typescript
182
+ import { SnapRecords, RenderType, RowsPerPage } from './SnapRecords';
183
+
184
+ const snapRecords = new SnapRecords('table-container', {
185
+ url: 'https://api.example.com/data',
186
+ columns: ['id', 'name', 'email', 'status'],
187
+ columnTitles: ['ID', 'Name', 'Email', 'Status'],
188
+ columnFormatters: {
189
+ status: (value) => `<span class="${value}">${String(value).toUpperCase()}</span>`,
190
+ name: (value) => String(value).toLowerCase(),
191
+ },
192
+ format: RenderType.TABLE,
193
+ rowsPerPage: RowsPerPage.TWENTY,
194
+ useCache: true,
195
+ usePushState: true,
196
+ language: 'pt_PT',
197
+ headerCellClasses: ['id-col', 'name-col no-sorting', 'email-col', 'status-col'],
198
+ cacheExpiry: 7200000,
199
+ selectable: true,
200
+ lifecycleHooks: {
201
+ preDataLoad: (params) => console.log('Fetching:', params),
202
+ postDataLoad: (data) => console.log('Loaded:', data),
203
+ preRender: () => console.log('Rendering...'),
204
+ postRender: () => console.log('Render complete'),
205
+ selectionChanged: (rows) => console.log('Selected:', rows),
206
+ },
207
+ theme: 'dark',
208
+ draggableColumns: true,
209
+ prevButton: {
210
+ text: '<i class="fa fa-arrow-left"></i> Previous',
211
+ isHtml: true,
212
+ template: (page) => `<span>Back to page ${page}</span>`,
213
+ },
214
+ nextButton: {
215
+ text: 'Next',
216
+ isHtml: false,
217
+ template: (page) => `Next: ${page}`,
218
+ },
219
+ retryAttempts: 5,
220
+ preloadNextPage: true,
221
+ persistState: true,
222
+ destroyOnUnload: true,
223
+ debug: true,
224
+ lazyLoadMedia: true,
225
+ formatCacheSize: 1000,
226
+ });
227
+
228
+ const api = snapRecords.getApi();
229
+ api.search({ status: 'active' }, true);
230
+ api.gotoPage(2);
231
+ api.setTheme('light');
232
+ api.setRenderMode(RenderType.MOBILE_CARDS);
233
+ ```
234
+
235
+ ## Configuration Options
236
+
237
+ The `SnapRecordsOptions<T>` interface defines all configuration options. Key options include (see `config.md` for full details):
238
+
239
+ - `url` (string, required): API URL for data fetching.
240
+ - `columns` (string[], required): Column keys to display.
241
+ - `columnTitles` (string[]): Custom header titles.
242
+ - `columnFormatters` ({ [key: string]: (value, row) => string }): Custom cell formatters, cached with `lru-cache`.
243
+ - `format` (RenderType): Rendering mode (`TABLE`, `LIST`, `MOBILE_CARDS`). Default: `TABLE`.
244
+ - `rowsPerPage` (RowsPerPage): Rows per page (10, 20, 50, 100, 250, 500, 1000). Default: 10.
245
+ - `useCache` (boolean): Enables IndexedDB caching. Default: `false`.
246
+ - `usePushState` (boolean): Updates browser URL with state. Default: `false`.
247
+ - `language` (string): UI language. Default: `en_US`.
248
+ - `headerCellClasses` (string[]): Header CSS classes, with `no-sorting` to disable sorting.
249
+ - `selectable` (boolean): Enables row selection. Default: `false`.
250
+ - `draggableColumns` (boolean): Enables column drag-and-drop. Default: `false`.
251
+ - `persistState` (boolean): Saves state to `localStorage`. Default: `false`.
252
+ - `destroyOnUnload` (boolean): Destroys instance on window unload. Default: `true`.
253
+ - `debug` (boolean): Enables debug logs. Default: `false`.
254
+ - `lazyLoadMedia` (boolean): Enables lazy loading for images. Default: `false`.
255
+ - `formatCacheSize` (number): Sets the maximum size of the LRU format cache. Default: 500.
256
+ - `lifecycleHooks` (LifecycleHooks<T>): Callbacks for lifecycle events.
257
+ - `prevButton`, `nextButton`: Customizes pagination buttons with text, HTML, or templates.
258
+
259
+ ## API Methods
260
+
261
+ The `SnapApi` class provides methods for interacting with the component:
262
+
263
+ - `search(filters: Record<string, string>, merge?: boolean): void` - Applies filters and reloads data.
264
+ - `updateParams(params: Partial<Pick<SnapRecordsState<T>, 'currentPage' | 'rowsPerPage' | 'filters' | 'sortConditions'>>): void` - Updates multiple parameters.
265
+ - `reset(): void` - Clears filters, sorting, and state.
266
+ - `refresh(): void` - Reloads current data view.
267
+ - `gotoPage(page: number): void` - Navigates to a page.
268
+ - `setTheme(theme: 'light' | 'dark' | 'default'): void` - Sets the theme.
269
+ - `setRenderMode(mode: RenderType): void` - Changes rendering mode.
270
+ - `setRowsPerPage(newRowsPerPage: RowsPerPage): void` - Sets rows per page.
271
+ - `setLanguage(newLanguage: string): Promise<void>` - Sets UI language.
272
+ - `getData(): ReadonlyArray<T>` - Returns current data.
273
+ - `getTotals(): { totalRecords: number }` - Returns total records.
274
+ - `getSelectedRows(): T[]` - Returns selected rows.
275
+ - `clearSelection(): void` - Clears row selections.
276
+ - `destroy(): void` - Destroys the instance, clearing elements and cache.
277
+
278
+ Example:
279
+
280
+ ```typescript
281
+ const api = snapRecords.getApi();
282
+ api.search({ status: 'active' }, true);
283
+ api.gotoPage(2);
284
+ api.setRenderMode(RenderType.LIST);
285
+ api.clearSelection();
286
+ api.destroy();
287
+ ```
288
+
289
+ ## Styling
290
+
291
+ Customize styles via `src/scss/SnapRecords.scss`. The compiled `snap-records.css` must be included in your application:
292
+
293
+ ```html
294
+ <link rel="stylesheet" href="/path/to/snap-records.css" />
295
+ ```
296
+
297
+ Key classes:
298
+
299
+ - `.snap-records`: Table container.
300
+ - `.snap-list`: List view container.
301
+ - `.snap-mobile-cards-container`: Mobile cards container.
302
+ - `.theme-light`, `.theme-dark`, `.theme-default`: Theme classes.
303
+ - `.snap-column-resize-handle`: Column resize handle.
304
+ - `.snap-draggable-column`: Draggable column indicator.
305
+ - `.snap-current-row`, `.snap-selected`: Row highlighting for navigation and selection.
306
+
307
+ Override styles in your CSS as needed.
308
+
309
+ ## Accessibility
310
+
311
+ SnapRecords prioritizes accessibility:
312
+
313
+ - **ARIA Attributes**: Supports `aria-sort`, `aria-selected`, `aria-label` for table, list, and card modes.
314
+ - **Keyboard Navigation**: ArrowUp/Down for row navigation, Enter/Space for selection, PageUp/Down for pagination (see `keyboard.md`).
315
+ - **Screen Reader Support**: Announces updates (e.g., row selection, mode changes) via ARIA live regions.
316
+
317
+ ## State Management
318
+
319
+ The `SnapRecordsState` interface manages state, including:
320
+
321
+ - Current page, rows per page, filters, sort conditions.
322
+ - Column order, widths, titles.
323
+ - Data, total records, format, language, theme.
324
+
325
+ State is persisted to `localStorage` when `persistState` is `true`, managed by `StateManager.ts`.
326
+
327
+ ## Internationalization
328
+
329
+ 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:
330
+
331
+ ```json
332
+ {
333
+ "errors": {
334
+ "generic": "An error occurred.",
335
+ "invalidConfig": "Invalid configuration: {reason}",
336
+ "containerNotFound": "Container with ID {id} not found.",
337
+ "dataLoadingFailed": "Failed to load data: {error}",
338
+ "renderFailed": "Failed to render: {error}"
339
+ },
340
+ "loading": "Loading...",
341
+ "totalRecords": "Total records: {total}",
342
+ "filteredRecords": "Filtered records: {total}",
343
+ "errorTitle": "Error",
344
+ "errorMessage": "An unexpected error occurred.",
345
+ "noDataAvailable": "No data available.",
346
+ "previous": "Previous",
347
+ "next": "Next",
348
+ "retry": "Retry",
349
+ "pagination": {
350
+ "showingRecords": "Showing {start} to {end} of {total} records"
351
+ },
352
+ "currentPage": "Page {page}",
353
+ "jumpToPage": "Jump to page",
354
+ "pageNavigation": "Page navigation",
355
+ "sortAscending": "Sort ascending",
356
+ "sortDescending": "Sort descending",
357
+ "removeSort": "Remove sort",
358
+ "rowSelected": "Row selected",
359
+ "rowDeselected": "Row deselected",
360
+ "columnResizeHandle": "Resize column",
361
+ "dragColumn": "Drag column {col}",
362
+ "loadMore": "Load more"
363
+ }
364
+ ```
365
+
366
+ ## Troubleshooting
367
+
368
+ Common issues and solutions:
369
+
370
+ - **"Container with ID 'table-container' not found"**:
371
+ Ensure the container element exists in the DOM before initializing SnapRecords:
372
+
373
+ ```html
374
+ <div id="table-container"></div>
375
+ ```
376
+
377
+ - **"Translation file for pt_PT not found"**:
378
+ Verify that `/lang/pt_PT.json` is in your public directory and accessible via HTTP.
379
+
380
+ - **Styles not applied**:
381
+ Ensure `snap-records.css` is included in your HTML or bundler:
382
+
383
+ ```html
384
+ <link rel="stylesheet" href="/path/to/snap-records.css" />
385
+ ```
386
+
387
+ - **Data not rendering**:
388
+ Confirm that the server response includes a unique `id` field for each row, as required by the `Identifiable` interface:
389
+
390
+ ```json
391
+ {
392
+ "data": [
393
+ { "id": 1, "name": "John" },
394
+ { "id": 2, "name": "Jane" }
395
+ ],
396
+ "totalRecords": 2
397
+ }
398
+ ```
399
+
400
+ - **Keyboard navigation not working**:
401
+ Ensure `selectable: true` for row navigation and that the container is focused (`snapRecords.container.focus()`).
402
+
403
+ ## Development
404
+
405
+ ### Building
406
+
407
+ Compile TypeScript and SCSS:
408
+
409
+ ```bash
410
+ npm run build
411
+ ```
412
+
413
+ This runs `npm run build:js` (for `tsc --noEmit` and `vite build`) and `npm run build:css` (for SCSS compilation with source maps).
414
+
415
+ ### Testing
416
+
417
+ Tests are in the `tests/` directory, using Jest with JSDOM. `tests/SnapRecords.test.ts` covers initialization, API methods, user interactions (sorting, resizing, drag-and-drop), and rendering modes. Run tests with:
418
+
419
+ ```bash
420
+ npm test
421
+ ```
422
+
423
+ ### Extending
424
+
425
+ Add custom translations by creating a JSON file in `/lang`:
426
+
427
+ ```json
428
+ {
429
+ "loading": "Chargement...",
430
+ "errors": {
431
+ "generic": "Une erreur est survenue."
432
+ // ...
433
+ }
434
+ }
435
+ ```
436
+
437
+ Customize rendering or event handling by providing custom `renderer`, `eventManager`, `stateManager`, `urlManager`, or `cacheManager` in the options.
438
+
439
+ ## Additional Notes
440
+
441
+ - **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:
442
+
443
+ ```json
444
+ {
445
+ "data": [
446
+ { "id": 1, "name": "John", "email": "john@example.com" },
447
+ { "id": 2, "name": "Jane", "email": "jane@example.com" }
448
+ ],
449
+ "totalRecords": 2
450
+ }
451
+ ```
452
+
453
+ Failure to include a unique `id` may result in rendering errors or inconsistent behavior.
454
+
455
+ - **CSS Requirement**: The compiled `snap-records.css` file is required for proper styling of the table, list, or mobile card views, including support for column resizing, drag-and-drop, and row highlighting.
456
+
457
+ ## Contributing
458
+
459
+ 1. Fork the repository.
460
+ 2. Create a feature branch (`git checkout -b feature/new-feature`).
461
+ 3. Commit changes (`git commit -m 'Add new feature'`).
462
+ 4. Push to the branch (`git push origin feature/new-feature`).
463
+ 5. Open a pull request.
464
+
465
+ ## License
466
+
467
+ MIT License. See [`LICENSE`](./docs/LICENSE.txt) for details.
468
+
469
+ ## Support
470
+
471
+ For issues, feature requests, or questions, please open an issue on the repository or contact the maintainer.