neiki-table 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 neikiri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,445 @@
1
+ <p align="center">
2
+ <img src="assets/img/logo.svg" alt="Neiki's Social Bar" width="400">
3
+ </p>
4
+
5
+ <h1 align="center">Neiki's Table</h1>
6
+
7
+ <p align="center">
8
+ <img src="https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E" alt="JavaScript">
9
+ <img src="https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white" alt="HTML5">
10
+ <img src="https://img.shields.io/badge/css-%23663399.svg?style=for-the-badge&logo=css&logoColor=white" alt="CSS">
11
+ <img src="https://img.shields.io/badge/web%20components-29ABE2.svg?style=for-the-badge&logo=webcomponentsdotorg&logoColor=white" alt="Web Components">
12
+ <br>
13
+ <img src="https://img.shields.io/badge/License-MIT-2563EB?style=for-the-badge&logo=open-source-initiative&logoColor=white&labelColor=000F15&logoWidth=20" alt="License">
14
+ <img src="https://img.shields.io/badge/Version-1.0.0-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
15
+ </p>
16
+
17
+ <p align="center">
18
+ <b>Lightweight, CDN-ready Data Table Web Component</b><br>
19
+ <i>Zero dependencies, framework-independent, drop into any page.</i>
20
+ </p>
21
+
22
+ <p align="center">
23
+ <img src="https://img.shields.io/badge/Languages-8%20Built--in-3b82f6?style=flat&labelColor=383C43" />
24
+ <img src="https://img.shields.io/badge/Themes-Light%20%2F%20Dark%20%2F%20Auto-8b5cf6?style=flat&labelColor=383C43" />
25
+ <img src="https://img.shields.io/badge/Setup-Zero%20Config-22c55e?style=flat&labelColor=383C43" />
26
+ <img src="https://img.shields.io/badge/Size-Lightweight-f97316?style=flat&labelColor=383C43" />
27
+ </p>
28
+
29
+ ---
30
+
31
+ <p align="center">
32
+ <img src="assets/img/preview.png" alt="Neiki's Table" width="900">
33
+ </p>
34
+
35
+ ---
36
+
37
+ **Live version:** [https://neikiri.dev/table](https://neikiri.dev/table)
38
+
39
+ ---
40
+
41
+ ## 🧭 Overview
42
+
43
+ Neiki's Table is a Web Component written in plain JavaScript with **zero dependencies**. Drop a single `<neiki-table>` tag onto a page and get an accessible, responsive, themeable data table — sorting, search, per-column filters, pagination, inline editing, row selection and CSV/JSON export — with no framework, bundler, or build step required.
44
+
45
+ ```html
46
+ <script src="https://cdn.neikiri.dev/neiki-table/neiki-table.min.js"></script>
47
+
48
+ <neiki-table id="users"></neiki-table>
49
+
50
+ <script>
51
+ const table = document.querySelector('#users');
52
+ table.setColumns([
53
+ { key: 'name', label: 'Name' },
54
+ { key: 'email', label: 'Email' }
55
+ ]);
56
+ table.setData([
57
+ { id: 1, name: 'Alice Johnson', email: 'alice@example.com' },
58
+ { id: 2, name: 'John Smith', email: 'john@example.com' }
59
+ ]);
60
+ </script>
61
+ ```
62
+
63
+ That snippet is a complete, working data table with sorting, search, filters, pagination, inline editing, row selection and export already enabled.
64
+
65
+ ---
66
+
67
+ ## ✨ Why Neiki's Table?
68
+
69
+ - **One script, no dependencies.** The component ships as a single custom element. No React, Vue, Svelte, or Angular required — it works in plain HTML just as well as inside any framework.
70
+ - **CDN-ready.** Load it from jsDelivr or unpkg and start using `<neiki-table>` immediately.
71
+ - **Full-featured out of the box.** Sorting, debounced global search, per-column filters, numbered pagination, inline cell editing, row selection, column resizing, density modes, custom cell formatting, and CSV/JSON export plus copy-to-clipboard — all enabled by default, all individually toggleable.
72
+ - **Internationalized.** Ships with English, Czech, German, Spanish, French, Italian, Polish and Slovak translations. Switch locale live, or register your own with `addTranslations()`.
73
+ - **Accessible by design.** Semantic table markup, sortable headers exposed as buttons with `aria-sort`, labeled checkboxes, visible focus states, and keyboard support for sorting and editing.
74
+ - **Safe with multiple instances.** Load the script more than once, or use several tables on one page — the custom element registers itself once.
75
+ - **Secure by default.** Cell values are rendered as text (never `innerHTML`), and CSV export neutralizes formula-injection payloads (`=`, `+`, `-`, `@`) before writing the file.
76
+
77
+ ---
78
+
79
+ ## 🚀 Getting started
80
+
81
+ The recommended install is the single bundled script from the CDN.
82
+
83
+ ```html
84
+ <script src="https://cdn.neikiri.dev/neiki-table/neiki-table.min.js"></script>
85
+ ```
86
+
87
+ <details>
88
+ <summary><b>Other installation options</b> (pinned version, jsDelivr, unpkg, npm, self-hosted)</summary>
89
+ <br>
90
+
91
+ **Pin a specific version (recommended for production)**
92
+
93
+ ```html
94
+ <script src="https://cdn.neikiri.dev/neiki-table/1.0.0/neiki-table.min.js"></script>
95
+ ```
96
+
97
+ **Load CSS and JS separately**
98
+
99
+ ```html
100
+ <!-- Latest -->
101
+ <link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-table/neiki-table.css">
102
+ <script src="https://cdn.neikiri.dev/neiki-table/neiki-table.js"></script>
103
+
104
+ <!-- Or pinned -->
105
+ <link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-table/1.0.0/neiki-table.css">
106
+ <script src="https://cdn.neikiri.dev/neiki-table/1.0.0/neiki-table.js"></script>
107
+ ```
108
+
109
+ **Alternative CDN — jsDelivr**
110
+
111
+ ```html
112
+ <script src="https://cdn.jsdelivr.net/npm/neiki-table@latest/dist/neiki-table.min.js"></script>
113
+ <!-- Pinned -->
114
+ <script src="https://cdn.jsdelivr.net/npm/neiki-table@1.0.0/dist/neiki-table.min.js"></script>
115
+ ```
116
+
117
+ **Alternative CDN — unpkg**
118
+
119
+ ```html
120
+ <script src="https://unpkg.com/neiki-table@1.0.0/dist/neiki-table.min.js"></script>
121
+ ```
122
+
123
+ **Package manager**
124
+
125
+ ```bash
126
+ npm install neiki-table
127
+ # or
128
+ yarn add neiki-table
129
+ # or
130
+ pnpm add neiki-table
131
+ ```
132
+
133
+ **Self-hosted**
134
+
135
+ ```html
136
+ <script src="path/to/dist/neiki-table.min.js"></script>
137
+ ```
138
+
139
+ The built `dist/neiki-table.min.js` bundles its CSS inline — one file is all you need, no separate stylesheet to keep track of. `dist/neiki-table.css` and `.min.css` are also published for reference (e.g. to preview the default styles or diff a customization), but the component never fetches them at runtime.
140
+
141
+ </details>
142
+
143
+ ---
144
+
145
+ ## 🧩 Basic usage
146
+
147
+ ```html
148
+ <neiki-table id="users" locale="en" theme="auto" row-key="id"></neiki-table>
149
+
150
+ <script>
151
+ const table = document.querySelector('#users');
152
+
153
+ table.setColumns([
154
+ { key: 'name', label: 'Name', type: 'text' },
155
+ { key: 'email', label: 'Email', type: 'text' },
156
+ { key: 'role', label: 'Role', type: 'select', options: ['Admin', 'Editor', 'Viewer'] },
157
+ { key: 'active', label: 'Active', type: 'boolean' },
158
+ { key: 'joined', label: 'Joined', type: 'date' }
159
+ ]);
160
+
161
+ table.setData([
162
+ { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', active: true, joined: '2023-02-14' },
163
+ { id: 2, name: 'John Smith', email: 'john@example.com', role: 'Editor', active: true, joined: '2023-06-01' }
164
+ ]);
165
+ </script>
166
+ ```
167
+
168
+ Multiple instances can coexist on the same page, each with independent configuration and data.
169
+
170
+ ---
171
+
172
+ ## 📐 Columns
173
+
174
+ Columns are defined with `setColumns()` (or the `columns` attribute, as a JSON string, for quick static demos):
175
+
176
+ | Property | Type | Default | Description |
177
+ |----------|------|---------|--------------|
178
+ | `key` | string | — | Property name to read from each row object |
179
+ | `label` | string | `key` | Column header text |
180
+ | `type` | `text`, `number`, `boolean`, `date`, `select` | `text` | Controls formatting, sorting, filtering and the inline editor |
181
+ | `options` | array | — | For `type: 'select'` — an array of strings, or `{ value, label }` objects |
182
+ | `sortable` | boolean | `true` | Whether the column header can be clicked to sort |
183
+ | `filterable` | boolean | `true` | Whether the column gets a filter control in the filter row |
184
+ | `editable` | boolean | `true` | Whether cells in this column can be inline-edited (also gated by the table's `editable` config) |
185
+ | `resizable` | boolean | `true` | Whether the column can be resized by dragging its header edge (also gated by the table's `resizable` config) |
186
+ | `align` | `left`, `center`, `right` | `left` (`right` for `number`) | Horizontal text alignment for the column's cells and header |
187
+ | `format` | function | — | `(value, row) => string` — custom display text for the cell. Applies to what's rendered, searched, filtered and exported. Output is always inserted with `textContent`, so it can't inject markup |
188
+ | `width` | string | — | Initial CSS width for the column (e.g. `'120px'`) |
189
+
190
+ ```javascript
191
+ table.setColumns([
192
+ { key: 'name', label: 'Name' },
193
+ { key: 'salary', label: 'Salary', type: 'number', align: 'right',
194
+ format: (v) => v == null ? '' : '$' + Number(v).toLocaleString('en-US') }
195
+ ]);
196
+ ```
197
+
198
+ ---
199
+
200
+ ## 🗃️ Data
201
+
202
+ ```javascript
203
+ table.setData(rows); // replaces all rows (array of plain objects)
204
+ table.getData(); // full current dataset (deep-ish copy)
205
+ table.getSelectedRows(); // currently selected rows
206
+ ```
207
+
208
+ Each row should have a stable identifier under the key configured by `row-key` (default `id`); it's used to track selection, editing, and sorting stability across re-renders.
209
+
210
+ ---
211
+
212
+ ## 🏷️ Attributes
213
+
214
+ | Attribute | Values | Default | Description |
215
+ |-----------|--------|---------|--------------|
216
+ | `locale` | `en`, `cs`, `de`, `es`, `fr`, `it`, `pl`, `sk`, or a custom locale registered via `addTranslations()` | `en` | UI language |
217
+ | `theme` | `light`, `dark`, `auto` | `auto` | Visual theme |
218
+ | `density` | `compact`, `normal`, `spacious` | `normal` | Vertical spacing / row height |
219
+ | `row-key` | string | `id` | Property used as each row's stable identifier |
220
+ | `searchable` | boolean attribute | on | Shows/enables the global search box |
221
+ | `filterable` | boolean attribute | on | Shows/enables the per-column filter row |
222
+ | `selectable` | boolean attribute | on | Shows/enables the row-selection checkbox column |
223
+ | `editable` | boolean attribute | on | Enables inline cell editing (per-column `editable: false` still overrides) |
224
+ | `resizable` | boolean attribute | on | Enables column resizing (per-column `resizable: false` still overrides) |
225
+ | `paginated` | boolean attribute | on | Shows/enables pagination controls |
226
+ | `exportable` | boolean attribute | on | Shows/enables the CSV/JSON export and copy buttons |
227
+ | `loading` | boolean attribute | off | Shows a loading overlay over the table body |
228
+ | `search-debounce` | number (ms) | `180` | Delay before a typed search query is applied (`0` = immediate) |
229
+ | `page-size` | number | `10` | Rows per page |
230
+ | `columns` | JSON string | — | Declarative alternative to `setColumns()` |
231
+ | `data` | JSON string | — | Declarative alternative to `setData()` |
232
+
233
+ ---
234
+
235
+ ## ⚙️ Methods
236
+
237
+ ```javascript
238
+ const table = document.querySelector('neiki-table');
239
+
240
+ table.setColumns(columns); // define columns, returns `table`
241
+ table.getColumns(); // current column definitions
242
+ table.setData(rows); // replace all data, returns `table`
243
+ table.getData(); // current dataset
244
+
245
+ table.setConfig({ pageSize: 25 }); // merge partial config, returns `table`
246
+ table.getConfig(); // full resolved config
247
+
248
+ table.setLocale('cs'); // switch UI language
249
+ table.getLocale(); // current locale
250
+ table.addTranslations('uk', {...}); // register/extend a locale's dictionary
251
+
252
+ table.setDensity('compact'); // 'compact' | 'normal' | 'spacious'
253
+ table.setLoading(true); // toggle the loading overlay
254
+
255
+ table.sortBy('name', 'asc'); // sort programmatically ('asc' | 'desc')
256
+ table.search('jan'); // set the global search query
257
+ table.setFilter('role', 'Admin'); // set a single column filter
258
+ table.clearFilters(); // clear search + all filters
259
+
260
+ table.goToPage(2); // navigate to a page (1-based)
261
+ table.setPageSize(50); // change rows per page
262
+
263
+ table.selectRow(rowKey, true); // select/deselect a single row
264
+ table.selectAll(true); // select/deselect all currently filtered rows
265
+ table.clearSelection();
266
+ table.getSelectedRows(); // currently selected rows
267
+
268
+ table.exportCSV('filename.csv'); // download selection, or filtered view if none selected
269
+ table.exportJSON('filename.json');
270
+ table.copyCSV(); // copy the same CSV to the clipboard
271
+
272
+ table.refresh(); // force a re-render
273
+ ```
274
+
275
+ ---
276
+
277
+ ## 📡 Events
278
+
279
+ All events bubble and are composed (cross Shadow DOM boundary), with details on `event.detail`.
280
+
281
+ | Event | Fired when | `detail` |
282
+ |-------|------------|----------|
283
+ | `neiki-table:ready` | The component finished its first render | `{ config }` |
284
+ | `neiki-table:sort` | The sort column/direction changes | `{ key, dir }` |
285
+ | `neiki-table:search` | The global search query changes | `{ query }` |
286
+ | `neiki-table:filter` | A column filter changes, or filters are cleared | `{ filters }` |
287
+ | `neiki-table:page-change` | The page or page size changes | `{ page, pageSize? }` |
288
+ | `neiki-table:select` | Row selection changes | `{ selected }` (array of row keys) |
289
+ | `neiki-table:cell-edit` | An inline edit is committed | `{ rowKey, key, oldValue, newValue, row }` |
290
+ | `neiki-table:row-click` | A row body is clicked (ignoring inline controls) | `{ rowKey, row }` |
291
+ | `neiki-table:column-resize` | A column is resized by dragging | `{ key, width }` |
292
+ | `neiki-table:export` | CSV/JSON export runs | `{ format, count }` |
293
+ | `neiki-table:copy` | Copy-to-clipboard runs | `{ format, count, ok }` |
294
+ | `neiki-table:error` | Invalid `columns`/`data` JSON attribute | `{ reason }` |
295
+
296
+ ```javascript
297
+ table.addEventListener('neiki-table:cell-edit', (event) => {
298
+ console.log(event.detail.key, event.detail.oldValue, '→', event.detail.newValue);
299
+ });
300
+ ```
301
+
302
+ ---
303
+
304
+ ## 🎨 CSS variables
305
+
306
+ All variables use the `--ntbl-*` prefix and can be overridden per instance or globally:
307
+
308
+ ```css
309
+ neiki-table {
310
+ --ntbl-radius: 12px;
311
+ --ntbl-accent: #7c3aed;
312
+ --ntbl-bg: #ffffff;
313
+ --ntbl-color: #1f2328;
314
+ }
315
+ ```
316
+
317
+ | Variable | Purpose |
318
+ |----------|---------|
319
+ | `--ntbl-radius` | Outer container border radius |
320
+ | `--ntbl-font-size` | Base font size |
321
+ | `--ntbl-row-height` | Approximate row height |
322
+ | `--ntbl-shadow` | Outer container box-shadow |
323
+ | `--ntbl-bg` / `--ntbl-color` | Base background / text color |
324
+ | `--ntbl-muted` | Secondary text color (footer, empty state) |
325
+ | `--ntbl-border` | Border color used throughout |
326
+ | `--ntbl-header-bg` | Table header background |
327
+ | `--ntbl-row-hover` / `--ntbl-row-selected` | Row hover / selected background |
328
+ | `--ntbl-stripe` | Zebra-striping tint for even rows |
329
+ | `--ntbl-accent` / `--ntbl-focus-ring` | Accent and keyboard focus ring color |
330
+ | `--ntbl-input-bg` / `--ntbl-input-border` | Search/filter/edit input colors |
331
+ | `--ntbl-badge-true-bg` / `--ntbl-badge-true-color` | Boolean "yes" badge colors |
332
+ | `--ntbl-badge-false-bg` / `--ntbl-badge-false-color` | Boolean "no" badge colors |
333
+
334
+ ---
335
+
336
+ ## 🌗 Themes
337
+
338
+ | Theme | Description |
339
+ |-------|-------------|
340
+ | `light` | Light background, dark text |
341
+ | `dark` | Dark background, light text |
342
+ | `auto` | Follows `prefers-color-scheme`, updates live if the OS theme changes |
343
+
344
+ ---
345
+
346
+ ## 🌍 Internationalization
347
+
348
+ Built-in locales: `en`, `cs`, `de`, `es`, `fr`, `it`, `pl`, `sk`. Set the `locale` attribute/config, or switch at runtime with `setLocale()`. To add a language (or override strings in an existing one):
349
+
350
+ ```javascript
351
+ table.addTranslations('uk', {
352
+ searchPlaceholder: 'Пошук…',
353
+ noResults: 'Відповідних рядків не знайдено',
354
+ // ...see src/neiki-table.js for the full key list
355
+ });
356
+ table.setLocale('uk');
357
+ ```
358
+
359
+ ---
360
+
361
+ ## ♿ Accessibility
362
+
363
+ - Sortable column headers are keyboard-focusable (`tabindex="0"`, `role="button"`) and expose `aria-sort`.
364
+ - Row and select-all checkboxes carry descriptive `aria-label`s.
365
+ - Editable cells are focusable and can be opened for editing with the keyboard (`Enter`), and committed/cancelled with `Enter`/`Escape`.
366
+ - Visible `:focus-visible` outlines on every interactive element.
367
+ - `prefers-reduced-motion: reduce` disables transitions.
368
+ - Colors default to sufficient contrast in both light and dark themes.
369
+
370
+ ---
371
+
372
+ ## 🔒 Security
373
+
374
+ - Cell content is always rendered with `textContent`, never `innerHTML` — row data can never inject markup into the page.
375
+ - CSV export neutralizes formula-injection payloads: values starting with `=`, `+`, `-`, `@`, tab, or carriage return are prefixed with a leading apostrophe before being written, so spreadsheet software won't execute them as formulas.
376
+ - CSV fields are quoted and escaped per RFC 4180 when they contain commas, quotes, or newlines.
377
+ - Export downloads use `Blob` + a temporary `<a download>` link — no navigation, no server round-trip, no user data leaves the page.
378
+
379
+ See [SECURITY.md](SECURITY.md) for the full policy and how to report vulnerabilities.
380
+
381
+ ---
382
+
383
+ ## 🎬 Demo
384
+
385
+ Open [`demo/index.html`](demo/index.html) in a browser (or serve the repo locally) to see sorting, search, filters, pagination, inline editing, row selection, export, locale switching, and theming in action.
386
+
387
+ ---
388
+
389
+ ## 🛠️ Build / minify
390
+
391
+ ```bash
392
+ npm run build
393
+ ```
394
+
395
+ Runs [`minify.py`](minify.py), which reads `src/neiki-table.js` and `src/neiki-table.css` and produces:
396
+
397
+ ```
398
+ dist/neiki-table.js # CSS embedded inline, unminified
399
+ dist/neiki-table.min.js # CSS embedded inline, minified (recommended)
400
+ dist/neiki-table.css # standalone copy, for reference
401
+ dist/neiki-table.min.css # standalone copy, for reference
402
+ ```
403
+
404
+ The CSS is baked directly into both JavaScript bundles at build time — loading either `dist` script is enough on its own, no separate stylesheet request required. JS minification uses [Terser](https://github.com/terser/terser) via `npx` when available, falling back to an unminified copy otherwise.
405
+
406
+ ```bash
407
+ npm test
408
+ ```
409
+
410
+ Runs `node --check` against the source file as a syntax sanity check.
411
+
412
+ ---
413
+
414
+ ## 🌐 Browser support
415
+
416
+ Neiki's Table uses Custom Elements v1, Shadow DOM, and standard DOM APIs, and targets current versions of modern browsers.
417
+
418
+ | Browser | Support |
419
+ |---------|---------|
420
+ | Chrome | Latest |
421
+ | Firefox | Latest |
422
+ | Safari | Latest |
423
+ | Edge | Latest |
424
+
425
+ > Internet Explorer is not supported.
426
+
427
+ ---
428
+
429
+ ## 🤝 Contributing
430
+
431
+ Contributions are welcome. Please review [CONTRIBUTING.md](CONTRIBUTING.md) and the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before opening an issue or pull request. Security-related reports should follow [SECURITY.md](SECURITY.md).
432
+
433
+ The component source lives in `src/` (`neiki-table.js`, `neiki-table.css`); the distributable builds are in `dist/`.
434
+
435
+ ---
436
+
437
+ ## 📄 License
438
+
439
+ Released under the **MIT License**. See the [LICENSE](LICENSE) file for details.
440
+
441
+ ---
442
+
443
+ <p align="center">
444
+ Made with ❤️ for the web community
445
+ </p>