@jungherz-de/notionkit-elements 1.20.1 → 1.21.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungherz-de/notionkit-elements",
3
- "version": "1.20.1",
3
+ "version": "1.21.0",
4
4
  "description": "Vanilla JavaScript Web Components wrapping the NotionKit CSS foundation – <nk-*> custom elements with Shadow DOM, automatic theme sync and native form participation.",
5
5
  "license": "MIT",
6
6
  "author": "Jungherz GmbH <info@jungherz.com> (https://www.jungherz.com)",
@@ -63,10 +63,10 @@
63
63
  "dark-mode"
64
64
  ],
65
65
  "peerDependencies": {
66
- "@jungherz-de/notionkit": ">=1.20.0"
66
+ "@jungherz-de/notionkit": ">=1.21.0"
67
67
  },
68
68
  "devDependencies": {
69
- "@jungherz-de/notionkit": "^1.20.0",
69
+ "@jungherz-de/notionkit": "^1.21.0",
70
70
  "@playwright/test": "^1.50.0",
71
71
  "@rollup/plugin-node-resolve": "^15.0.0",
72
72
  "@rollup/plugin-terser": "^0.4.0",
@@ -13,6 +13,15 @@ import { renderPropertyCell, compareBy } from '../../util/property-cell.js';
13
13
  // type 'actions' (NotionKit 1.12.0) sets buttons in each row; a click on one
14
14
  // fires nk-action { action, row, id, anchor } instead of selecting the row,
15
15
  // and its header sorts nothing.
16
+ //
17
+ // A checkbox column with `selectAll: true` (1.21.0) is a selection column:
18
+ // its header holds a checkbox instead of a name – empty, a dash for some
19
+ // rows, a check for all – and sorts nothing (th.select-all, NotionKit
20
+ // 1.21.0). A click or Space sets the column for every row the view has –
21
+ // a dash selects all, as Notion does –, renders again and fires one
22
+ // nk-change { key, value, all: true, rows }, so an app that keeps its own
23
+ // selection follows with one update. What "all" means beyond these rows –
24
+ // more pages, a filter on the server – stays with the app.
16
25
  class NkTableView extends NkElement {
17
26
  static get observedAttributes() { return ['name', 'label', 'badge', 'count', 'new-row', 'new-row-label', 'sortable', 'sort-key', 'sort-dir', 'wrap']; }
18
27
 
@@ -42,6 +51,7 @@ class NkTableView extends NkElement {
42
51
  this._table.classList.toggle('wrap', this.getBoolAttr('wrap'));
43
52
  const tr = document.createElement('tr');
44
53
  for (const col of this._columns) {
54
+ if (col.type === 'checkbox' && col.selectAll) { tr.appendChild(this._selectAllHeader(col)); continue; }
45
55
  const actions = col.type === 'actions';
46
56
  const th = this.createElement('th', actions ? ['actions'] : [], actions ? { scope: 'col' } : { 'data-key': col.key, scope: 'col' });
47
57
  if (col.icon) { const i = this.createElement('span', ['th-icon']); i.textContent = col.icon; th.appendChild(i); }
@@ -65,6 +75,40 @@ class NkTableView extends NkElement {
65
75
  }
66
76
  this._newRow.textContent = this.getAttribute('new-row-label') || this.str('newPage');
67
77
  this._newRow.style.display = this.getBoolAttr('new-row') ? '' : 'none';
78
+ this._syncSelectAll();
79
+ }
80
+
81
+ /** The header of a selection column: the cells' own checkbox, named "Select all". */
82
+ _selectAllHeader(col) {
83
+ const th = this.createElement('th', ['select-all'], { scope: 'col' });
84
+ const label = this.createElement('label', ['nk-check']);
85
+ label.appendChild(this.createElement('input', [], { type: 'checkbox', 'aria-label': this.str('selectAll'), 'data-select-all': col.key }));
86
+ th.appendChild(label);
87
+ if (col.width) th.style.width = col.width;
88
+ return th;
89
+ }
90
+
91
+ /** Every header box shows its column: checked for all rows, a dash for some, empty for none – and nothing to choose without rows. */
92
+ _syncSelectAll() {
93
+ for (const input of this._thead?.querySelectorAll('input[data-select-all]') ?? []) {
94
+ const key = input.dataset.selectAll, on = this._rows.filter(r => r[key]).length;
95
+ input.checked = on > 0 && on === this._rows.length;
96
+ input.indeterminate = on > 0 && on < this._rows.length;
97
+ input.disabled = !this._rows.length;
98
+ }
99
+ }
100
+
101
+ /** A click or Space on a header box: the column for every row, one event. */
102
+ _selectAll(input) {
103
+ const key = input.dataset.selectAll;
104
+ // A dash selects all, as Notion does; only a full column clears.
105
+ const value = !(this._rows.length && this._rows.every(r => r[key]));
106
+ for (const row of this._rows) row[key] = value;
107
+ const focused = this._shadow.activeElement === input;
108
+ this._render();
109
+ // Space should leave the focus where it was – on the new header box.
110
+ if (focused) [...this._thead.querySelectorAll('input[data-select-all]')].find(i => i.dataset.selectAll === key)?.focus();
111
+ this.emit('nk-change', { key, value, all: true, rows: this._rows });
68
112
  }
69
113
 
70
114
  setupEvents() {
@@ -95,8 +139,10 @@ class NkTableView extends NkElement {
95
139
  this._onChange = (e) => {
96
140
  const input = e.target;
97
141
  if (input.type !== 'checkbox') return;
142
+ if (input.dataset.selectAll != null) { this._selectAll(input); return; }
98
143
  const row = this._rowById(input.dataset.rowId);
99
144
  if (row) row[input.dataset.key] = input.checked;
145
+ this._syncSelectAll();
100
146
  this.emit('nk-change', { row, key: input.dataset.key, value: input.checked });
101
147
  };
102
148
  this._onKey = (e) => { if ((e.key === 'Enter' || e.key === ' ') && e.target === this._newRow) { e.preventDefault(); this.emit('nk-action', { action: 'new-row' }); } };
@@ -19,7 +19,7 @@ const BUILT_IN = {
19
19
  appearance: 'Appearance: {mode} – click: {next}', themeAuto: 'automatic (follows the system)', themeLight: 'light', themeDark: 'dark',
20
20
  message: 'Message', breadcrumb: 'Breadcrumb', commandPalette: 'Command palette', comment: 'Comment',
21
21
  searchEmoji: 'Search emoji', settings: 'Settings', title: 'Title', changeIcon: 'Change icon',
22
- add: 'Add', more: 'More',
22
+ add: 'Add', more: 'More', selectAll: 'Select all',
23
23
  },
24
24
  de: {
25
25
  copy: 'Kopieren', copied: 'Kopiert', show: 'Zeigen', hide: 'Verbergen',
@@ -34,7 +34,7 @@ const BUILT_IN = {
34
34
  appearance: 'Darstellung: {mode} – Klick: {next}', themeAuto: 'automatisch (wie das System)', themeLight: 'hell', themeDark: 'dunkel',
35
35
  message: 'Nachricht', breadcrumb: 'Pfad', commandPalette: 'Befehlspalette', comment: 'Kommentar',
36
36
  searchEmoji: 'Emoji suchen', settings: 'Einstellungen', title: 'Titel', changeIcon: 'Symbol ändern',
37
- add: 'Hinzufügen', more: 'Mehr',
37
+ add: 'Hinzufügen', more: 'Mehr', selectAll: 'Alle auswählen',
38
38
  },
39
39
  };
40
40
  const custom = {};