@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.
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/components/nk-ai-input-row.js +57 -0
- package/dist/components/nk-ai-msg.js +67 -0
- package/dist/components/nk-ai-thread.js +14 -0
- package/dist/components/nk-app.js +23 -0
- package/dist/components/nk-avatar-group.js +35 -0
- package/dist/components/nk-banner.js +30 -0
- package/dist/components/nk-block-host.js +35 -0
- package/dist/components/nk-board-view.js +129 -0
- package/dist/components/nk-breadcrumb.js +78 -0
- package/dist/components/nk-btn.js +107 -0
- package/dist/components/nk-callout.js +36 -0
- package/dist/components/nk-check.js +77 -0
- package/dist/components/nk-cmdk.js +221 -0
- package/dist/components/nk-code.js +66 -0
- package/dist/components/nk-comment.js +41 -0
- package/dist/components/nk-comments.js +57 -0
- package/dist/components/nk-danger-zone.js +27 -0
- package/dist/components/nk-database.js +100 -0
- package/dist/components/nk-divider.js +12 -0
- package/dist/components/nk-emoji-picker.js +112 -0
- package/dist/components/nk-empty.js +36 -0
- package/dist/components/nk-field.js +50 -0
- package/dist/components/nk-filter-bar.js +78 -0
- package/dist/components/nk-heading.js +29 -0
- package/dist/components/nk-input.js +108 -0
- package/dist/components/nk-kbd.js +15 -0
- package/dist/components/nk-member-list.js +30 -0
- package/dist/components/nk-member-row.js +46 -0
- package/dist/components/nk-mention.js +31 -0
- package/dist/components/nk-menu-item.js +68 -0
- package/dist/components/nk-menu.js +45 -0
- package/dist/components/nk-modal.js +166 -0
- package/dist/components/nk-model-card.js +89 -0
- package/dist/components/nk-page-actions.js +15 -0
- package/dist/components/nk-page-cover.js +35 -0
- package/dist/components/nk-page-title.js +71 -0
- package/dist/components/nk-page.js +71 -0
- package/dist/components/nk-pop.js +90 -0
- package/dist/components/nk-profile-row.js +25 -0
- package/dist/components/nk-progress.js +43 -0
- package/dist/components/nk-quote.js +32 -0
- package/dist/components/nk-radio.js +160 -0
- package/dist/components/nk-section-label.js +42 -0
- package/dist/components/nk-segmented.js +99 -0
- package/dist/components/nk-select.js +127 -0
- package/dist/components/nk-settings-pane.js +40 -0
- package/dist/components/nk-settings-user.js +36 -0
- package/dist/components/nk-sidebar.js +81 -0
- package/dist/components/nk-skeleton.js +31 -0
- package/dist/components/nk-slider.js +85 -0
- package/dist/components/nk-stat.js +40 -0
- package/dist/components/nk-stats.js +14 -0
- package/dist/components/nk-switch.js +69 -0
- package/dist/components/nk-synced.js +26 -0
- package/dist/components/nk-tab.js +54 -0
- package/dist/components/nk-table-view.js +109 -0
- package/dist/components/nk-tabs.js +82 -0
- package/dist/components/nk-tag.js +32 -0
- package/dist/components/nk-template-btn.js +43 -0
- package/dist/components/nk-textarea.js +95 -0
- package/dist/components/nk-theme-toggle.js +67 -0
- package/dist/components/nk-toast.js +51 -0
- package/dist/components/nk-todo.js +73 -0
- package/dist/components/nk-toggle.js +56 -0
- package/dist/components/nk-topbar.js +24 -0
- package/dist/components/nk-tree-item.js +155 -0
- package/dist/components/nk-tree.js +123 -0
- package/dist/components/nk-workspace-switcher.js +82 -0
- package/dist/components/shared/base-6gEcoatG.js +317 -0
- package/dist/components/shared/focus-DXrK9oLe.js +47 -0
- package/dist/components/shared/property-cell-Dn91uFkf.js +92 -0
- package/dist/notionkit-elements.esm.js +4639 -0
- package/dist/notionkit-elements.js +4718 -0
- package/dist/notionkit-elements.min.js +1 -0
- package/package.json +72 -0
- package/src/base.js +296 -0
- package/src/components/content/nk-callout.js +35 -0
- package/src/components/content/nk-code.js +51 -0
- package/src/components/content/nk-divider.js +11 -0
- package/src/components/content/nk-heading.js +28 -0
- package/src/components/content/nk-kbd.js +14 -0
- package/src/components/content/nk-progress.js +42 -0
- package/src/components/content/nk-quote.js +31 -0
- package/src/components/content/nk-tag.js +31 -0
- package/src/components/content/nk-todo.js +72 -0
- package/src/components/content/nk-toggle.js +55 -0
- package/src/components/data/nk-ai-input-row.js +56 -0
- package/src/components/data/nk-ai-msg.js +66 -0
- package/src/components/data/nk-ai-thread.js +13 -0
- package/src/components/data/nk-board-view.js +128 -0
- package/src/components/data/nk-comment.js +40 -0
- package/src/components/data/nk-comments.js +56 -0
- package/src/components/data/nk-database.js +99 -0
- package/src/components/data/nk-filter-bar.js +77 -0
- package/src/components/data/nk-table-view.js +108 -0
- package/src/components/forms/nk-btn.js +106 -0
- package/src/components/forms/nk-check.js +76 -0
- package/src/components/forms/nk-field.js +49 -0
- package/src/components/forms/nk-input.js +107 -0
- package/src/components/forms/nk-radio.js +159 -0
- package/src/components/forms/nk-select.js +126 -0
- package/src/components/forms/nk-slider.js +84 -0
- package/src/components/forms/nk-switch.js +68 -0
- package/src/components/forms/nk-textarea.js +94 -0
- package/src/components/overlays/nk-cmdk.js +201 -0
- package/src/components/overlays/nk-emoji-picker.js +94 -0
- package/src/components/overlays/nk-menu-item.js +67 -0
- package/src/components/overlays/nk-menu.js +44 -0
- package/src/components/overlays/nk-modal.js +165 -0
- package/src/components/overlays/nk-pop.js +89 -0
- package/src/components/overlays/nk-settings-pane.js +39 -0
- package/src/components/overlays/nk-settings-user.js +35 -0
- package/src/components/overlays/nk-toast.js +50 -0
- package/src/components/page/nk-avatar-group.js +34 -0
- package/src/components/page/nk-banner.js +29 -0
- package/src/components/page/nk-block-host.js +34 -0
- package/src/components/page/nk-danger-zone.js +26 -0
- package/src/components/page/nk-empty.js +35 -0
- package/src/components/page/nk-member-list.js +29 -0
- package/src/components/page/nk-member-row.js +45 -0
- package/src/components/page/nk-mention.js +30 -0
- package/src/components/page/nk-model-card.js +88 -0
- package/src/components/page/nk-page-actions.js +14 -0
- package/src/components/page/nk-page-cover.js +34 -0
- package/src/components/page/nk-page-title.js +70 -0
- package/src/components/page/nk-page.js +70 -0
- package/src/components/page/nk-profile-row.js +24 -0
- package/src/components/page/nk-segmented.js +98 -0
- package/src/components/page/nk-skeleton.js +30 -0
- package/src/components/page/nk-stat.js +39 -0
- package/src/components/page/nk-stats.js +13 -0
- package/src/components/page/nk-synced.js +25 -0
- package/src/components/page/nk-tab.js +53 -0
- package/src/components/page/nk-tabs.js +81 -0
- package/src/components/page/nk-template-btn.js +42 -0
- package/src/components/shell/nk-app.js +22 -0
- package/src/components/shell/nk-breadcrumb.js +77 -0
- package/src/components/shell/nk-section-label.js +41 -0
- package/src/components/shell/nk-sidebar.js +80 -0
- package/src/components/shell/nk-theme-toggle.js +66 -0
- package/src/components/shell/nk-topbar.js +23 -0
- package/src/components/shell/nk-tree-item.js +154 -0
- package/src/components/shell/nk-tree.js +122 -0
- package/src/components/shell/nk-workspace-switcher.js +81 -0
- package/src/index.js +88 -0
- package/src/util/emoji.js +17 -0
- package/src/util/focus.js +45 -0
- package/src/util/fuzzy.js +19 -0
- package/src/util/highlight.js +14 -0
- package/src/util/property-cell.js +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jungherz GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://img.shields.io/badge/version-1.0.0-2383e2?style=flat-square" alt="Version">
|
|
3
|
+
<a href="https://www.npmjs.com/package/@jungherz-de/notionkit-elements"><img src="https://img.shields.io/npm/v/@jungherz-de/notionkit-elements?style=flat-square&color=529cca" alt="npm"></a>
|
|
4
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.0.0-9065b0?style=flat-square" alt="Changelog"></a>
|
|
5
|
+
<img src="https://img.shields.io/badge/license-MIT-448361?style=flat-square" alt="MIT">
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
# NotionKit Elements
|
|
9
|
+
|
|
10
|
+
Vanilla-JS Web Components for the [NotionKit](https://notionkit.jungherz.com) CSS foundation – 68 `<nk-*>` custom elements with Shadow DOM, automatic light/dark sync, native form participation and pixel parity with the class markup. Same word stem as the CSS classes: `.nk-callout` becomes `<nk-callout>`, a modifier class becomes an attribute, a state class becomes a boolean attribute.
|
|
11
|
+
|
|
12
|
+
- **Docs:** https://notionkit-elements.jungherz.com/docs.html – attributes, slots, events, before/after against the class markup
|
|
13
|
+
- **Showcase:** https://notionkit-elements.jungherz.com/showcase.html – every element live, both themes, brand switch
|
|
14
|
+
- **Demo app:** https://notionkit-elements.jungherz.com/app.html – the NotionKit reference app, elements only
|
|
15
|
+
- **AI reference:** [SKILL.md](SKILL.md) – copy-paste markup, six app skeletons, rules and common mistakes
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<!DOCTYPE html>
|
|
21
|
+
<html lang="en" data-theme="light">
|
|
22
|
+
<head>
|
|
23
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.1.1/notionkit.min.css">
|
|
24
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.0.0/dist/notionkit-elements.min.js"></script>
|
|
25
|
+
</head>
|
|
26
|
+
<body class="nk-body">
|
|
27
|
+
<nk-field label="Display name" desc="Shown next to your comments">
|
|
28
|
+
<nk-input name="name" value="Ada Lovelace"></nk-input>
|
|
29
|
+
</nk-field>
|
|
30
|
+
<nk-btn variant="primary">Save</nk-btn>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @jungherz-de/notionkit-elements @jungherz-de/notionkit
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import '@jungherz-de/notionkit/notionkit.css';
|
|
41
|
+
import '@jungherz-de/notionkit-elements'; // everything
|
|
42
|
+
import '@jungherz-de/notionkit-elements/components/nk-btn.js'; // or one element (shared code is one extra chunk)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Two prerequisites, always: `notionkit.css` on the document (the peer dependency, `>= 1.0.0`; 1.1.1 is what the elements are built and tested against) and `class="nk-body"` on `<body>`. The elements ship no visual CSS of their own – every rule comes from the foundation. The bundle injects the design tokens once as `@layer notionkit-defaults`, so any plain `:root { --nk-accent: … }` of yours re-brands every element in both themes.
|
|
46
|
+
|
|
47
|
+
## Elements
|
|
48
|
+
|
|
49
|
+
| Group | Elements |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Forms & controls | `nk-btn` `nk-input` `nk-textarea` `nk-select` `nk-switch` `nk-check` `nk-radio` `nk-slider` `nk-field` |
|
|
52
|
+
| Content | `nk-tag` `nk-progress` `nk-callout` `nk-divider` `nk-heading` `nk-toggle` `nk-todo` `nk-kbd` `nk-code` `nk-quote` |
|
|
53
|
+
| App shell & navigation | `nk-app` `nk-sidebar` `nk-workspace-switcher` `nk-section-label` `nk-tree` `nk-tree-item` `nk-topbar` `nk-breadcrumb` `nk-theme-toggle` |
|
|
54
|
+
| Page shell & blocks | `nk-page` `nk-page-cover` `nk-page-title` `nk-page-actions` `nk-block-host` `nk-banner` `nk-empty` `nk-skeleton` `nk-synced` `nk-tabs` `nk-tab` `nk-segmented` `nk-stats` `nk-stat` `nk-avatar-group` `nk-mention` `nk-template-btn` `nk-model-card` `nk-profile-row` `nk-danger-zone` `nk-member-list` `nk-member-row` |
|
|
55
|
+
| Overlays | `nk-modal` `nk-settings-pane` `nk-settings-user` `nk-cmdk` `nk-menu` `nk-menu-item` `nk-pop` `nk-emoji-picker` `nk-toast` |
|
|
56
|
+
| Data & collaboration | `nk-database` `nk-table-view` `nk-board-view` `nk-filter-bar` `nk-comments` `nk-comment` `nk-ai-thread` `nk-ai-msg` `nk-ai-input-row` (+ the exported `renderPropertyCell()`) |
|
|
57
|
+
|
|
58
|
+
Static content goes in through attributes and slots, dynamic data through properties (`tree.data`, `database.rows`, `palette.commands`). Interactions fire custom events with fixed names: `nk-select`, `nk-change`, `nk-view-change`, `nk-command`, `nk-toggle`, `nk-submit`, `nk-action`. No fetching, no two-way binding – the elements render what they get.
|
|
59
|
+
|
|
60
|
+
## How it works
|
|
61
|
+
|
|
62
|
+
- Each element's shadow root adopts the NotionKit **components** sheet only; tokens are inherited from the document. A `.nk-wrapper` inside the root mirrors `data-theme` from `<html>` – one observer for all instances.
|
|
63
|
+
- Every host is `display: contents`: the inner `.nk-*` element sits in the parent layout exactly where the class markup would. That is what makes the pixel parity hold – and why spacing belongs on a wrapper you own, not on the host.
|
|
64
|
+
- Form controls are form-associated custom elements: `FormData`, `reset`, `required` and `<fieldset disabled>` just work. `<nk-radio>`s with the same `name` form a real group across shadow roots, with one tab stop and arrow keys.
|
|
65
|
+
- Elements that copy light-DOM children (`nk-select` options, breadcrumb crumbs) watch them; `element.refresh()` is the escape hatch. Listeners are re-armed on every connect, so a moved element keeps working.
|
|
66
|
+
- `::slotted()` matches only the assigned node: pass icons directly (`<span slot="icon">📁</span>`), never wrapped.
|
|
67
|
+
- Overlays (`nk-modal`, `nk-cmdk`, `nk-toast`) go directly under `<body>`; they lock scroll, make the rest of the page `inert` and return focus.
|
|
68
|
+
- The editor stays an adapter: `nk-block-host` is the shell, `docs-editor.js` the TipTap recipe (used in the demo app). `<nk-editor>` follows in v1.1 as an optional import, never in the core bundle.
|
|
69
|
+
|
|
70
|
+
## Development
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install
|
|
74
|
+
npm run build # dist/: IIFE, minified IIFE, ESM, per-component ESM + shared chunk
|
|
75
|
+
npm run build:all # + docs.html, showcase.html, index.html, de/, SKILL.md, llms.txt
|
|
76
|
+
npm test # Playwright: pixel parity vs. class markup (both themes), branding, reconnect, radio groups, light-DOM drift, forms, overlays, data views, per-component import
|
|
77
|
+
npm run check:coverage && npm run check:versions
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`tools/catalog.mjs` is the single source for docs, showcase, SKILL.md, llms.txt, the coverage check and the parity test. `dist/` and the generated pages are committed; CI rebuilds them and fails on drift. `dist/` embeds the installed NotionKit version – release order: publish the CSS package first, `npm install` here, rebuild, commit, tag.
|
|
81
|
+
|
|
82
|
+
## The NotionKit family
|
|
83
|
+
|
|
84
|
+
| Layer | Package |
|
|
85
|
+
|---|---|
|
|
86
|
+
| **NotionKit** – CSS foundation | [`@jungherz-de/notionkit`](https://github.com/JUNGHERZ/NotionKit) |
|
|
87
|
+
| **NotionKit Elements** – this package | `@jungherz-de/notionkit-elements` |
|
|
88
|
+
| NotionKit Web – Astro template | planned |
|
|
89
|
+
|
|
90
|
+
MIT · Jungherz GmbH
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-ai-input-row placeholder="Ask Mona something …"></nk-ai-input-row>
|
|
4
|
+
// → <div class="nk-ai-input-row"><span>✨</span><input><button class="nk-ai-send">↑</button></div>
|
|
5
|
+
// Enter or the send button fires nk-submit { text } and clears the field.
|
|
6
|
+
class NkAiInputRow extends NkElement {
|
|
7
|
+
static get observedAttributes() { return ['placeholder', 'value', 'disabled', 'icon']; }
|
|
8
|
+
|
|
9
|
+
render() {
|
|
10
|
+
this._row = this.createElement('div', ['nk-ai-input-row']);
|
|
11
|
+
this._icon = document.createElement('span');
|
|
12
|
+
this._icon.style.fontSize = '14px';
|
|
13
|
+
this._input = this.createElement('input', [], { type: 'text', 'aria-label': 'Message' });
|
|
14
|
+
this._btn = this.createElement('button', ['nk-ai-send'], { type: 'button', 'aria-label': 'Send' });
|
|
15
|
+
this._btn.textContent = '↑';
|
|
16
|
+
this._row.append(this._icon, this._input, this._btn);
|
|
17
|
+
this._wrapper.appendChild(this._row);
|
|
18
|
+
this._sync();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_sync() {
|
|
22
|
+
this._input.placeholder = this.getAttribute('placeholder') || 'Ask something …';
|
|
23
|
+
if (this.hasAttribute('value') && this._input.value !== this.getAttribute('value')) this._input.value = this.getAttribute('value');
|
|
24
|
+
this._input.disabled = this._btn.disabled = this.getBoolAttr('disabled');
|
|
25
|
+
const icon = this.getAttribute('icon') ?? '✨';
|
|
26
|
+
this._icon.textContent = icon;
|
|
27
|
+
this._icon.style.display = icon ? '' : 'none';
|
|
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 || this.getBoolAttr('disabled')) 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-ai-input-row', NkAiInputRow);
|
|
56
|
+
|
|
57
|
+
export { NkAiInputRow };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-ai-msg role="user" name="You" avatar="MK">Summarise the open tasks.</nk-ai-msg>
|
|
4
|
+
// <nk-ai-msg role="assistant" name="Mona" badge="· AI" avatar="✨">
|
|
5
|
+
// Two tasks are open …
|
|
6
|
+
// <button slot="actions" value="copy">📋 Copy</button><button slot="actions" value="retry">↻ Rephrase</button>
|
|
7
|
+
// </nk-ai-msg>
|
|
8
|
+
// → <div class="nk-ai-msg user"><span class="mini-avatar">MK</span><div class="a-body"><div class="a-name">You</div>…<div class="nk-ai-actions">…</div></div></div>
|
|
9
|
+
// Action buttons stay in the light DOM (slotted twins) and fire nk-action { action: value }.
|
|
10
|
+
class NkAiMsg extends NkElement {
|
|
11
|
+
static get observedAttributes() { return ['role', 'name', 'badge', 'avatar', 'color']; }
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
this._box = this.createElement('div', ['nk-ai-msg']);
|
|
15
|
+
this._avatar = this.createElement('span', ['mini-avatar']);
|
|
16
|
+
const avatarSlot = this.createElement('slot', [], { name: 'avatar' });
|
|
17
|
+
avatarSlot.appendChild(this._avatar);
|
|
18
|
+
const body = this.createElement('div', ['a-body']);
|
|
19
|
+
this._name = this.createElement('div', ['a-name']);
|
|
20
|
+
this._nameText = document.createTextNode('');
|
|
21
|
+
this._badge = document.createElement('span');
|
|
22
|
+
this._name.append(this._nameText, this._badge);
|
|
23
|
+
this._actions = this.createElement('div', ['nk-ai-actions']);
|
|
24
|
+
this._actionSlot = this.createElement('slot', [], { name: 'actions' });
|
|
25
|
+
this._actions.appendChild(this._actionSlot);
|
|
26
|
+
body.append(this._name, document.createElement('slot'), this._actions);
|
|
27
|
+
this._box.append(avatarSlot, body);
|
|
28
|
+
this._wrapper.appendChild(this._box);
|
|
29
|
+
this._sync();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_sync() {
|
|
33
|
+
const user = this.getAttribute('role') === 'user';
|
|
34
|
+
this._box.classList.toggle('user', user);
|
|
35
|
+
const name = this.getAttribute('name');
|
|
36
|
+
this._nameText.data = name || '';
|
|
37
|
+
const badge = this.getAttribute('badge');
|
|
38
|
+
this._badge.textContent = badge ? ` ${badge}` : '';
|
|
39
|
+
this._name.style.display = name ? '' : 'none';
|
|
40
|
+
this._avatar.textContent = this.getAttribute('avatar') || (user ? (name || 'U').slice(0, 2).toUpperCase() : '✨');
|
|
41
|
+
const color = this.getAttribute('color');
|
|
42
|
+
this._avatar.style.background = color || '';
|
|
43
|
+
this._actions.style.display = this._actionSlot.assignedElements().length ? '' : 'none';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setupEvents() {
|
|
47
|
+
this._onSlot = () => this._sync();
|
|
48
|
+
this._onClick = (e) => {
|
|
49
|
+
const btn = e.target.closest?.('button');
|
|
50
|
+
if (btn && this._actionSlot.assignedElements().includes(btn)) this.emit('nk-action', { action: btn.value || btn.textContent.trim(), value: btn.value || btn.textContent.trim() });
|
|
51
|
+
};
|
|
52
|
+
this._actionSlot.addEventListener('slotchange', this._onSlot);
|
|
53
|
+
this.addEventListener('click', this._onClick);
|
|
54
|
+
this._sync();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
teardownEvents() {
|
|
58
|
+
this._actionSlot?.removeEventListener('slotchange', this._onSlot);
|
|
59
|
+
this.removeEventListener('click', this._onClick);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onAttributeChanged() { this._sync(); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
customElements.define('nk-ai-msg', NkAiMsg);
|
|
66
|
+
|
|
67
|
+
export { NkAiMsg };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-ai-thread><nk-ai-msg …>…</nk-ai-msg>…</nk-ai-thread> → <div class="nk-ai-thread">
|
|
4
|
+
class NkAiThread extends NkElement {
|
|
5
|
+
render() {
|
|
6
|
+
const box = this.createElement('div', ['nk-ai-thread'], { role: 'log', 'aria-live': 'polite' });
|
|
7
|
+
box.appendChild(document.createElement('slot'));
|
|
8
|
+
this._wrapper.appendChild(box);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
customElements.define('nk-ai-thread', NkAiThread);
|
|
13
|
+
|
|
14
|
+
export { NkAiThread };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-app>
|
|
4
|
+
// <nk-sidebar slot="sidebar">…</nk-sidebar>
|
|
5
|
+
// <nk-topbar>…</nk-topbar>
|
|
6
|
+
// <nk-page>…</nk-page>
|
|
7
|
+
// </nk-app>
|
|
8
|
+
// → <div class="nk-app"><aside class="nk-sidebar">…</aside><main class="nk-main">…</main></div>
|
|
9
|
+
// The outermost element of a workspace app: a full-height flex row.
|
|
10
|
+
class NkApp extends NkElement {
|
|
11
|
+
render() {
|
|
12
|
+
const app = this.createElement('div', ['nk-app']);
|
|
13
|
+
app.appendChild(this.createElement('slot', [], { name: 'sidebar' }));
|
|
14
|
+
const main = this.createElement('main', ['nk-main']);
|
|
15
|
+
main.appendChild(document.createElement('slot'));
|
|
16
|
+
app.appendChild(main);
|
|
17
|
+
this._wrapper.appendChild(app);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
customElements.define('nk-app', NkApp);
|
|
22
|
+
|
|
23
|
+
export { NkApp };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-avatar-group more="+2">
|
|
4
|
+
// <span class="mini-avatar" style="background:#448361">SL</span>…
|
|
5
|
+
// </nk-avatar-group>
|
|
6
|
+
// → <div class="nk-avatar-group">…<span class="mini-avatar more">+2</span></div>
|
|
7
|
+
// The avatars stay in the light DOM (::slotted twins size and overlap them);
|
|
8
|
+
// the "more" bubble is rendered here from the attribute.
|
|
9
|
+
class NkAvatarGroup extends NkElement {
|
|
10
|
+
static get observedAttributes() { return ['more']; }
|
|
11
|
+
|
|
12
|
+
render() {
|
|
13
|
+
const box = this.createElement('div', ['nk-avatar-group']);
|
|
14
|
+
box.appendChild(document.createElement('slot'));
|
|
15
|
+
this._more = this.createElement('span', ['mini-avatar', 'more']);
|
|
16
|
+
box.appendChild(this._more);
|
|
17
|
+
this._wrapper.appendChild(box);
|
|
18
|
+
this._sync();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_sync() {
|
|
22
|
+
const more = this.getAttribute('more');
|
|
23
|
+
this._more.textContent = more || '';
|
|
24
|
+
this._more.style.display = more ? '' : 'none';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
onAttributeChanged() { this._sync(); }
|
|
28
|
+
|
|
29
|
+
get more() { return this.getAttribute('more'); }
|
|
30
|
+
set more(v) { v == null ? this.removeAttribute('more') : this.setAttribute('more', v); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
customElements.define('nk-avatar-group', NkAvatarGroup);
|
|
34
|
+
|
|
35
|
+
export { NkAvatarGroup };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-banner variant="info">ℹ️ <span>Text</span><span slot="action">Open</span></nk-banner>
|
|
4
|
+
// → <div class="nk-banner info">…<span class="b-action">Open</span></div>
|
|
5
|
+
const VARIANTS = ['info', 'success', 'warning'];
|
|
6
|
+
|
|
7
|
+
class NkBanner extends NkElement {
|
|
8
|
+
static get observedAttributes() { return ['variant']; }
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
this._box = this.createElement('div', this._classes(), { role: 'status' });
|
|
12
|
+
this._box.appendChild(document.createElement('slot'));
|
|
13
|
+
this._box.appendChild(this.createElement('slot', [], { name: 'action' }));
|
|
14
|
+
this._wrapper.appendChild(this._box);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_classes() {
|
|
18
|
+
const v = this.getAttribute('variant');
|
|
19
|
+
return ['nk-banner', ...(VARIANTS.includes(v) ? [v] : [])];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onAttributeChanged() { if (this._box) this._box.className = this._classes().join(' '); }
|
|
23
|
+
|
|
24
|
+
get variant() { return this.getAttribute('variant'); }
|
|
25
|
+
set variant(v) { v ? this.setAttribute('variant', v) : this.removeAttribute('variant'); }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
customElements.define('nk-banner', NkBanner);
|
|
29
|
+
|
|
30
|
+
export { NkBanner };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-block-host handle><p>Editable content lives here.</p></nk-block-host>
|
|
4
|
+
// → <div class="nk-block-host"><span class="nk-block-handle">⠿</span>…</div>
|
|
5
|
+
// The optical shell for editor content (hover wash, focus ring, drop target).
|
|
6
|
+
// Mount an editor into the light DOM; <nk-editor> (v1.1) does that for TipTap.
|
|
7
|
+
class NkBlockHost extends NkElement {
|
|
8
|
+
static get observedAttributes() { return ['handle', 'drop-target']; }
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
this._box = this.createElement('div', ['nk-block-host']);
|
|
12
|
+
this._handle = this.createElement('span', ['nk-block-handle'], { 'aria-hidden': 'true' });
|
|
13
|
+
this._handle.textContent = '⠿';
|
|
14
|
+
this._box.appendChild(this._handle);
|
|
15
|
+
this._box.appendChild(document.createElement('slot'));
|
|
16
|
+
this._wrapper.appendChild(this._box);
|
|
17
|
+
this._sync();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_sync() {
|
|
21
|
+
this._handle.style.display = this.getBoolAttr('handle') ? '' : 'none';
|
|
22
|
+
this._box.classList.toggle('nk-drop-target', this.getBoolAttr('drop-target'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onAttributeChanged() { this._sync(); }
|
|
26
|
+
|
|
27
|
+
get handle() { return this.getBoolAttr('handle'); }
|
|
28
|
+
set handle(v) { this.setBoolAttr('handle', v); }
|
|
29
|
+
get dropTarget() { return this.getBoolAttr('drop-target'); }
|
|
30
|
+
set dropTarget(v) { this.setBoolAttr('drop-target', v); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
customElements.define('nk-block-host', NkBlockHost);
|
|
34
|
+
|
|
35
|
+
export { NkBlockHost };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
import { t as tagFor, r as renderPropertyCell } from './shared/property-cell-Dn91uFkf.js';
|
|
3
|
+
|
|
4
|
+
// <nk-board-view name="board" label="▤ Board" group-by="status" new-row></nk-board-view>
|
|
5
|
+
// Groups rows by a select column (default: the first select column); one
|
|
6
|
+
// column per option, in option order. Cards show the title column and the
|
|
7
|
+
// `meta-keys` (default: date and progress columns). Drag a card onto another
|
|
8
|
+
// column to change its value – the row is updated and nk-change fires.
|
|
9
|
+
// → <div class="nk-board active"><div class="nk-board-col">…</div>…</div>
|
|
10
|
+
class NkBoardView extends NkElement {
|
|
11
|
+
static get observedAttributes() { return ['name', 'label', 'badge', 'count', 'group-by', 'title-key', 'meta-keys', 'new-row']; }
|
|
12
|
+
|
|
13
|
+
render() {
|
|
14
|
+
this._board = this.createElement('div', ['nk-board', 'active']);
|
|
15
|
+
this._wrapper.appendChild(this._board);
|
|
16
|
+
this._columns ??= []; this._rows ??= [];
|
|
17
|
+
this._render();
|
|
18
|
+
this.closest('nk-database')?.requestSync?.();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
_groupColumn() {
|
|
22
|
+
const key = this.getAttribute('group-by');
|
|
23
|
+
return this._columns.find(c => c.key === key) || this._columns.find(c => c.type === 'select') || null;
|
|
24
|
+
}
|
|
25
|
+
_titleColumn() {
|
|
26
|
+
const key = this.getAttribute('title-key');
|
|
27
|
+
return this._columns.find(c => c.key === key) || this._columns.find(c => c.title) || this._columns[0] || null;
|
|
28
|
+
}
|
|
29
|
+
_metaColumns() {
|
|
30
|
+
const keys = (this.getAttribute('meta-keys') || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
31
|
+
if (keys.length) return keys.map(k => this._columns.find(c => c.key === k)).filter(Boolean);
|
|
32
|
+
return this._columns.filter(c => c.type === 'date' || c.type === 'progress');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_render() {
|
|
36
|
+
if (!this._board) return;
|
|
37
|
+
this._board.replaceChildren();
|
|
38
|
+
const group = this._groupColumn(), title = this._titleColumn();
|
|
39
|
+
if (!group) return;
|
|
40
|
+
const options = group.options?.length ? group.options : [...new Set(this._rows.map(r => r[group.key]))].map(v => ({ value: v, label: String(v) }));
|
|
41
|
+
for (const opt of options) {
|
|
42
|
+
const col = this.createElement('div', ['nk-board-col'], { 'data-value': opt.value });
|
|
43
|
+
const header = this.createElement('div', ['nk-board-col-header']);
|
|
44
|
+
const cards = this._rows.filter(r => r[group.key] === opt.value || r[group.key] === opt.label);
|
|
45
|
+
const count = this.createElement('span', ['count']);
|
|
46
|
+
count.textContent = String(cards.length);
|
|
47
|
+
header.append(tagFor(group, opt.value), count);
|
|
48
|
+
col.appendChild(header);
|
|
49
|
+
for (const row of cards) {
|
|
50
|
+
const card = this.createElement('div', ['nk-card'], { draggable: 'true', 'data-id': row.id ?? '', tabindex: '0' });
|
|
51
|
+
const t = this.createElement('div', ['card-title']);
|
|
52
|
+
t.textContent = `${row.icon ? row.icon + ' ' : ''}${title ? row[title.key] ?? '' : ''}`;
|
|
53
|
+
const meta = this.createElement('div', ['card-meta']);
|
|
54
|
+
for (const c of this._metaColumns()) {
|
|
55
|
+
const v = row[c.key];
|
|
56
|
+
if (v === undefined || v === null || v === '') continue;
|
|
57
|
+
const s = document.createElement('span');
|
|
58
|
+
if (c.type === 'progress') s.textContent = `▰ ${v}%`;
|
|
59
|
+
else if (c.type === 'date') s.textContent = `📅 ${v}`;
|
|
60
|
+
else s.appendChild(renderPropertyCell(c, v, row));
|
|
61
|
+
meta.appendChild(s);
|
|
62
|
+
}
|
|
63
|
+
card.append(t, meta);
|
|
64
|
+
col.appendChild(card);
|
|
65
|
+
}
|
|
66
|
+
if (this.getBoolAttr('new-row')) {
|
|
67
|
+
const add = this.createElement('div', ['nk-new-row'], { role: 'button', tabindex: '0', 'data-add': opt.value });
|
|
68
|
+
add.style.padding = '6px 10px';
|
|
69
|
+
add.textContent = '+';
|
|
70
|
+
col.appendChild(add);
|
|
71
|
+
}
|
|
72
|
+
this._board.appendChild(col);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setupEvents() {
|
|
77
|
+
this._onClick = (e) => {
|
|
78
|
+
const add = e.target.closest('[data-add]');
|
|
79
|
+
if (add) { this.emit('nk-action', { action: 'new-row', value: add.dataset.add }); return; }
|
|
80
|
+
const card = e.target.closest('.nk-card');
|
|
81
|
+
if (card) { const row = this._rowById(card.dataset.id); this.emit('nk-select', { row, id: row?.id, value: row?.id }); }
|
|
82
|
+
};
|
|
83
|
+
this._onDragStart = (e) => { const card = e.target.closest?.('.nk-card'); if (!card) return; this._dragId = card.dataset.id; e.dataTransfer.effectAllowed = 'move'; try { e.dataTransfer.setData('text/plain', card.dataset.id); } catch {} };
|
|
84
|
+
this._onDragOver = (e) => { if (this._dragId != null && e.target.closest('.nk-board-col')) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; } };
|
|
85
|
+
this._onDrop = (e) => {
|
|
86
|
+
const col = e.target.closest('.nk-board-col');
|
|
87
|
+
if (!col || this._dragId == null) return;
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
this.move(this._dragId, col.dataset.value);
|
|
90
|
+
this._dragId = null;
|
|
91
|
+
};
|
|
92
|
+
this._board.addEventListener('click', this._onClick);
|
|
93
|
+
this._board.addEventListener('dragstart', this._onDragStart);
|
|
94
|
+
this._board.addEventListener('dragover', this._onDragOver);
|
|
95
|
+
this._board.addEventListener('drop', this._onDrop);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
teardownEvents() {
|
|
99
|
+
this._board?.removeEventListener('click', this._onClick);
|
|
100
|
+
this._board?.removeEventListener('dragstart', this._onDragStart);
|
|
101
|
+
this._board?.removeEventListener('dragover', this._onDragOver);
|
|
102
|
+
this._board?.removeEventListener('drop', this._onDrop);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Moves a row into another column (sets the group value) and reports it. */
|
|
106
|
+
move(id, value) {
|
|
107
|
+
const row = this._rowById(id), group = this._groupColumn();
|
|
108
|
+
if (!row || !group || row[group.key] === value) return;
|
|
109
|
+
row[group.key] = value;
|
|
110
|
+
this._render();
|
|
111
|
+
this.emit('nk-change', { row, key: group.key, value });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
|
|
115
|
+
onAttributeChanged() { this._render(); }
|
|
116
|
+
|
|
117
|
+
get name() { return this.getAttribute('name') || 'board'; }
|
|
118
|
+
get columns() { return this._columns; }
|
|
119
|
+
set columns(v) { this._columns = Array.isArray(v) ? v : []; this._render(); }
|
|
120
|
+
get rows() { return this._rows; }
|
|
121
|
+
set rows(v) { this._rows = Array.isArray(v) ? v : []; this._render(); }
|
|
122
|
+
set data({ columns, rows }) { this.setData(columns, rows); }
|
|
123
|
+
setData(columns, rows) { this._columns = columns || []; this._rows = rows || []; this._render(); }
|
|
124
|
+
refresh() { this._render(); }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
customElements.define('nk-board-view', NkBoardView);
|
|
128
|
+
|
|
129
|
+
export { NkBoardView };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { N as NkElement } from './shared/base-6gEcoatG.js';
|
|
2
|
+
|
|
3
|
+
// <nk-breadcrumb>
|
|
4
|
+
// <a href="/overview">📊 Project overview</a>
|
|
5
|
+
// <span>🚀 NotionKit MVP</span>
|
|
6
|
+
// </nk-breadcrumb>
|
|
7
|
+
// → <nav class="nk-breadcrumb"><a class="crumb">…</a><span class="sep">/</span><span class="crumb current">…</span></nav>
|
|
8
|
+
//
|
|
9
|
+
// The children are *cloned* into the shadow tree so the separators can be
|
|
10
|
+
// interleaved and `.crumb:hover` (no ::slotted twin) applies. A
|
|
11
|
+
// MutationObserver keeps the copy in step. Clicking a clone fires nk-select
|
|
12
|
+
// and, unless prevented, forwards the click to the original – so an <a>
|
|
13
|
+
// navigates exactly once and framework listeners see it.
|
|
14
|
+
class NkBreadcrumb extends NkElement {
|
|
15
|
+
static get observedAttributes() { return ['separator']; }
|
|
16
|
+
static get observesLightDom() { return true; }
|
|
17
|
+
|
|
18
|
+
render() {
|
|
19
|
+
this._nav = this.createElement('nav', ['nk-breadcrumb'], { 'aria-label': 'Breadcrumb' });
|
|
20
|
+
this._wrapper.appendChild(this._nav);
|
|
21
|
+
this.projectLightDom();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
projectLightDom() {
|
|
25
|
+
if (!this._nav) return;
|
|
26
|
+
const originals = [...this.children];
|
|
27
|
+
const sep = this.getAttribute('separator') ?? '/';
|
|
28
|
+
const signature = originals.map(o => o.outerHTML).join('') + sep;
|
|
29
|
+
if (signature === this._signature) return;
|
|
30
|
+
this._signature = signature;
|
|
31
|
+
this._nav.innerHTML = '';
|
|
32
|
+
const explicitCurrent = originals.some(o => o.hasAttribute('current') || o.classList.contains('current'));
|
|
33
|
+
originals.forEach((original, i) => {
|
|
34
|
+
if (i > 0) {
|
|
35
|
+
const s = this.createElement('span', ['sep'], { 'aria-hidden': 'true' });
|
|
36
|
+
s.textContent = sep;
|
|
37
|
+
this._nav.appendChild(s);
|
|
38
|
+
}
|
|
39
|
+
const clone = original.cloneNode(true);
|
|
40
|
+
clone.removeAttribute('slot');
|
|
41
|
+
clone.classList.add('crumb');
|
|
42
|
+
const current = explicitCurrent ? (original.hasAttribute('current') || original.classList.contains('current')) : i === originals.length - 1;
|
|
43
|
+
clone.classList.toggle('current', current);
|
|
44
|
+
if (current) clone.setAttribute('aria-current', 'page');
|
|
45
|
+
clone.dataset.index = i;
|
|
46
|
+
this._nav.appendChild(clone);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
setupEvents() {
|
|
51
|
+
this._onClick = (e) => {
|
|
52
|
+
const clone = e.target.closest('.crumb');
|
|
53
|
+
if (!clone) return;
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
const index = Number(clone.dataset.index);
|
|
56
|
+
// Look the original up at click time: a framework may have replaced it.
|
|
57
|
+
const original = this.children[index];
|
|
58
|
+
const ok = this.emit('nk-select', {
|
|
59
|
+
index, label: clone.textContent.trim(), href: original?.getAttribute('href') ?? null,
|
|
60
|
+
value: original?.dataset.value ?? original?.getAttribute('href') ?? clone.textContent.trim(),
|
|
61
|
+
current: clone.classList.contains('current'),
|
|
62
|
+
});
|
|
63
|
+
if (ok) original?.click();
|
|
64
|
+
};
|
|
65
|
+
this._nav.addEventListener('click', this._onClick);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
teardownEvents() { this._nav?.removeEventListener('click', this._onClick); }
|
|
69
|
+
|
|
70
|
+
onAttributeChanged() { this.projectLightDom(); }
|
|
71
|
+
|
|
72
|
+
get separator() { return this.getAttribute('separator') ?? '/'; }
|
|
73
|
+
set separator(v) { this.setAttribute('separator', v); }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
customElements.define('nk-breadcrumb', NkBreadcrumb);
|
|
77
|
+
|
|
78
|
+
export { NkBreadcrumb };
|