@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,43 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-template-btn icon="📅" value="week-plan">Insert week plan</nk-template-btn>
4
+ // → <button class="nk-template-btn">📅 Insert week plan</button>
5
+ class NkTemplateBtn extends NkElement {
6
+ static get observedAttributes() { return ['icon', 'value', 'disabled']; }
7
+
8
+ render() {
9
+ this._btn = this.createElement('button', ['nk-template-btn'], { type: 'button' });
10
+ // One flex item: "📅 Insert week plan" is a single text run in the class
11
+ // markup, so icon, space and label must not become separate flex items.
12
+ const run = document.createElement('span');
13
+ this._icon = document.createElement('span');
14
+ this._space = document.createTextNode(' ');
15
+ run.append(this._icon, this._space, document.createElement('slot'));
16
+ this._btn.appendChild(run);
17
+ this._wrapper.appendChild(this._btn);
18
+ this._sync();
19
+ }
20
+
21
+ _sync() {
22
+ const icon = this.getAttribute('icon');
23
+ this._icon.textContent = icon || '';
24
+ this._space.data = icon ? ' ' : '';
25
+ this._btn.disabled = this.getBoolAttr('disabled');
26
+ }
27
+
28
+ setupEvents() {
29
+ this._onClick = () => this.emit('nk-select', { value: this.value, label: this.textContent.trim() });
30
+ this._btn.addEventListener('click', this._onClick);
31
+ }
32
+
33
+ teardownEvents() { this._btn?.removeEventListener('click', this._onClick); }
34
+ onAttributeChanged() { this._sync(); }
35
+ focus(o) { this._btn?.focus(o); }
36
+
37
+ get value() { return this.getAttribute('value') ?? this.textContent.trim(); }
38
+ set value(v) { this.setAttribute('value', v); }
39
+ }
40
+
41
+ customElements.define('nk-template-btn', NkTemplateBtn);
42
+
43
+ export { NkTemplateBtn };
@@ -0,0 +1,95 @@
1
+ import { a as NkFormElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-textarea name="bio" rows="3" wide>Text</nk-textarea> → <textarea class="nk-textarea wide">
4
+ // The initial value is the `value` attribute, or the element's text content.
5
+ const FORWARDED = ['placeholder', 'name', 'rows', 'maxlength', 'minlength', 'autocomplete', 'aria-label'];
6
+ const BOOLEANS = ['required', 'disabled', 'readonly'];
7
+
8
+ class NkTextarea extends NkFormElement {
9
+ static get observedAttributes() { return ['value', 'wide', ...FORWARDED, ...BOOLEANS]; }
10
+
11
+ render() {
12
+ this._input = this.createElement('textarea', ['nk-textarea']);
13
+ for (const attr of [...FORWARDED, ...BOOLEANS]) this._forward(attr);
14
+ this._input.value = this.getAttribute('value') ?? this.textContent.trim();
15
+ this._syncWide();
16
+ this._wrapper.appendChild(this._input);
17
+ this._syncFormValue();
18
+ }
19
+
20
+ _forward(attr) {
21
+ if (BOOLEANS.includes(attr)) {
22
+ this._input[attr === 'readonly' ? 'readOnly' : attr] = this.getBoolAttr(attr);
23
+ return;
24
+ }
25
+ const v = this.getAttribute(attr);
26
+ if (v === null) this._input.removeAttribute(attr);
27
+ else this._input.setAttribute(attr, v);
28
+ }
29
+
30
+ _syncWide() { this._input.classList.toggle('wide', this.getBoolAttr('wide')); }
31
+
32
+ setupEvents() {
33
+ this._onInput = () => {
34
+ this._syncFormValue();
35
+ this.emit('nk-input', { value: this._input.value, name: this.name });
36
+ };
37
+ this._onChange = () => {
38
+ this._syncFormValue();
39
+ this.emit('nk-change', { value: this._input.value, name: this.name });
40
+ this.dispatchEvent(new Event('change', { bubbles: true }));
41
+ };
42
+ this._input.addEventListener('input', this._onInput);
43
+ this._input.addEventListener('change', this._onChange);
44
+ }
45
+
46
+ teardownEvents() {
47
+ this._input?.removeEventListener('input', this._onInput);
48
+ this._input?.removeEventListener('change', this._onChange);
49
+ }
50
+
51
+ onAttributeChanged(name) {
52
+ if (!this._input) return;
53
+ if (name === 'value') { this._input.value = this.getAttribute('value') ?? ''; this._syncFormValue(); return; }
54
+ if (name === 'wide') { this._syncWide(); return; }
55
+ this._forward(name);
56
+ this._syncFormValue();
57
+ }
58
+
59
+ _syncFormValue() {
60
+ this.setFormValue(this._input.value);
61
+ this.syncValidityFrom(this._input);
62
+ }
63
+
64
+ resetValue() {
65
+ this._input.value = this.getAttribute('value') ?? this.textContent.trim();
66
+ this._syncFormValue();
67
+ }
68
+
69
+ restoreValue(state) {
70
+ if (typeof state === 'string') { this._input.value = state; this._syncFormValue(); }
71
+ }
72
+
73
+ onFormDisabled(disabled) { this._input.disabled = disabled || this.getBoolAttr('disabled'); }
74
+
75
+ focus(options) { this._input?.focus(options); }
76
+ blur() { this._input?.blur(); }
77
+
78
+ get value() { return this._input?.value ?? (this.getAttribute('value') ?? ''); }
79
+ set value(v) {
80
+ if (this._input) { this._input.value = v ?? ''; this._syncFormValue(); }
81
+ else this.setAttribute('value', v ?? '');
82
+ }
83
+ get name() { return this.getAttribute('name'); }
84
+ set name(v) { this.setAttribute('name', v); }
85
+ get disabled() { return this.getBoolAttr('disabled'); }
86
+ set disabled(v) { this.setBoolAttr('disabled', v); }
87
+ get required() { return this.getBoolAttr('required'); }
88
+ set required(v) { this.setBoolAttr('required', v); }
89
+ get wide() { return this.getBoolAttr('wide'); }
90
+ set wide(v) { this.setBoolAttr('wide', v); }
91
+ }
92
+
93
+ customElements.define('nk-textarea', NkTextarea);
94
+
95
+ export { NkTextarea };
@@ -0,0 +1,67 @@
1
+ import { N as NkElement, g as getCurrentTheme } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-theme-toggle slot="actions"></nk-theme-toggle>
4
+ // → <button class="nk-topbar-btn nk-theme-toggle">🌙</button>
5
+ // Flips data-theme on <html> (the only theme source), remembers the choice in
6
+ // localStorage (`storage-key`, default nk-theme), applies a stored or
7
+ // system preference on first connect when <html> carries no theme yet, and
8
+ // accepts postMessage({ nkTheme }) from a parent page (the landing page's
9
+ // embedded demo uses that).
10
+ class NkThemeToggle extends NkElement {
11
+ static get observedAttributes() { return ['storage-key', 'title'];
12
+
13
+ }
14
+
15
+ render() {
16
+ this._btn = this.createElement('button', ['nk-topbar-btn', 'nk-theme-toggle'], { type: 'button' });
17
+ this._btn.title = this.getAttribute('title') || 'Toggle light / dark';
18
+ this._wrapper.appendChild(this._btn);
19
+ const root = document.documentElement;
20
+ let stored = null;
21
+ try { stored = localStorage.getItem(this.storageKey); } catch {}
22
+ if (stored === 'light' || stored === 'dark') root.setAttribute('data-theme', stored);
23
+ else if (!root.hasAttribute('data-theme')) root.setAttribute('data-theme', matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
24
+ this._syncGlyph();
25
+ }
26
+
27
+ _syncGlyph() {
28
+ if (this._btn) this._btn.textContent = getCurrentTheme() === 'dark' ? '☀️' : '🌙';
29
+ }
30
+
31
+ _syncTheme(theme) {
32
+ super._syncTheme(theme);
33
+ this._syncGlyph();
34
+ }
35
+
36
+ setupEvents() {
37
+ this._onClick = () => this.apply(getCurrentTheme() === 'dark' ? 'light' : 'dark');
38
+ this._onMessage = (e) => { const t = e.data?.nkTheme; if (t === 'light' || t === 'dark') this.apply(t); };
39
+ this._btn.addEventListener('click', this._onClick);
40
+ window.addEventListener('message', this._onMessage);
41
+ }
42
+
43
+ teardownEvents() {
44
+ this._btn?.removeEventListener('click', this._onClick);
45
+ window.removeEventListener('message', this._onMessage);
46
+ }
47
+
48
+ onAttributeChanged(name) {
49
+ if (name === 'title' && this._btn) this._btn.title = this.getAttribute('title') || 'Toggle light / dark';
50
+ }
51
+
52
+ /** Sets the theme on <html>, persists it and reports it. */
53
+ apply(theme) {
54
+ document.documentElement.setAttribute('data-theme', theme);
55
+ try { localStorage.setItem(this.storageKey, theme); } catch {}
56
+ this._syncGlyph();
57
+ this.emit('nk-change', { value: theme });
58
+ }
59
+
60
+ get storageKey() { return this.getAttribute('storage-key') || 'nk-theme'; }
61
+ get value() { return getCurrentTheme(); }
62
+ set value(v) { this.apply(v); }
63
+ }
64
+
65
+ customElements.define('nk-theme-toggle', NkThemeToggle);
66
+
67
+ export { NkThemeToggle };
@@ -0,0 +1,51 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-toast id="toast"></nk-toast> … toast.show('Saved');
4
+ // → <div class="nk-toast show">✓ Saved</div>
5
+ // One node, bottom-centred, auto-hides after `duration` ms (default 2200).
6
+ class NkToast extends NkElement {
7
+ static get observedAttributes() { return ['open', 'duration', 'icon']; }
8
+
9
+ render() {
10
+ this._box = this.createElement('div', ['nk-toast'], { role: 'status', 'aria-live': 'polite' });
11
+ this._icon = document.createElement('span');
12
+ this._msg = document.createElement('span');
13
+ this._box.append(this._icon, this._msg, document.createElement('slot'));
14
+ this._wrapper.appendChild(this._box);
15
+ this._sync();
16
+ }
17
+
18
+ _sync() {
19
+ this._box.classList.toggle('show', this.getBoolAttr('open'));
20
+ const icon = this.getAttribute('icon') ?? '✓';
21
+ this._icon.textContent = icon;
22
+ this._icon.style.display = icon ? '' : 'none';
23
+ }
24
+
25
+ /** Shows `message` (or the slotted content when omitted) and hides it again after `duration`. */
26
+ show(message, { duration } = {}) {
27
+ if (message !== undefined) this._msg.textContent = message;
28
+ clearTimeout(this._timer);
29
+ this.setBoolAttr('open', true);
30
+ const ms = duration ?? Number(this.getAttribute('duration')) ?? 2200;
31
+ if (ms > 0) this._timer = setTimeout(() => this.close(), ms || 2200);
32
+ }
33
+
34
+ close() { clearTimeout(this._timer); this.setBoolAttr('open', false); }
35
+
36
+ onAttributeChanged(name) {
37
+ this._sync();
38
+ if (name === 'open') this.emit('nk-toggle', { open: this.getBoolAttr('open') });
39
+ }
40
+
41
+ disconnectedCallback() { super.disconnectedCallback(); clearTimeout(this._timer); }
42
+
43
+ get open() { return this.getBoolAttr('open'); }
44
+ set open(v) { this.setBoolAttr('open', v); }
45
+ get message() { return this._msg?.textContent ?? ''; }
46
+ set message(v) { if (this._msg) this._msg.textContent = v; }
47
+ }
48
+
49
+ customElements.define('nk-toast', NkToast);
50
+
51
+ export { NkToast };
@@ -0,0 +1,73 @@
1
+ import { a as NkFormElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-todo name="task" value="1" checked>Write the docs</nk-todo>
4
+ // → <label class="nk-todo"><input type="checkbox"><span>Write the docs</span></label>
5
+ // `.nk-todo input:checked + span` strikes the text through — both siblings
6
+ // are rendered here so the combinator works; the text is slotted into <span>.
7
+ class NkTodo extends NkFormElement {
8
+ static get observedAttributes() { return ['checked', 'disabled', 'name', 'value']; }
9
+
10
+ render() {
11
+ const label = this.createElement('label', ['nk-todo']);
12
+ this._input = this.createElement('input', [], { type: 'checkbox' });
13
+ this._apply();
14
+ this._defaultChecked = this.getBoolAttr('checked');
15
+ const text = document.createElement('span');
16
+ text.appendChild(document.createElement('slot'));
17
+ label.appendChild(this._input);
18
+ label.appendChild(text);
19
+ this._wrapper.appendChild(label);
20
+ this._syncFormValue();
21
+ }
22
+
23
+ _apply() {
24
+ this._input.checked = this.getBoolAttr('checked');
25
+ this._input.disabled = this.getBoolAttr('disabled') || !!this._formDisabled;
26
+ const name = this.getAttribute('name');
27
+ name ? this._input.setAttribute('name', name) : this._input.removeAttribute('name');
28
+ this._input.value = this.getAttribute('value') || 'on';
29
+ }
30
+
31
+ setupEvents() {
32
+ this._onChange = () => {
33
+ this._syncing = true;
34
+ this.setBoolAttr('checked', this._input.checked);
35
+ this._syncing = false;
36
+ this._syncFormValue();
37
+ this.emit('nk-change', { checked: this._input.checked, value: this._input.value, name: this.name });
38
+ this.dispatchEvent(new Event('change', { bubbles: true }));
39
+ };
40
+ this._input.addEventListener('change', this._onChange);
41
+ }
42
+
43
+ teardownEvents() {
44
+ this._input?.removeEventListener('change', this._onChange);
45
+ }
46
+
47
+ onAttributeChanged() {
48
+ if (this._syncing || !this._input) return;
49
+ this._apply();
50
+ this._syncFormValue();
51
+ }
52
+
53
+ _syncFormValue() {
54
+ this.setFormValue(this._input.checked ? this._input.value : null, this._input.checked ? 'checked' : 'unchecked');
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
+ get checked() { return this._input?.checked ?? this.getBoolAttr('checked'); }
62
+ set checked(v) { this.setBoolAttr('checked', v); }
63
+ get disabled() { return this.getBoolAttr('disabled'); }
64
+ set disabled(v) { this.setBoolAttr('disabled', v); }
65
+ get name() { return this.getAttribute('name'); }
66
+ set name(v) { this.setAttribute('name', v); }
67
+ get value() { return this.getAttribute('value') || 'on'; }
68
+ set value(v) { this.setAttribute('value', v); }
69
+ }
70
+
71
+ customElements.define('nk-todo', NkTodo);
72
+
73
+ export { NkTodo };
@@ -0,0 +1,56 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-toggle label="Details" open>Body…</nk-toggle>
4
+ // → <details class="nk-toggle" open><summary>Details</summary><div class="toggle-body">…</div></details>
5
+ // The summary marker is a pseudo-element and cannot be styled on slotted
6
+ // content, so the summary is rendered here (label attribute or slot="label").
7
+ class NkToggle extends NkElement {
8
+ static get observedAttributes() { return ['label', 'open']; }
9
+
10
+ render() {
11
+ this._details = this.createElement('details', ['nk-toggle']);
12
+ const summary = document.createElement('summary');
13
+ this._label = document.createElement('span');
14
+ const labelSlot = this.createElement('slot', [], { name: 'label' });
15
+ labelSlot.appendChild(this._label);
16
+ summary.appendChild(labelSlot);
17
+ const body = this.createElement('div', ['toggle-body']);
18
+ body.appendChild(document.createElement('slot'));
19
+ this._details.appendChild(summary);
20
+ this._details.appendChild(body);
21
+ this._details.open = this.getBoolAttr('open');
22
+ this._wrapper.appendChild(this._details);
23
+ this._sync();
24
+ }
25
+
26
+ setupEvents() {
27
+ this._onToggle = () => {
28
+ this._syncing = true;
29
+ this.setBoolAttr('open', this._details.open);
30
+ this._syncing = false;
31
+ this.emit('nk-toggle', { open: this._details.open });
32
+ };
33
+ this._details.addEventListener('toggle', this._onToggle);
34
+ }
35
+
36
+ teardownEvents() {
37
+ this._details?.removeEventListener('toggle', this._onToggle);
38
+ }
39
+
40
+ onAttributeChanged(name) {
41
+ if (!this._details) return;
42
+ if (name === 'open') { if (!this._syncing) this._details.open = this.getBoolAttr('open'); return; }
43
+ this._sync();
44
+ }
45
+
46
+ _sync() { this._label.textContent = this.getAttribute('label') || ''; }
47
+
48
+ get open() { return this.getBoolAttr('open'); }
49
+ set open(v) { this.setBoolAttr('open', v); }
50
+ get label() { return this.getAttribute('label'); }
51
+ set label(v) { this.setAttribute('label', v); }
52
+ }
53
+
54
+ customElements.define('nk-toggle', NkToggle);
55
+
56
+ export { NkToggle };
@@ -0,0 +1,24 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-topbar>
4
+ // <nk-breadcrumb>…</nk-breadcrumb>
5
+ // <nk-btn slot="actions" variant="share">Share</nk-btn>
6
+ // <nk-theme-toggle slot="actions"></nk-theme-toggle>
7
+ // </nk-topbar>
8
+ // → <div class="nk-topbar">…<div class="nk-topbar-actions">…</div></div>
9
+ // display:contents host, so the bar is a direct flex child of .nk-main.
10
+ class NkTopbar extends NkElement {
11
+
12
+ render() {
13
+ const bar = this.createElement('div', ['nk-topbar']);
14
+ bar.appendChild(document.createElement('slot'));
15
+ const actions = this.createElement('div', ['nk-topbar-actions']);
16
+ actions.appendChild(this.createElement('slot', [], { name: 'actions' }));
17
+ bar.appendChild(actions);
18
+ this._wrapper.appendChild(bar);
19
+ }
20
+ }
21
+
22
+ customElements.define('nk-topbar', NkTopbar);
23
+
24
+ export { NkTopbar };
@@ -0,0 +1,155 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-tree-item icon="📊" value="overview" open>
4
+ // Project overview
5
+ // <nk-tree-item icon="🚀" value="mvp" active>NotionKit MVP</nk-tree-item>
6
+ // </nk-tree-item>
7
+ //
8
+ // → <div class="nk-tree-item active"><span class="nk-toggle-arrow open">▸</span>
9
+ // <span class="icon">📊</span><span class="label">…</span>
10
+ // <span class="actions"><span>+</span><span>⋯</span></span></div>
11
+ // <div class="nk-tree-children">…</div>
12
+ //
13
+ // Both halves – the row and the .nk-tree-children box that must be its next
14
+ // sibling – are rendered here. Slots are assigned manually: text becomes the
15
+ // label, nested <nk-tree-item>s become the children, `slot="icon"` /
16
+ // `slot="end"` nodes go where they say. The hover actions are shadow-internal
17
+ // (`.nk-tree-item:hover .actions` is a hover-parent rule) and report through
18
+ // nk-action.
19
+ class NkTreeItem extends NkElement {
20
+ static get slotAssignment() { return 'manual'; }
21
+ static get observedAttributes() { return ['label', 'icon', 'value', 'href', 'active', 'open', 'compact', 'no-actions']; }
22
+
23
+ render() {
24
+ this._row = this.createElement('div', ['nk-tree-item'], { role: 'treeitem', tabindex: '-1' });
25
+ this._arrow = this.createElement('span', ['nk-toggle-arrow'], { 'aria-hidden': 'true' });
26
+ this._arrow.textContent = '▸';
27
+ this._iconSlot = this.createElement('slot', [], { name: 'icon' });
28
+ this._icon = this.createElement('span', ['icon']);
29
+ this._iconSlot.appendChild(this._icon);
30
+ this._label = this.createElement('span', ['label']);
31
+ this._labelSlot = this.createElement('slot', [], { name: 'label' });
32
+ this._labelText = document.createTextNode('');
33
+ this._labelSlot.appendChild(this._labelText);
34
+ this._label.appendChild(this._labelSlot);
35
+ this._actions = this.createElement('span', ['actions']);
36
+ for (const [action, glyph, title] of [['add', '+', 'Add'], ['more', '⋯', 'More']]) {
37
+ const b = this.createElement('span', [], { 'data-action': action, role: 'button', title });
38
+ b.textContent = glyph;
39
+ this._actions.appendChild(b);
40
+ }
41
+ this._endSlot = this.createElement('slot', [], { name: 'end' });
42
+ this._row.append(this._arrow, this._iconSlot, this._label, this._actions, this._endSlot);
43
+ this._children = this.createElement('div', ['nk-tree-children'], { role: 'group' });
44
+ this._childSlot = this.createElement('slot', [], { name: 'children' });
45
+ this._children.appendChild(this._childSlot);
46
+ this._wrapper.append(this._row, this._children);
47
+ this._sync();
48
+ this.closest('nk-tree')?.requestSync?.();
49
+ }
50
+
51
+ /** Routes light-DOM nodes: nested items → children, slot="icon"/"end" → there, the rest → label. */
52
+ assignSlots() {
53
+ if (!this._row) return;
54
+ const icon = [], end = [], children = [], label = [];
55
+ for (const node of this.childNodes) {
56
+ if (node.nodeType === Node.ELEMENT_NODE) {
57
+ const slot = node.getAttribute('slot');
58
+ if (slot === 'icon') icon.push(node);
59
+ else if (slot === 'end') end.push(node);
60
+ else if (node.localName === 'nk-tree-item') children.push(node);
61
+ else label.push(node);
62
+ } else if (node.nodeType === Node.TEXT_NODE && node.data.trim()) {
63
+ label.push(node);
64
+ }
65
+ }
66
+ this._iconSlot.assign(...icon);
67
+ this._endSlot.assign(...end);
68
+ this._childSlot.assign(...children);
69
+ this._labelSlot.assign(...(this.hasAttribute('label') ? [] : label));
70
+ this._hasChildren = children.length > 0;
71
+ this._hasEnd = end.length > 0;
72
+ this._sync();
73
+ }
74
+
75
+ _sync() {
76
+ const active = this.getBoolAttr('active'), open = this.getBoolAttr('open');
77
+ this._row.classList.toggle('active', active);
78
+ this._row.classList.toggle('compact', this.getBoolAttr('compact'));
79
+ this._row.setAttribute('aria-selected', active ? 'true' : 'false');
80
+ this._arrow.classList.toggle('open', open);
81
+ this._arrow.style.display = this._hasChildren ? '' : 'none';
82
+ if (this._hasChildren) this._row.setAttribute('aria-expanded', open ? 'true' : 'false');
83
+ else this._row.removeAttribute('aria-expanded');
84
+ this._children.classList.toggle('collapsed', !open || !this._hasChildren);
85
+ this._icon.textContent = this.getAttribute('icon') || '';
86
+ this._icon.style.display = this.getAttribute('icon') ? '' : 'none';
87
+ this._labelText.data = this.getAttribute('label') || '';
88
+ // .actions is display:none until hover (a hover-parent rule); `hidden`
89
+ // would lose to that rule, so the block is detached instead.
90
+ const wantActions = !this.getBoolAttr('no-actions') && !this._hasEnd;
91
+ if (wantActions && !this._actions.isConnected) this._row.insertBefore(this._actions, this._endSlot);
92
+ if (!wantActions && this._actions.isConnected) this._actions.remove();
93
+ const href = this.getAttribute('href');
94
+ href ? this._row.setAttribute('data-href', href) : this._row.removeAttribute('data-href');
95
+ }
96
+
97
+ setupEvents() {
98
+ this._onRowClick = (e) => {
99
+ const action = e.target.closest?.('[data-action]');
100
+ if (action) { e.stopPropagation(); this.emit('nk-action', { action: action.dataset.action, value: this.value, label: this.label }); return; }
101
+ if (e.target === this._arrow || this._arrow.contains(e.target)) { e.stopPropagation(); this.toggle(); return; }
102
+ this.select();
103
+ };
104
+ this._onKey = (e) => {
105
+ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.select(); }
106
+ };
107
+ this._row.addEventListener('click', this._onRowClick);
108
+ this._row.addEventListener('keydown', this._onKey);
109
+ }
110
+
111
+ teardownEvents() {
112
+ this._row?.removeEventListener('click', this._onRowClick);
113
+ this._row?.removeEventListener('keydown', this._onKey);
114
+ }
115
+
116
+ onAttributeChanged(name) {
117
+ if (name === 'label') this.assignSlots();
118
+ else this._sync();
119
+ if (name === 'open') this.emit('nk-toggle', { open: this.getBoolAttr('open'), value: this.value });
120
+ }
121
+
122
+ /** Fires nk-select; a listener may preventDefault to keep the item from becoming active. */
123
+ select() {
124
+ const ok = this.emit('nk-select', { value: this.value, label: this.label, href: this.getAttribute('href'), item: this });
125
+ if (!ok) return;
126
+ const tree = this.closest('nk-tree');
127
+ if (tree) tree.setActive(this);
128
+ else this.setBoolAttr('active', true);
129
+ if (this.getAttribute('href')) location.assign(this.getAttribute('href'));
130
+ }
131
+
132
+ toggle() { this.setBoolAttr('open', !this.getBoolAttr('open')); }
133
+ focus(options) { this._row?.focus(options); }
134
+ /** Roving tabindex, managed by <nk-tree>. */
135
+ setTabbable(v) { if (this._row) this._row.tabIndex = v ? 0 : -1; }
136
+ get tabbable() { return this._row?.tabIndex === 0; }
137
+ get hasChildren() { return !!this._hasChildren; }
138
+
139
+ get label() { return this.getAttribute('label') ?? this._labelSlot?.assignedNodes().map(n => n.textContent).join('').trim() ?? ''; }
140
+ set label(v) { this.setAttribute('label', v); }
141
+ get value() { return this.getAttribute('value') ?? this.label; }
142
+ set value(v) { this.setAttribute('value', v); }
143
+ get icon() { return this.getAttribute('icon'); }
144
+ set icon(v) { v == null ? this.removeAttribute('icon') : this.setAttribute('icon', v); }
145
+ get active() { return this.getBoolAttr('active'); }
146
+ set active(v) { this.setBoolAttr('active', v); }
147
+ get open() { return this.getBoolAttr('open'); }
148
+ set open(v) { this.setBoolAttr('open', v); }
149
+ get compact() { return this.getBoolAttr('compact'); }
150
+ set compact(v) { this.setBoolAttr('compact', v); }
151
+ }
152
+
153
+ customElements.define('nk-tree-item', NkTreeItem);
154
+
155
+ export { NkTreeItem };