@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,36 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-settings-user slot="user" name="Marcel Karas" mail="marcel@…" avatar="MK"></nk-settings-user>
4
+ // → <div class="nk-settings-user"><div class="avatar">MK</div><div class="u-text"><div class="name">…</div><div class="mail">…</div></div></div>
5
+ class NkSettingsUser extends NkElement {
6
+ static get observedAttributes() { return ['name', 'mail', 'avatar']; }
7
+
8
+ render() {
9
+ const box = this.createElement('div', ['nk-settings-user']);
10
+ this._avatar = this.createElement('div', ['avatar']);
11
+ const s = this.createElement('slot', [], { name: 'avatar' });
12
+ s.appendChild(this._avatar);
13
+ const text = this.createElement('div', ['u-text']);
14
+ this._name = this.createElement('div', ['name']);
15
+ this._mail = this.createElement('div', ['mail']);
16
+ text.append(this._name, this._mail);
17
+ box.append(s, text);
18
+ this._wrapper.appendChild(box);
19
+ this._sync();
20
+ }
21
+
22
+ _sync() {
23
+ const name = this.getAttribute('name') || '';
24
+ this._name.textContent = name;
25
+ const mail = this.getAttribute('mail');
26
+ this._mail.textContent = mail || '';
27
+ this._mail.style.display = mail ? '' : 'none';
28
+ this._avatar.textContent = this.getAttribute('avatar') || name.split(/\s+/).map(w => w[0]).join('').slice(0, 2).toUpperCase();
29
+ }
30
+
31
+ onAttributeChanged() { this._sync(); }
32
+ }
33
+
34
+ customElements.define('nk-settings-user', NkSettingsUser);
35
+
36
+ export { NkSettingsUser };
@@ -0,0 +1,81 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-sidebar slot="sidebar">
4
+ // <nk-workspace-switcher slot="workspace" name="Acme"></nk-workspace-switcher>
5
+ // <nk-tree>…</nk-tree>
6
+ // <nk-tree-item slot="footer" icon="⚙️">Settings</nk-tree-item>
7
+ // </nk-sidebar>
8
+ // → <aside class="nk-sidebar"><div class="nk-workspace">…</div>
9
+ // <div class="nk-sidebar-scroll">…</div><div class="nk-sidebar-footer">…</div></aside>
10
+ //
11
+ // The host is display:contents so the <aside> is a direct flex child of
12
+ // .nk-app, exactly like the class markup. Below 860px notionkit.css hides the
13
+ // aside; `open` brings it back as an off-canvas drawer (position only – the
14
+ // look is still the stylesheet's).
15
+ const drawerSheet = new CSSStyleSheet();
16
+ drawerSheet.replaceSync(`
17
+ @media (max-width: 860px) {
18
+ :host([open]) .nk-sidebar { display: flex; position: fixed; inset: 0 auto 0 0; z-index: 60; }
19
+ :host([open]) .nk-sidebar-backdrop { display: block; }
20
+ }
21
+ .nk-sidebar-backdrop { display: none; position: fixed; inset: 0; z-index: 59; background: var(--nk-scrim-soft); }
22
+ `);
23
+
24
+ class NkSidebar extends NkElement {
25
+ static get hostStyles() { return drawerSheet; }
26
+ static get observedAttributes() { return ['open']; }
27
+
28
+ render() {
29
+ this._backdrop = this.createElement('div', ['nk-sidebar-backdrop']);
30
+ this._aside = this.createElement('aside', ['nk-sidebar']);
31
+ this._aside.appendChild(this.createElement('slot', [], { name: 'workspace' }));
32
+ const scroll = this.createElement('div', ['nk-sidebar-scroll']);
33
+ scroll.appendChild(document.createElement('slot'));
34
+ this._aside.appendChild(scroll);
35
+ this._footer = this.createElement('div', ['nk-sidebar-footer']);
36
+ this._footerSlot = this.createElement('slot', [], { name: 'footer' });
37
+ this._footer.appendChild(this._footerSlot);
38
+ this._aside.appendChild(this._footer);
39
+ this._wrapper.appendChild(this._backdrop);
40
+ this._wrapper.appendChild(this._aside);
41
+ this._syncFooter();
42
+ }
43
+
44
+ _syncFooter() {
45
+ const nodes = this._footerSlot.assignedElements();
46
+ // The footer box carries padding and a border; only render it when used.
47
+ this._footer.style.display = nodes.length ? '' : 'none';
48
+ // Footer rows are 26px in the class version (.nk-sidebar-footer .nk-tree-item).
49
+ for (const el of nodes) if (el.localName === 'nk-tree-item') el.setAttribute('compact', '');
50
+ }
51
+
52
+ setupEvents() {
53
+ this._onSlot = () => this._syncFooter();
54
+ this._onBackdrop = () => this.close();
55
+ this._onKey = (e) => { if (e.key === 'Escape' && this.getBoolAttr('open')) this.close(); };
56
+ this._footerSlot.addEventListener('slotchange', this._onSlot);
57
+ this._backdrop.addEventListener('click', this._onBackdrop);
58
+ document.addEventListener('keydown', this._onKey);
59
+ }
60
+
61
+ teardownEvents() {
62
+ this._footerSlot?.removeEventListener('slotchange', this._onSlot);
63
+ this._backdrop?.removeEventListener('click', this._onBackdrop);
64
+ document.removeEventListener('keydown', this._onKey);
65
+ }
66
+
67
+ onAttributeChanged(name) {
68
+ if (name === 'open') this.emit('nk-toggle', { open: this.getBoolAttr('open') });
69
+ }
70
+
71
+ show() { this.setBoolAttr('open', true); }
72
+ close() { this.setBoolAttr('open', false); }
73
+ toggle() { this.setBoolAttr('open', !this.getBoolAttr('open')); }
74
+
75
+ get open() { return this.getBoolAttr('open'); }
76
+ set open(v) { this.setBoolAttr('open', v); }
77
+ }
78
+
79
+ customElements.define('nk-sidebar', NkSidebar);
80
+
81
+ export { NkSidebar };
@@ -0,0 +1,31 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-skeleton height="18" width="60%"></nk-skeleton>
4
+ // <nk-skeleton lines="3" widths="100%,85%,40%"></nk-skeleton>
5
+ // → <div class="nk-skeleton" style="height:…;width:…"></div> × lines
6
+ class NkSkeleton extends NkElement {
7
+ static get observedAttributes() { return ['lines', 'height', 'width', 'widths']; }
8
+
9
+ render() { this._build(); }
10
+
11
+ _build() {
12
+ this._wrapper.replaceChildren();
13
+ const lines = Math.max(1, Number(this.getAttribute('lines')) || 1);
14
+ const height = this.getAttribute('height') || '13';
15
+ const widths = (this.getAttribute('widths') || '').split(',').map(s => s.trim()).filter(Boolean);
16
+ const width = this.getAttribute('width');
17
+ for (let i = 0; i < lines; i++) {
18
+ const line = this.createElement('div', ['nk-skeleton'], { 'aria-hidden': 'true' });
19
+ line.style.height = /^\d+(\.\d+)?$/.test(height) ? `${height}px` : height;
20
+ const w = widths[i] ?? width;
21
+ if (w) line.style.width = w;
22
+ this._wrapper.appendChild(line);
23
+ }
24
+ }
25
+
26
+ onAttributeChanged() { this._build(); }
27
+ }
28
+
29
+ customElements.define('nk-skeleton', NkSkeleton);
30
+
31
+ export { NkSkeleton };
@@ -0,0 +1,85 @@
1
+ import { a as NkFormElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-slider name="size" min="12" max="18" value="14" show-value></nk-slider>
4
+ // → <input type="range" class="nk-slider"> <div class="nk-slider-value">14</div>
5
+ const FORWARDED = ['name', 'min', 'max', 'step', 'aria-label'];
6
+
7
+ class NkSlider extends NkFormElement {
8
+ static get observedAttributes() { return ['value', 'disabled', 'show-value', 'unit', ...FORWARDED]; }
9
+
10
+ render() {
11
+ this._input = this.createElement('input', ['nk-slider'], { type: 'range' });
12
+ this._out = this.createElement('div', ['nk-slider-value']);
13
+ this._apply();
14
+ this._input.value = this.getAttribute('value') ?? this._input.value;
15
+ this._defaultValue = this._input.value;
16
+ this._wrapper.appendChild(this._input);
17
+ this._wrapper.appendChild(this._out);
18
+ this._syncOut();
19
+ this._syncFormValue();
20
+ }
21
+
22
+ _apply() {
23
+ for (const attr of FORWARDED) {
24
+ const v = this.getAttribute(attr);
25
+ v === null ? this._input.removeAttribute(attr) : this._input.setAttribute(attr, v);
26
+ }
27
+ this._input.disabled = this.getBoolAttr('disabled') || !!this._formDisabled;
28
+ this._out.style.display = this.getBoolAttr('show-value') ? '' : 'none';
29
+ }
30
+
31
+ _syncOut() {
32
+ this._out.textContent = `${this._input.value}${this.getAttribute('unit') || ''}`;
33
+ }
34
+
35
+ setupEvents() {
36
+ this._onInput = () => {
37
+ this._syncOut();
38
+ this._syncFormValue();
39
+ this.emit('nk-input', { value: Number(this._input.value), name: this.name });
40
+ };
41
+ this._onChange = () => {
42
+ this._syncFormValue();
43
+ this.emit('nk-change', { value: Number(this._input.value), name: this.name });
44
+ this.dispatchEvent(new Event('change', { bubbles: true }));
45
+ };
46
+ this._input.addEventListener('input', this._onInput);
47
+ this._input.addEventListener('change', this._onChange);
48
+ }
49
+
50
+ teardownEvents() {
51
+ this._input?.removeEventListener('input', this._onInput);
52
+ this._input?.removeEventListener('change', this._onChange);
53
+ }
54
+
55
+ onAttributeChanged(name) {
56
+ if (!this._input) return;
57
+ if (name === 'value') this._input.value = this.getAttribute('value') ?? '';
58
+ else this._apply();
59
+ this._syncOut();
60
+ this._syncFormValue();
61
+ }
62
+
63
+ _syncFormValue() { this.setFormValue(this._input.value); }
64
+ resetValue() { this._input.value = this._defaultValue; this._syncOut(); this._syncFormValue(); }
65
+ restoreValue(state) { if (typeof state === 'string') { this._input.value = state; this._syncOut(); this._syncFormValue(); } }
66
+ onFormDisabled(disabled) { this._formDisabled = disabled; this._apply(); }
67
+
68
+ focus(options) { this._input?.focus(options); }
69
+
70
+ get value() { return Number(this._input?.value ?? this.getAttribute('value')); }
71
+ set value(v) {
72
+ if (this._input) { this._input.value = v; this._syncOut(); this._syncFormValue(); }
73
+ else this.setAttribute('value', v);
74
+ }
75
+ get name() { return this.getAttribute('name'); }
76
+ set name(v) { this.setAttribute('name', v); }
77
+ get disabled() { return this.getBoolAttr('disabled'); }
78
+ set disabled(v) { this.setBoolAttr('disabled', v); }
79
+ get showValue() { return this.getBoolAttr('show-value'); }
80
+ set showValue(v) { this.setBoolAttr('show-value', v); }
81
+ }
82
+
83
+ customElements.define('nk-slider', NkSlider);
84
+
85
+ export { NkSlider };
@@ -0,0 +1,40 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-stat label="Active pages" value="128" delta="▲ 12 this week" trend="up"></nk-stat>
4
+ // → <div class="nk-stat"><div class="s-label">…</div><div class="s-value">…</div><div class="s-delta up">…</div></div>
5
+ class NkStat extends NkElement {
6
+ static get observedAttributes() { return ['label', 'value', 'delta', 'trend']; }
7
+
8
+ render() {
9
+ const box = this.createElement('div', ['nk-stat']);
10
+ this._parts = {};
11
+ for (const [slot, cls] of [['label', 's-label'], ['value', 's-value'], ['delta', 's-delta']]) {
12
+ const el = this.createElement('div', [cls]);
13
+ const s = this.createElement('slot', [], { name: slot });
14
+ s.appendChild(el);
15
+ box.appendChild(s);
16
+ this._parts[slot] = el;
17
+ }
18
+ this._wrapper.appendChild(box);
19
+ this._sync();
20
+ }
21
+
22
+ _sync() {
23
+ for (const [slot, el] of Object.entries(this._parts)) {
24
+ const v = this.getAttribute(slot);
25
+ el.textContent = v ?? '';
26
+ el.style.display = v === null ? 'none' : '';
27
+ }
28
+ const trend = this.getAttribute('trend');
29
+ this._parts.delta.className = 's-delta' + (trend === 'up' || trend === 'down' ? ` ${trend}` : '');
30
+ }
31
+
32
+ onAttributeChanged() { this._sync(); }
33
+
34
+ get value() { return this.getAttribute('value'); }
35
+ set value(v) { this.setAttribute('value', v); }
36
+ }
37
+
38
+ customElements.define('nk-stat', NkStat);
39
+
40
+ export { NkStat };
@@ -0,0 +1,14 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-stats><nk-stat …></nk-stat>…</nk-stats> → <div class="nk-stats">
4
+ class NkStats extends NkElement {
5
+ render() {
6
+ const row = this.createElement('div', ['nk-stats']);
7
+ row.appendChild(document.createElement('slot'));
8
+ this._wrapper.appendChild(row);
9
+ }
10
+ }
11
+
12
+ customElements.define('nk-stats', NkStats);
13
+
14
+ export { NkStats };
@@ -0,0 +1,69 @@
1
+ import { a as NkFormElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-switch name="notify" checked></nk-switch>
4
+ // → <button class="nk-switch" role="switch" aria-checked="true">
5
+ // notionkit.css styles the state through [aria-checked]; the toggling is here.
6
+ class NkSwitch extends NkFormElement {
7
+ static get observedAttributes() { return ['checked', 'disabled', 'name', 'value', 'label']; }
8
+
9
+ render() {
10
+ this._btn = this.createElement('button', ['nk-switch'], { type: 'button', role: 'switch' });
11
+ this._defaultChecked = this.getBoolAttr('checked');
12
+ this._apply();
13
+ this._wrapper.appendChild(this._btn);
14
+ this._syncFormValue();
15
+ }
16
+
17
+ _apply() {
18
+ this._btn.setAttribute('aria-checked', this.getBoolAttr('checked') ? 'true' : 'false');
19
+ this._btn.disabled = this.getBoolAttr('disabled') || !!this._formDisabled;
20
+ const label = this.getAttribute('label');
21
+ label ? this._btn.setAttribute('aria-label', label) : this._btn.removeAttribute('aria-label');
22
+ }
23
+
24
+ setupEvents() {
25
+ this._onClick = () => this.toggle();
26
+ this._btn.addEventListener('click', this._onClick);
27
+ }
28
+
29
+ teardownEvents() {
30
+ this._btn?.removeEventListener('click', this._onClick);
31
+ }
32
+
33
+ toggle() {
34
+ if (this.getBoolAttr('disabled') || this._formDisabled) return;
35
+ this.checked = !this.checked;
36
+ this.emit('nk-change', { checked: this.checked, value: this.getAttribute('value') || 'on', name: this.name });
37
+ this.dispatchEvent(new Event('change', { bubbles: true }));
38
+ }
39
+
40
+ onAttributeChanged() {
41
+ if (!this._btn) return;
42
+ this._apply();
43
+ this._syncFormValue();
44
+ }
45
+
46
+ _syncFormValue() {
47
+ const val = this.getAttribute('value') || 'on';
48
+ this.setFormValue(this.getBoolAttr('checked') ? val : null, this.getBoolAttr('checked') ? 'checked' : 'unchecked');
49
+ }
50
+
51
+ resetValue() { this.setBoolAttr('checked', this._defaultChecked); }
52
+ restoreValue(state) { if (state === 'checked' || state === 'unchecked') this.setBoolAttr('checked', state === 'checked'); }
53
+ onFormDisabled(disabled) { this._formDisabled = disabled; this._apply(); }
54
+
55
+ focus(options) { this._btn?.focus(options); }
56
+
57
+ get checked() { return this.getBoolAttr('checked'); }
58
+ set checked(v) { this.setBoolAttr('checked', v); }
59
+ get disabled() { return this.getBoolAttr('disabled'); }
60
+ set disabled(v) { this.setBoolAttr('disabled', v); }
61
+ get name() { return this.getAttribute('name'); }
62
+ set name(v) { this.setAttribute('name', v); }
63
+ get value() { return this.getAttribute('value') || 'on'; }
64
+ set value(v) { this.setAttribute('value', v); }
65
+ }
66
+
67
+ customElements.define('nk-switch', NkSwitch);
68
+
69
+ export { NkSwitch };
@@ -0,0 +1,26 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-synced badge="⟳ 3 places">…</nk-synced>
4
+ // → <div class="nk-synced"><span class="synced-badge">⟳ 3 places</span>…</div>
5
+ class NkSynced extends NkElement {
6
+ static get observedAttributes() { return ['badge']; }
7
+
8
+ render() {
9
+ const box = this.createElement('div', ['nk-synced']);
10
+ this._badge = this.createElement('span', ['synced-badge']);
11
+ box.appendChild(this._badge);
12
+ box.appendChild(document.createElement('slot'));
13
+ this._wrapper.appendChild(box);
14
+ this._sync();
15
+ }
16
+
17
+ _sync() { this._badge.textContent = this.getAttribute('badge') ?? '⟳ synced'; }
18
+ onAttributeChanged() { this._sync(); }
19
+
20
+ get badge() { return this.getAttribute('badge'); }
21
+ set badge(v) { this.setAttribute('badge', v); }
22
+ }
23
+
24
+ customElements.define('nk-synced', NkSynced);
25
+
26
+ export { NkSynced };
@@ -0,0 +1,54 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-tab value="notes" active>📝 Notes</nk-tab> → <span class="nk-tab active" role="tab">
4
+ class NkTab extends NkElement {
5
+ static get observedAttributes() { return ['active', 'value', 'disabled']; }
6
+
7
+ render() {
8
+ this._tab = this.createElement('span', ['nk-tab'], { role: 'tab' });
9
+ this._tab.appendChild(document.createElement('slot'));
10
+ this._wrapper.appendChild(this._tab);
11
+ this._sync();
12
+ }
13
+
14
+ _sync() {
15
+ const active = this.getBoolAttr('active');
16
+ this._tab.classList.toggle('active', active);
17
+ this._tab.setAttribute('aria-selected', active ? 'true' : 'false');
18
+ this._tab.tabIndex = active ? 0 : -1;
19
+ this._tab.setAttribute('aria-disabled', this.getBoolAttr('disabled') ? 'true' : 'false');
20
+ }
21
+
22
+ setupEvents() {
23
+ this._onClick = () => this.select();
24
+ this._onKey = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); this.select(); } };
25
+ this._tab.addEventListener('click', this._onClick);
26
+ this._tab.addEventListener('keydown', this._onKey);
27
+ }
28
+
29
+ teardownEvents() {
30
+ this._tab?.removeEventListener('click', this._onClick);
31
+ this._tab?.removeEventListener('keydown', this._onKey);
32
+ }
33
+
34
+ select() {
35
+ if (this.getBoolAttr('disabled')) return;
36
+ const ok = this.emit('nk-select', { value: this.value, label: this.textContent.trim(), item: this });
37
+ if (!ok) return;
38
+ const tabs = this.closest('nk-tabs');
39
+ if (tabs) tabs.value = this.value;
40
+ else this.setBoolAttr('active', true);
41
+ }
42
+
43
+ onAttributeChanged() { this._sync(); }
44
+ focus(o) { this._tab?.focus(o); }
45
+
46
+ get value() { return this.getAttribute('value') ?? this.textContent.trim(); }
47
+ set value(v) { this.setAttribute('value', v); }
48
+ get active() { return this.getBoolAttr('active'); }
49
+ set active(v) { this.setBoolAttr('active', v); }
50
+ }
51
+
52
+ customElements.define('nk-tab', NkTab);
53
+
54
+ export { NkTab };
@@ -0,0 +1,109 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+ import { c as compareBy, r as renderPropertyCell } from './shared/property-cell-Dn91uFkf.js';
3
+
4
+ // <nk-table-view name="table" label="▦ Table" count new-row sortable></nk-table-view>
5
+ // Standalone: view.columns = […]; view.rows = […]; Inside <nk-database> the
6
+ // database pushes `data`. → <div class="nk-table-wrap"><table class="nk-table">…</table><div class="nk-new-row">+ New page</div></div>
7
+ // Cells are plain markup from renderPropertyCell(); rows fire nk-select,
8
+ // checkboxes nk-change, headers nk-action { action: 'sort' } (and sort locally
9
+ // with `sortable`), the add row nk-action { action: 'new-row' }.
10
+ class NkTableView extends NkElement {
11
+ static get observedAttributes() { return ['name', 'label', 'badge', 'count', 'new-row', 'new-row-label', 'sortable', 'sort-key', 'sort-dir']; }
12
+
13
+ render() {
14
+ this._wrap = this.createElement('div', ['nk-table-wrap']);
15
+ this._table = this.createElement('table', ['nk-table']);
16
+ this._thead = document.createElement('thead');
17
+ this._tbody = document.createElement('tbody');
18
+ this._table.append(this._thead, this._tbody);
19
+ this._newRow = this.createElement('div', ['nk-new-row'], { role: 'button', tabindex: '0' });
20
+ this._wrap.append(this._table, this._newRow);
21
+ this._wrapper.appendChild(this._wrap);
22
+ this._columns ??= []; this._rows ??= [];
23
+ this._render();
24
+ this.closest('nk-database')?.requestSync?.();
25
+ }
26
+
27
+ _sortedRows() {
28
+ const key = this.getAttribute('sort-key');
29
+ const col = this._columns.find(c => c.key === key);
30
+ if (!col) return this._rows;
31
+ return [...this._rows].sort(compareBy(col, this.getAttribute('sort-dir') === 'desc' ? -1 : 1));
32
+ }
33
+
34
+ _render() {
35
+ if (!this._thead) return;
36
+ const tr = document.createElement('tr');
37
+ for (const col of this._columns) {
38
+ const th = this.createElement('th', [], { 'data-key': col.key, scope: 'col' });
39
+ if (col.icon) { const i = this.createElement('span', ['th-icon']); i.textContent = col.icon; th.appendChild(i); }
40
+ th.appendChild(document.createTextNode(col.label ?? col.key));
41
+ if (col.key === this.getAttribute('sort-key')) th.appendChild(document.createTextNode(this.getAttribute('sort-dir') === 'desc' ? ' ↓' : ' ↑'));
42
+ if (col.width) th.style.width = col.width;
43
+ tr.appendChild(th);
44
+ }
45
+ this._thead.replaceChildren(tr);
46
+ this._tbody.replaceChildren();
47
+ for (const row of this._sortedRows()) {
48
+ const r = this.createElement('tr', [], { 'data-id': row.id ?? '' });
49
+ for (const col of this._columns) {
50
+ const td = document.createElement('td');
51
+ td.appendChild(renderPropertyCell(col, row[col.key], row));
52
+ r.appendChild(td);
53
+ }
54
+ this._tbody.appendChild(r);
55
+ }
56
+ this._newRow.textContent = this.getAttribute('new-row-label') || '+ New page';
57
+ this._newRow.style.display = this.getBoolAttr('new-row') ? '' : 'none';
58
+ }
59
+
60
+ setupEvents() {
61
+ this._onClick = (e) => {
62
+ if (this._newRow.contains(e.target)) { this.emit('nk-action', { action: 'new-row' }); return; }
63
+ const th = e.target.closest('th[data-key]');
64
+ if (th) {
65
+ const key = th.dataset.key;
66
+ const dir = this.getAttribute('sort-key') === key && this.getAttribute('sort-dir') !== 'desc' ? 'desc' : 'asc';
67
+ const ok = this.emit('nk-action', { action: 'sort', key, value: dir });
68
+ if (ok && this.getBoolAttr('sortable')) { this.setAttribute('sort-key', key); this.setAttribute('sort-dir', dir); }
69
+ return;
70
+ }
71
+ if (e.target.closest('input[type=checkbox], a')) return;
72
+ const tr = e.target.closest('tr[data-id]');
73
+ if (tr) { const row = this._rowById(tr.dataset.id); this.emit('nk-select', { row, id: row?.id, value: row?.id }); }
74
+ };
75
+ this._onChange = (e) => {
76
+ const input = e.target;
77
+ if (input.type !== 'checkbox') return;
78
+ const row = this._rowById(input.dataset.rowId);
79
+ if (row) row[input.dataset.key] = input.checked;
80
+ this.emit('nk-change', { row, key: input.dataset.key, value: input.checked });
81
+ };
82
+ this._onKey = (e) => { if ((e.key === 'Enter' || e.key === ' ') && e.target === this._newRow) { e.preventDefault(); this.emit('nk-action', { action: 'new-row' }); } };
83
+ this._wrap.addEventListener('click', this._onClick);
84
+ this._wrap.addEventListener('change', this._onChange);
85
+ this._wrap.addEventListener('keydown', this._onKey);
86
+ }
87
+
88
+ teardownEvents() {
89
+ this._wrap?.removeEventListener('click', this._onClick);
90
+ this._wrap?.removeEventListener('change', this._onChange);
91
+ this._wrap?.removeEventListener('keydown', this._onKey);
92
+ }
93
+
94
+ _rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
95
+ onAttributeChanged() { this._render(); }
96
+
97
+ get name() { return this.getAttribute('name') || 'table'; }
98
+ get columns() { return this._columns; }
99
+ set columns(v) { this._columns = Array.isArray(v) ? v : []; this._render(); }
100
+ get rows() { return this._rows; }
101
+ set rows(v) { this._rows = Array.isArray(v) ? v : []; this._render(); }
102
+ set data({ columns, rows }) { this.setData(columns, rows); }
103
+ setData(columns, rows) { this._columns = columns || []; this._rows = rows || []; this._render(); }
104
+ refresh() { this._render(); }
105
+ }
106
+
107
+ customElements.define('nk-table-view', NkTableView);
108
+
109
+ export { NkTableView };
@@ -0,0 +1,82 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-tabs value="notes">
4
+ // <nk-tab value="notes">📝 Notes</nk-tab>
5
+ // <nk-tab value="tasks">✅ Tasks</nk-tab>
6
+ // <div slot="panel" data-tab="notes" class="nk-tab-panel">…</div>
7
+ // <div slot="panel" data-tab="tasks" class="nk-tab-panel">…</div>
8
+ // </nk-tabs>
9
+ // → <div class="nk-tabs" role="tablist">…</div> + the panels below it.
10
+ // The tabs keep one tab active and hide every panel whose data-tab does not
11
+ // match `value` (through the hidden attribute – the panels stay yours).
12
+ const KEYS = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };
13
+
14
+ class NkTabs extends NkElement {
15
+ static get observedAttributes() { return ['value']; }
16
+
17
+ render() {
18
+ this._strip = this.createElement('div', ['nk-tabs'], { role: 'tablist' });
19
+ this._tabSlot = document.createElement('slot');
20
+ this._strip.appendChild(this._tabSlot);
21
+ this._panelSlot = this.createElement('slot', [], { name: 'panel' });
22
+ this._wrapper.append(this._strip, this._panelSlot);
23
+ this._sync();
24
+ }
25
+
26
+ get tabs() { return [...this.querySelectorAll(':scope > nk-tab')]; }
27
+ get panels() { return [...this.querySelectorAll(':scope > [slot="panel"]')]; }
28
+
29
+ _sync() {
30
+ const tabs = this.tabs;
31
+ if (!tabs.length) return;
32
+ let value = this.getAttribute('value');
33
+ if (value === null || !tabs.some(t => t.getAttribute('value') === value || (t.getAttribute('value') === null && t.textContent.trim() === value))) {
34
+ const preset = tabs.find(t => t.hasAttribute('active')) || tabs[0];
35
+ value = preset.getAttribute('value') ?? preset.textContent.trim();
36
+ }
37
+ for (const tab of tabs) {
38
+ const v = tab.getAttribute('value') ?? tab.textContent.trim();
39
+ if (v === value) tab.setAttribute('active', ''); else tab.removeAttribute('active');
40
+ }
41
+ for (const panel of this.panels) panel.hidden = panel.dataset.tab !== value;
42
+ this._value = value;
43
+ }
44
+
45
+ setupEvents() {
46
+ this._onSlot = () => this._sync();
47
+ this._onKey = (e) => {
48
+ const dir = KEYS[e.key];
49
+ if (!dir) return;
50
+ const tabs = this.tabs.filter(t => !t.hasAttribute('disabled'));
51
+ const i = tabs.indexOf(e.target.closest?.('nk-tab'));
52
+ if (i < 0 || tabs.length < 2) return;
53
+ e.preventDefault();
54
+ const next = tabs[(i + dir + tabs.length) % tabs.length];
55
+ next.select();
56
+ next.focus();
57
+ };
58
+ this._tabSlot.addEventListener('slotchange', this._onSlot);
59
+ this._panelSlot.addEventListener('slotchange', this._onSlot);
60
+ this.addEventListener('keydown', this._onKey);
61
+ this._sync();
62
+ }
63
+
64
+ teardownEvents() {
65
+ this._tabSlot?.removeEventListener('slotchange', this._onSlot);
66
+ this._panelSlot?.removeEventListener('slotchange', this._onSlot);
67
+ this.removeEventListener('keydown', this._onKey);
68
+ }
69
+
70
+ onAttributeChanged() {
71
+ const before = this._value;
72
+ this._sync();
73
+ if (this._value !== before) this.emit('nk-change', { value: this._value });
74
+ }
75
+
76
+ get value() { return this._value ?? this.getAttribute('value'); }
77
+ set value(v) { this.setAttribute('value', v); }
78
+ }
79
+
80
+ customElements.define('nk-tabs', NkTabs);
81
+
82
+ export { NkTabs };
@@ -0,0 +1,32 @@
1
+ import { N as NkElement } from './shared/base-6gEcoatG.js';
2
+
3
+ // <nk-tag color="blue">Text</nk-tag> — modifier class .blue becomes color="blue"
4
+ const COLORS = ['blue', 'green', 'orange', 'purple'];
5
+
6
+ class NkTag extends NkElement {
7
+ static get observedAttributes() { return ['color']; }
8
+
9
+ render() {
10
+ this._tag = this.createElement('span', this._computeClasses());
11
+ this._tag.appendChild(document.createElement('slot'));
12
+ this._wrapper.appendChild(this._tag);
13
+ }
14
+
15
+ onAttributeChanged() {
16
+ if (this._tag) this._tag.className = this._computeClasses().join(' ');
17
+ }
18
+
19
+ _computeClasses() {
20
+ const classes = ['nk-tag'];
21
+ const color = this.getAttribute('color');
22
+ if (COLORS.includes(color)) classes.push(color);
23
+ return classes;
24
+ }
25
+
26
+ get color() { return this.getAttribute('color'); }
27
+ set color(v) { v ? this.setAttribute('color', v) : this.removeAttribute('color'); }
28
+ }
29
+
30
+ customElements.define('nk-tag', NkTag);
31
+
32
+ export { NkTag };