@jungherz-de/notionkit-elements 1.5.3 → 1.7.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 (115) hide show
  1. package/README.md +27 -18
  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 +31 -4
  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 +3 -2
  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 +16 -6
  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 +59 -37
  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 +27 -3
  36. package/dist/components/nk-menu.js +88 -2
  37. package/dist/components/nk-modal.js +3 -2
  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 +21 -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-sheet.js +97 -0
  58. package/dist/components/nk-sidebar.js +17 -37
  59. package/dist/components/nk-skeleton.js +2 -1
  60. package/dist/components/nk-slider.js +2 -1
  61. package/dist/components/nk-stat.js +2 -1
  62. package/dist/components/nk-stats.js +2 -1
  63. package/dist/components/nk-steps.js +61 -0
  64. package/dist/components/nk-switch.js +2 -1
  65. package/dist/components/nk-synced.js +2 -1
  66. package/dist/components/nk-tab-bar-item.js +2 -1
  67. package/dist/components/nk-tab-bar.js +2 -1
  68. package/dist/components/nk-tab.js +2 -1
  69. package/dist/components/nk-table-view.js +6 -2
  70. package/dist/components/nk-tabs.js +2 -1
  71. package/dist/components/nk-tag.js +2 -1
  72. package/dist/components/nk-template-btn.js +2 -1
  73. package/dist/components/nk-textarea.js +2 -1
  74. package/dist/components/nk-theme-toggle.js +2 -1
  75. package/dist/components/nk-toast.js +2 -1
  76. package/dist/components/nk-todo.js +2 -1
  77. package/dist/components/nk-toggle.js +2 -1
  78. package/dist/components/nk-topbar.js +2 -1
  79. package/dist/components/nk-tree-item.js +2 -1
  80. package/dist/components/nk-tree.js +2 -1
  81. package/dist/components/nk-workspace-switcher.js +2 -1
  82. package/dist/components/shared/avatar-BiqCaHOt.js +19 -0
  83. package/dist/components/shared/{focus-DXrK9oLe.js → focus-BNAChOXO.js} +12 -4
  84. package/dist/components/shared/{property-cell-CtXQpUjQ.js → property-cell-o7N30kAQ.js} +8 -3
  85. package/dist/notionkit-elements.esm.js +759 -120
  86. package/dist/notionkit-elements.js +767 -119
  87. package/dist/notionkit-elements.min.js +1 -1
  88. package/package.json +7 -6
  89. package/src/components/content/nk-progress.js +3 -1
  90. package/src/components/data/nk-ai-msg.js +8 -2
  91. package/src/components/data/nk-comment.js +7 -5
  92. package/src/components/data/nk-database.js +14 -5
  93. package/src/components/data/nk-filter-bar.js +57 -36
  94. package/src/components/data/nk-list-view.js +93 -0
  95. package/src/components/data/nk-table-view.js +2 -0
  96. package/src/components/forms/nk-btn.js +29 -3
  97. package/src/components/overlays/nk-menu-item.js +25 -2
  98. package/src/components/overlays/nk-menu.js +86 -1
  99. package/src/components/overlays/nk-sheet.js +95 -0
  100. package/src/components/page/nk-avatar.js +55 -0
  101. package/src/components/page/nk-member-row.js +8 -5
  102. package/src/components/page/nk-page-cover.js +10 -10
  103. package/src/components/page/nk-page.js +10 -3
  104. package/src/components/page/nk-panel.js +59 -0
  105. package/src/components/page/nk-panels.js +16 -0
  106. package/src/components/page/nk-prop.js +52 -0
  107. package/src/components/page/nk-props.js +19 -0
  108. package/src/components/page/nk-segmented.js +19 -0
  109. package/src/components/page/nk-steps.js +59 -0
  110. package/src/components/shell/nk-sidebar.js +15 -36
  111. package/src/index.js +18 -0
  112. package/src/util/avatar.js +17 -0
  113. package/src/util/focus.js +11 -3
  114. package/src/util/property-cell.js +7 -3
  115. package/dist/components/shared/base-C3eJwHKA.js +0 -366
@@ -1,15 +1,19 @@
1
- import { N as NkElement } from './shared/base-C3eJwHKA.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-btn variant="primary" small>Save</nk-btn> → <button class="nk-btn primary small">
4
5
  // <nk-btn href="/docs" variant="secondary">Docs</nk-btn> → <a class="nk-btn secondary">
5
6
  // <nk-btn variant="topbar">⭐</nk-btn> → <button class="nk-topbar-btn">
7
+ // <nk-btn variant="tool" active>Filter</nk-btn> → <button class="nk-db-tool active"> (slot="tools" of nk-database)
8
+ // <nk-btn variant="sidebar" aria-label="Menu">☰</nk-btn> → <button class="nk-topbar-btn nk-sidebar-toggle">:
9
+ // shown on phones only, it opens the page's <nk-sidebar> as a drawer.
6
10
  //
7
11
  // Modifier classes become attributes. A slotted <svg> is sized by the
8
12
  // `.nk-btn ::slotted(svg)` twin; pass the icon itself, never wrapped.
9
13
  const VARIANTS = ['primary', 'secondary', 'danger', 'danger-solid', 'topbar', 'share'];
10
14
 
11
15
  class NkBtn extends NkElement {
12
- static get observedAttributes() { return ['variant', 'small', 'disabled', 'type', 'href', 'title', 'aria-label']; }
16
+ static get observedAttributes() { return ['variant', 'small', 'disabled', 'type', 'href', 'title', 'aria-label', 'aria-haspopup', 'active']; }
13
17
 
14
18
  render() {
15
19
  this._build();
@@ -37,13 +41,22 @@ class NkBtn extends NkElement {
37
41
  else el.setAttribute('aria-disabled', disabled ? 'true' : 'false');
38
42
  const title = this.getAttribute('title');
39
43
  title ? el.setAttribute('title', title) : el.removeAttribute('title');
40
- const label = this.getAttribute('aria-label');
41
- label ? el.setAttribute('aria-label', label) : el.removeAttribute('aria-label');
44
+ // The accessible name and the popup hint belong on the button itself.
45
+ for (const name of ['aria-label', 'aria-haspopup']) {
46
+ const v = this.getAttribute(name);
47
+ v ? el.setAttribute(name, v) : el.removeAttribute(name);
48
+ }
49
+ if (this.getAttribute('variant') === 'sidebar') el.setAttribute('aria-expanded', this._sidebar()?.open ? 'true' : 'false');
50
+ else el.removeAttribute('aria-expanded');
42
51
  }
43
52
 
53
+ /** The sidebar the ☰ opens: the one in its own app, else the page's. */
54
+ _sidebar() { return this.closest('nk-app')?.querySelector('nk-sidebar') ?? document.querySelector('nk-sidebar'); }
55
+
44
56
  setupEvents() {
45
57
  this._onClick = (e) => {
46
58
  if (this.getBoolAttr('disabled')) { e.preventDefault(); e.stopPropagation(); return; }
59
+ if (this.getAttribute('variant') === 'sidebar') { this._sidebar()?.toggle?.(); return; }
47
60
  // A button inside a shadow root is not a submit button of the outer
48
61
  // form; forward the intent to the form the host sits in.
49
62
  const type = this.getAttribute('type');
@@ -52,11 +65,17 @@ class NkBtn extends NkElement {
52
65
  if (form) type === 'submit' ? form.requestSubmit() : form.reset();
53
66
  }
54
67
  };
68
+ // The ☰ says whether its drawer is open, however the drawer closed.
69
+ this._onToggle = (e) => {
70
+ if (this.getAttribute('variant') === 'sidebar' && e.target === this._sidebar()) this._btn.setAttribute('aria-expanded', e.detail.open ? 'true' : 'false');
71
+ };
55
72
  this._btn.addEventListener('click', this._onClick);
73
+ document.addEventListener('nk-toggle', this._onToggle);
56
74
  }
57
75
 
58
76
  teardownEvents() {
59
77
  this._btn?.removeEventListener('click', this._onClick);
78
+ document.removeEventListener('nk-toggle', this._onToggle);
60
79
  }
61
80
 
62
81
  onAttributeChanged(name) {
@@ -66,7 +85,11 @@ class NkBtn extends NkElement {
66
85
  this._build();
67
86
  break;
68
87
  case 'variant':
88
+ this._btn.className = this._computeClasses().join(' ');
89
+ this._applyState(this._btn);
90
+ break;
69
91
  case 'small':
92
+ case 'active':
70
93
  this._btn.className = this._computeClasses().join(' ');
71
94
  break;
72
95
  case 'type':
@@ -81,6 +104,8 @@ class NkBtn extends NkElement {
81
104
  const variant = this.getAttribute('variant');
82
105
  if (variant === 'topbar') return ['nk-topbar-btn'];
83
106
  if (variant === 'share') return ['nk-topbar-btn', 'nk-share-btn'];
107
+ if (variant === 'sidebar') return ['nk-topbar-btn', 'nk-sidebar-toggle'];
108
+ if (variant === 'tool') return this.getBoolAttr('active') ? ['nk-db-tool', 'active'] : ['nk-db-tool'];
84
109
  const classes = ['nk-btn'];
85
110
  if (VARIANTS.includes(variant)) classes.push(variant);
86
111
  if (this.getBoolAttr('small')) classes.push('small');
@@ -92,6 +117,8 @@ class NkBtn extends NkElement {
92
117
 
93
118
  get variant() { return this.getAttribute('variant'); }
94
119
  set variant(v) { v ? this.setAttribute('variant', v) : this.removeAttribute('variant'); }
120
+ get active() { return this.getBoolAttr('active'); }
121
+ set active(v) { this.setBoolAttr('active', v); }
95
122
  get small() { return this.getBoolAttr('small'); }
96
123
  set small(v) { this.setBoolAttr('small', v); }
97
124
  get disabled() { return this.getBoolAttr('disabled'); }
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-C3eJwHKA.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
4
  // <nk-callout icon="💡">text</nk-callout>
4
5
  // <nk-callout><span slot="icon">📌</span>text</nk-callout>
@@ -1,4 +1,5 @@
1
- import { a as NkFormElement } from './shared/base-C3eJwHKA.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-C3eJwHKA.js';
2
- import { l as lockScroll, i as inertOutside, u as unlockScroll } from './shared/focus-DXrK9oLe.js';
1
+ import { NkElement } from './base.js';
2
+ import { l as lockScroll, i as inertOutside, u as unlockScroll } from './shared/focus-BNAChOXO.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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>
@@ -6,16 +7,24 @@ import { N as NkElement } from './shared/base-C3eJwHKA.js';
6
7
  // </nk-database>
7
8
  // db.columns = [{ key: 'name', label: 'Name', type: 'text', icon: '📄', title: true }, { key: 'status', type: 'select', options: [...] }, …];
8
9
  // db.rows = [{ id: 1, icon: '🧭', name: '…', status: 'done', … }];
9
- // → <div class="nk-database"><div class="nk-db-tabs">…</div>…active view…</div>
10
+ // <nk-btn slot="tools" variant="tool">Filter</nk-btn> … sit right of the tabs,
11
+ // <nk-filter-bar slot="filters"> under them (NotionKit 1.7.0's toolbar).
12
+ // → <div class="nk-database"><div class="nk-db-toolbar"><div class="nk-db-tabs">…</div>
13
+ // <div class="tools">…</div></div>…filters…active view…</div>
10
14
  // The tabs come from the child views; data is pushed into every view.
11
15
  class NkDatabase extends NkElement {
12
16
  static get observedAttributes() { return ['view', 'add-view']; }
13
17
 
14
18
  render() {
15
19
  this._box = this.createElement('div', ['nk-database']);
20
+ this._toolbar = this.createElement('div', ['nk-db-toolbar']);
16
21
  this._tabs = this.createElement('div', ['nk-db-tabs'], { role: 'tablist' });
22
+ const tools = this.createElement('div', ['tools']);
23
+ this._toolsSlot = this.createElement('slot', [], { name: 'tools' });
24
+ tools.appendChild(this._toolsSlot);
25
+ this._toolbar.append(this._tabs, tools);
17
26
  this._slot = document.createElement('slot');
18
- this._box.append(this._tabs, this._slot);
27
+ this._box.append(this._toolbar, this.createElement('slot', [], { name: 'filters' }), this._slot);
19
28
  this._wrapper.appendChild(this._box);
20
29
  this._columns = []; this._rows = [];
21
30
  this._sync();
@@ -43,12 +52,11 @@ class NkDatabase extends NkElement {
43
52
  v.setData?.(this._columns, this._rows);
44
53
  }
45
54
  if (this.getBoolAttr('add-view')) {
46
- const add = this.createElement('span', ['nk-db-tab'], { role: 'button', 'data-add': '', tabindex: '0' });
47
- add.style.color = 'var(--nk-text-tertiary)';
55
+ const add = this.createElement('span', ['nk-db-tab', 'add'], { role: 'button', 'data-add': '', tabindex: '0' });
48
56
  add.textContent = '+';
49
57
  this._tabs.appendChild(add);
50
58
  }
51
- this._tabs.style.display = views.length ? '' : 'none';
59
+ this._toolbar.hidden = !views.length && !this._toolsSlot.assignedElements().length;
52
60
  this._current = current;
53
61
  }
54
62
 
@@ -70,6 +78,7 @@ class NkDatabase extends NkElement {
70
78
  this._tabs.addEventListener('click', this._onClick);
71
79
  this._tabs.addEventListener('keydown', this._onKey);
72
80
  this._slot.addEventListener('slotchange', this._onSlot);
81
+ this._toolsSlot.addEventListener('slotchange', this._onSlot);
73
82
  this._sync();
74
83
  }
75
84
 
@@ -77,6 +86,7 @@ class NkDatabase extends NkElement {
77
86
  this._tabs?.removeEventListener('click', this._onClick);
78
87
  this._tabs?.removeEventListener('keydown', this._onKey);
79
88
  this._slot?.removeEventListener('slotchange', this._onSlot);
89
+ this._toolsSlot?.removeEventListener('slotchange', this._onSlot);
80
90
  }
81
91
 
82
92
  onAttributeChanged(name) {
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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,60 +1,77 @@
1
- import { N as NkElement } from './shared/base-C3eJwHKA.js';
1
+ import { NkElement } from './base.js';
2
+ import '@jungherz-de/notionkit/notionkit-styles.js';
2
3
 
3
- // <nk-filter-bar search placeholder="Search rows …"></nk-filter-bar>
4
- // bar.filters = [{ key: 'status', label: 'Status: Done', value: 'done' }];
5
- // Composed from existing classes (nk-btn secondary small, nk-input, nk-tag):
6
- // filter / sort buttons fire nk-action, chips remove on ×, search and chip
7
- // changes fire nk-change { filters, search }. The wrapper is layout only.
4
+ // <nk-filter-bar add no-filter no-sort></nk-filter-bar>
5
+ // bar.filters = [{ key: 'status', value: 'done', op: 'is-not', label: 'Status: Open' }];
6
+ // The filters in effect as NotionKit's filter pills (1.7.0), no inline style:
7
+ // → <div class="nk-filter-row">[Filter][Sort]
8
+ // <span class="nk-filter-pill active"><button>Status: Open</button><button class="fp-remove">×</button></span>…
9
+ // <button class="nk-filter-pill add">+ Filter</button> …slot… [search]</div>
10
+ // Filter and Sort are .nk-db-tool buttons (hide them with no-filter / no-sort
11
+ // when the database toolbar carries its own); they fire nk-action. A pill's
12
+ // label fires nk-action { action: 'edit', index, filter }, the add pill
13
+ // nk-action { action: 'add' } – each with the clicked button as `anchor`, to
14
+ // open a menu under it. × removes the filter; every change to filters or
15
+ // search fires nk-change { filters, search }. In slot="filters" of
16
+ // <nk-database> the row sits right under the view tabs. The texts are
17
+ // attributes, for other languages: filter-label, sort-label, add-label,
18
+ // remove-label (the × reads "<remove-label>: <pill>") and placeholder.
8
19
  class NkFilterBar extends NkElement {
9
- static get observedAttributes() { return ['search', 'placeholder', 'no-filter', 'no-sort', 'value']; }
20
+ static get observedAttributes() { return ['search', 'placeholder', 'no-filter', 'no-sort', 'value', 'add', 'add-label', 'filter-label', 'sort-label', 'remove-label']; }
10
21
 
11
22
  render() {
12
- this._bar = document.createElement('div');
13
- this._bar.style.cssText = 'display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:8px 0';
14
- this._filterBtn = this.createElement('button', ['nk-btn', 'secondary', 'small'], { type: 'button', 'data-action': 'filter' });
15
- this._filterBtn.textContent = '⚲ Filter';
16
- this._sortBtn = this.createElement('button', ['nk-btn', 'secondary', 'small'], { type: 'button', 'data-action': 'sort' });
17
- this._sortBtn.textContent = '↕ Sort';
18
- this._chips = document.createElement('span');
19
- this._chips.style.cssText = 'display:inline-flex;gap:4px;flex-wrap:wrap';
23
+ this._row = this.createElement('div', ['nk-filter-row']);
24
+ this._filterBtn = this.createElement('button', ['nk-db-tool'], { type: 'button', 'data-action': 'filter' });
25
+ this._sortBtn = this.createElement('button', ['nk-db-tool'], { type: 'button', 'data-action': 'sort' });
26
+ this._pillStart = document.createComment('pills');
27
+ this._addBtn = this.createElement('button', ['nk-filter-pill', 'add'], { type: 'button', 'data-action': 'add' });
20
28
  this._input = this.createElement('input', ['nk-input'], { type: 'search', autocomplete: 'off' });
21
- this._input.style.marginLeft = 'auto';
22
- this._bar.append(this._filterBtn, this._sortBtn, this._chips, document.createElement('slot'), this._input);
23
- this._wrapper.appendChild(this._bar);
29
+ this._row.append(this._filterBtn, this._sortBtn, this._pillStart, this._addBtn, document.createElement('slot'), this._input);
30
+ this._wrapper.appendChild(this._row);
24
31
  this._filters = [];
25
32
  this._sync();
26
33
  }
27
34
 
28
35
  _sync() {
29
- this._filterBtn.style.display = this.getBoolAttr('no-filter') ? 'none' : '';
30
- this._sortBtn.style.display = this.getBoolAttr('no-sort') ? 'none' : '';
31
- this._input.style.display = this.getBoolAttr('search') ? '' : 'none';
36
+ this._filterBtn.textContent = this.getAttribute('filter-label') || 'Filter';
37
+ this._filterBtn.hidden = this.getBoolAttr('no-filter');
38
+ this._filterBtn.classList.toggle('active', this._filters.length > 0);
39
+ this._sortBtn.textContent = this.getAttribute('sort-label') || 'Sort';
40
+ this._sortBtn.hidden = this.getBoolAttr('no-sort');
41
+ this._addBtn.textContent = this.getAttribute('add-label') || '+ Filter';
42
+ this._addBtn.hidden = !this.getBoolAttr('add');
43
+ this._input.hidden = !this.getBoolAttr('search');
32
44
  this._input.placeholder = this.getAttribute('placeholder') || 'Search …';
33
45
  if (this.hasAttribute('value') && this._input.value !== this.getAttribute('value')) this._input.value = this.getAttribute('value');
34
- this._chips.replaceChildren();
35
- this._filters.forEach((f, i) => {
36
- const chip = this.createElement('span', ['nk-tag', f.color || 'blue'], { 'data-index': i });
37
- chip.style.cursor = 'pointer';
38
- chip.textContent = `${f.label ?? `${f.key}: ${f.value}`} ×`;
39
- chip.title = 'Remove filter';
40
- this._chips.appendChild(chip);
41
- });
46
+ for (const old of this._row.querySelectorAll('[data-pill]')) old.remove();
47
+ const removeLabel = this.getAttribute('remove-label') || 'Remove filter';
48
+ this._pillStart.after(...this._filters.map((f, i) => {
49
+ const pill = this.createElement('span', ['nk-filter-pill', 'active'], { 'data-pill': i });
50
+ const label = this.createElement('button', [], { type: 'button', 'data-edit': i });
51
+ label.textContent = f.label ?? `${f.key}: ${f.value}`;
52
+ const remove = this.createElement('button', ['fp-remove'], { type: 'button', 'data-remove': i, 'aria-label': `${removeLabel}: ${label.textContent}` });
53
+ remove.textContent = '×';
54
+ pill.append(label, remove);
55
+ return pill;
56
+ }));
42
57
  }
43
58
 
44
59
  setupEvents() {
45
60
  this._onClick = (e) => {
61
+ const remove = e.target.closest('[data-remove]');
62
+ if (remove) { this._filters.splice(Number(remove.dataset.remove), 1); this._sync(); this._emitChange(); return; }
63
+ const edit = e.target.closest('[data-edit]');
64
+ if (edit) { const index = Number(edit.dataset.edit); this.emit('nk-action', { action: 'edit', index, filter: this._filters[index], anchor: edit }); return; }
46
65
  const btn = e.target.closest('[data-action]');
47
- if (btn) { this.emit('nk-action', { action: btn.dataset.action }); return; }
48
- const chip = e.target.closest('[data-index]');
49
- if (chip) { this._filters.splice(Number(chip.dataset.index), 1); this._sync(); this._emitChange(); }
66
+ if (btn) this.emit('nk-action', { action: btn.dataset.action, anchor: btn });
50
67
  };
51
68
  this._onInput = () => { clearTimeout(this._debounce); this._debounce = setTimeout(() => this._emitChange(), 150); };
52
- this._bar.addEventListener('click', this._onClick);
69
+ this._row.addEventListener('click', this._onClick);
53
70
  this._input.addEventListener('input', this._onInput);
54
71
  }
55
72
 
56
73
  teardownEvents() {
57
- this._bar?.removeEventListener('click', this._onClick);
74
+ this._row?.removeEventListener('click', this._onClick);
58
75
  this._input?.removeEventListener('input', this._onInput);
59
76
  clearTimeout(this._debounce);
60
77
  }
@@ -63,13 +80,18 @@ class NkFilterBar extends NkElement {
63
80
  onAttributeChanged() { this._sync(); }
64
81
 
65
82
  get filters() { return this._filters; }
66
- set filters(v) { this._filters = Array.isArray(v) ? v : []; this._sync(); }
83
+ set filters(v) { this._filters = Array.isArray(v) ? v : []; if (this._row) this._sync(); }
67
84
  get value() { return this._input?.value ?? ''; }
68
85
  set value(v) { if (this._input) this._input.value = v ?? ''; }
69
- /** Applies filters + search to rows: equality per filter key, substring search over every string field. */
86
+ /**
87
+ * Applies filters and search to rows: a filter keeps rows where row[key]
88
+ * equals value – or differs from it with op: 'is-not'; the search looks
89
+ * for the text in every string field (a person's name).
90
+ */
70
91
  apply(rows) {
71
92
  const q = this.value.trim().toLowerCase();
72
- return rows.filter(r => this._filters.every(f => r[f.key] === f.value) && (!q || Object.values(r).some(v => String(typeof v === 'object' && v ? v.name ?? '' : v ?? '').toLowerCase().includes(q))));
93
+ return rows.filter(r => this._filters.every(f => (f.op === 'is-not' ? r[f.key] !== f.value : r[f.key] === f.value))
94
+ && (!q || Object.values(r).some(v => String(typeof v === 'object' && v ? v.name ?? '' : v ?? '').toLowerCase().includes(q))));
73
95
  }
74
96
  }
75
97
 
@@ -1,4 +1,5 @@
1
- import { N as NkElement } from './shared/base-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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-C3eJwHKA.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>