@jungherz-de/notionkit-elements 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 +90 -0
- package/dist/components/nk-ai-input-row.js +57 -0
- package/dist/components/nk-ai-msg.js +67 -0
- package/dist/components/nk-ai-thread.js +14 -0
- package/dist/components/nk-app.js +23 -0
- package/dist/components/nk-avatar-group.js +35 -0
- package/dist/components/nk-banner.js +30 -0
- package/dist/components/nk-block-host.js +35 -0
- package/dist/components/nk-board-view.js +129 -0
- package/dist/components/nk-breadcrumb.js +78 -0
- package/dist/components/nk-btn.js +107 -0
- package/dist/components/nk-callout.js +36 -0
- package/dist/components/nk-check.js +77 -0
- package/dist/components/nk-cmdk.js +221 -0
- package/dist/components/nk-code.js +66 -0
- package/dist/components/nk-comment.js +41 -0
- package/dist/components/nk-comments.js +57 -0
- package/dist/components/nk-danger-zone.js +27 -0
- package/dist/components/nk-database.js +100 -0
- package/dist/components/nk-divider.js +12 -0
- package/dist/components/nk-emoji-picker.js +112 -0
- package/dist/components/nk-empty.js +36 -0
- package/dist/components/nk-field.js +50 -0
- package/dist/components/nk-filter-bar.js +78 -0
- package/dist/components/nk-heading.js +29 -0
- package/dist/components/nk-input.js +108 -0
- package/dist/components/nk-kbd.js +15 -0
- package/dist/components/nk-member-list.js +30 -0
- package/dist/components/nk-member-row.js +46 -0
- package/dist/components/nk-mention.js +31 -0
- package/dist/components/nk-menu-item.js +68 -0
- package/dist/components/nk-menu.js +45 -0
- package/dist/components/nk-modal.js +166 -0
- package/dist/components/nk-model-card.js +89 -0
- package/dist/components/nk-page-actions.js +15 -0
- package/dist/components/nk-page-cover.js +35 -0
- package/dist/components/nk-page-title.js +71 -0
- package/dist/components/nk-page.js +71 -0
- package/dist/components/nk-pop.js +90 -0
- package/dist/components/nk-profile-row.js +25 -0
- package/dist/components/nk-progress.js +43 -0
- package/dist/components/nk-quote.js +32 -0
- package/dist/components/nk-radio.js +160 -0
- package/dist/components/nk-section-label.js +42 -0
- package/dist/components/nk-segmented.js +99 -0
- package/dist/components/nk-select.js +127 -0
- package/dist/components/nk-settings-pane.js +40 -0
- package/dist/components/nk-settings-user.js +36 -0
- package/dist/components/nk-sidebar.js +81 -0
- package/dist/components/nk-skeleton.js +31 -0
- package/dist/components/nk-slider.js +85 -0
- package/dist/components/nk-stat.js +40 -0
- package/dist/components/nk-stats.js +14 -0
- package/dist/components/nk-switch.js +69 -0
- package/dist/components/nk-synced.js +26 -0
- package/dist/components/nk-tab.js +54 -0
- package/dist/components/nk-table-view.js +109 -0
- package/dist/components/nk-tabs.js +82 -0
- package/dist/components/nk-tag.js +32 -0
- package/dist/components/nk-template-btn.js +43 -0
- package/dist/components/nk-textarea.js +95 -0
- package/dist/components/nk-theme-toggle.js +67 -0
- package/dist/components/nk-toast.js +51 -0
- package/dist/components/nk-todo.js +73 -0
- package/dist/components/nk-toggle.js +56 -0
- package/dist/components/nk-topbar.js +24 -0
- package/dist/components/nk-tree-item.js +155 -0
- package/dist/components/nk-tree.js +123 -0
- package/dist/components/nk-workspace-switcher.js +82 -0
- package/dist/components/shared/base-6gEcoatG.js +317 -0
- package/dist/components/shared/focus-DXrK9oLe.js +47 -0
- package/dist/components/shared/property-cell-Dn91uFkf.js +92 -0
- package/dist/notionkit-elements.esm.js +4639 -0
- package/dist/notionkit-elements.js +4718 -0
- package/dist/notionkit-elements.min.js +1 -0
- package/package.json +72 -0
- package/src/base.js +296 -0
- package/src/components/content/nk-callout.js +35 -0
- package/src/components/content/nk-code.js +51 -0
- package/src/components/content/nk-divider.js +11 -0
- package/src/components/content/nk-heading.js +28 -0
- package/src/components/content/nk-kbd.js +14 -0
- package/src/components/content/nk-progress.js +42 -0
- package/src/components/content/nk-quote.js +31 -0
- package/src/components/content/nk-tag.js +31 -0
- package/src/components/content/nk-todo.js +72 -0
- package/src/components/content/nk-toggle.js +55 -0
- package/src/components/data/nk-ai-input-row.js +56 -0
- package/src/components/data/nk-ai-msg.js +66 -0
- package/src/components/data/nk-ai-thread.js +13 -0
- package/src/components/data/nk-board-view.js +128 -0
- package/src/components/data/nk-comment.js +40 -0
- package/src/components/data/nk-comments.js +56 -0
- package/src/components/data/nk-database.js +99 -0
- package/src/components/data/nk-filter-bar.js +77 -0
- package/src/components/data/nk-table-view.js +108 -0
- package/src/components/forms/nk-btn.js +106 -0
- package/src/components/forms/nk-check.js +76 -0
- package/src/components/forms/nk-field.js +49 -0
- package/src/components/forms/nk-input.js +107 -0
- package/src/components/forms/nk-radio.js +159 -0
- package/src/components/forms/nk-select.js +126 -0
- package/src/components/forms/nk-slider.js +84 -0
- package/src/components/forms/nk-switch.js +68 -0
- package/src/components/forms/nk-textarea.js +94 -0
- package/src/components/overlays/nk-cmdk.js +201 -0
- package/src/components/overlays/nk-emoji-picker.js +94 -0
- package/src/components/overlays/nk-menu-item.js +67 -0
- package/src/components/overlays/nk-menu.js +44 -0
- package/src/components/overlays/nk-modal.js +165 -0
- package/src/components/overlays/nk-pop.js +89 -0
- package/src/components/overlays/nk-settings-pane.js +39 -0
- package/src/components/overlays/nk-settings-user.js +35 -0
- package/src/components/overlays/nk-toast.js +50 -0
- package/src/components/page/nk-avatar-group.js +34 -0
- package/src/components/page/nk-banner.js +29 -0
- package/src/components/page/nk-block-host.js +34 -0
- package/src/components/page/nk-danger-zone.js +26 -0
- package/src/components/page/nk-empty.js +35 -0
- package/src/components/page/nk-member-list.js +29 -0
- package/src/components/page/nk-member-row.js +45 -0
- package/src/components/page/nk-mention.js +30 -0
- package/src/components/page/nk-model-card.js +88 -0
- package/src/components/page/nk-page-actions.js +14 -0
- package/src/components/page/nk-page-cover.js +34 -0
- package/src/components/page/nk-page-title.js +70 -0
- package/src/components/page/nk-page.js +70 -0
- package/src/components/page/nk-profile-row.js +24 -0
- package/src/components/page/nk-segmented.js +98 -0
- package/src/components/page/nk-skeleton.js +30 -0
- package/src/components/page/nk-stat.js +39 -0
- package/src/components/page/nk-stats.js +13 -0
- package/src/components/page/nk-synced.js +25 -0
- package/src/components/page/nk-tab.js +53 -0
- package/src/components/page/nk-tabs.js +81 -0
- package/src/components/page/nk-template-btn.js +42 -0
- package/src/components/shell/nk-app.js +22 -0
- package/src/components/shell/nk-breadcrumb.js +77 -0
- package/src/components/shell/nk-section-label.js +41 -0
- package/src/components/shell/nk-sidebar.js +80 -0
- package/src/components/shell/nk-theme-toggle.js +66 -0
- package/src/components/shell/nk-topbar.js +23 -0
- package/src/components/shell/nk-tree-item.js +154 -0
- package/src/components/shell/nk-tree.js +122 -0
- package/src/components/shell/nk-workspace-switcher.js +81 -0
- package/src/index.js +88 -0
- package/src/util/emoji.js +17 -0
- package/src/util/focus.js +45 -0
- package/src/util/fuzzy.js +19 -0
- package/src/util/highlight.js +14 -0
- package/src/util/property-cell.js +90 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-ai-input-row placeholder="Ask Mona something …"></nk-ai-input-row>
|
|
4
|
+
// → <div class="nk-ai-input-row"><span>✨</span><input><button class="nk-ai-send">↑</button></div>
|
|
5
|
+
// Enter or the send button fires nk-submit { text } and clears the field.
|
|
6
|
+
class NkAiInputRow extends NkElement {
|
|
7
|
+
static get observedAttributes() { return ['placeholder', 'value', 'disabled', 'icon']; }
|
|
8
|
+
|
|
9
|
+
render() {
|
|
10
|
+
this._row = this.createElement('div', ['nk-ai-input-row']);
|
|
11
|
+
this._icon = document.createElement('span');
|
|
12
|
+
this._icon.style.fontSize = '14px';
|
|
13
|
+
this._input = this.createElement('input', [], { type: 'text', 'aria-label': 'Message' });
|
|
14
|
+
this._btn = this.createElement('button', ['nk-ai-send'], { type: 'button', 'aria-label': 'Send' });
|
|
15
|
+
this._btn.textContent = '↑';
|
|
16
|
+
this._row.append(this._icon, this._input, this._btn);
|
|
17
|
+
this._wrapper.appendChild(this._row);
|
|
18
|
+
this._sync();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_sync() {
|
|
22
|
+
this._input.placeholder = this.getAttribute('placeholder') || 'Ask something …';
|
|
23
|
+
if (this.hasAttribute('value') && this._input.value !== this.getAttribute('value')) this._input.value = this.getAttribute('value');
|
|
24
|
+
this._input.disabled = this._btn.disabled = this.getBoolAttr('disabled');
|
|
25
|
+
const icon = this.getAttribute('icon') ?? '✨';
|
|
26
|
+
this._icon.textContent = icon;
|
|
27
|
+
this._icon.style.display = icon ? '' : 'none';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setupEvents() {
|
|
31
|
+
this._onSend = () => this.submit();
|
|
32
|
+
this._onKey = (e) => { if (e.key === 'Enter') { e.preventDefault(); this.submit(); } };
|
|
33
|
+
this._btn.addEventListener('click', this._onSend);
|
|
34
|
+
this._input.addEventListener('keydown', this._onKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
teardownEvents() {
|
|
38
|
+
this._btn?.removeEventListener('click', this._onSend);
|
|
39
|
+
this._input?.removeEventListener('keydown', this._onKey);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
submit() {
|
|
43
|
+
const text = this._input.value.trim();
|
|
44
|
+
if (!text || this.getBoolAttr('disabled')) return;
|
|
45
|
+
const ok = this.emit('nk-submit', { text });
|
|
46
|
+
if (ok) this._input.value = '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onAttributeChanged() { this._sync(); }
|
|
50
|
+
focus(o) { this._input?.focus(o); }
|
|
51
|
+
get value() { return this._input?.value ?? ''; }
|
|
52
|
+
set value(v) { if (this._input) this._input.value = v ?? ''; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
customElements.define('nk-ai-input-row', NkAiInputRow);
|
|
56
|
+
export { NkAiInputRow };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-ai-msg role="user" name="You" avatar="MK">Summarise the open tasks.</nk-ai-msg>
|
|
4
|
+
// <nk-ai-msg role="assistant" name="Mona" badge="· AI" avatar="✨">
|
|
5
|
+
// Two tasks are open …
|
|
6
|
+
// <button slot="actions" value="copy">📋 Copy</button><button slot="actions" value="retry">↻ Rephrase</button>
|
|
7
|
+
// </nk-ai-msg>
|
|
8
|
+
// → <div class="nk-ai-msg user"><span class="mini-avatar">MK</span><div class="a-body"><div class="a-name">You</div>…<div class="nk-ai-actions">…</div></div></div>
|
|
9
|
+
// Action buttons stay in the light DOM (slotted twins) and fire nk-action { action: value }.
|
|
10
|
+
class NkAiMsg extends NkElement {
|
|
11
|
+
static get observedAttributes() { return ['role', 'name', 'badge', 'avatar', 'color']; }
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
this._box = this.createElement('div', ['nk-ai-msg']);
|
|
15
|
+
this._avatar = this.createElement('span', ['mini-avatar']);
|
|
16
|
+
const avatarSlot = this.createElement('slot', [], { name: 'avatar' });
|
|
17
|
+
avatarSlot.appendChild(this._avatar);
|
|
18
|
+
const body = this.createElement('div', ['a-body']);
|
|
19
|
+
this._name = this.createElement('div', ['a-name']);
|
|
20
|
+
this._nameText = document.createTextNode('');
|
|
21
|
+
this._badge = document.createElement('span');
|
|
22
|
+
this._name.append(this._nameText, this._badge);
|
|
23
|
+
this._actions = this.createElement('div', ['nk-ai-actions']);
|
|
24
|
+
this._actionSlot = this.createElement('slot', [], { name: 'actions' });
|
|
25
|
+
this._actions.appendChild(this._actionSlot);
|
|
26
|
+
body.append(this._name, document.createElement('slot'), this._actions);
|
|
27
|
+
this._box.append(avatarSlot, body);
|
|
28
|
+
this._wrapper.appendChild(this._box);
|
|
29
|
+
this._sync();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_sync() {
|
|
33
|
+
const user = this.getAttribute('role') === 'user';
|
|
34
|
+
this._box.classList.toggle('user', user);
|
|
35
|
+
const name = this.getAttribute('name');
|
|
36
|
+
this._nameText.data = name || '';
|
|
37
|
+
const badge = this.getAttribute('badge');
|
|
38
|
+
this._badge.textContent = badge ? ` ${badge}` : '';
|
|
39
|
+
this._name.style.display = name ? '' : 'none';
|
|
40
|
+
this._avatar.textContent = this.getAttribute('avatar') || (user ? (name || 'U').slice(0, 2).toUpperCase() : '✨');
|
|
41
|
+
const color = this.getAttribute('color');
|
|
42
|
+
this._avatar.style.background = color || '';
|
|
43
|
+
this._actions.style.display = this._actionSlot.assignedElements().length ? '' : 'none';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setupEvents() {
|
|
47
|
+
this._onSlot = () => this._sync();
|
|
48
|
+
this._onClick = (e) => {
|
|
49
|
+
const btn = e.target.closest?.('button');
|
|
50
|
+
if (btn && this._actionSlot.assignedElements().includes(btn)) this.emit('nk-action', { action: btn.value || btn.textContent.trim(), value: btn.value || btn.textContent.trim() });
|
|
51
|
+
};
|
|
52
|
+
this._actionSlot.addEventListener('slotchange', this._onSlot);
|
|
53
|
+
this.addEventListener('click', this._onClick);
|
|
54
|
+
this._sync();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
teardownEvents() {
|
|
58
|
+
this._actionSlot?.removeEventListener('slotchange', this._onSlot);
|
|
59
|
+
this.removeEventListener('click', this._onClick);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onAttributeChanged() { this._sync(); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
customElements.define('nk-ai-msg', NkAiMsg);
|
|
66
|
+
export { NkAiMsg };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-ai-thread><nk-ai-msg …>…</nk-ai-msg>…</nk-ai-thread> → <div class="nk-ai-thread">
|
|
4
|
+
class NkAiThread extends NkElement {
|
|
5
|
+
render() {
|
|
6
|
+
const box = this.createElement('div', ['nk-ai-thread'], { role: 'log', 'aria-live': 'polite' });
|
|
7
|
+
box.appendChild(document.createElement('slot'));
|
|
8
|
+
this._wrapper.appendChild(box);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
customElements.define('nk-ai-thread', NkAiThread);
|
|
13
|
+
export { NkAiThread };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
import { renderPropertyCell, tagFor } from '../../util/property-cell.js';
|
|
3
|
+
|
|
4
|
+
// <nk-board-view name="board" label="▤ Board" group-by="status" new-row></nk-board-view>
|
|
5
|
+
// Groups rows by a select column (default: the first select column); one
|
|
6
|
+
// column per option, in option order. Cards show the title column and the
|
|
7
|
+
// `meta-keys` (default: date and progress columns). Drag a card onto another
|
|
8
|
+
// column to change its value – the row is updated and nk-change fires.
|
|
9
|
+
// → <div class="nk-board active"><div class="nk-board-col">…</div>…</div>
|
|
10
|
+
class NkBoardView extends NkElement {
|
|
11
|
+
static get observedAttributes() { return ['name', 'label', 'badge', 'count', 'group-by', 'title-key', 'meta-keys', 'new-row']; }
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
this._board = this.createElement('div', ['nk-board', 'active']);
|
|
15
|
+
this._wrapper.appendChild(this._board);
|
|
16
|
+
this._columns ??= []; this._rows ??= [];
|
|
17
|
+
this._render();
|
|
18
|
+
this.closest('nk-database')?.requestSync?.();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_groupColumn() {
|
|
22
|
+
const key = this.getAttribute('group-by');
|
|
23
|
+
return this._columns.find(c => c.key === key) || this._columns.find(c => c.type === 'select') || null;
|
|
24
|
+
}
|
|
25
|
+
_titleColumn() {
|
|
26
|
+
const key = this.getAttribute('title-key');
|
|
27
|
+
return this._columns.find(c => c.key === key) || this._columns.find(c => c.title) || this._columns[0] || null;
|
|
28
|
+
}
|
|
29
|
+
_metaColumns() {
|
|
30
|
+
const keys = (this.getAttribute('meta-keys') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
31
|
+
if (keys.length) return keys.map(k => this._columns.find(c => c.key === k)).filter(Boolean);
|
|
32
|
+
return this._columns.filter(c => c.type === 'date' || c.type === 'progress');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_render() {
|
|
36
|
+
if (!this._board) return;
|
|
37
|
+
this._board.replaceChildren();
|
|
38
|
+
const group = this._groupColumn(), title = this._titleColumn();
|
|
39
|
+
if (!group) return;
|
|
40
|
+
const options = group.options?.length ? group.options : [...new Set(this._rows.map(r => r[group.key]))].map(v => ({ value: v, label: String(v) }));
|
|
41
|
+
for (const opt of options) {
|
|
42
|
+
const col = this.createElement('div', ['nk-board-col'], { 'data-value': opt.value });
|
|
43
|
+
const header = this.createElement('div', ['nk-board-col-header']);
|
|
44
|
+
const cards = this._rows.filter(r => r[group.key] === opt.value || r[group.key] === opt.label);
|
|
45
|
+
const count = this.createElement('span', ['count']);
|
|
46
|
+
count.textContent = String(cards.length);
|
|
47
|
+
header.append(tagFor(group, opt.value), count);
|
|
48
|
+
col.appendChild(header);
|
|
49
|
+
for (const row of cards) {
|
|
50
|
+
const card = this.createElement('div', ['nk-card'], { draggable: 'true', 'data-id': row.id ?? '', tabindex: '0' });
|
|
51
|
+
const t = this.createElement('div', ['card-title']);
|
|
52
|
+
t.textContent = `${row.icon ? row.icon + ' ' : ''}${title ? row[title.key] ?? '' : ''}`;
|
|
53
|
+
const meta = this.createElement('div', ['card-meta']);
|
|
54
|
+
for (const c of this._metaColumns()) {
|
|
55
|
+
const v = row[c.key];
|
|
56
|
+
if (v === undefined || v === null || v === '') continue;
|
|
57
|
+
const s = document.createElement('span');
|
|
58
|
+
if (c.type === 'progress') s.textContent = `▰ ${v}%`;
|
|
59
|
+
else if (c.type === 'date') s.textContent = `📅 ${v}`;
|
|
60
|
+
else s.appendChild(renderPropertyCell(c, v, row));
|
|
61
|
+
meta.appendChild(s);
|
|
62
|
+
}
|
|
63
|
+
card.append(t, meta);
|
|
64
|
+
col.appendChild(card);
|
|
65
|
+
}
|
|
66
|
+
if (this.getBoolAttr('new-row')) {
|
|
67
|
+
const add = this.createElement('div', ['nk-new-row'], { role: 'button', tabindex: '0', 'data-add': opt.value });
|
|
68
|
+
add.style.padding = '6px 10px';
|
|
69
|
+
add.textContent = '+';
|
|
70
|
+
col.appendChild(add);
|
|
71
|
+
}
|
|
72
|
+
this._board.appendChild(col);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setupEvents() {
|
|
77
|
+
this._onClick = (e) => {
|
|
78
|
+
const add = e.target.closest('[data-add]');
|
|
79
|
+
if (add) { this.emit('nk-action', { action: 'new-row', value: add.dataset.add }); return; }
|
|
80
|
+
const card = e.target.closest('.nk-card');
|
|
81
|
+
if (card) { const row = this._rowById(card.dataset.id); this.emit('nk-select', { row, id: row?.id, value: row?.id }); }
|
|
82
|
+
};
|
|
83
|
+
this._onDragStart = (e) => { const card = e.target.closest?.('.nk-card'); if (!card) return; this._dragId = card.dataset.id; e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/plain', card.dataset.id); } catch {} };
|
|
84
|
+
this._onDragOver = (e) => { if (this._dragId != null && e.target.closest('.nk-board-col')) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } };
|
|
85
|
+
this._onDrop = (e) => {
|
|
86
|
+
const col = e.target.closest('.nk-board-col');
|
|
87
|
+
if (!col || this._dragId == null) return;
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
this.move(this._dragId, col.dataset.value);
|
|
90
|
+
this._dragId = null;
|
|
91
|
+
};
|
|
92
|
+
this._board.addEventListener('click', this._onClick);
|
|
93
|
+
this._board.addEventListener('dragstart', this._onDragStart);
|
|
94
|
+
this._board.addEventListener('dragover', this._onDragOver);
|
|
95
|
+
this._board.addEventListener('drop', this._onDrop);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
teardownEvents() {
|
|
99
|
+
this._board?.removeEventListener('click', this._onClick);
|
|
100
|
+
this._board?.removeEventListener('dragstart', this._onDragStart);
|
|
101
|
+
this._board?.removeEventListener('dragover', this._onDragOver);
|
|
102
|
+
this._board?.removeEventListener('drop', this._onDrop);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Moves a row into another column (sets the group value) and reports it. */
|
|
106
|
+
move(id, value) {
|
|
107
|
+
const row = this._rowById(id), group = this._groupColumn();
|
|
108
|
+
if (!row || !group || row[group.key] === value) return;
|
|
109
|
+
row[group.key] = value;
|
|
110
|
+
this._render();
|
|
111
|
+
this.emit('nk-change', { row, key: group.key, value });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
|
|
115
|
+
onAttributeChanged() { this._render(); }
|
|
116
|
+
|
|
117
|
+
get name() { return this.getAttribute('name') || 'board'; }
|
|
118
|
+
get columns() { return this._columns; }
|
|
119
|
+
set columns(v) { this._columns = Array.isArray(v) ? v : []; this._render(); }
|
|
120
|
+
get rows() { return this._rows; }
|
|
121
|
+
set rows(v) { this._rows = Array.isArray(v) ? v : []; this._render(); }
|
|
122
|
+
set data({ columns, rows }) { this.setData(columns, rows); }
|
|
123
|
+
setData(columns, rows) { this._columns = columns || []; this._rows = rows || []; this._render(); }
|
|
124
|
+
refresh() { this._render(); }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
customElements.define('nk-board-view', NkBoardView);
|
|
128
|
+
export { NkBoardView };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-comment author="Sara Lindt" time="1 hr ago" avatar="SL" color="#448361">The board view feels close.</nk-comment>
|
|
4
|
+
// → <div class="nk-comment"><span class="mini-avatar">SL</span><div><div class="c-head"><b>Sara Lindt</b> · 1 hr ago</div><div class="c-body">…</div></div></div>
|
|
5
|
+
// slot="head" adds content after the name (e.g. an <nk-tag>).
|
|
6
|
+
class NkComment extends NkElement {
|
|
7
|
+
static get observedAttributes() { return ['author', 'time', 'avatar', 'color']; }
|
|
8
|
+
|
|
9
|
+
render() {
|
|
10
|
+
const box = this.createElement('div', ['nk-comment']);
|
|
11
|
+
this._avatar = this.createElement('span', ['mini-avatar']);
|
|
12
|
+
const avatarSlot = this.createElement('slot', [], { name: 'avatar' });
|
|
13
|
+
avatarSlot.appendChild(this._avatar);
|
|
14
|
+
const text = document.createElement('div');
|
|
15
|
+
this._head = this.createElement('div', ['c-head']);
|
|
16
|
+
this._author = document.createElement('b');
|
|
17
|
+
this._time = document.createTextNode('');
|
|
18
|
+
this._head.append(this._author, this.createElement('slot', [], { name: 'head' }), this._time);
|
|
19
|
+
const body = this.createElement('div', ['c-body']);
|
|
20
|
+
body.appendChild(document.createElement('slot'));
|
|
21
|
+
text.append(this._head, body);
|
|
22
|
+
box.append(avatarSlot, text);
|
|
23
|
+
this._wrapper.appendChild(box);
|
|
24
|
+
this._sync();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_sync() {
|
|
28
|
+
const author = this.getAttribute('author') || '';
|
|
29
|
+
this._author.textContent = author;
|
|
30
|
+
const time = this.getAttribute('time');
|
|
31
|
+
this._time.data = time ? ` · ${time}` : '';
|
|
32
|
+
this._avatar.textContent = this.getAttribute('avatar') || author.split(/\s+/).map(w => w[0]).join('').slice(0, 2).toUpperCase();
|
|
33
|
+
this._avatar.style.background = this.getAttribute('color') || 'var(--nk-text-tertiary)';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onAttributeChanged() { this._sync(); }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
customElements.define('nk-comment', NkComment);
|
|
40
|
+
export { NkComment };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-comments placeholder="Comment …" send-label="Send">
|
|
4
|
+
// <nk-comment …>…</nk-comment>
|
|
5
|
+
// </nk-comments>
|
|
6
|
+
// → <div class="nk-comments">…<div class="nk-comment-input"><input class="nk-input"><button class="nk-btn primary small">Send</button></div></div>
|
|
7
|
+
// Enter or the button fires nk-submit { text } and clears the field.
|
|
8
|
+
class NkComments extends NkElement {
|
|
9
|
+
static get observedAttributes() { return ['placeholder', 'send-label', 'no-input', 'disabled']; }
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
const box = this.createElement('div', ['nk-comments']);
|
|
13
|
+
box.appendChild(document.createElement('slot'));
|
|
14
|
+
this._row = this.createElement('div', ['nk-comment-input']);
|
|
15
|
+
this._input = this.createElement('input', ['nk-input'], { type: 'text', 'aria-label': 'Comment' });
|
|
16
|
+
this._btn = this.createElement('button', ['nk-btn', 'primary', 'small'], { type: 'button' });
|
|
17
|
+
this._row.append(this._input, this._btn);
|
|
18
|
+
box.appendChild(this._row);
|
|
19
|
+
this._wrapper.appendChild(box);
|
|
20
|
+
this._sync();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_sync() {
|
|
24
|
+
this._input.placeholder = this.getAttribute('placeholder') || 'Comment …';
|
|
25
|
+
this._btn.textContent = this.getAttribute('send-label') || 'Send';
|
|
26
|
+
this._row.style.display = this.getBoolAttr('no-input') ? 'none' : '';
|
|
27
|
+
this._input.disabled = this._btn.disabled = this.getBoolAttr('disabled');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
setupEvents() {
|
|
31
|
+
this._onSend = () => this.submit();
|
|
32
|
+
this._onKey = (e) => { if (e.key === 'Enter') { e.preventDefault(); this.submit(); } };
|
|
33
|
+
this._btn.addEventListener('click', this._onSend);
|
|
34
|
+
this._input.addEventListener('keydown', this._onKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
teardownEvents() {
|
|
38
|
+
this._btn?.removeEventListener('click', this._onSend);
|
|
39
|
+
this._input?.removeEventListener('keydown', this._onKey);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
submit() {
|
|
43
|
+
const text = this._input.value.trim();
|
|
44
|
+
if (!text) return;
|
|
45
|
+
const ok = this.emit('nk-submit', { text });
|
|
46
|
+
if (ok) this._input.value = '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onAttributeChanged() { this._sync(); }
|
|
50
|
+
focus(o) { this._input?.focus(o); }
|
|
51
|
+
get value() { return this._input?.value ?? ''; }
|
|
52
|
+
set value(v) { if (this._input) this._input.value = v ?? ''; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
customElements.define('nk-comments', NkComments);
|
|
56
|
+
export { NkComments };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-database id="db" view="table" add-view>
|
|
4
|
+
// <nk-table-view name="table" label="▦ Table" count></nk-table-view>
|
|
5
|
+
// <nk-board-view name="board" label="▤ Board" group-by="status"></nk-board-view>
|
|
6
|
+
// </nk-database>
|
|
7
|
+
// db.columns = [{ key: 'name', label: 'Name', type: 'text', icon: '📄', title: true }, { key: 'status', type: 'select', options: [...] }, …];
|
|
8
|
+
// db.rows = [{ id: 1, icon: '🧭', name: '…', status: 'done', … }];
|
|
9
|
+
// → <div class="nk-database"><div class="nk-db-tabs">…</div>…active view…</div>
|
|
10
|
+
// The tabs come from the child views; data is pushed into every view.
|
|
11
|
+
class NkDatabase extends NkElement {
|
|
12
|
+
static get observedAttributes() { return ['view', 'add-view']; }
|
|
13
|
+
|
|
14
|
+
render() {
|
|
15
|
+
this._box = this.createElement('div', ['nk-database']);
|
|
16
|
+
this._tabs = this.createElement('div', ['nk-db-tabs'], { role: 'tablist' });
|
|
17
|
+
this._slot = document.createElement('slot');
|
|
18
|
+
this._box.append(this._tabs, this._slot);
|
|
19
|
+
this._wrapper.appendChild(this._box);
|
|
20
|
+
this._columns = []; this._rows = [];
|
|
21
|
+
this._sync();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get views() { return this._slot?.assignedElements().filter(el => /^nk-[a-z]+-view$/.test(el.localName)) ?? []; }
|
|
25
|
+
|
|
26
|
+
_sync() {
|
|
27
|
+
if (!this._tabs) return;
|
|
28
|
+
const views = this.views;
|
|
29
|
+
let current = this.getAttribute('view');
|
|
30
|
+
if (!views.some(v => v.name === current)) current = views[0]?.name ?? null;
|
|
31
|
+
this._tabs.replaceChildren();
|
|
32
|
+
for (const v of views) {
|
|
33
|
+
const tab = this.createElement('span', ['nk-db-tab'], { role: 'tab', 'data-view': v.name, tabindex: '0' });
|
|
34
|
+
tab.classList.toggle('active', v.name === current);
|
|
35
|
+
tab.setAttribute('aria-selected', v.name === current ? 'true' : 'false');
|
|
36
|
+
tab.append(document.createTextNode(v.getAttribute('label') || v.name));
|
|
37
|
+
const badge = v.hasAttribute('count') ? String(this._rows.length) : v.getAttribute('badge');
|
|
38
|
+
if (badge) { const b = this.createElement('span', ['badge']); b.textContent = badge; tab.appendChild(b); }
|
|
39
|
+
this._tabs.appendChild(tab);
|
|
40
|
+
v.hidden = v.name !== current;
|
|
41
|
+
// A method call, not a property write: a view that has not been upgraded
|
|
42
|
+
// yet would otherwise get an own `data` property that shadows its setter.
|
|
43
|
+
v.setData?.(this._columns, this._rows);
|
|
44
|
+
}
|
|
45
|
+
if (this.getBoolAttr('add-view')) {
|
|
46
|
+
const add = this.createElement('span', ['nk-db-tab'], { role: 'button', 'data-add': '', tabindex: '0' });
|
|
47
|
+
add.style.color = 'var(--nk-text-tertiary)';
|
|
48
|
+
add.textContent = '+';
|
|
49
|
+
this._tabs.appendChild(add);
|
|
50
|
+
}
|
|
51
|
+
this._tabs.style.display = views.length ? '' : 'none';
|
|
52
|
+
this._current = current;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Views upgrade after the database; each one asks for a resync once rendered. */
|
|
56
|
+
requestSync() {
|
|
57
|
+
if (this._syncQueued) return;
|
|
58
|
+
this._syncQueued = true;
|
|
59
|
+
queueMicrotask(() => { this._syncQueued = false; this._sync(); });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setupEvents() {
|
|
63
|
+
this._onClick = (e) => {
|
|
64
|
+
const tab = e.target.closest('[data-view]');
|
|
65
|
+
if (tab) { this.view = tab.dataset.view; return; }
|
|
66
|
+
if (e.target.closest('[data-add]')) this.emit('nk-action', { action: 'add-view' });
|
|
67
|
+
};
|
|
68
|
+
this._onKey = (e) => { if ((e.key === 'Enter' || e.key === ' ') && e.target.closest('[data-view],[data-add]')) { e.preventDefault(); e.target.click(); } };
|
|
69
|
+
this._onSlot = () => this._sync();
|
|
70
|
+
this._tabs.addEventListener('click', this._onClick);
|
|
71
|
+
this._tabs.addEventListener('keydown', this._onKey);
|
|
72
|
+
this._slot.addEventListener('slotchange', this._onSlot);
|
|
73
|
+
this._sync();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
teardownEvents() {
|
|
77
|
+
this._tabs?.removeEventListener('click', this._onClick);
|
|
78
|
+
this._tabs?.removeEventListener('keydown', this._onKey);
|
|
79
|
+
this._slot?.removeEventListener('slotchange', this._onSlot);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
onAttributeChanged(name) {
|
|
83
|
+
const before = this._current;
|
|
84
|
+
this._sync();
|
|
85
|
+
if (name === 'view' && this._current !== before) this.emit('nk-view-change', { view: this._current });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get columns() { return this._columns; }
|
|
89
|
+
set columns(v) { this._columns = Array.isArray(v) ? v : []; this._sync(); }
|
|
90
|
+
get rows() { return this._rows; }
|
|
91
|
+
set rows(v) { this._rows = Array.isArray(v) ? v : []; this._sync(); }
|
|
92
|
+
get view() { return this._current ?? this.getAttribute('view'); }
|
|
93
|
+
set view(v) { this.setAttribute('view', v); }
|
|
94
|
+
/** Re-renders every view after in-place row mutations. */
|
|
95
|
+
refresh() { this._sync(); }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
customElements.define('nk-database', NkDatabase);
|
|
99
|
+
export { NkDatabase };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-filter-bar search placeholder="Search rows …"></nk-filter-bar>
|
|
4
|
+
// bar.filters = [{ key: 'status', label: 'Status: Done', value: 'done' }];
|
|
5
|
+
// Composed from existing classes (nk-btn secondary small, nk-input, nk-tag):
|
|
6
|
+
// filter / sort buttons fire nk-action, chips remove on ×, search and chip
|
|
7
|
+
// changes fire nk-change { filters, search }. The wrapper is layout only.
|
|
8
|
+
class NkFilterBar extends NkElement {
|
|
9
|
+
static get observedAttributes() { return ['search', 'placeholder', 'no-filter', 'no-sort', 'value']; }
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
this._bar = document.createElement('div');
|
|
13
|
+
this._bar.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:8px 0';
|
|
14
|
+
this._filterBtn = this.createElement('button', ['nk-btn', 'secondary', 'small'], { type: 'button', 'data-action': 'filter' });
|
|
15
|
+
this._filterBtn.textContent = '⚲ Filter';
|
|
16
|
+
this._sortBtn = this.createElement('button', ['nk-btn', 'secondary', 'small'], { type: 'button', 'data-action': 'sort' });
|
|
17
|
+
this._sortBtn.textContent = '↕ Sort';
|
|
18
|
+
this._chips = document.createElement('span');
|
|
19
|
+
this._chips.style.cssText = 'display:inline-flex;gap:4px;flex-wrap:wrap';
|
|
20
|
+
this._input = this.createElement('input', ['nk-input'], { type: 'search', autocomplete: 'off' });
|
|
21
|
+
this._input.style.marginLeft = 'auto';
|
|
22
|
+
this._bar.append(this._filterBtn, this._sortBtn, this._chips, document.createElement('slot'), this._input);
|
|
23
|
+
this._wrapper.appendChild(this._bar);
|
|
24
|
+
this._filters = [];
|
|
25
|
+
this._sync();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_sync() {
|
|
29
|
+
this._filterBtn.style.display = this.getBoolAttr('no-filter') ? 'none' : '';
|
|
30
|
+
this._sortBtn.style.display = this.getBoolAttr('no-sort') ? 'none' : '';
|
|
31
|
+
this._input.style.display = this.getBoolAttr('search') ? '' : 'none';
|
|
32
|
+
this._input.placeholder = this.getAttribute('placeholder') || 'Search …';
|
|
33
|
+
if (this.hasAttribute('value') && this._input.value !== this.getAttribute('value')) this._input.value = this.getAttribute('value');
|
|
34
|
+
this._chips.replaceChildren();
|
|
35
|
+
this._filters.forEach((f, i) => {
|
|
36
|
+
const chip = this.createElement('span', ['nk-tag', f.color || 'blue'], { 'data-index': i });
|
|
37
|
+
chip.style.cursor = 'pointer';
|
|
38
|
+
chip.textContent = `${f.label ?? `${f.key}: ${f.value}`} ×`;
|
|
39
|
+
chip.title = 'Remove filter';
|
|
40
|
+
this._chips.appendChild(chip);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setupEvents() {
|
|
45
|
+
this._onClick = (e) => {
|
|
46
|
+
const btn = e.target.closest('[data-action]');
|
|
47
|
+
if (btn) { this.emit('nk-action', { action: btn.dataset.action }); return; }
|
|
48
|
+
const chip = e.target.closest('[data-index]');
|
|
49
|
+
if (chip) { this._filters.splice(Number(chip.dataset.index), 1); this._sync(); this._emitChange(); }
|
|
50
|
+
};
|
|
51
|
+
this._onInput = () => { clearTimeout(this._debounce); this._debounce = setTimeout(() => this._emitChange(), 150); };
|
|
52
|
+
this._bar.addEventListener('click', this._onClick);
|
|
53
|
+
this._input.addEventListener('input', this._onInput);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
teardownEvents() {
|
|
57
|
+
this._bar?.removeEventListener('click', this._onClick);
|
|
58
|
+
this._input?.removeEventListener('input', this._onInput);
|
|
59
|
+
clearTimeout(this._debounce);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_emitChange() { this.emit('nk-change', { filters: [...this._filters], search: this._input.value }); }
|
|
63
|
+
onAttributeChanged() { this._sync(); }
|
|
64
|
+
|
|
65
|
+
get filters() { return this._filters; }
|
|
66
|
+
set filters(v) { this._filters = Array.isArray(v) ? v : []; this._sync(); }
|
|
67
|
+
get value() { return this._input?.value ?? ''; }
|
|
68
|
+
set value(v) { if (this._input) this._input.value = v ?? ''; }
|
|
69
|
+
/** Applies filters + search to rows: equality per filter key, substring search over every string field. */
|
|
70
|
+
apply(rows) {
|
|
71
|
+
const q = this.value.trim().toLowerCase();
|
|
72
|
+
return rows.filter(r => this._filters.every(f => r[f.key] === f.value) && (!q || Object.values(r).some(v => String(typeof v === 'object' && v ? v.name ?? '' : v ?? '').toLowerCase().includes(q))));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
customElements.define('nk-filter-bar', NkFilterBar);
|
|
77
|
+
export { NkFilterBar };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
import { renderPropertyCell, compareBy } from '../../util/property-cell.js';
|
|
3
|
+
|
|
4
|
+
// <nk-table-view name="table" label="▦ Table" count new-row sortable></nk-table-view>
|
|
5
|
+
// Standalone: view.columns = […]; view.rows = […]; Inside <nk-database> the
|
|
6
|
+
// database pushes `data`. → <div class="nk-table-wrap"><table class="nk-table">…</table><div class="nk-new-row">+ New page</div></div>
|
|
7
|
+
// Cells are plain markup from renderPropertyCell(); rows fire nk-select,
|
|
8
|
+
// checkboxes nk-change, headers nk-action { action: 'sort' } (and sort locally
|
|
9
|
+
// with `sortable`), the add row nk-action { action: 'new-row' }.
|
|
10
|
+
class NkTableView extends NkElement {
|
|
11
|
+
static get observedAttributes() { return ['name', 'label', 'badge', 'count', 'new-row', 'new-row-label', 'sortable', 'sort-key', 'sort-dir']; }
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
this._wrap = this.createElement('div', ['nk-table-wrap']);
|
|
15
|
+
this._table = this.createElement('table', ['nk-table']);
|
|
16
|
+
this._thead = document.createElement('thead');
|
|
17
|
+
this._tbody = document.createElement('tbody');
|
|
18
|
+
this._table.append(this._thead, this._tbody);
|
|
19
|
+
this._newRow = this.createElement('div', ['nk-new-row'], { role: 'button', tabindex: '0' });
|
|
20
|
+
this._wrap.append(this._table, this._newRow);
|
|
21
|
+
this._wrapper.appendChild(this._wrap);
|
|
22
|
+
this._columns ??= []; this._rows ??= [];
|
|
23
|
+
this._render();
|
|
24
|
+
this.closest('nk-database')?.requestSync?.();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
_sortedRows() {
|
|
28
|
+
const key = this.getAttribute('sort-key');
|
|
29
|
+
const col = this._columns.find(c => c.key === key);
|
|
30
|
+
if (!col) return this._rows;
|
|
31
|
+
return [...this._rows].sort(compareBy(col, this.getAttribute('sort-dir') === 'desc' ? -1 : 1));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_render() {
|
|
35
|
+
if (!this._thead) return;
|
|
36
|
+
const tr = document.createElement('tr');
|
|
37
|
+
for (const col of this._columns) {
|
|
38
|
+
const th = this.createElement('th', [], { 'data-key': col.key, scope: 'col' });
|
|
39
|
+
if (col.icon) { const i = this.createElement('span', ['th-icon']); i.textContent = col.icon; th.appendChild(i); }
|
|
40
|
+
th.appendChild(document.createTextNode(col.label ?? col.key));
|
|
41
|
+
if (col.key === this.getAttribute('sort-key')) th.appendChild(document.createTextNode(this.getAttribute('sort-dir') === 'desc' ? ' ↓' : ' ↑'));
|
|
42
|
+
if (col.width) th.style.width = col.width;
|
|
43
|
+
tr.appendChild(th);
|
|
44
|
+
}
|
|
45
|
+
this._thead.replaceChildren(tr);
|
|
46
|
+
this._tbody.replaceChildren();
|
|
47
|
+
for (const row of this._sortedRows()) {
|
|
48
|
+
const r = this.createElement('tr', [], { 'data-id': row.id ?? '' });
|
|
49
|
+
for (const col of this._columns) {
|
|
50
|
+
const td = document.createElement('td');
|
|
51
|
+
td.appendChild(renderPropertyCell(col, row[col.key], row));
|
|
52
|
+
r.appendChild(td);
|
|
53
|
+
}
|
|
54
|
+
this._tbody.appendChild(r);
|
|
55
|
+
}
|
|
56
|
+
this._newRow.textContent = this.getAttribute('new-row-label') || '+ New page';
|
|
57
|
+
this._newRow.style.display = this.getBoolAttr('new-row') ? '' : 'none';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setupEvents() {
|
|
61
|
+
this._onClick = (e) => {
|
|
62
|
+
if (this._newRow.contains(e.target)) { this.emit('nk-action', { action: 'new-row' }); return; }
|
|
63
|
+
const th = e.target.closest('th[data-key]');
|
|
64
|
+
if (th) {
|
|
65
|
+
const key = th.dataset.key;
|
|
66
|
+
const dir = this.getAttribute('sort-key') === key && this.getAttribute('sort-dir') !== 'desc' ? 'desc' : 'asc';
|
|
67
|
+
const ok = this.emit('nk-action', { action: 'sort', key, value: dir });
|
|
68
|
+
if (ok && this.getBoolAttr('sortable')) { this.setAttribute('sort-key', key); this.setAttribute('sort-dir', dir); }
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (e.target.closest('input[type=checkbox], a')) return;
|
|
72
|
+
const tr = e.target.closest('tr[data-id]');
|
|
73
|
+
if (tr) { const row = this._rowById(tr.dataset.id); this.emit('nk-select', { row, id: row?.id, value: row?.id }); }
|
|
74
|
+
};
|
|
75
|
+
this._onChange = (e) => {
|
|
76
|
+
const input = e.target;
|
|
77
|
+
if (input.type !== 'checkbox') return;
|
|
78
|
+
const row = this._rowById(input.dataset.rowId);
|
|
79
|
+
if (row) row[input.dataset.key] = input.checked;
|
|
80
|
+
this.emit('nk-change', { row, key: input.dataset.key, value: input.checked });
|
|
81
|
+
};
|
|
82
|
+
this._onKey = (e) => { if ((e.key === 'Enter' || e.key === ' ') && e.target === this._newRow) { e.preventDefault(); this.emit('nk-action', { action: 'new-row' }); } };
|
|
83
|
+
this._wrap.addEventListener('click', this._onClick);
|
|
84
|
+
this._wrap.addEventListener('change', this._onChange);
|
|
85
|
+
this._wrap.addEventListener('keydown', this._onKey);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
teardownEvents() {
|
|
89
|
+
this._wrap?.removeEventListener('click', this._onClick);
|
|
90
|
+
this._wrap?.removeEventListener('change', this._onChange);
|
|
91
|
+
this._wrap?.removeEventListener('keydown', this._onKey);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
|
|
95
|
+
onAttributeChanged() { this._render(); }
|
|
96
|
+
|
|
97
|
+
get name() { return this.getAttribute('name') || 'table'; }
|
|
98
|
+
get columns() { return this._columns; }
|
|
99
|
+
set columns(v) { this._columns = Array.isArray(v) ? v : []; this._render(); }
|
|
100
|
+
get rows() { return this._rows; }
|
|
101
|
+
set rows(v) { this._rows = Array.isArray(v) ? v : []; this._render(); }
|
|
102
|
+
set data({ columns, rows }) { this.setData(columns, rows); }
|
|
103
|
+
setData(columns, rows) { this._columns = columns || []; this._rows = rows || []; this._render(); }
|
|
104
|
+
refresh() { this._render(); }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
customElements.define('nk-table-view', NkTableView);
|
|
108
|
+
export { NkTableView };
|