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 +21 -0
- package/README.md +445 -0
- package/dist/neiki-table.css +636 -0
- package/dist/neiki-table.js +1609 -0
- package/dist/neiki-table.min.css +2 -0
- package/dist/neiki-table.min.js +8 -0
- package/package.json +40 -0
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>
|