@jungherz-de/notionkit-elements 1.5.2 → 1.6.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.
Files changed (103) hide show
  1. package/README.md +25 -16
  2. package/dist/components/base.js +346 -0
  3. package/dist/components/nk-ai-input-row.js +2 -1
  4. package/dist/components/nk-ai-msg.js +10 -3
  5. package/dist/components/nk-ai-thread.js +2 -1
  6. package/dist/components/nk-app.js +2 -1
  7. package/dist/components/nk-avatar-group.js +2 -1
  8. package/dist/components/nk-avatar.js +57 -0
  9. package/dist/components/nk-banner.js +2 -1
  10. package/dist/components/nk-block-host.js +2 -1
  11. package/dist/components/nk-board-view.js +4 -2
  12. package/dist/components/nk-breadcrumb.js +2 -1
  13. package/dist/components/nk-btn.js +2 -1
  14. package/dist/components/nk-callout.js +2 -1
  15. package/dist/components/nk-check.js +2 -1
  16. package/dist/components/nk-cmdk.js +2 -1
  17. package/dist/components/nk-code.js +2 -1
  18. package/dist/components/nk-comment.js +9 -6
  19. package/dist/components/nk-comments.js +2 -1
  20. package/dist/components/nk-danger-zone.js +2 -1
  21. package/dist/components/nk-database.js +2 -1
  22. package/dist/components/nk-divider.js +2 -1
  23. package/dist/components/nk-emoji-picker.js +2 -1
  24. package/dist/components/nk-empty.js +2 -1
  25. package/dist/components/nk-field.js +2 -1
  26. package/dist/components/nk-fields.js +2 -1
  27. package/dist/components/nk-filter-bar.js +2 -1
  28. package/dist/components/nk-heading.js +2 -1
  29. package/dist/components/nk-input.js +2 -1
  30. package/dist/components/nk-kbd.js +2 -1
  31. package/dist/components/nk-list-view.js +96 -0
  32. package/dist/components/nk-member-list.js +2 -1
  33. package/dist/components/nk-member-row.js +10 -6
  34. package/dist/components/nk-mention.js +2 -1
  35. package/dist/components/nk-menu-item.js +23 -3
  36. package/dist/components/nk-menu.js +2 -1
  37. package/dist/components/nk-modal.js +2 -1
  38. package/dist/components/nk-model-card.js +2 -1
  39. package/dist/components/nk-page-actions.js +2 -1
  40. package/dist/components/nk-page-cover.js +12 -11
  41. package/dist/components/nk-page-title.js +2 -1
  42. package/dist/components/nk-page.js +12 -4
  43. package/dist/components/nk-panel.js +61 -0
  44. package/dist/components/nk-panels.js +18 -0
  45. package/dist/components/nk-pop.js +2 -1
  46. package/dist/components/nk-profile-row.js +2 -1
  47. package/dist/components/nk-progress.js +5 -2
  48. package/dist/components/nk-prop.js +54 -0
  49. package/dist/components/nk-props.js +21 -0
  50. package/dist/components/nk-quote.js +2 -1
  51. package/dist/components/nk-radio.js +2 -1
  52. package/dist/components/nk-section-label.js +2 -1
  53. package/dist/components/nk-segmented.js +2 -1
  54. package/dist/components/nk-select.js +2 -1
  55. package/dist/components/nk-settings-pane.js +2 -1
  56. package/dist/components/nk-settings-user.js +2 -1
  57. package/dist/components/nk-sidebar.js +2 -1
  58. package/dist/components/nk-skeleton.js +2 -1
  59. package/dist/components/nk-slider.js +2 -1
  60. package/dist/components/nk-stat.js +2 -1
  61. package/dist/components/nk-stats.js +2 -1
  62. package/dist/components/nk-switch.js +2 -1
  63. package/dist/components/nk-synced.js +2 -1
  64. package/dist/components/nk-tab-bar-item.js +2 -1
  65. package/dist/components/nk-tab-bar.js +2 -1
  66. package/dist/components/nk-tab.js +2 -1
  67. package/dist/components/nk-table-view.js +6 -2
  68. package/dist/components/nk-tabs.js +2 -1
  69. package/dist/components/nk-tag.js +2 -1
  70. package/dist/components/nk-template-btn.js +2 -1
  71. package/dist/components/nk-textarea.js +2 -1
  72. package/dist/components/nk-theme-toggle.js +2 -1
  73. package/dist/components/nk-toast.js +2 -1
  74. package/dist/components/nk-todo.js +2 -1
  75. package/dist/components/nk-toggle.js +2 -1
  76. package/dist/components/nk-topbar.js +2 -1
  77. package/dist/components/nk-tree-item.js +2 -1
  78. package/dist/components/nk-tree.js +2 -1
  79. package/dist/components/nk-workspace-switcher.js +2 -1
  80. package/dist/components/shared/avatar-BiqCaHOt.js +19 -0
  81. package/dist/components/shared/{property-cell-CtXQpUjQ.js → property-cell-o7N30kAQ.js} +8 -3
  82. package/dist/notionkit-elements.esm.js +374 -34
  83. package/dist/notionkit-elements.js +380 -33
  84. package/dist/notionkit-elements.min.js +1 -1
  85. package/package.json +4 -3
  86. package/src/components/content/nk-progress.js +3 -1
  87. package/src/components/data/nk-ai-msg.js +8 -2
  88. package/src/components/data/nk-comment.js +7 -5
  89. package/src/components/data/nk-list-view.js +93 -0
  90. package/src/components/data/nk-table-view.js +2 -0
  91. package/src/components/overlays/nk-menu-item.js +21 -2
  92. package/src/components/page/nk-avatar.js +55 -0
  93. package/src/components/page/nk-member-row.js +8 -5
  94. package/src/components/page/nk-page-cover.js +10 -10
  95. package/src/components/page/nk-page.js +10 -3
  96. package/src/components/page/nk-panel.js +59 -0
  97. package/src/components/page/nk-panels.js +16 -0
  98. package/src/components/page/nk-prop.js +52 -0
  99. package/src/components/page/nk-props.js +19 -0
  100. package/src/index.js +14 -0
  101. package/src/util/avatar.js +17 -0
  102. package/src/util/property-cell.js +7 -3
  103. package/dist/components/shared/base-AWFyTskN.js +0 -366
@@ -1,4 +1,5 @@
1
- import { a as NkFormElement } from './shared/base-AWFyTskN.js';
1
+ import { NkFormElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-check name="mail" value="weekly" checked>Weekly digest</nk-check>
4
5
  // → <label class="nk-check"><input type="checkbox"> Weekly digest</label>
@@ -1,5 +1,6 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
2
  import { l as lockScroll, i as inertOutside, u as unlockScroll } from './shared/focus-DXrK9oLe.js';
3
+ import '@jungherz-de/notionkit/notionkit-styles.js';
3
4
 
4
5
  // Subsequence scoring for the command palette: every query character must
5
6
  // appear in order; consecutive hits, word starts and an early first hit score
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // Minimal HTML highlighter emitting the .tag / .attr hooks .nk-code styles.
4
5
  // Mirrors tools/highlight.mjs in the NotionKit foundation – no third-party
@@ -1,14 +1,17 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import { i as initialsOf, p as paintAvatar } from './shared/avatar-BiqCaHOt.js';
3
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
4
 
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
+ // <nk-comment author="Sara Lindt" time="1 hr ago" avatar="SL" color="green">The board view feels close.</nk-comment>
6
+ // → <div class="nk-comment"><span class="nk-avatar green">SL</span><div><div class="c-head"><b>Sara Lindt</b> · 1 hr ago</div><div class="c-body">…</div></div></div>
7
+ // color: one of Notion's nine names, or any CSS background; without it the avatar gradient.
5
8
  // slot="head" adds content after the name (e.g. an <nk-tag>).
6
9
  class NkComment extends NkElement {
7
10
  static get observedAttributes() { return ['author', 'time', 'avatar', 'color']; }
8
11
 
9
12
  render() {
10
13
  const box = this.createElement('div', ['nk-comment']);
11
- this._avatar = this.createElement('span', ['mini-avatar']);
14
+ this._avatar = this.createElement('span', ['nk-avatar']);
12
15
  const avatarSlot = this.createElement('slot', [], { name: 'avatar' });
13
16
  avatarSlot.appendChild(this._avatar);
14
17
  const text = document.createElement('div');
@@ -29,8 +32,8 @@ class NkComment extends NkElement {
29
32
  this._author.textContent = author;
30
33
  const time = this.getAttribute('time');
31
34
  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)';
35
+ this._avatar.textContent = this.getAttribute('avatar') || initialsOf(author);
36
+ paintAvatar(this._avatar, this.getAttribute('color'));
34
37
  }
35
38
 
36
39
  onAttributeChanged() { this._sync(); }
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-comments placeholder="Comment …" send-label="Send">
4
5
  // <nk-comment …>…</nk-comment>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-danger-zone title="Danger zone">…</nk-danger-zone>
4
5
  // → <div class="nk-danger-zone"><div class="dz-title">Danger zone</div>…</div>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-database id="db" view="table" add-view>
4
5
  // <nk-table-view name="table" label="▦ Table" count></nk-table-view>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-divider></nk-divider> → <hr class="nk-divider">
4
5
  class NkDivider extends NkElement {
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // Built-in emoji set for <nk-emoji-picker>: eight categories, names for
4
5
  // search. Override with `picker.emojis = [{ char, name, cat }]`.
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-empty icon="🗂️" title="No entries yet" desc="Create the first one."><nk-btn variant="primary" small>+ New</nk-btn></nk-empty>
4
5
  // → <div class="nk-empty"><div class="e-icon">🗂️</div><div class="e-title">…</div><div class="e-desc">…</div><div class="e-actions">…</div></div>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-field label="Display name" desc="Shown next to your comments">
4
5
  // <nk-input value="Marcel"></nk-input>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-fields>
4
5
  // <nk-field label="Name"><nk-input name="name"></nk-input></nk-field>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-filter-bar search placeholder="Search rows …"></nk-filter-bar>
4
5
  // bar.filters = [{ key: 'status', label: 'Status: Done', value: 'done' }];
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-heading level="2">Section</nk-heading> → <h2 class="nk-heading">
4
5
  // The heading level is real markup (h1–h4), so the outline stays honest.
@@ -1,4 +1,5 @@
1
- import { a as NkFormElement } from './shared/base-AWFyTskN.js';
1
+ import { NkFormElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-input name="title" value="…" placeholder="…" wide></nk-input> → <input class="nk-input wide">
4
5
  // Takes part in the surrounding <form> through ElementInternals.
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-kbd>⌘</nk-kbd> <nk-kbd>K</nk-kbd> → <kbd class="nk-kbd">
4
5
  class NkKbd extends NkElement {
@@ -0,0 +1,96 @@
1
+ import { NkElement } from './base.js';
2
+ import { r as renderPropertyCell } from './shared/property-cell-o7N30kAQ.js';
3
+ import '@jungherz-de/notionkit/notionkit-styles.js';
4
+ import './shared/avatar-BiqCaHOt.js';
5
+
6
+ // <nk-list-view name="list" label="☰ List" meta-keys="due,status" new-row></nk-list-view>
7
+ // The third database view: one line per row – icon and title, the `meta-keys`
8
+ // on the right (default: the select and date columns, in column order). Dates
9
+ // and text stand as text, selects as tags, a person as avatar and name.
10
+ // Standalone: view.columns = […]; view.rows = […]. Inside <nk-database> the
11
+ // database pushes the data. Rows fire nk-select, the add row nk-action
12
+ // { action: 'new-row' }.
13
+ // → <div class="nk-list"><div class="nk-list-item"><span class="l-icon">…</span><span class="l-title">…</span><span class="l-meta">…</span></div>…</div>
14
+ class NkListView extends NkElement {
15
+ static get observedAttributes() { return ['name', 'label', 'badge', 'count', 'title-key', 'meta-keys', 'new-row', 'new-row-label']; }
16
+
17
+ render() {
18
+ this._box = document.createElement('div');
19
+ this._list = this.createElement('div', ['nk-list'], { role: 'list' });
20
+ this._newRow = this.createElement('div', ['nk-new-row'], { role: 'button', tabindex: '0' });
21
+ this._box.append(this._list, this._newRow);
22
+ this._wrapper.appendChild(this._box);
23
+ this._columns ??= []; this._rows ??= [];
24
+ this._render();
25
+ this.closest('nk-database')?.requestSync?.();
26
+ }
27
+
28
+ _titleColumn() {
29
+ const key = this.getAttribute('title-key');
30
+ return this._columns.find(c => c.key === key) || this._columns.find(c => c.title) || this._columns[0] || null;
31
+ }
32
+ _metaColumns() {
33
+ const keys = (this.getAttribute('meta-keys') || '').split(',').map(s => s.trim()).filter(Boolean);
34
+ if (keys.length) return keys.map(k => this._columns.find(c => c.key === k)).filter(Boolean);
35
+ return this._columns.filter(c => c.type === 'select' || c.type === 'date');
36
+ }
37
+
38
+ _render() {
39
+ if (!this._list) return;
40
+ const title = this._titleColumn(), meta = this._metaColumns();
41
+ this._list.replaceChildren();
42
+ for (const row of this._rows) {
43
+ const item = this.createElement('div', ['nk-list-item'], { role: 'listitem', tabindex: '0', 'data-id': row.id ?? '' });
44
+ const icon = this.createElement('span', ['l-icon']);
45
+ icon.textContent = row.icon || '';
46
+ if (!row.icon) icon.style.display = 'none';
47
+ const t = this.createElement('span', ['l-title']);
48
+ t.textContent = title ? String(row[title.key] ?? '') : '';
49
+ const m = this.createElement('span', ['l-meta']);
50
+ for (const c of meta) {
51
+ const v = row[c.key];
52
+ if (v === undefined || v === null || v === '') continue;
53
+ m.appendChild(c.type === 'date' || c.type === 'text' || !c.type ? document.createTextNode(String(v)) : renderPropertyCell(c, v, row));
54
+ }
55
+ item.append(icon, t, m);
56
+ this._list.appendChild(item);
57
+ }
58
+ this._newRow.textContent = this.getAttribute('new-row-label') || '+ New page';
59
+ this._newRow.style.display = this.getBoolAttr('new-row') ? '' : 'none';
60
+ }
61
+
62
+ setupEvents() {
63
+ this._onClick = (e) => {
64
+ if (this._newRow.contains(e.target)) { this.emit('nk-action', { action: 'new-row' }); return; }
65
+ const item = e.target.closest('.nk-list-item');
66
+ if (item) { const row = this._rowById(item.dataset.id); this.emit('nk-select', { row, id: row?.id, value: row?.id }); }
67
+ };
68
+ this._onKey = (e) => {
69
+ if (e.key !== 'Enter' && e.key !== ' ') return;
70
+ if (e.target === this._newRow || e.target.classList?.contains('nk-list-item')) { e.preventDefault(); e.target.click(); }
71
+ };
72
+ this._box.addEventListener('click', this._onClick);
73
+ this._box.addEventListener('keydown', this._onKey);
74
+ }
75
+
76
+ teardownEvents() {
77
+ this._box?.removeEventListener('click', this._onClick);
78
+ this._box?.removeEventListener('keydown', this._onKey);
79
+ }
80
+
81
+ _rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
82
+ onAttributeChanged() { this._render(); }
83
+
84
+ get name() { return this.getAttribute('name') || 'list'; }
85
+ get columns() { return this._columns; }
86
+ set columns(v) { this._columns = Array.isArray(v) ? v : []; this._render(); }
87
+ get rows() { return this._rows; }
88
+ set rows(v) { this._rows = Array.isArray(v) ? v : []; this._render(); }
89
+ set data({ columns, rows }) { this.setData(columns, rows); }
90
+ setData(columns, rows) { this._columns = columns || []; this._rows = rows || []; this._render(); }
91
+ refresh() { this._render(); }
92
+ }
93
+
94
+ customElements.define('nk-list-view', NkListView);
95
+
96
+ export { NkListView };
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-member-list><nk-member-row …></nk-member-row>…</nk-member-list>
4
5
  // → <div class="nk-member-list">…</div>; the last row gets `last` (no border).
@@ -1,15 +1,19 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import { i as initialsOf, p as paintAvatar } from './shared/avatar-BiqCaHOt.js';
3
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
4
 
3
- // <nk-member-row name="Sara Lindt" mail="sara@…" avatar="SL" color="#448361">
5
+ // <nk-member-row name="Sara Lindt" mail="sara@…" avatar="SL" color="green">
4
6
  // <nk-select slot="role" compact>…</nk-select>
5
7
  // </nk-member-row>
6
- // → <div class="nk-member-row"><span class="mini-avatar">SL</span><div>Sara Lindt<div class="m-mail">…</div></div><select …></div>
8
+ // → <div class="nk-member-row"><span class="nk-avatar green">SL</span><div>Sara Lindt<div class="m-mail">…</div></div><select …></div>
9
+ // color: one of Notion's nine names, or any CSS background; without it the
10
+ // avatar gradient. The row sizes the avatar to 28px.
7
11
  class NkMemberRow extends NkElement {
8
12
  static get observedAttributes() { return ['name', 'mail', 'avatar', 'color', 'last']; }
9
13
 
10
14
  render() {
11
15
  this._row = this.createElement('div', ['nk-member-row']);
12
- this._avatar = this.createElement('span', ['mini-avatar']);
16
+ this._avatar = this.createElement('span', ['nk-avatar']);
13
17
  const avatarSlot = this.createElement('slot', [], { name: 'avatar' });
14
18
  avatarSlot.appendChild(this._avatar);
15
19
  const text = document.createElement('div');
@@ -33,8 +37,8 @@ class NkMemberRow extends NkElement {
33
37
  this._mail.textContent = mail || '';
34
38
  this._mail.style.display = mail ? '' : 'none';
35
39
  const avatar = this.getAttribute('avatar');
36
- this._avatar.textContent = avatar || (this.getAttribute('name') || '').split(/\s+/).map(w => w[0]).join('').slice(0, 2).toUpperCase();
37
- this._avatar.style.background = this.getAttribute('color') || 'var(--nk-text-tertiary)';
40
+ this._avatar.textContent = avatar || initialsOf(this.getAttribute('name'));
41
+ paintAvatar(this._avatar, this.getAttribute('color'));
38
42
  this._row.classList.toggle('last', this.getBoolAttr('last'));
39
43
  }
40
44
 
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-mention type="person"><span slot="avatar" class="mini-avatar" style="background:#448361">SL</span>Sara Lindt</nk-mention>
4
5
  // <nk-mention type="page">📄 Onboarding</nk-mention> <nk-mention type="date">📅 May 20</nk-mention>
@@ -1,11 +1,16 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-menu-item icon="✏️" shortcut="⌘E" value="rename">Rename</nk-menu-item>
4
5
  // <nk-menu-item type="separator"></nk-menu-item> <nk-menu-item type="label">Danger</nk-menu-item>
5
6
  // <nk-menu-item icon="🗑️" danger value="delete">Delete</nk-menu-item>
7
+ // <nk-menu-item type="switch" icon="🔡" value="small" checked>Small text</nk-menu-item>
6
8
  // → <div class="nk-menu-item danger"><span class="m-icon">🗑️</span>Delete<span class="m-shortcut">…</span></div>
9
+ // A switch item carries a .nk-switch on the right, as "Small text" does in
10
+ // Notion's page menu: a click flips `checked` and fires nk-change { value,
11
+ // checked } instead of nk-select, so the menu around it stays open.
7
12
  class NkMenuItem extends NkElement {
8
- static get observedAttributes() { return ['icon', 'shortcut', 'danger', 'value', 'type', 'disabled']; }
13
+ static get observedAttributes() { return ['icon', 'shortcut', 'danger', 'value', 'type', 'disabled', 'checked']; }
9
14
 
10
15
  render() { this._build(); }
11
16
 
@@ -18,12 +23,15 @@ class NkMenuItem extends NkElement {
18
23
  el = this.createElement('div', ['nk-menu-label']);
19
24
  el.appendChild(document.createElement('slot'));
20
25
  } else {
21
- el = this.createElement('div', ['nk-menu-item'], { role: 'menuitem', tabindex: '-1' });
26
+ const isSwitch = type === 'switch';
27
+ el = this.createElement('div', ['nk-menu-item'], { role: isSwitch ? 'menuitemcheckbox' : 'menuitem', tabindex: '-1' });
22
28
  this._icon = this.createElement('span', ['m-icon']);
23
29
  const iconSlot = this.createElement('slot', [], { name: 'icon' });
24
30
  iconSlot.appendChild(this._icon);
25
31
  this._shortcut = this.createElement('span', ['m-shortcut']);
26
32
  el.append(iconSlot, document.createElement('slot'), this._shortcut);
33
+ this._switch = isSwitch ? this.createElement('span', ['nk-switch'], { 'aria-hidden': 'true' }) : null;
34
+ if (this._switch) el.appendChild(this._switch);
27
35
  }
28
36
  if (this._el) { this._el.removeEventListener('click', this._onClick); this._el.replaceWith(el); } else this._wrapper.appendChild(el);
29
37
  this._el = el;
@@ -41,6 +49,11 @@ class NkMenuItem extends NkElement {
41
49
  this._shortcut.style.display = sc ? '' : 'none';
42
50
  this._el.classList.toggle('danger', this.getBoolAttr('danger'));
43
51
  this._el.setAttribute('aria-disabled', this.getBoolAttr('disabled') ? 'true' : 'false');
52
+ if (this._switch) {
53
+ const on = this.getBoolAttr('checked') ? 'true' : 'false';
54
+ this._el.setAttribute('aria-checked', on);
55
+ this._switch.setAttribute('aria-checked', on);
56
+ }
44
57
  }
45
58
 
46
59
  setupEvents() {
@@ -52,6 +65,11 @@ class NkMenuItem extends NkElement {
52
65
 
53
66
  select() {
54
67
  if (!this._el.classList.contains('nk-menu-item') || this.getBoolAttr('disabled')) return;
68
+ if (this._switch) {
69
+ this.checked = !this.checked;
70
+ this.emit('nk-change', { value: this.value, checked: this.checked, item: this });
71
+ return;
72
+ }
55
73
  this.emit('nk-select', { value: this.value, label: this.textContent.trim(), item: this });
56
74
  }
57
75
 
@@ -59,6 +77,8 @@ class NkMenuItem extends NkElement {
59
77
  focus(o) { this._el?.focus(o); }
60
78
  get selectable() { return this._el?.classList.contains('nk-menu-item') && !this.getBoolAttr('disabled'); }
61
79
 
80
+ get checked() { return this.getBoolAttr('checked'); }
81
+ set checked(v) { this.setBoolAttr('checked', v); }
62
82
  get value() { return this.getAttribute('value') ?? this.textContent.trim(); }
63
83
  set value(v) { this.setAttribute('value', v); }
64
84
  }
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-menu>
4
5
  // <nk-menu-item type="label">Page</nk-menu-item>
@@ -1,5 +1,6 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
2
  import { l as lockScroll, i as inertOutside, f as firstFocusable, u as unlockScroll } from './shared/focus-DXrK9oLe.js';
3
+ import '@jungherz-de/notionkit/notionkit-styles.js';
3
4
 
4
5
  // <nk-modal id="settings">
5
6
  // <nk-settings-user slot="user" name="…" mail="…"></nk-settings-user>
@@ -1,4 +1,5 @@
1
- import { a as NkFormElement } from './shared/base-AWFyTskN.js';
1
+ import { NkFormElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-model-card name="model" value="gpt" title="Mona Pro" desc="Best for long documents" selected></nk-model-card>
4
5
  // → <div class="nk-model-card selected"><div class="m-radio"></div><div><div class="m-name">…</div><div class="m-desc">…</div></div></div>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-page-actions><span>👤 Ada</span><span>📅 12 May</span><span>🏷️ <nk-tag color="purple">Design</nk-tag></span></nk-page-actions>
4
5
  // → <div class="nk-page-meta">…</div> – the meta row under the title
@@ -1,12 +1,17 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <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
+ // <nk-page-cover slot="cover" src="cover.jpg" position="center 30%"></nk-page-cover> → a picture
6
+ // → <div class="nk-cover"><img src="cover.jpg" alt=""></div>: the picture fills
7
+ // the band, cropped rather than stretched; `position` moves the crop, as
8
+ // Notion's "Reposition" does. The gradient stays underneath while it loads.
5
9
  class NkPageCover extends NkElement {
6
- static get observedAttributes() { return ['src']; }
10
+ static get observedAttributes() { return ['src', 'position']; }
7
11
 
8
12
  render() {
9
13
  this._cover = this.createElement('div', ['nk-cover']);
14
+ this._img = this.createElement('img', [], { alt: '' });
10
15
  this._wrapper.appendChild(this._cover);
11
16
  this._sync();
12
17
  }
@@ -14,14 +19,10 @@ class NkPageCover extends NkElement {
14
19
  _sync() {
15
20
  const src = this.getAttribute('src');
16
21
  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
- }
22
+ this._img.src = src;
23
+ this._img.style.objectPosition = this.getAttribute('position') || '';
24
+ if (!this._img.isConnected) this._cover.appendChild(this._img);
25
+ } else this._img.remove();
25
26
  }
26
27
 
27
28
  onAttributeChanged() { this._sync(); }
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-page-title>NotionKit MVP</nk-page-title> → <h1 class="nk-page-title">
4
5
  // <nk-page-title editable placeholder="Untitled">…</nk-page-title>
@@ -1,6 +1,7 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
- // <nk-page icon="🚀" cover>
4
+ // <nk-page icon="🚀" cover full small>
4
5
  // <nk-page-title>NotionKit MVP</nk-page-title>
5
6
  // <nk-page-actions>…</nk-page-actions>
6
7
  // <p class="lead">…</p>
@@ -12,9 +13,10 @@ import { N as NkElement } from './shared/base-AWFyTskN.js';
12
13
  // The host is display:contents, so .nk-page-scroll is the flex child of
13
14
  // .nk-main that scrolls. `narrow` drops the scroll wrapper for pages that are
14
15
  // 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
+ // here because its ::slotted twin is keyed on the parent (.nk-page). `full` and
17
+ // `small` are Notion's page options – full width, 14px text – set on the page.
16
18
  class NkPage extends NkElement {
17
- static get observedAttributes() { return ['icon', 'cover', 'narrow']; }
19
+ static get observedAttributes() { return ['icon', 'cover', 'narrow', 'full', 'small']; }
18
20
 
19
21
  render() {
20
22
  this._page = this.createElement('div', ['nk-page']);
@@ -51,6 +53,8 @@ class NkPage extends NkElement {
51
53
  // (attribute or a slotted nk-page-cover) the icon sits in the padding.
52
54
  const slotted = this._coverSlot.assignedNodes().some(n => n.nodeType === Node.ELEMENT_NODE || n.data.trim());
53
55
  this._page.classList.toggle('covered', this.getBoolAttr('cover') || slotted);
56
+ this._page.classList.toggle('full', this.getBoolAttr('full'));
57
+ this._page.classList.toggle('small', this.getBoolAttr('small'));
54
58
  }
55
59
 
56
60
  setupEvents() {
@@ -72,6 +76,10 @@ class NkPage extends NkElement {
72
76
  set icon(v) { v == null ? this.removeAttribute('icon') : this.setAttribute('icon', v); }
73
77
  get cover() { return this.getBoolAttr('cover'); }
74
78
  set cover(v) { this.setBoolAttr('cover', v); }
79
+ get full() { return this.getBoolAttr('full'); }
80
+ set full(v) { this.setBoolAttr('full', v); }
81
+ get small() { return this.getBoolAttr('small'); }
82
+ set small(v) { this.setBoolAttr('small', v); }
75
83
  }
76
84
 
77
85
  customElements.define('nk-page', NkPage);
@@ -0,0 +1,61 @@
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
3
+
4
+ // <nk-panel title="Weekly review"><p>Three pages changed.</p></nk-panel>
5
+ // <nk-panel href="/roadmap" cover="covers/aurora.svg" icon="🚀" title="Roadmap"><p>2 min ago</p></nk-panel>
6
+ // → <a class="nk-panel" href="/roadmap"><div class="nk-cover"><img src="…" alt=""></div>
7
+ // <div class="nk-page-icon">🚀</div><h3>Roadmap</h3>…</a>
8
+ // `cover` without a value draws the token gradient, with one a picture; `icon`
9
+ // overlaps the cover as on a page – a page tile, as on Notion's Home. With
10
+ // `href` the panel is a link. `title` is the heading, never a tooltip.
11
+ class NkPanel extends NkElement {
12
+ static get observedAttributes() { return ['title', 'icon', 'cover', 'href', 'target']; }
13
+
14
+ render() {
15
+ this._cover = this.createElement('div', ['nk-cover']);
16
+ this._img = this.createElement('img', [], { alt: '' });
17
+ this._icon = this.createElement('div', ['nk-page-icon']);
18
+ this._heading = document.createElement('h3');
19
+ this._slot = document.createElement('slot');
20
+ this.takeTitle();
21
+ this._build();
22
+ }
23
+
24
+ _build() {
25
+ const href = this.getAttribute('href');
26
+ const box = this.createElement(href ? 'a' : 'div', ['nk-panel']);
27
+ box.append(this._cover, this._icon, this._heading, this._slot);
28
+ this._wrapper.replaceChildren(box);
29
+ this._box = box;
30
+ this._sync();
31
+ }
32
+
33
+ _sync() {
34
+ const href = this.getAttribute('href');
35
+ if (href) { this._box.setAttribute('href', href); this._box.target = this.getAttribute('target') || ''; }
36
+ const cover = this.getAttribute('cover');
37
+ this._cover.style.display = cover === null ? 'none' : '';
38
+ if (cover) { this._img.src = cover; if (!this._img.isConnected) this._cover.appendChild(this._img); }
39
+ else this._img.remove();
40
+ const icon = this.getAttribute('icon');
41
+ this._icon.textContent = icon || '';
42
+ this._icon.style.display = icon ? '' : 'none';
43
+ this._heading.textContent = this._titleText || '';
44
+ this._heading.style.display = this._titleText ? '' : 'none';
45
+ }
46
+
47
+ onAttributeChanged(name, _old, value) {
48
+ if (name === 'title' && !this.takeTitle(value)) return;
49
+ if (name === 'href' && (value === null) !== (this._box.localName === 'div')) { this._build(); return; }
50
+ this._sync();
51
+ }
52
+
53
+ get title() { return this._titleText ?? ''; }
54
+ set title(v) { this._titleText = v == null ? '' : String(v); if (this._initialized) this._sync(); }
55
+ get href() { return this.getAttribute('href'); }
56
+ set href(v) { v == null ? this.removeAttribute('href') : this.setAttribute('href', v); }
57
+ }
58
+
59
+ customElements.define('nk-panel', NkPanel);
60
+
61
+ export { NkPanel };
@@ -0,0 +1,18 @@
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
3
+
4
+ // <nk-panels><nk-panel …></nk-panel><nk-panel …></nk-panel></nk-panels>
5
+ // → <div class="nk-panels">…</div>: columns of at least 200px that share the
6
+ // row and fall to one on a phone. The <nk-panel> hosts are display: contents,
7
+ // so each .nk-panel is a cell of this grid.
8
+ class NkPanels extends NkElement {
9
+ render() {
10
+ const grid = this.createElement('div', ['nk-panels']);
11
+ grid.appendChild(document.createElement('slot'));
12
+ this._wrapper.appendChild(grid);
13
+ }
14
+ }
15
+
16
+ customElements.define('nk-panels', NkPanels);
17
+
18
+ export { NkPanels };
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-pop placement="bottom-start">
4
5
  // <nk-btn slot="trigger" variant="secondary">Options</nk-btn>
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-profile-row avatar="MK"><nk-btn variant="secondary" small>Change photo</nk-btn></nk-profile-row>
4
5
  // → <div class="nk-profile-row"><div class="big-avatar">MK</div>…</div>
@@ -1,9 +1,11 @@
1
- import { N as NkElement } from './shared/base-AWFyTskN.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-progress value="72" label="72%"></nk-progress>
5
+ // <nk-progress value="72" label="72 %" wide></nk-progress> → fills its row, the label stays beside it
4
6
  // The bar is `.nk-progress > i` — a child combinator, so both nodes live here.
5
7
  class NkProgress extends NkElement {
6
- static get observedAttributes() { return ['value', 'max', 'label']; }
8
+ static get observedAttributes() { return ['value', 'max', 'label', 'wide']; }
7
9
 
8
10
  render() {
9
11
  this._bar = this.createElement('span', ['nk-progress'], { role: 'progressbar' });
@@ -22,6 +24,7 @@ class NkProgress extends NkElement {
22
24
  const max = Number(this.getAttribute('max')) || 100;
23
25
  const value = Math.min(max, Math.max(0, Number(this.getAttribute('value')) || 0));
24
26
  this._fill.style.width = `${(value / max) * 100}%`;
27
+ this._bar.classList.toggle('wide', this.getBoolAttr('wide'));
25
28
  this._bar.setAttribute('aria-valuenow', value);
26
29
  this._bar.setAttribute('aria-valuemin', 0);
27
30
  this._bar.setAttribute('aria-valuemax', max);