@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.
Files changed (152) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/dist/components/nk-ai-input-row.js +57 -0
  4. package/dist/components/nk-ai-msg.js +67 -0
  5. package/dist/components/nk-ai-thread.js +14 -0
  6. package/dist/components/nk-app.js +23 -0
  7. package/dist/components/nk-avatar-group.js +35 -0
  8. package/dist/components/nk-banner.js +30 -0
  9. package/dist/components/nk-block-host.js +35 -0
  10. package/dist/components/nk-board-view.js +129 -0
  11. package/dist/components/nk-breadcrumb.js +78 -0
  12. package/dist/components/nk-btn.js +107 -0
  13. package/dist/components/nk-callout.js +36 -0
  14. package/dist/components/nk-check.js +77 -0
  15. package/dist/components/nk-cmdk.js +221 -0
  16. package/dist/components/nk-code.js +66 -0
  17. package/dist/components/nk-comment.js +41 -0
  18. package/dist/components/nk-comments.js +57 -0
  19. package/dist/components/nk-danger-zone.js +27 -0
  20. package/dist/components/nk-database.js +100 -0
  21. package/dist/components/nk-divider.js +12 -0
  22. package/dist/components/nk-emoji-picker.js +112 -0
  23. package/dist/components/nk-empty.js +36 -0
  24. package/dist/components/nk-field.js +50 -0
  25. package/dist/components/nk-filter-bar.js +78 -0
  26. package/dist/components/nk-heading.js +29 -0
  27. package/dist/components/nk-input.js +108 -0
  28. package/dist/components/nk-kbd.js +15 -0
  29. package/dist/components/nk-member-list.js +30 -0
  30. package/dist/components/nk-member-row.js +46 -0
  31. package/dist/components/nk-mention.js +31 -0
  32. package/dist/components/nk-menu-item.js +68 -0
  33. package/dist/components/nk-menu.js +45 -0
  34. package/dist/components/nk-modal.js +166 -0
  35. package/dist/components/nk-model-card.js +89 -0
  36. package/dist/components/nk-page-actions.js +15 -0
  37. package/dist/components/nk-page-cover.js +35 -0
  38. package/dist/components/nk-page-title.js +71 -0
  39. package/dist/components/nk-page.js +71 -0
  40. package/dist/components/nk-pop.js +90 -0
  41. package/dist/components/nk-profile-row.js +25 -0
  42. package/dist/components/nk-progress.js +43 -0
  43. package/dist/components/nk-quote.js +32 -0
  44. package/dist/components/nk-radio.js +160 -0
  45. package/dist/components/nk-section-label.js +42 -0
  46. package/dist/components/nk-segmented.js +99 -0
  47. package/dist/components/nk-select.js +127 -0
  48. package/dist/components/nk-settings-pane.js +40 -0
  49. package/dist/components/nk-settings-user.js +36 -0
  50. package/dist/components/nk-sidebar.js +81 -0
  51. package/dist/components/nk-skeleton.js +31 -0
  52. package/dist/components/nk-slider.js +85 -0
  53. package/dist/components/nk-stat.js +40 -0
  54. package/dist/components/nk-stats.js +14 -0
  55. package/dist/components/nk-switch.js +69 -0
  56. package/dist/components/nk-synced.js +26 -0
  57. package/dist/components/nk-tab.js +54 -0
  58. package/dist/components/nk-table-view.js +109 -0
  59. package/dist/components/nk-tabs.js +82 -0
  60. package/dist/components/nk-tag.js +32 -0
  61. package/dist/components/nk-template-btn.js +43 -0
  62. package/dist/components/nk-textarea.js +95 -0
  63. package/dist/components/nk-theme-toggle.js +67 -0
  64. package/dist/components/nk-toast.js +51 -0
  65. package/dist/components/nk-todo.js +73 -0
  66. package/dist/components/nk-toggle.js +56 -0
  67. package/dist/components/nk-topbar.js +24 -0
  68. package/dist/components/nk-tree-item.js +155 -0
  69. package/dist/components/nk-tree.js +123 -0
  70. package/dist/components/nk-workspace-switcher.js +82 -0
  71. package/dist/components/shared/base-6gEcoatG.js +317 -0
  72. package/dist/components/shared/focus-DXrK9oLe.js +47 -0
  73. package/dist/components/shared/property-cell-Dn91uFkf.js +92 -0
  74. package/dist/notionkit-elements.esm.js +4639 -0
  75. package/dist/notionkit-elements.js +4718 -0
  76. package/dist/notionkit-elements.min.js +1 -0
  77. package/package.json +72 -0
  78. package/src/base.js +296 -0
  79. package/src/components/content/nk-callout.js +35 -0
  80. package/src/components/content/nk-code.js +51 -0
  81. package/src/components/content/nk-divider.js +11 -0
  82. package/src/components/content/nk-heading.js +28 -0
  83. package/src/components/content/nk-kbd.js +14 -0
  84. package/src/components/content/nk-progress.js +42 -0
  85. package/src/components/content/nk-quote.js +31 -0
  86. package/src/components/content/nk-tag.js +31 -0
  87. package/src/components/content/nk-todo.js +72 -0
  88. package/src/components/content/nk-toggle.js +55 -0
  89. package/src/components/data/nk-ai-input-row.js +56 -0
  90. package/src/components/data/nk-ai-msg.js +66 -0
  91. package/src/components/data/nk-ai-thread.js +13 -0
  92. package/src/components/data/nk-board-view.js +128 -0
  93. package/src/components/data/nk-comment.js +40 -0
  94. package/src/components/data/nk-comments.js +56 -0
  95. package/src/components/data/nk-database.js +99 -0
  96. package/src/components/data/nk-filter-bar.js +77 -0
  97. package/src/components/data/nk-table-view.js +108 -0
  98. package/src/components/forms/nk-btn.js +106 -0
  99. package/src/components/forms/nk-check.js +76 -0
  100. package/src/components/forms/nk-field.js +49 -0
  101. package/src/components/forms/nk-input.js +107 -0
  102. package/src/components/forms/nk-radio.js +159 -0
  103. package/src/components/forms/nk-select.js +126 -0
  104. package/src/components/forms/nk-slider.js +84 -0
  105. package/src/components/forms/nk-switch.js +68 -0
  106. package/src/components/forms/nk-textarea.js +94 -0
  107. package/src/components/overlays/nk-cmdk.js +201 -0
  108. package/src/components/overlays/nk-emoji-picker.js +94 -0
  109. package/src/components/overlays/nk-menu-item.js +67 -0
  110. package/src/components/overlays/nk-menu.js +44 -0
  111. package/src/components/overlays/nk-modal.js +165 -0
  112. package/src/components/overlays/nk-pop.js +89 -0
  113. package/src/components/overlays/nk-settings-pane.js +39 -0
  114. package/src/components/overlays/nk-settings-user.js +35 -0
  115. package/src/components/overlays/nk-toast.js +50 -0
  116. package/src/components/page/nk-avatar-group.js +34 -0
  117. package/src/components/page/nk-banner.js +29 -0
  118. package/src/components/page/nk-block-host.js +34 -0
  119. package/src/components/page/nk-danger-zone.js +26 -0
  120. package/src/components/page/nk-empty.js +35 -0
  121. package/src/components/page/nk-member-list.js +29 -0
  122. package/src/components/page/nk-member-row.js +45 -0
  123. package/src/components/page/nk-mention.js +30 -0
  124. package/src/components/page/nk-model-card.js +88 -0
  125. package/src/components/page/nk-page-actions.js +14 -0
  126. package/src/components/page/nk-page-cover.js +34 -0
  127. package/src/components/page/nk-page-title.js +70 -0
  128. package/src/components/page/nk-page.js +70 -0
  129. package/src/components/page/nk-profile-row.js +24 -0
  130. package/src/components/page/nk-segmented.js +98 -0
  131. package/src/components/page/nk-skeleton.js +30 -0
  132. package/src/components/page/nk-stat.js +39 -0
  133. package/src/components/page/nk-stats.js +13 -0
  134. package/src/components/page/nk-synced.js +25 -0
  135. package/src/components/page/nk-tab.js +53 -0
  136. package/src/components/page/nk-tabs.js +81 -0
  137. package/src/components/page/nk-template-btn.js +42 -0
  138. package/src/components/shell/nk-app.js +22 -0
  139. package/src/components/shell/nk-breadcrumb.js +77 -0
  140. package/src/components/shell/nk-section-label.js +41 -0
  141. package/src/components/shell/nk-sidebar.js +80 -0
  142. package/src/components/shell/nk-theme-toggle.js +66 -0
  143. package/src/components/shell/nk-topbar.js +23 -0
  144. package/src/components/shell/nk-tree-item.js +154 -0
  145. package/src/components/shell/nk-tree.js +122 -0
  146. package/src/components/shell/nk-workspace-switcher.js +81 -0
  147. package/src/index.js +88 -0
  148. package/src/util/emoji.js +17 -0
  149. package/src/util/focus.js +45 -0
  150. package/src/util/fuzzy.js +19 -0
  151. package/src/util/highlight.js +14 -0
  152. package/src/util/property-cell.js +90 -0
@@ -0,0 +1,107 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-btn variant="primary" small>Save</nk-btn> → <button class="nk-btn primary small">
4
+ // <nk-btn href="/docs" variant="secondary">Docs</nk-btn> → <a class="nk-btn secondary">
5
+ // <nk-btn variant="topbar">⭐</nk-btn> → <button class="nk-topbar-btn">
6
+ //
7
+ // Modifier classes become attributes. A slotted <svg> is sized by the
8
+ // `.nk-btn ::slotted(svg)` twin; pass the icon itself, never wrapped.
9
+ const VARIANTS = ['primary', 'secondary', 'danger', 'danger-solid', 'topbar', 'share'];
10
+
11
+ class NkBtn extends NkElement {
12
+ static get observedAttributes() { return ['variant', 'small', 'disabled', 'type', 'href', 'title', 'aria-label']; }
13
+
14
+ render() {
15
+ this._build();
16
+ }
17
+
18
+ _build() {
19
+ const href = this.getAttribute('href');
20
+ const el = href ? this.createElement('a', [], { href }) : this.createElement('button', [], { type: this.getAttribute('type') || 'button' });
21
+ el.className = this._computeClasses().join(' ');
22
+ el.appendChild(document.createElement('slot'));
23
+ this._applyState(el);
24
+ if (this._btn) {
25
+ this._btn.removeEventListener('click', this._onClick);
26
+ this._btn.replaceWith(el);
27
+ } else {
28
+ this._wrapper.appendChild(el);
29
+ }
30
+ this._btn = el;
31
+ if (this._onClick) el.addEventListener('click', this._onClick);
32
+ }
33
+
34
+ _applyState(el) {
35
+ const disabled = this.getBoolAttr('disabled');
36
+ if (el.tagName === 'BUTTON') el.disabled = disabled;
37
+ else el.setAttribute('aria-disabled', disabled ? 'true' : 'false');
38
+ const title = this.getAttribute('title');
39
+ 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');
42
+ }
43
+
44
+ setupEvents() {
45
+ this._onClick = (e) => {
46
+ if (this.getBoolAttr('disabled')) { e.preventDefault(); e.stopPropagation(); return; }
47
+ // A button inside a shadow root is not a submit button of the outer
48
+ // form; forward the intent to the form the host sits in.
49
+ const type = this.getAttribute('type');
50
+ if (type === 'submit' || type === 'reset') {
51
+ const form = this.closest('form');
52
+ if (form) type === 'submit' ? form.requestSubmit() : form.reset();
53
+ }
54
+ };
55
+ this._btn.addEventListener('click', this._onClick);
56
+ }
57
+
58
+ teardownEvents() {
59
+ this._btn?.removeEventListener('click', this._onClick);
60
+ }
61
+
62
+ onAttributeChanged(name) {
63
+ if (!this._btn) return;
64
+ switch (name) {
65
+ case 'href':
66
+ this._build();
67
+ break;
68
+ case 'variant':
69
+ case 'small':
70
+ this._btn.className = this._computeClasses().join(' ');
71
+ break;
72
+ case 'type':
73
+ if (this._btn.tagName === 'BUTTON') this._btn.setAttribute('type', this.getAttribute('type') || 'button');
74
+ break;
75
+ default:
76
+ this._applyState(this._btn);
77
+ }
78
+ }
79
+
80
+ _computeClasses() {
81
+ const variant = this.getAttribute('variant');
82
+ if (variant === 'topbar') return ['nk-topbar-btn'];
83
+ if (variant === 'share') return ['nk-topbar-btn', 'nk-share-btn'];
84
+ const classes = ['nk-btn'];
85
+ if (VARIANTS.includes(variant)) classes.push(variant);
86
+ if (this.getBoolAttr('small')) classes.push('small');
87
+ return classes;
88
+ }
89
+
90
+ focus(options) { this._btn?.focus(options); }
91
+ click() { this._btn?.click(); }
92
+
93
+ get variant() { return this.getAttribute('variant'); }
94
+ set variant(v) { v ? this.setAttribute('variant', v) : this.removeAttribute('variant'); }
95
+ get small() { return this.getBoolAttr('small'); }
96
+ set small(v) { this.setBoolAttr('small', v); }
97
+ get disabled() { return this.getBoolAttr('disabled'); }
98
+ set disabled(v) { this.setBoolAttr('disabled', v); }
99
+ get type() { return this.getAttribute('type') || 'button'; }
100
+ set type(v) { this.setAttribute('type', v); }
101
+ get href() { return this.getAttribute('href'); }
102
+ set href(v) { v ? this.setAttribute('href', v) : this.removeAttribute('href'); }
103
+ }
104
+
105
+ customElements.define('nk-btn', NkBtn);
106
+
107
+ export { NkBtn };
@@ -0,0 +1,36 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-callout icon="💡">text</nk-callout>
4
+ // <nk-callout><span slot="icon">📌</span>text</nk-callout>
5
+ // The .c-icon rule has ::slotted(.c-icon) / ::slotted([slot="icon"]) twins in
6
+ // notionkit.css – those size an icon passed in from the light DOM. Pass the
7
+ // icon node itself, never wrapped: ::slotted() only matches the assigned node.
8
+ class NkCallout extends NkElement {
9
+ static get observedAttributes() { return ['icon']; }
10
+
11
+ render() {
12
+ this._box = this.createElement('div', ['nk-callout']);
13
+ this._icon = this.createElement('span', ['c-icon']);
14
+ const iconSlot = this.createElement('slot', [], { name: 'icon' });
15
+ iconSlot.appendChild(this._icon);
16
+ const body = document.createElement('div');
17
+ body.appendChild(document.createElement('slot'));
18
+ this._box.appendChild(iconSlot);
19
+ this._box.appendChild(body);
20
+ this._wrapper.appendChild(this._box);
21
+ this._syncIcon();
22
+ }
23
+
24
+ onAttributeChanged() { this._syncIcon(); }
25
+
26
+ _syncIcon() {
27
+ if (this._icon) this._icon.textContent = this.getAttribute('icon') || '💡';
28
+ }
29
+
30
+ get icon() { return this.getAttribute('icon'); }
31
+ set icon(v) { v == null ? this.removeAttribute('icon') : this.setAttribute('icon', v); }
32
+ }
33
+
34
+ customElements.define('nk-callout', NkCallout);
35
+
36
+ export { NkCallout };
@@ -0,0 +1,77 @@
1
+ import { a as NkFormElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-check name="mail" value="weekly" checked>Weekly digest</nk-check>
4
+ // → <label class="nk-check"><input type="checkbox"> Weekly digest</label>
5
+ class NkCheck extends NkFormElement {
6
+ static get observedAttributes() { return ['checked', 'disabled', 'name', 'value', 'indeterminate', 'required']; }
7
+
8
+ render() {
9
+ const label = this.createElement('label', ['nk-check']);
10
+ this._input = this.createElement('input', [], { type: 'checkbox' });
11
+ this._apply();
12
+ this._defaultChecked = this.getBoolAttr('checked');
13
+ label.appendChild(this._input);
14
+ label.appendChild(document.createElement('slot'));
15
+ this._wrapper.appendChild(label);
16
+ this._syncFormValue();
17
+ }
18
+
19
+ _apply() {
20
+ this._input.checked = this.getBoolAttr('checked');
21
+ this._input.indeterminate = this.getBoolAttr('indeterminate');
22
+ this._input.disabled = this.getBoolAttr('disabled') || !!this._formDisabled;
23
+ this._input.required = this.getBoolAttr('required');
24
+ const name = this.getAttribute('name');
25
+ name ? this._input.setAttribute('name', name) : this._input.removeAttribute('name');
26
+ this._input.value = this.getAttribute('value') || 'on';
27
+ }
28
+
29
+ setupEvents() {
30
+ this._onChange = () => {
31
+ this._syncing = true;
32
+ this.setBoolAttr('checked', this._input.checked);
33
+ this.removeAttribute('indeterminate');
34
+ this._syncing = false;
35
+ this._syncFormValue();
36
+ this.emit('nk-change', { checked: this._input.checked, value: this._input.value, name: this.name });
37
+ this.dispatchEvent(new Event('change', { bubbles: true }));
38
+ };
39
+ this._input.addEventListener('change', this._onChange);
40
+ }
41
+
42
+ teardownEvents() {
43
+ this._input?.removeEventListener('change', this._onChange);
44
+ }
45
+
46
+ onAttributeChanged() {
47
+ if (this._syncing || !this._input) return;
48
+ this._apply();
49
+ this._syncFormValue();
50
+ }
51
+
52
+ _syncFormValue() {
53
+ this.setFormValue(this._input.checked ? this._input.value : null, this._input.checked ? 'checked' : 'unchecked');
54
+ this.syncValidityFrom(this._input);
55
+ }
56
+
57
+ resetValue() { this.setBoolAttr('checked', this._defaultChecked); }
58
+ restoreValue(state) { if (state === 'checked' || state === 'unchecked') this.setBoolAttr('checked', state === 'checked'); }
59
+ onFormDisabled(disabled) { this._formDisabled = disabled; this._apply(); }
60
+
61
+ focus(options) { this._input?.focus(options); }
62
+
63
+ get checked() { return this._input?.checked ?? this.getBoolAttr('checked'); }
64
+ set checked(v) { this.setBoolAttr('checked', v); }
65
+ get indeterminate() { return this.getBoolAttr('indeterminate'); }
66
+ set indeterminate(v) { this.setBoolAttr('indeterminate', v); }
67
+ get disabled() { return this.getBoolAttr('disabled'); }
68
+ set disabled(v) { this.setBoolAttr('disabled', v); }
69
+ get name() { return this.getAttribute('name'); }
70
+ set name(v) { this.setAttribute('name', v); }
71
+ get value() { return this.getAttribute('value') || 'on'; }
72
+ set value(v) { this.setAttribute('value', v); }
73
+ }
74
+
75
+ customElements.define('nk-check', NkCheck);
76
+
77
+ export { NkCheck };
@@ -0,0 +1,221 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+ import { l as lockScroll, i as inertOutside, u as unlockScroll } from './shared/focus-DXrK9oLe.js';
3
+
4
+ // Subsequence scoring for the command palette: every query character must
5
+ // appear in order; consecutive hits, word starts and an early first hit score
6
+ // higher. Returns 0 when the query does not match.
7
+ function fuzzyScore(query, text) {
8
+ const q = query.trim().toLowerCase();
9
+ if (!q) return 1;
10
+ const t = text.toLowerCase();
11
+ let ti = 0, score = 0, streak = 0;
12
+ for (let qi = 0; qi < q.length; qi++) {
13
+ const idx = t.indexOf(q[qi], ti);
14
+ if (idx < 0) return 0;
15
+ const wordStart = idx === 0 || /[\s\-_/.]/.test(t[idx - 1]);
16
+ streak = idx === ti ? streak + 1 : 0;
17
+ score += 1 + streak * 2 + (wordStart ? 3 : 0) - Math.min(3, (idx - ti) * 0.25);
18
+ ti = idx + 1;
19
+ }
20
+ if (t.startsWith(q)) score += 10;
21
+ return Math.max(0.01, score / (1 + t.length / 40));
22
+ }
23
+
24
+ // <nk-cmdk id="palette" placeholder="Search or type a command …"></nk-cmdk>
25
+ // palette.commands = [
26
+ // { group: 'Pages', items: [{ id: 'mvp', icon: '🚀', label: 'NotionKit MVP', keywords: 'project' }] },
27
+ // { group: 'Actions', items: [{ id: 'theme', icon: '🌙', label: 'Toggle theme', shortcut: '⌘⇧L', action: () => … }] },
28
+ // ];
29
+ // → <div class="nk-cmdk-backdrop open"><div class="nk-cmdk"><div class="nk-cmdk-input-row">🔍<input><kbd>esc</kbd></div>
30
+ // <div class="nk-cmdk-list">…</div><div class="nk-cmdk-footer">…</div></div></div>
31
+ // mod+K toggles (attribute `hotkey`), ↑↓ move, Enter picks (nk-command +
32
+ // item.action), Escape / backdrop close. Fuzzy subsequence search over
33
+ // label + keywords. Put the element directly under <body>.
34
+ class NkCmdk extends NkElement {
35
+ static get observedAttributes() { return ['open', 'hotkey', 'placeholder']; }
36
+
37
+ render() {
38
+ this._backdrop = this.createElement('div', ['nk-cmdk-backdrop']);
39
+ this._backdrop.inert = true;
40
+ this._box = this.createElement('div', ['nk-cmdk'], { role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Command palette' });
41
+ const row = this.createElement('div', ['nk-cmdk-input-row']);
42
+ const glass = document.createElement('span');
43
+ glass.style.fontSize = '15px';
44
+ glass.textContent = '🔍';
45
+ this._input = this.createElement('input', [], { type: 'text', autocomplete: 'off', role: 'combobox', 'aria-expanded': 'true', 'aria-autocomplete': 'list' });
46
+ const esc = this.createElement('kbd', ['nk-kbd']);
47
+ esc.textContent = 'esc';
48
+ row.append(glass, this._input, esc);
49
+ this._list = this.createElement('div', ['nk-cmdk-list'], { role: 'listbox', id: 'list' });
50
+ this._input.setAttribute('aria-controls', 'list');
51
+ this._footer = this.createElement('div', ['nk-cmdk-footer']);
52
+ const footerSlot = this.createElement('slot', [], { name: 'footer' });
53
+ footerSlot.innerHTML = '<span><kbd class="nk-kbd">↑</kbd><kbd class="nk-kbd">↓</kbd> navigate</span><span><kbd class="nk-kbd">↵</kbd> open</span><span><kbd class="nk-kbd">⌘</kbd><kbd class="nk-kbd">K</kbd> toggle</span>';
54
+ this._footer.appendChild(footerSlot);
55
+ this._box.append(row, this._list, this._footer);
56
+ this._backdrop.appendChild(this._box);
57
+ this._wrapper.appendChild(this._backdrop);
58
+ this._commands = [];
59
+ this._index = 0;
60
+ this._syncAttrs();
61
+ this._renderList();
62
+ this._syncOpen();
63
+ }
64
+
65
+ _syncAttrs() { this._input.placeholder = this.getAttribute('placeholder') || 'Search or type a command …'; }
66
+
67
+ /** Filtered, flat list in group order, best matches first within a group. */
68
+ results() {
69
+ const q = this._input?.value ?? '';
70
+ const out = [];
71
+ for (const group of this._commands) {
72
+ const scored = (group.items || []).map(item => ({ item, group: group.group, score: fuzzyScore(q, `${item.label} ${item.keywords || ''}`) })).filter(r => r.score > 0);
73
+ scored.sort((a, b) => b.score - a.score);
74
+ out.push(...scored);
75
+ }
76
+ return out;
77
+ }
78
+
79
+ _renderList() {
80
+ if (!this._list) return;
81
+ const results = this.results();
82
+ this._index = Math.min(this._index, Math.max(0, results.length - 1));
83
+ this._list.replaceChildren();
84
+ if (!results.length) {
85
+ const empty = this.createElement('div', ['nk-cmdk-empty']);
86
+ empty.textContent = `No results for “${this._input.value}”`;
87
+ this._list.appendChild(empty);
88
+ this._input.removeAttribute('aria-activedescendant');
89
+ return;
90
+ }
91
+ let lastGroup = null;
92
+ results.forEach((r, i) => {
93
+ if (r.group && r.group !== lastGroup) {
94
+ const g = this.createElement('div', ['nk-cmdk-group']);
95
+ g.textContent = r.group;
96
+ this._list.appendChild(g);
97
+ lastGroup = r.group;
98
+ }
99
+ const el = this.createElement('div', ['nk-cmdk-item'], { role: 'option', id: `opt-${i}`, 'data-index': i });
100
+ el.classList.toggle('selected', i === this._index);
101
+ el.setAttribute('aria-selected', i === this._index ? 'true' : 'false');
102
+ const icon = this.createElement('span', ['m-icon']);
103
+ icon.textContent = r.item.icon || '';
104
+ const label = document.createElement('span');
105
+ label.textContent = r.item.label;
106
+ el.append(icon, label);
107
+ if (r.item.shortcut) { const sc = this.createElement('span', ['m-shortcut']); sc.textContent = r.item.shortcut; el.appendChild(sc); }
108
+ this._list.appendChild(el);
109
+ });
110
+ this._input.setAttribute('aria-activedescendant', `opt-${this._index}`);
111
+ }
112
+
113
+ _move(dir) {
114
+ const n = this.results().length;
115
+ if (!n) return;
116
+ this._index = (this._index + dir + n) % n;
117
+ for (const el of this._list.querySelectorAll('.nk-cmdk-item')) {
118
+ const on = Number(el.dataset.index) === this._index;
119
+ el.classList.toggle('selected', on);
120
+ el.setAttribute('aria-selected', on ? 'true' : 'false');
121
+ if (on) el.scrollIntoView({ block: 'nearest' });
122
+ }
123
+ this._input.setAttribute('aria-activedescendant', `opt-${this._index}`);
124
+ }
125
+
126
+ pick(index = this._index) {
127
+ const r = this.results()[index];
128
+ if (!r) return;
129
+ const query = this._input.value;
130
+ this.close();
131
+ const ok = this.emit('nk-command', { id: r.item.id ?? r.item.label, item: r.item, query });
132
+ if (ok && typeof r.item.action === 'function') r.item.action(r.item);
133
+ }
134
+
135
+ _matchesHotkey(e) {
136
+ const spec = (this.getAttribute('hotkey') || 'mod+k').toLowerCase().split('+');
137
+ const key = spec.pop();
138
+ const mod = spec.includes('mod') ? (e.metaKey || e.ctrlKey) : true;
139
+ const shift = spec.includes('shift') ? e.shiftKey : !e.shiftKey;
140
+ const alt = spec.includes('alt') ? e.altKey : !e.altKey;
141
+ return mod && shift && alt && e.key.toLowerCase() === key;
142
+ }
143
+
144
+ _syncOpen() {
145
+ const open = this.getBoolAttr('open');
146
+ this._backdrop.classList.toggle('open', open);
147
+ this._backdrop.inert = !open;
148
+ if (open === this._wasOpen) return;
149
+ this._wasOpen = open;
150
+ if (open) {
151
+ this._returnFocus = document.activeElement;
152
+ this._input.value = '';
153
+ this._index = 0;
154
+ this._renderList();
155
+ lockScroll();
156
+ this._undoInert = inertOutside(this);
157
+ requestAnimationFrame(() => this._input.focus({ preventScroll: true }));
158
+ } else {
159
+ unlockScroll();
160
+ this._undoInert?.(); this._undoInert = null;
161
+ const back = this._returnFocus;
162
+ if (back && typeof back.focus === 'function' && back.isConnected) back.focus({ preventScroll: true });
163
+ this._returnFocus = null;
164
+ }
165
+ }
166
+
167
+ setupEvents() {
168
+ this._onDocKey = (e) => {
169
+ if (this._matchesHotkey(e)) { e.preventDefault(); this.toggle(); return; }
170
+ if (e.key === 'Escape' && this.getBoolAttr('open')) { e.stopPropagation(); this.close(); }
171
+ };
172
+ this._onInput = () => { this._index = 0; this._renderList(); };
173
+ this._onInputKey = (e) => {
174
+ if (e.key === 'ArrowDown') { e.preventDefault(); this._move(1); }
175
+ else if (e.key === 'ArrowUp') { e.preventDefault(); this._move(-1); }
176
+ else if (e.key === 'Enter') { e.preventDefault(); this.pick(); }
177
+ };
178
+ this._onListClick = (e) => { const el = e.target.closest('.nk-cmdk-item'); if (el) this.pick(Number(el.dataset.index)); };
179
+ this._onListMove = (e) => { const el = e.target.closest('.nk-cmdk-item'); if (el && Number(el.dataset.index) !== this._index) { this._index = Number(el.dataset.index); this._move(0); } };
180
+ this._onListDown = (e) => e.preventDefault(); // keep the input focused
181
+ this._onBackdrop = (e) => { if (e.target === this._backdrop) this.close(); };
182
+ document.addEventListener('keydown', this._onDocKey);
183
+ this._input.addEventListener('input', this._onInput);
184
+ this._input.addEventListener('keydown', this._onInputKey);
185
+ this._list.addEventListener('click', this._onListClick);
186
+ this._list.addEventListener('mousemove', this._onListMove);
187
+ this._list.addEventListener('mousedown', this._onListDown);
188
+ this._backdrop.addEventListener('click', this._onBackdrop);
189
+ }
190
+
191
+ teardownEvents() {
192
+ document.removeEventListener('keydown', this._onDocKey);
193
+ this._input?.removeEventListener('input', this._onInput);
194
+ this._input?.removeEventListener('keydown', this._onInputKey);
195
+ this._list?.removeEventListener('click', this._onListClick);
196
+ this._list?.removeEventListener('mousemove', this._onListMove);
197
+ this._list?.removeEventListener('mousedown', this._onListDown);
198
+ this._backdrop?.removeEventListener('click', this._onBackdrop);
199
+ if (this._wasOpen) { unlockScroll(); this._undoInert?.(); this._undoInert = null; this._wasOpen = false; }
200
+ }
201
+
202
+ onAttributeChanged(name) {
203
+ if (name === 'open') { this._syncOpen(); this.emit('nk-toggle', { open: this.getBoolAttr('open') }); }
204
+ else this._syncAttrs();
205
+ }
206
+
207
+ show() { this.setBoolAttr('open', true); }
208
+ close() { this.setBoolAttr('open', false); }
209
+ toggle() { this.setBoolAttr('open', !this.getBoolAttr('open')); }
210
+
211
+ get commands() { return this._commands; }
212
+ set commands(list) { this._commands = Array.isArray(list) ? list : []; this._index = 0; this._renderList(); }
213
+ get open() { return this.getBoolAttr('open'); }
214
+ set open(v) { this.setBoolAttr('open', v); }
215
+ get query() { return this._input?.value ?? ''; }
216
+ set query(v) { if (this._input) { this._input.value = v; this._index = 0; this._renderList(); } }
217
+ }
218
+
219
+ customElements.define('nk-cmdk', NkCmdk);
220
+
221
+ export { NkCmdk };
@@ -0,0 +1,66 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // Minimal HTML highlighter emitting the .tag / .attr hooks .nk-code styles.
4
+ // Mirrors tools/highlight.mjs in the NotionKit foundation – no third-party
5
+ // highlighter, no other languages. Anything that is not HTML is escaped only.
6
+ function escapeHtml(s) {
7
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
8
+ }
9
+
10
+ function highlightHtml(source) {
11
+ return escapeHtml(source).replace(/&lt;(\/?)([a-zA-Z][\w-]*)([^&]*?)(\/?)&gt;/g, (m, close, tag, attrs, self) => {
12
+ const attrHtml = attrs.replace(/([\w-]+)(=)("[^"]*"|'[^']*')?/g,
13
+ (a, name, eq, val) => `<span class="attr">${name}</span>${eq}${val ?? ''}`);
14
+ return `&lt;${close}<span class="tag">${tag}</span>${attrHtml}${self}&gt;`;
15
+ });
16
+ }
17
+
18
+ // <nk-code lang="html">&lt;div class="nk-btn"&gt;…</nk-code>
19
+ // The block keeps whatever whitespace it is given (white-space: pre). Text
20
+ // content is slotted as-is; add `highlight` to colour HTML tags/attributes,
21
+ // which copies the text into the shadow tree (light DOM is then ignored).
22
+ class NkCode extends NkElement {
23
+ static get observedAttributes() { return ['lang', 'highlight']; }
24
+ static get observesLightDom() { return true; }
25
+
26
+ render() {
27
+ this._box = this.createElement('div', ['nk-code']);
28
+ this._lang = this.createElement('span', ['lang']);
29
+ this._slot = document.createElement('slot');
30
+ this._pre = this.createElement('span', ['hl']);
31
+ this._box.appendChild(this._lang);
32
+ this._box.appendChild(this._slot);
33
+ this._box.appendChild(this._pre);
34
+ this._wrapper.appendChild(this._box);
35
+ this._sync();
36
+ this.projectLightDom();
37
+ }
38
+
39
+ onAttributeChanged() { this._sync(); this.projectLightDom(); }
40
+
41
+ _sync() {
42
+ if (!this._lang) return;
43
+ const lang = this.getAttribute('lang');
44
+ this._lang.textContent = lang || '';
45
+ this._lang.style.display = lang ? '' : 'none';
46
+ }
47
+
48
+ projectLightDom() {
49
+ if (!this._pre) return;
50
+ const highlight = this.hasAttribute('highlight');
51
+ this._slot.style.display = highlight ? 'none' : '';
52
+ this._pre.style.display = highlight ? '' : 'none';
53
+ if (!highlight) return;
54
+ const text = this.textContent.replace(/^\n/, '');
55
+ if (text === this._lastText) return;
56
+ this._lastText = text;
57
+ this._pre.innerHTML = highlightHtml(text);
58
+ }
59
+
60
+ get lang() { return this.getAttribute('lang'); }
61
+ set lang(v) { v == null ? this.removeAttribute('lang') : this.setAttribute('lang', v); }
62
+ }
63
+
64
+ customElements.define('nk-code', NkCode);
65
+
66
+ export { NkCode };
@@ -0,0 +1,41 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-comment author="Sara Lindt" time="1 hr ago" avatar="SL" color="#448361">The board view feels close.</nk-comment>
4
+ // → <div class="nk-comment"><span class="mini-avatar">SL</span><div><div class="c-head"><b>Sara Lindt</b> · 1 hr ago</div><div class="c-body">…</div></div></div>
5
+ // slot="head" adds content after the name (e.g. an <nk-tag>).
6
+ class NkComment extends NkElement {
7
+ static get observedAttributes() { return ['author', 'time', 'avatar', 'color']; }
8
+
9
+ render() {
10
+ const box = this.createElement('div', ['nk-comment']);
11
+ this._avatar = this.createElement('span', ['mini-avatar']);
12
+ const avatarSlot = this.createElement('slot', [], { name: 'avatar' });
13
+ avatarSlot.appendChild(this._avatar);
14
+ const text = document.createElement('div');
15
+ this._head = this.createElement('div', ['c-head']);
16
+ this._author = document.createElement('b');
17
+ this._time = document.createTextNode('');
18
+ this._head.append(this._author, this.createElement('slot', [], { name: 'head' }), this._time);
19
+ const body = this.createElement('div', ['c-body']);
20
+ body.appendChild(document.createElement('slot'));
21
+ text.append(this._head, body);
22
+ box.append(avatarSlot, text);
23
+ this._wrapper.appendChild(box);
24
+ this._sync();
25
+ }
26
+
27
+ _sync() {
28
+ const author = this.getAttribute('author') || '';
29
+ this._author.textContent = author;
30
+ const time = this.getAttribute('time');
31
+ this._time.data = time ? ` · ${time}` : '';
32
+ this._avatar.textContent = this.getAttribute('avatar') || author.split(/\s+/).map(w => w[0]).join('').slice(0, 2).toUpperCase();
33
+ this._avatar.style.background = this.getAttribute('color') || 'var(--nk-text-tertiary)';
34
+ }
35
+
36
+ onAttributeChanged() { this._sync(); }
37
+ }
38
+
39
+ customElements.define('nk-comment', NkComment);
40
+
41
+ export { NkComment };
@@ -0,0 +1,57 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-comments placeholder="Comment …" send-label="Send">
4
+ // <nk-comment …>…</nk-comment>
5
+ // </nk-comments>
6
+ // → <div class="nk-comments">…<div class="nk-comment-input"><input class="nk-input"><button class="nk-btn primary small">Send</button></div></div>
7
+ // Enter or the button fires nk-submit { text } and clears the field.
8
+ class NkComments extends NkElement {
9
+ static get observedAttributes() { return ['placeholder', 'send-label', 'no-input', 'disabled']; }
10
+
11
+ render() {
12
+ const box = this.createElement('div', ['nk-comments']);
13
+ box.appendChild(document.createElement('slot'));
14
+ this._row = this.createElement('div', ['nk-comment-input']);
15
+ this._input = this.createElement('input', ['nk-input'], { type: 'text', 'aria-label': 'Comment' });
16
+ this._btn = this.createElement('button', ['nk-btn', 'primary', 'small'], { type: 'button' });
17
+ this._row.append(this._input, this._btn);
18
+ box.appendChild(this._row);
19
+ this._wrapper.appendChild(box);
20
+ this._sync();
21
+ }
22
+
23
+ _sync() {
24
+ this._input.placeholder = this.getAttribute('placeholder') || 'Comment …';
25
+ this._btn.textContent = this.getAttribute('send-label') || 'Send';
26
+ this._row.style.display = this.getBoolAttr('no-input') ? 'none' : '';
27
+ this._input.disabled = this._btn.disabled = this.getBoolAttr('disabled');
28
+ }
29
+
30
+ setupEvents() {
31
+ this._onSend = () => this.submit();
32
+ this._onKey = (e) => { if (e.key === 'Enter') { e.preventDefault(); this.submit(); } };
33
+ this._btn.addEventListener('click', this._onSend);
34
+ this._input.addEventListener('keydown', this._onKey);
35
+ }
36
+
37
+ teardownEvents() {
38
+ this._btn?.removeEventListener('click', this._onSend);
39
+ this._input?.removeEventListener('keydown', this._onKey);
40
+ }
41
+
42
+ submit() {
43
+ const text = this._input.value.trim();
44
+ if (!text) return;
45
+ const ok = this.emit('nk-submit', { text });
46
+ if (ok) this._input.value = '';
47
+ }
48
+
49
+ onAttributeChanged() { this._sync(); }
50
+ focus(o) { this._input?.focus(o); }
51
+ get value() { return this._input?.value ?? ''; }
52
+ set value(v) { if (this._input) this._input.value = v ?? ''; }
53
+ }
54
+
55
+ customElements.define('nk-comments', NkComments);
56
+
57
+ export { NkComments };
@@ -0,0 +1,27 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-danger-zone title="Danger zone">…</nk-danger-zone>
4
+ // → <div class="nk-danger-zone"><div class="dz-title">Danger zone</div>…</div>
5
+ class NkDangerZone extends NkElement {
6
+ static get observedAttributes() { return ['title']; }
7
+
8
+ render() {
9
+ const box = this.createElement('div', ['nk-danger-zone']);
10
+ this._title = this.createElement('div', ['dz-title']);
11
+ box.appendChild(this._title);
12
+ box.appendChild(document.createElement('slot'));
13
+ this._wrapper.appendChild(box);
14
+ this._sync();
15
+ }
16
+
17
+ _sync() {
18
+ const t = this.getAttribute('title');
19
+ this._title.textContent = t || '';
20
+ this._title.style.display = t ? '' : 'none';
21
+ }
22
+ onAttributeChanged() { this._sync(); }
23
+ }
24
+
25
+ customElements.define('nk-danger-zone', NkDangerZone);
26
+
27
+ export { NkDangerZone };