@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,88 @@
|
|
|
1
|
+
import { NkFormElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-model-card name="model" value="gpt" title="Mona Pro" desc="Best for long documents" selected></nk-model-card>
|
|
4
|
+
// → <div class="nk-model-card selected"><div class="m-radio"></div><div><div class="m-name">…</div><div class="m-desc">…</div></div></div>
|
|
5
|
+
// Cards with the same `name` in the same tree behave like radios and submit
|
|
6
|
+
// the selected value with the form.
|
|
7
|
+
class NkModelCard extends NkFormElement {
|
|
8
|
+
static get observedAttributes() { return ['name', 'value', 'title', 'desc', 'selected', 'disabled']; }
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
this._card = this.createElement('div', ['nk-model-card'], { role: 'radio', tabindex: '0' });
|
|
12
|
+
this._card.appendChild(this.createElement('div', ['m-radio']));
|
|
13
|
+
const text = document.createElement('div');
|
|
14
|
+
this._name = this.createElement('div', ['m-name']);
|
|
15
|
+
const titleSlot = this.createElement('slot', [], { name: 'title' });
|
|
16
|
+
titleSlot.appendChild(this._name);
|
|
17
|
+
this._desc = this.createElement('div', ['m-desc']);
|
|
18
|
+
const descSlot = this.createElement('slot', [], { name: 'desc' });
|
|
19
|
+
descSlot.appendChild(this._desc);
|
|
20
|
+
text.append(titleSlot, descSlot);
|
|
21
|
+
this._card.appendChild(text);
|
|
22
|
+
this._wrapper.appendChild(this._card);
|
|
23
|
+
this._defaultSelected = this.getBoolAttr('selected');
|
|
24
|
+
this._sync();
|
|
25
|
+
if (this.getBoolAttr('selected')) this._unselectPeers();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_sync() {
|
|
29
|
+
const on = this.getBoolAttr('selected');
|
|
30
|
+
this._card.classList.toggle('selected', on);
|
|
31
|
+
this._card.setAttribute('aria-checked', on ? 'true' : 'false');
|
|
32
|
+
this._card.setAttribute('aria-disabled', this.getBoolAttr('disabled') ? 'true' : 'false');
|
|
33
|
+
this._name.textContent = this.getAttribute('title') || '';
|
|
34
|
+
const desc = this.getAttribute('desc');
|
|
35
|
+
this._desc.textContent = desc || '';
|
|
36
|
+
this._desc.style.display = desc ? '' : 'none';
|
|
37
|
+
this.setFormValue(on ? (this.getAttribute('value') || 'on') : null, on ? 'selected' : 'unselected');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_group() {
|
|
41
|
+
const name = this.getAttribute('name');
|
|
42
|
+
const root = this.getRootNode();
|
|
43
|
+
if (!name || typeof root?.querySelectorAll !== 'function') return [this];
|
|
44
|
+
return [...root.querySelectorAll('nk-model-card')].filter(el => el.getAttribute('name') === name);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_unselectPeers() {
|
|
48
|
+
for (const el of this._group()) if (el !== this && el.hasAttribute('selected')) el.removeAttribute('selected');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setupEvents() {
|
|
52
|
+
this._onClick = () => this.select();
|
|
53
|
+
this._onKey = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.select(); } };
|
|
54
|
+
this._card.addEventListener('click', this._onClick);
|
|
55
|
+
this._card.addEventListener('keydown', this._onKey);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
teardownEvents() {
|
|
59
|
+
this._card?.removeEventListener('click', this._onClick);
|
|
60
|
+
this._card?.removeEventListener('keydown', this._onKey);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
select() {
|
|
64
|
+
if (this.getBoolAttr('disabled') || this.getBoolAttr('selected')) return;
|
|
65
|
+
this.setBoolAttr('selected', true);
|
|
66
|
+
this.emit('nk-change', { value: this.value, name: this.name, checked: true });
|
|
67
|
+
this.emit('nk-select', { value: this.value, label: this.getAttribute('title') });
|
|
68
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onAttributeChanged(name) {
|
|
72
|
+
this._sync();
|
|
73
|
+
if (name === 'selected' && this.getBoolAttr('selected')) this._unselectPeers();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
resetValue() { this.setBoolAttr('selected', this._defaultSelected); }
|
|
77
|
+
focus(o) { this._card?.focus(o); }
|
|
78
|
+
|
|
79
|
+
get selected() { return this.getBoolAttr('selected'); }
|
|
80
|
+
set selected(v) { this.setBoolAttr('selected', v); }
|
|
81
|
+
get value() { return this.getAttribute('value') || 'on'; }
|
|
82
|
+
set value(v) { this.setAttribute('value', v); }
|
|
83
|
+
get name() { return this.getAttribute('name'); }
|
|
84
|
+
set name(v) { this.setAttribute('name', v); }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
customElements.define('nk-model-card', NkModelCard);
|
|
88
|
+
export { NkModelCard };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-page-actions><span>👤 Ada</span><span>📅 12 May</span><span>🏷️ <nk-tag color="purple">Design</nk-tag></span></nk-page-actions>
|
|
4
|
+
// → <div class="nk-page-meta">…</div> – the meta row under the title
|
|
5
|
+
class NkPageActions extends NkElement {
|
|
6
|
+
render() {
|
|
7
|
+
const row = this.createElement('div', ['nk-page-meta']);
|
|
8
|
+
row.appendChild(document.createElement('slot'));
|
|
9
|
+
this._wrapper.appendChild(row);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
customElements.define('nk-page-actions', NkPageActions);
|
|
14
|
+
export { NkPageActions };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-page-cover slot="cover"></nk-page-cover> → the token gradient
|
|
4
|
+
// <nk-page-cover slot="cover" src="cover.jpg"></nk-page-cover> → an image
|
|
5
|
+
class NkPageCover extends NkElement {
|
|
6
|
+
static get observedAttributes() { return ['src']; }
|
|
7
|
+
|
|
8
|
+
render() {
|
|
9
|
+
this._cover = this.createElement('div', ['nk-cover']);
|
|
10
|
+
this._wrapper.appendChild(this._cover);
|
|
11
|
+
this._sync();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_sync() {
|
|
15
|
+
const src = this.getAttribute('src');
|
|
16
|
+
if (src) {
|
|
17
|
+
this._cover.style.backgroundImage = `url("${src}")`;
|
|
18
|
+
this._cover.style.backgroundSize = 'cover';
|
|
19
|
+
this._cover.style.backgroundPosition = 'center';
|
|
20
|
+
} else {
|
|
21
|
+
this._cover.style.backgroundImage = '';
|
|
22
|
+
this._cover.style.backgroundSize = '';
|
|
23
|
+
this._cover.style.backgroundPosition = '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onAttributeChanged() { this._sync(); }
|
|
28
|
+
|
|
29
|
+
get src() { return this.getAttribute('src'); }
|
|
30
|
+
set src(v) { v == null ? this.removeAttribute('src') : this.setAttribute('src', v); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
customElements.define('nk-page-cover', NkPageCover);
|
|
34
|
+
export { NkPageCover };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-page-title>NotionKit MVP</nk-page-title> → <h1 class="nk-page-title">
|
|
4
|
+
// <nk-page-title editable placeholder="Untitled">…</nk-page-title>
|
|
5
|
+
// With `editable` the text is copied into the shadow heading (contenteditable
|
|
6
|
+
// cannot edit slotted nodes) and nk-change fires on blur / Enter.
|
|
7
|
+
class NkPageTitle extends NkElement {
|
|
8
|
+
static get observedAttributes() { return ['editable', 'placeholder', 'value']; }
|
|
9
|
+
static get observesLightDom() { return true; }
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
this._h1 = this.createElement('h1', ['nk-page-title']);
|
|
13
|
+
this._slot = document.createElement('slot');
|
|
14
|
+
this._h1.appendChild(this._slot);
|
|
15
|
+
this._wrapper.appendChild(this._h1);
|
|
16
|
+
this._sync();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_sync() {
|
|
20
|
+
const editable = this.getBoolAttr('editable');
|
|
21
|
+
if (editable) {
|
|
22
|
+
if (this._slot.isConnected) this._slot.remove();
|
|
23
|
+
this._h1.setAttribute('contenteditable', 'plaintext-only');
|
|
24
|
+
this._h1.setAttribute('spellcheck', 'false');
|
|
25
|
+
this._h1.setAttribute('role', 'textbox');
|
|
26
|
+
this._h1.setAttribute('aria-label', 'Title');
|
|
27
|
+
const text = this.getAttribute('value') ?? this.textContent.trim();
|
|
28
|
+
if (this._h1.textContent !== text && document.activeElement !== this) this._h1.textContent = text;
|
|
29
|
+
this._h1.setAttribute('data-placeholder', this.getAttribute('placeholder') || '');
|
|
30
|
+
} else {
|
|
31
|
+
this._h1.removeAttribute('contenteditable');
|
|
32
|
+
this._h1.removeAttribute('role');
|
|
33
|
+
if (this.hasAttribute('value')) { if (this._slot.isConnected) this._slot.remove(); this._h1.textContent = this.getAttribute('value'); }
|
|
34
|
+
else if (!this._slot.isConnected) { this._h1.textContent = ''; this._h1.appendChild(this._slot); }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
projectLightDom() { if (this._h1 && !this.hasAttribute('value')) this._sync(); }
|
|
39
|
+
|
|
40
|
+
setupEvents() {
|
|
41
|
+
this._committed = this.value;
|
|
42
|
+
this._onKey = (e) => { if (e.key === 'Enter') { e.preventDefault(); this._h1.blur(); } };
|
|
43
|
+
this._onBlur = () => this._commit();
|
|
44
|
+
this._h1.addEventListener('keydown', this._onKey);
|
|
45
|
+
this._h1.addEventListener('blur', this._onBlur);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
teardownEvents() {
|
|
49
|
+
this._h1?.removeEventListener('keydown', this._onKey);
|
|
50
|
+
this._h1?.removeEventListener('blur', this._onBlur);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_commit() {
|
|
54
|
+
const value = this._h1.textContent.trim();
|
|
55
|
+
if (value === this._committed) return;
|
|
56
|
+
this._committed = value;
|
|
57
|
+
this.emit('nk-change', { value });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onAttributeChanged() { this._sync(); }
|
|
61
|
+
|
|
62
|
+
get value() { return this.getBoolAttr('editable') ? (this._h1?.textContent.trim() ?? '') : (this.getAttribute('value') ?? this.textContent.trim()); }
|
|
63
|
+
set value(v) { this.setAttribute('value', v); }
|
|
64
|
+
get editable() { return this.getBoolAttr('editable'); }
|
|
65
|
+
set editable(v) { this.setBoolAttr('editable', v); }
|
|
66
|
+
focus(o) { this._h1?.focus(o); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
customElements.define('nk-page-title', NkPageTitle);
|
|
70
|
+
export { NkPageTitle };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-page icon="🚀" cover>
|
|
4
|
+
// <nk-page-title>NotionKit MVP</nk-page-title>
|
|
5
|
+
// <nk-page-actions>…</nk-page-actions>
|
|
6
|
+
// <p class="lead">…</p>
|
|
7
|
+
// …blocks…
|
|
8
|
+
// </nk-page>
|
|
9
|
+
// → <div class="nk-page-scroll"><div class="nk-cover"></div><div class="nk-page">
|
|
10
|
+
// <div class="nk-page-icon">🚀</div>…</div></div>
|
|
11
|
+
//
|
|
12
|
+
// The host is display:contents, so .nk-page-scroll is the flex child of
|
|
13
|
+
// .nk-main that scrolls. `narrow` drops the scroll wrapper for pages that are
|
|
14
|
+
// the document itself (landing / docs skeleton). The page icon is rendered
|
|
15
|
+
// here because its ::slotted twin is keyed on the parent (.nk-page).
|
|
16
|
+
class NkPage extends NkElement {
|
|
17
|
+
static get observedAttributes() { return ['icon', 'cover', 'narrow']; }
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
this._page = this.createElement('div', ['nk-page']);
|
|
21
|
+
this._icon = this.createElement('div', ['nk-page-icon'], { role: 'button', title: 'Change icon' });
|
|
22
|
+
const iconSlot = this.createElement('slot', [], { name: 'icon' });
|
|
23
|
+
iconSlot.appendChild(this._icon);
|
|
24
|
+
this._page.appendChild(iconSlot);
|
|
25
|
+
this._page.appendChild(document.createElement('slot'));
|
|
26
|
+
this._cover = this.createElement('div', ['nk-cover']);
|
|
27
|
+
this._coverSlot = this.createElement('slot', [], { name: 'cover' });
|
|
28
|
+
this._coverSlot.appendChild(this._cover);
|
|
29
|
+
this._build();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_build() {
|
|
33
|
+
this._wrapper.replaceChildren();
|
|
34
|
+
if (this.getBoolAttr('narrow')) {
|
|
35
|
+
this._wrapper.append(this._coverSlot, this._page);
|
|
36
|
+
} else {
|
|
37
|
+
const scroll = this.createElement('div', ['nk-page-scroll']);
|
|
38
|
+
scroll.append(this._coverSlot, this._page);
|
|
39
|
+
this._wrapper.appendChild(scroll);
|
|
40
|
+
}
|
|
41
|
+
this._sync();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_sync() {
|
|
45
|
+
const icon = this.getAttribute('icon');
|
|
46
|
+
this._icon.textContent = icon || '';
|
|
47
|
+
this._icon.style.display = icon ? '' : 'none';
|
|
48
|
+
this._cover.style.display = this.getBoolAttr('cover') ? '' : 'none';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setupEvents() {
|
|
52
|
+
this._onIcon = () => this.emit('nk-action', { action: 'icon', value: this.getAttribute('icon') });
|
|
53
|
+
this._icon.addEventListener('click', this._onIcon);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
teardownEvents() { this._icon?.removeEventListener('click', this._onIcon); }
|
|
57
|
+
|
|
58
|
+
onAttributeChanged(name) {
|
|
59
|
+
if (name === 'narrow') this._build();
|
|
60
|
+
else this._sync();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get icon() { return this.getAttribute('icon'); }
|
|
64
|
+
set icon(v) { v == null ? this.removeAttribute('icon') : this.setAttribute('icon', v); }
|
|
65
|
+
get cover() { return this.getBoolAttr('cover'); }
|
|
66
|
+
set cover(v) { this.setBoolAttr('cover', v); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
customElements.define('nk-page', NkPage);
|
|
70
|
+
export { NkPage };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-profile-row avatar="MK"><nk-btn variant="secondary" small>Change photo</nk-btn></nk-profile-row>
|
|
4
|
+
// → <div class="nk-profile-row"><div class="big-avatar">MK</div>…</div>
|
|
5
|
+
class NkProfileRow extends NkElement {
|
|
6
|
+
static get observedAttributes() { return ['avatar']; }
|
|
7
|
+
|
|
8
|
+
render() {
|
|
9
|
+
const row = this.createElement('div', ['nk-profile-row']);
|
|
10
|
+
this._avatar = this.createElement('div', ['big-avatar']);
|
|
11
|
+
const s = this.createElement('slot', [], { name: 'avatar' });
|
|
12
|
+
s.appendChild(this._avatar);
|
|
13
|
+
row.appendChild(s);
|
|
14
|
+
row.appendChild(document.createElement('slot'));
|
|
15
|
+
this._wrapper.appendChild(row);
|
|
16
|
+
this._sync();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_sync() { this._avatar.textContent = this.getAttribute('avatar') || ''; }
|
|
20
|
+
onAttributeChanged() { this._sync(); }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
customElements.define('nk-profile-row', NkProfileRow);
|
|
24
|
+
export { NkProfileRow };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { NkFormElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-segmented name="range" value="week">
|
|
4
|
+
// <button value="week">Week</button><button value="month">Month</button>
|
|
5
|
+
// </nk-segmented>
|
|
6
|
+
// → <div class="nk-segmented">…</div> with .active on the chosen button.
|
|
7
|
+
// The buttons stay in the light DOM (their look comes from the ::slotted
|
|
8
|
+
// twins); the element sets type/role, moves .active and submits the value.
|
|
9
|
+
const KEYS = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };
|
|
10
|
+
|
|
11
|
+
class NkSegmented extends NkFormElement {
|
|
12
|
+
static get observedAttributes() { return ['value', 'name', 'disabled']; }
|
|
13
|
+
|
|
14
|
+
render() {
|
|
15
|
+
this._box = this.createElement('div', ['nk-segmented'], { role: 'radiogroup' });
|
|
16
|
+
this._slot = document.createElement('slot');
|
|
17
|
+
this._box.appendChild(this._slot);
|
|
18
|
+
this._wrapper.appendChild(this._box);
|
|
19
|
+
this._sync();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get buttons() { return this._slot?.assignedElements().filter(el => el.localName === 'button') ?? []; }
|
|
23
|
+
_valueOf(btn) { return btn.getAttribute('value') ?? btn.textContent.trim(); }
|
|
24
|
+
|
|
25
|
+
_sync() {
|
|
26
|
+
const buttons = this.buttons;
|
|
27
|
+
if (!buttons.length) return;
|
|
28
|
+
let value = this.getAttribute('value');
|
|
29
|
+
if (value === null || !buttons.some(b => this._valueOf(b) === value)) {
|
|
30
|
+
value = this._valueOf(buttons.find(b => b.classList.contains('active')) || buttons[0]);
|
|
31
|
+
}
|
|
32
|
+
if (this._defaultValue === undefined) this._defaultValue = value;
|
|
33
|
+
const disabled = this.getBoolAttr('disabled') || !!this._formDisabled;
|
|
34
|
+
for (const b of buttons) {
|
|
35
|
+
const on = this._valueOf(b) === value;
|
|
36
|
+
b.type = 'button';
|
|
37
|
+
b.setAttribute('role', 'radio');
|
|
38
|
+
b.setAttribute('aria-checked', on ? 'true' : 'false');
|
|
39
|
+
b.classList.toggle('active', on);
|
|
40
|
+
b.tabIndex = on ? 0 : -1;
|
|
41
|
+
b.disabled = disabled;
|
|
42
|
+
}
|
|
43
|
+
this._value = value;
|
|
44
|
+
this.setFormValue(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setupEvents() {
|
|
48
|
+
this._onSlot = () => this._sync();
|
|
49
|
+
this._onClick = (e) => {
|
|
50
|
+
const btn = e.target.closest?.('button');
|
|
51
|
+
if (!btn || !this.buttons.includes(btn) || btn.disabled) return;
|
|
52
|
+
this._choose(this._valueOf(btn));
|
|
53
|
+
};
|
|
54
|
+
this._onKey = (e) => {
|
|
55
|
+
const dir = KEYS[e.key];
|
|
56
|
+
if (!dir) return;
|
|
57
|
+
const buttons = this.buttons;
|
|
58
|
+
const i = buttons.indexOf(e.target.closest?.('button'));
|
|
59
|
+
if (i < 0 || buttons.length < 2) return;
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
const next = buttons[(i + dir + buttons.length) % buttons.length];
|
|
62
|
+
this._choose(this._valueOf(next));
|
|
63
|
+
next.focus();
|
|
64
|
+
};
|
|
65
|
+
this._slot.addEventListener('slotchange', this._onSlot);
|
|
66
|
+
this.addEventListener('click', this._onClick);
|
|
67
|
+
this.addEventListener('keydown', this._onKey);
|
|
68
|
+
this._sync();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
teardownEvents() {
|
|
72
|
+
this._slot?.removeEventListener('slotchange', this._onSlot);
|
|
73
|
+
this.removeEventListener('click', this._onClick);
|
|
74
|
+
this.removeEventListener('keydown', this._onKey);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_choose(value) {
|
|
78
|
+
if (value === this._value) return;
|
|
79
|
+
this._syncing = true;
|
|
80
|
+
this.setAttribute('value', value);
|
|
81
|
+
this._syncing = false;
|
|
82
|
+
this._sync();
|
|
83
|
+
this.emit('nk-change', { value, name: this.name });
|
|
84
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onAttributeChanged() { if (!this._syncing) this._sync(); }
|
|
88
|
+
resetValue() { if (this._defaultValue !== undefined) this.setAttribute('value', this._defaultValue); }
|
|
89
|
+
onFormDisabled(d) { this._formDisabled = d; this._sync(); }
|
|
90
|
+
|
|
91
|
+
get value() { return this._value ?? this.getAttribute('value'); }
|
|
92
|
+
set value(v) { this.setAttribute('value', v); }
|
|
93
|
+
get name() { return this.getAttribute('name'); }
|
|
94
|
+
set name(v) { this.setAttribute('name', v); }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
customElements.define('nk-segmented', NkSegmented);
|
|
98
|
+
export { NkSegmented };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-skeleton height="18" width="60%"></nk-skeleton>
|
|
4
|
+
// <nk-skeleton lines="3" widths="100%,85%,40%"></nk-skeleton>
|
|
5
|
+
// → <div class="nk-skeleton" style="height:…;width:…"></div> × lines
|
|
6
|
+
class NkSkeleton extends NkElement {
|
|
7
|
+
static get observedAttributes() { return ['lines', 'height', 'width', 'widths']; }
|
|
8
|
+
|
|
9
|
+
render() { this._build(); }
|
|
10
|
+
|
|
11
|
+
_build() {
|
|
12
|
+
this._wrapper.replaceChildren();
|
|
13
|
+
const lines = Math.max(1, Number(this.getAttribute('lines')) || 1);
|
|
14
|
+
const height = this.getAttribute('height') || '13';
|
|
15
|
+
const widths = (this.getAttribute('widths') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
16
|
+
const width = this.getAttribute('width');
|
|
17
|
+
for (let i = 0; i < lines; i++) {
|
|
18
|
+
const line = this.createElement('div', ['nk-skeleton'], { 'aria-hidden': 'true' });
|
|
19
|
+
line.style.height = /^\d+(\.\d+)?$/.test(height) ? `${height}px` : height;
|
|
20
|
+
const w = widths[i] ?? width;
|
|
21
|
+
if (w) line.style.width = w;
|
|
22
|
+
this._wrapper.appendChild(line);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onAttributeChanged() { this._build(); }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
customElements.define('nk-skeleton', NkSkeleton);
|
|
30
|
+
export { NkSkeleton };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-stat label="Active pages" value="128" delta="▲ 12 this week" trend="up"></nk-stat>
|
|
4
|
+
// → <div class="nk-stat"><div class="s-label">…</div><div class="s-value">…</div><div class="s-delta up">…</div></div>
|
|
5
|
+
class NkStat extends NkElement {
|
|
6
|
+
static get observedAttributes() { return ['label', 'value', 'delta', 'trend']; }
|
|
7
|
+
|
|
8
|
+
render() {
|
|
9
|
+
const box = this.createElement('div', ['nk-stat']);
|
|
10
|
+
this._parts = {};
|
|
11
|
+
for (const [slot, cls] of [['label', 's-label'], ['value', 's-value'], ['delta', 's-delta']]) {
|
|
12
|
+
const el = this.createElement('div', [cls]);
|
|
13
|
+
const s = this.createElement('slot', [], { name: slot });
|
|
14
|
+
s.appendChild(el);
|
|
15
|
+
box.appendChild(s);
|
|
16
|
+
this._parts[slot] = el;
|
|
17
|
+
}
|
|
18
|
+
this._wrapper.appendChild(box);
|
|
19
|
+
this._sync();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_sync() {
|
|
23
|
+
for (const [slot, el] of Object.entries(this._parts)) {
|
|
24
|
+
const v = this.getAttribute(slot);
|
|
25
|
+
el.textContent = v ?? '';
|
|
26
|
+
el.style.display = v === null ? 'none' : '';
|
|
27
|
+
}
|
|
28
|
+
const trend = this.getAttribute('trend');
|
|
29
|
+
this._parts.delta.className = 's-delta' + (trend === 'up' || trend === 'down' ? ` ${trend}` : '');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onAttributeChanged() { this._sync(); }
|
|
33
|
+
|
|
34
|
+
get value() { return this.getAttribute('value'); }
|
|
35
|
+
set value(v) { this.setAttribute('value', v); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
customElements.define('nk-stat', NkStat);
|
|
39
|
+
export { NkStat };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-stats><nk-stat …></nk-stat>…</nk-stats> → <div class="nk-stats">
|
|
4
|
+
class NkStats extends NkElement {
|
|
5
|
+
render() {
|
|
6
|
+
const row = this.createElement('div', ['nk-stats']);
|
|
7
|
+
row.appendChild(document.createElement('slot'));
|
|
8
|
+
this._wrapper.appendChild(row);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
customElements.define('nk-stats', NkStats);
|
|
13
|
+
export { NkStats };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-synced badge="⟳ 3 places">…</nk-synced>
|
|
4
|
+
// → <div class="nk-synced"><span class="synced-badge">⟳ 3 places</span>…</div>
|
|
5
|
+
class NkSynced extends NkElement {
|
|
6
|
+
static get observedAttributes() { return ['badge']; }
|
|
7
|
+
|
|
8
|
+
render() {
|
|
9
|
+
const box = this.createElement('div', ['nk-synced']);
|
|
10
|
+
this._badge = this.createElement('span', ['synced-badge']);
|
|
11
|
+
box.appendChild(this._badge);
|
|
12
|
+
box.appendChild(document.createElement('slot'));
|
|
13
|
+
this._wrapper.appendChild(box);
|
|
14
|
+
this._sync();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_sync() { this._badge.textContent = this.getAttribute('badge') ?? '⟳ synced'; }
|
|
18
|
+
onAttributeChanged() { this._sync(); }
|
|
19
|
+
|
|
20
|
+
get badge() { return this.getAttribute('badge'); }
|
|
21
|
+
set badge(v) { this.setAttribute('badge', v); }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
customElements.define('nk-synced', NkSynced);
|
|
25
|
+
export { NkSynced };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-tab value="notes" active>📝 Notes</nk-tab> → <span class="nk-tab active" role="tab">
|
|
4
|
+
class NkTab extends NkElement {
|
|
5
|
+
static get observedAttributes() { return ['active', 'value', 'disabled']; }
|
|
6
|
+
|
|
7
|
+
render() {
|
|
8
|
+
this._tab = this.createElement('span', ['nk-tab'], { role: 'tab' });
|
|
9
|
+
this._tab.appendChild(document.createElement('slot'));
|
|
10
|
+
this._wrapper.appendChild(this._tab);
|
|
11
|
+
this._sync();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_sync() {
|
|
15
|
+
const active = this.getBoolAttr('active');
|
|
16
|
+
this._tab.classList.toggle('active', active);
|
|
17
|
+
this._tab.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
18
|
+
this._tab.tabIndex = active ? 0 : -1;
|
|
19
|
+
this._tab.setAttribute('aria-disabled', this.getBoolAttr('disabled') ? 'true' : 'false');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setupEvents() {
|
|
23
|
+
this._onClick = () => this.select();
|
|
24
|
+
this._onKey = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.select(); } };
|
|
25
|
+
this._tab.addEventListener('click', this._onClick);
|
|
26
|
+
this._tab.addEventListener('keydown', this._onKey);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
teardownEvents() {
|
|
30
|
+
this._tab?.removeEventListener('click', this._onClick);
|
|
31
|
+
this._tab?.removeEventListener('keydown', this._onKey);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
select() {
|
|
35
|
+
if (this.getBoolAttr('disabled')) return;
|
|
36
|
+
const ok = this.emit('nk-select', { value: this.value, label: this.textContent.trim(), item: this });
|
|
37
|
+
if (!ok) return;
|
|
38
|
+
const tabs = this.closest('nk-tabs');
|
|
39
|
+
if (tabs) tabs.value = this.value;
|
|
40
|
+
else this.setBoolAttr('active', true);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
onAttributeChanged() { this._sync(); }
|
|
44
|
+
focus(o) { this._tab?.focus(o); }
|
|
45
|
+
|
|
46
|
+
get value() { return this.getAttribute('value') ?? this.textContent.trim(); }
|
|
47
|
+
set value(v) { this.setAttribute('value', v); }
|
|
48
|
+
get active() { return this.getBoolAttr('active'); }
|
|
49
|
+
set active(v) { this.setBoolAttr('active', v); }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
customElements.define('nk-tab', NkTab);
|
|
53
|
+
export { NkTab };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-tabs value="notes">
|
|
4
|
+
// <nk-tab value="notes">📝 Notes</nk-tab>
|
|
5
|
+
// <nk-tab value="tasks">✅ Tasks</nk-tab>
|
|
6
|
+
// <div slot="panel" data-tab="notes" class="nk-tab-panel">…</div>
|
|
7
|
+
// <div slot="panel" data-tab="tasks" class="nk-tab-panel">…</div>
|
|
8
|
+
// </nk-tabs>
|
|
9
|
+
// → <div class="nk-tabs" role="tablist">…</div> + the panels below it.
|
|
10
|
+
// The tabs keep one tab active and hide every panel whose data-tab does not
|
|
11
|
+
// match `value` (through the hidden attribute – the panels stay yours).
|
|
12
|
+
const KEYS = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };
|
|
13
|
+
|
|
14
|
+
class NkTabs extends NkElement {
|
|
15
|
+
static get observedAttributes() { return ['value']; }
|
|
16
|
+
|
|
17
|
+
render() {
|
|
18
|
+
this._strip = this.createElement('div', ['nk-tabs'], { role: 'tablist' });
|
|
19
|
+
this._tabSlot = document.createElement('slot');
|
|
20
|
+
this._strip.appendChild(this._tabSlot);
|
|
21
|
+
this._panelSlot = this.createElement('slot', [], { name: 'panel' });
|
|
22
|
+
this._wrapper.append(this._strip, this._panelSlot);
|
|
23
|
+
this._sync();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get tabs() { return [...this.querySelectorAll(':scope > nk-tab')]; }
|
|
27
|
+
get panels() { return [...this.querySelectorAll(':scope > [slot="panel"]')]; }
|
|
28
|
+
|
|
29
|
+
_sync() {
|
|
30
|
+
const tabs = this.tabs;
|
|
31
|
+
if (!tabs.length) return;
|
|
32
|
+
let value = this.getAttribute('value');
|
|
33
|
+
if (value === null || !tabs.some(t => t.getAttribute('value') === value || (t.getAttribute('value') === null && t.textContent.trim() === value))) {
|
|
34
|
+
const preset = tabs.find(t => t.hasAttribute('active')) || tabs[0];
|
|
35
|
+
value = preset.getAttribute('value') ?? preset.textContent.trim();
|
|
36
|
+
}
|
|
37
|
+
for (const tab of tabs) {
|
|
38
|
+
const v = tab.getAttribute('value') ?? tab.textContent.trim();
|
|
39
|
+
if (v === value) tab.setAttribute('active', ''); else tab.removeAttribute('active');
|
|
40
|
+
}
|
|
41
|
+
for (const panel of this.panels) panel.hidden = panel.dataset.tab !== value;
|
|
42
|
+
this._value = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setupEvents() {
|
|
46
|
+
this._onSlot = () => this._sync();
|
|
47
|
+
this._onKey = (e) => {
|
|
48
|
+
const dir = KEYS[e.key];
|
|
49
|
+
if (!dir) return;
|
|
50
|
+
const tabs = this.tabs.filter(t => !t.hasAttribute('disabled'));
|
|
51
|
+
const i = tabs.indexOf(e.target.closest?.('nk-tab'));
|
|
52
|
+
if (i < 0 || tabs.length < 2) return;
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
const next = tabs[(i + dir + tabs.length) % tabs.length];
|
|
55
|
+
next.select();
|
|
56
|
+
next.focus();
|
|
57
|
+
};
|
|
58
|
+
this._tabSlot.addEventListener('slotchange', this._onSlot);
|
|
59
|
+
this._panelSlot.addEventListener('slotchange', this._onSlot);
|
|
60
|
+
this.addEventListener('keydown', this._onKey);
|
|
61
|
+
this._sync();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
teardownEvents() {
|
|
65
|
+
this._tabSlot?.removeEventListener('slotchange', this._onSlot);
|
|
66
|
+
this._panelSlot?.removeEventListener('slotchange', this._onSlot);
|
|
67
|
+
this.removeEventListener('keydown', this._onKey);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onAttributeChanged() {
|
|
71
|
+
const before = this._value;
|
|
72
|
+
this._sync();
|
|
73
|
+
if (this._value !== before) this.emit('nk-change', { value: this._value });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get value() { return this._value ?? this.getAttribute('value'); }
|
|
77
|
+
set value(v) { this.setAttribute('value', v); }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
customElements.define('nk-tabs', NkTabs);
|
|
81
|
+
export { NkTabs };
|