@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,31 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-mention type="person"><span slot="avatar" class="mini-avatar" style="background:#448361">SL</span>Sara Lindt</nk-mention>
|
|
4
|
+
// <nk-mention type="page">📄 Onboarding</nk-mention> <nk-mention type="date">📅 May 20</nk-mention>
|
|
5
|
+
// → <span class="nk-mention person">…</span>
|
|
6
|
+
const TYPES = ['person', 'page', 'date'];
|
|
7
|
+
|
|
8
|
+
class NkMention extends NkElement {
|
|
9
|
+
static get observedAttributes() { return ['type']; }
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
this._box = this.createElement('span', this._classes());
|
|
13
|
+
this._box.appendChild(this.createElement('slot', [], { name: 'avatar' }));
|
|
14
|
+
this._box.appendChild(document.createElement('slot'));
|
|
15
|
+
this._wrapper.appendChild(this._box);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_classes() {
|
|
19
|
+
const t = this.getAttribute('type');
|
|
20
|
+
return ['nk-mention', ...(TYPES.includes(t) ? [t] : [])];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
onAttributeChanged() { if (this._box) this._box.className = this._classes().join(' '); }
|
|
24
|
+
|
|
25
|
+
get type() { return this.getAttribute('type'); }
|
|
26
|
+
set type(v) { v ? this.setAttribute('type', v) : this.removeAttribute('type'); }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
customElements.define('nk-mention', NkMention);
|
|
30
|
+
|
|
31
|
+
export { NkMention };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-menu-item icon="✏️" shortcut="⌘E" value="rename">Rename</nk-menu-item>
|
|
4
|
+
// <nk-menu-item type="separator"></nk-menu-item> <nk-menu-item type="label">Danger</nk-menu-item>
|
|
5
|
+
// <nk-menu-item icon="🗑️" danger value="delete">Delete</nk-menu-item>
|
|
6
|
+
// → <div class="nk-menu-item danger"><span class="m-icon">🗑️</span>Delete<span class="m-shortcut">…</span></div>
|
|
7
|
+
class NkMenuItem extends NkElement {
|
|
8
|
+
static get observedAttributes() { return ['icon', 'shortcut', 'danger', 'value', 'type', 'disabled']; }
|
|
9
|
+
|
|
10
|
+
render() { this._build(); }
|
|
11
|
+
|
|
12
|
+
_build() {
|
|
13
|
+
const type = this.getAttribute('type');
|
|
14
|
+
let el;
|
|
15
|
+
if (type === 'separator') {
|
|
16
|
+
el = this.createElement('div', ['nk-menu-sep'], { role: 'separator' });
|
|
17
|
+
} else if (type === 'label') {
|
|
18
|
+
el = this.createElement('div', ['nk-menu-label']);
|
|
19
|
+
el.appendChild(document.createElement('slot'));
|
|
20
|
+
} else {
|
|
21
|
+
el = this.createElement('div', ['nk-menu-item'], { role: 'menuitem', tabindex: '-1' });
|
|
22
|
+
this._icon = this.createElement('span', ['m-icon']);
|
|
23
|
+
const iconSlot = this.createElement('slot', [], { name: 'icon' });
|
|
24
|
+
iconSlot.appendChild(this._icon);
|
|
25
|
+
this._shortcut = this.createElement('span', ['m-shortcut']);
|
|
26
|
+
el.append(iconSlot, document.createElement('slot'), this._shortcut);
|
|
27
|
+
}
|
|
28
|
+
if (this._el) { this._el.removeEventListener('click', this._onClick); this._el.replaceWith(el); } else this._wrapper.appendChild(el);
|
|
29
|
+
this._el = el;
|
|
30
|
+
if (this._onClick) el.addEventListener('click', this._onClick);
|
|
31
|
+
this._sync();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_sync() {
|
|
35
|
+
if (!this._el.classList.contains('nk-menu-item')) return;
|
|
36
|
+
const icon = this.getAttribute('icon');
|
|
37
|
+
this._icon.textContent = icon || '';
|
|
38
|
+
this._icon.style.display = icon ? '' : 'none';
|
|
39
|
+
const sc = this.getAttribute('shortcut');
|
|
40
|
+
this._shortcut.textContent = sc || '';
|
|
41
|
+
this._shortcut.style.display = sc ? '' : 'none';
|
|
42
|
+
this._el.classList.toggle('danger', this.getBoolAttr('danger'));
|
|
43
|
+
this._el.setAttribute('aria-disabled', this.getBoolAttr('disabled') ? 'true' : 'false');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setupEvents() {
|
|
47
|
+
this._onClick = () => this.select();
|
|
48
|
+
this._el.addEventListener('click', this._onClick);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
teardownEvents() { this._el?.removeEventListener('click', this._onClick); }
|
|
52
|
+
|
|
53
|
+
select() {
|
|
54
|
+
if (!this._el.classList.contains('nk-menu-item') || this.getBoolAttr('disabled')) return;
|
|
55
|
+
this.emit('nk-select', { value: this.value, label: this.textContent.trim(), item: this });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onAttributeChanged(name) { name === 'type' ? this._build() : this._sync(); }
|
|
59
|
+
focus(o) { this._el?.focus(o); }
|
|
60
|
+
get selectable() { return this._el?.classList.contains('nk-menu-item') && !this.getBoolAttr('disabled'); }
|
|
61
|
+
|
|
62
|
+
get value() { return this.getAttribute('value') ?? this.textContent.trim(); }
|
|
63
|
+
set value(v) { this.setAttribute('value', v); }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
customElements.define('nk-menu-item', NkMenuItem);
|
|
67
|
+
|
|
68
|
+
export { NkMenuItem };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-menu>
|
|
4
|
+
// <nk-menu-item type="label">Page</nk-menu-item>
|
|
5
|
+
// <nk-menu-item icon="✏️" shortcut="⌘E" value="rename">Rename</nk-menu-item>
|
|
6
|
+
// <nk-menu-item type="separator"></nk-menu-item>
|
|
7
|
+
// <nk-menu-item icon="🗑️" danger value="delete">Delete</nk-menu-item>
|
|
8
|
+
// </nk-menu>
|
|
9
|
+
// → <div class="nk-pop nk-menu" role="menu">…</div>
|
|
10
|
+
// ↑↓ move between items, Enter selects, nk-select bubbles from the item.
|
|
11
|
+
class NkMenu extends NkElement {
|
|
12
|
+
render() {
|
|
13
|
+
this._box = this.createElement('div', ['nk-pop', 'nk-menu'], { role: 'menu' });
|
|
14
|
+
this._box.appendChild(document.createElement('slot'));
|
|
15
|
+
this._wrapper.appendChild(this._box);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get items() { return [...this.querySelectorAll(':scope > nk-menu-item')].filter(i => i.selectable); }
|
|
19
|
+
|
|
20
|
+
setupEvents() {
|
|
21
|
+
this._onKey = (e) => {
|
|
22
|
+
const dir = { ArrowDown: 1, ArrowUp: -1 }[e.key];
|
|
23
|
+
const current = e.target.closest?.('nk-menu-item');
|
|
24
|
+
const items = this.items;
|
|
25
|
+
if (!items.length) return;
|
|
26
|
+
if (dir) {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
const i = items.indexOf(current);
|
|
29
|
+
items[(i + dir + items.length) % items.length].focus();
|
|
30
|
+
} else if ((e.key === 'Enter' || e.key === ' ') && current) {
|
|
31
|
+
e.preventDefault(); current.select();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
this.addEventListener('keydown', this._onKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
teardownEvents() { this.removeEventListener('keydown', this._onKey); }
|
|
38
|
+
|
|
39
|
+
/** Focuses the first item (called by a popover when it opens). */
|
|
40
|
+
focusFirst() { this.items[0]?.focus(); }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
customElements.define('nk-menu', NkMenu);
|
|
44
|
+
|
|
45
|
+
export { NkMenu };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
import { l as lockScroll, i as inertOutside, f as firstFocusable, u as unlockScroll } from './shared/focus-DXrK9oLe.js';
|
|
3
|
+
|
|
4
|
+
// <nk-modal id="settings">
|
|
5
|
+
// <nk-settings-user slot="user" name="…" mail="…"></nk-settings-user>
|
|
6
|
+
// <nk-settings-pane name="profile" group="Account" icon="👤" label="My profile" title="My profile" active>…</nk-settings-pane>
|
|
7
|
+
// <nk-settings-pane name="members" group="Workspace" icon="👥" label="Members" title="Members">…</nk-settings-pane>
|
|
8
|
+
// </nk-modal>
|
|
9
|
+
// → <div class="nk-modal-backdrop open"><div class="nk-modal" role="dialog">
|
|
10
|
+
// <nav class="nk-settings-nav">…user… section labels + tree rows…</nav>
|
|
11
|
+
// <div class="nk-settings-content">…panes…</div></div></div>
|
|
12
|
+
//
|
|
13
|
+
// The modal renders the nav rows itself from the panes' label/icon/group, so
|
|
14
|
+
// `.nk-settings-nav .nk-tree-item` and the 860px icon-rail rules match with
|
|
15
|
+
// no JS breakpoint. Escape and the backdrop close it; focus moves in and
|
|
16
|
+
// back out; the page behind is scroll-locked and inert. Put the element
|
|
17
|
+
// directly under <body>: a transformed ancestor would trap the fixed backdrop.
|
|
18
|
+
class NkModal extends NkElement {
|
|
19
|
+
static get observedAttributes() { return ['open', 'pane']; }
|
|
20
|
+
|
|
21
|
+
render() {
|
|
22
|
+
this._backdrop = this.createElement('div', ['nk-modal-backdrop']);
|
|
23
|
+
this._backdrop.inert = true;
|
|
24
|
+
this._box = this.createElement('div', ['nk-modal'], { role: 'dialog', 'aria-modal': 'true', tabindex: '-1' });
|
|
25
|
+
this._nav = this.createElement('nav', ['nk-settings-nav'], { 'aria-label': 'Settings' });
|
|
26
|
+
this._nav.appendChild(this.createElement('slot', [], { name: 'user' }));
|
|
27
|
+
this._rows = document.createElement('div');
|
|
28
|
+
this._nav.appendChild(this._rows);
|
|
29
|
+
this._nav.appendChild(this.createElement('slot', [], { name: 'nav' }));
|
|
30
|
+
this._content = this.createElement('div', ['nk-settings-content']);
|
|
31
|
+
this._slot = document.createElement('slot');
|
|
32
|
+
this._content.appendChild(this._slot);
|
|
33
|
+
this._box.append(this._nav, this._content);
|
|
34
|
+
this._backdrop.appendChild(this._box);
|
|
35
|
+
this._wrapper.appendChild(this._backdrop);
|
|
36
|
+
this._syncOpen();
|
|
37
|
+
this.requestNav();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get panes() { return [...this.querySelectorAll(':scope > nk-settings-pane')]; }
|
|
41
|
+
|
|
42
|
+
/** Rebuilds the nav from the panes (debounced – panes upgrade after the modal). */
|
|
43
|
+
requestNav() {
|
|
44
|
+
if (this._navQueued) return;
|
|
45
|
+
this._navQueued = true;
|
|
46
|
+
queueMicrotask(() => { this._navQueued = false; this._buildNav(); });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_buildNav() {
|
|
50
|
+
if (!this._rows) return;
|
|
51
|
+
const panes = this.panes;
|
|
52
|
+
const labelled = panes.filter(p => p.getAttribute('label'));
|
|
53
|
+
this._rows.replaceChildren();
|
|
54
|
+
this._nav.style.display = (labelled.length || this.querySelector(':scope > [slot="nav"], :scope > [slot="user"]')) ? '' : 'none';
|
|
55
|
+
let lastGroup = null;
|
|
56
|
+
for (const pane of labelled) {
|
|
57
|
+
const group = pane.getAttribute('group');
|
|
58
|
+
if (group && group !== lastGroup) {
|
|
59
|
+
const label = this.createElement('div', ['nk-section-label']);
|
|
60
|
+
label.textContent = group;
|
|
61
|
+
this._rows.appendChild(label);
|
|
62
|
+
lastGroup = group;
|
|
63
|
+
}
|
|
64
|
+
const row = this.createElement('div', ['nk-tree-item'], { role: 'tab', tabindex: '0', 'data-pane': pane.name });
|
|
65
|
+
const icon = this.createElement('span', ['icon']);
|
|
66
|
+
icon.textContent = pane.getAttribute('icon') || '';
|
|
67
|
+
const text = this.createElement('span', ['label']);
|
|
68
|
+
text.textContent = pane.getAttribute('label');
|
|
69
|
+
row.append(icon, text);
|
|
70
|
+
this._rows.appendChild(row);
|
|
71
|
+
}
|
|
72
|
+
this._syncActive();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Marks the active pane and its nav row without rebuilding the rows (keeps focus). */
|
|
76
|
+
_syncActive() {
|
|
77
|
+
const panes = this.panes;
|
|
78
|
+
let current = this.getAttribute('pane');
|
|
79
|
+
if (!panes.some(p => p.name === current)) current = (panes.find(p => p.hasAttribute('active')) || panes[0])?.name ?? null;
|
|
80
|
+
for (const row of this._rows.querySelectorAll('[data-pane]')) {
|
|
81
|
+
const on = row.dataset.pane === current;
|
|
82
|
+
row.classList.toggle('active', on);
|
|
83
|
+
row.setAttribute('aria-selected', on ? 'true' : 'false');
|
|
84
|
+
}
|
|
85
|
+
for (const pane of panes) pane.active = pane.name === current;
|
|
86
|
+
this._current = current;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_syncOpen() {
|
|
90
|
+
const open = this.getBoolAttr('open');
|
|
91
|
+
this._backdrop.classList.toggle('open', open);
|
|
92
|
+
this._backdrop.inert = !open;
|
|
93
|
+
if (open === this._wasOpen) return;
|
|
94
|
+
this._wasOpen = open;
|
|
95
|
+
if (open) {
|
|
96
|
+
this._returnFocus = document.activeElement;
|
|
97
|
+
lockScroll();
|
|
98
|
+
this._undoInert = inertOutside(this);
|
|
99
|
+
requestAnimationFrame(() => {
|
|
100
|
+
const target = this._rows.querySelector('.nk-tree-item.active') || firstFocusable(this) || this._box;
|
|
101
|
+
target.focus({ preventScroll: true });
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
unlockScroll();
|
|
105
|
+
this._undoInert?.(); this._undoInert = null;
|
|
106
|
+
const back = this._returnFocus;
|
|
107
|
+
if (back && typeof back.focus === 'function' && back.isConnected) back.focus({ preventScroll: true });
|
|
108
|
+
this._returnFocus = null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
setupEvents() {
|
|
113
|
+
this._onBackdrop = (e) => { if (e.target === this._backdrop) this.close(); };
|
|
114
|
+
this._onKey = (e) => { if (e.key === 'Escape' && this.getBoolAttr('open')) { e.stopPropagation(); this.close(); } };
|
|
115
|
+
this._onNavClick = (e) => { const row = e.target.closest('[data-pane]'); if (row) { row.focus(); this.pane = row.dataset.pane; } };
|
|
116
|
+
this._onNavKey = (e) => {
|
|
117
|
+
const row = e.target.closest?.('[data-pane]');
|
|
118
|
+
if (!row) return;
|
|
119
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.pane = row.dataset.pane; return; }
|
|
120
|
+
const dir = { ArrowDown: 1, ArrowUp: -1 }[e.key];
|
|
121
|
+
if (!dir) return;
|
|
122
|
+
const rows = [...this._rows.querySelectorAll('[data-pane]')];
|
|
123
|
+
const next = rows[(rows.indexOf(row) + dir + rows.length) % rows.length];
|
|
124
|
+
e.preventDefault(); next.focus(); this.pane = next.dataset.pane;
|
|
125
|
+
};
|
|
126
|
+
this._onSlot = () => this.requestNav();
|
|
127
|
+
this._backdrop.addEventListener('click', this._onBackdrop);
|
|
128
|
+
document.addEventListener('keydown', this._onKey);
|
|
129
|
+
this._rows.addEventListener('click', this._onNavClick);
|
|
130
|
+
this._rows.addEventListener('keydown', this._onNavKey);
|
|
131
|
+
this._slot.addEventListener('slotchange', this._onSlot);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
teardownEvents() {
|
|
135
|
+
this._backdrop?.removeEventListener('click', this._onBackdrop);
|
|
136
|
+
document.removeEventListener('keydown', this._onKey);
|
|
137
|
+
this._rows?.removeEventListener('click', this._onNavClick);
|
|
138
|
+
this._rows?.removeEventListener('keydown', this._onNavKey);
|
|
139
|
+
this._slot?.removeEventListener('slotchange', this._onSlot);
|
|
140
|
+
if (this._wasOpen) { unlockScroll(); this._undoInert?.(); this._undoInert = null; this._wasOpen = false; }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
onAttributeChanged(name) {
|
|
144
|
+
if (name === 'open') {
|
|
145
|
+
this._syncOpen();
|
|
146
|
+
this.emit('nk-toggle', { open: this.getBoolAttr('open') });
|
|
147
|
+
} else if (name === 'pane') {
|
|
148
|
+
const before = this._current;
|
|
149
|
+
this._syncActive();
|
|
150
|
+
if (this._current !== before) this.emit('nk-select', { value: this._current, label: this.panes.find(p => p.name === this._current)?.getAttribute('label') ?? this._current });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
show(pane) { if (pane) this.setAttribute('pane', pane); this.setBoolAttr('open', true); }
|
|
155
|
+
close() { this.setBoolAttr('open', false); }
|
|
156
|
+
toggle() { this.setBoolAttr('open', !this.getBoolAttr('open')); }
|
|
157
|
+
|
|
158
|
+
get open() { return this.getBoolAttr('open'); }
|
|
159
|
+
set open(v) { this.setBoolAttr('open', v); }
|
|
160
|
+
get pane() { return this._current ?? this.getAttribute('pane'); }
|
|
161
|
+
set pane(v) { this.setAttribute('pane', v); }
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
customElements.define('nk-modal', NkModal);
|
|
165
|
+
|
|
166
|
+
export { NkModal };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { a as NkFormElement } from './shared/base-6gEcoatG.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
|
+
|
|
89
|
+
export { NkModelCard };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.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
|
+
|
|
15
|
+
export { NkPageActions };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.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
|
+
|
|
35
|
+
export { NkPageCover };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.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
|
+
|
|
71
|
+
export { NkPageTitle };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.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
|
+
|
|
71
|
+
export { NkPage };
|