@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/src/base.js
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NotionKit Elements – base classes
|
|
3
|
+
//
|
|
4
|
+
// Every <nk-*> element is a thin shell around the markup notionkit.css already
|
|
5
|
+
// styles. The shadow root adopts the *components* half of that stylesheet and
|
|
6
|
+
// nothing else; the element ships no visual CSS of its own.
|
|
7
|
+
// ============================================================
|
|
8
|
+
import { componentsSheet, tokensCss } from '@jungherz-de/notionkit/notionkit-styles.js';
|
|
9
|
+
|
|
10
|
+
// ── Design tokens ──
|
|
11
|
+
// The shadow roots deliberately adopt componentsSheet only. The full sheet
|
|
12
|
+
// would bring the :root / [data-theme] token blocks along, and those match the
|
|
13
|
+
// .nk-wrapper below — every --nk-* token would then be re-declared inside each
|
|
14
|
+
// shadow root, where a matching rule always beats an inherited value. A page's
|
|
15
|
+
// own `:root { --nk-accent: … }` would never arrive.
|
|
16
|
+
//
|
|
17
|
+
// So the token defaults go on the document once, and every shadow root
|
|
18
|
+
// inherits them like any other custom property. They sit in a cascade layer so
|
|
19
|
+
// an ordinary (unlayered) brand stylesheet — and notionkit.css itself, if the
|
|
20
|
+
// page loads it — wins over them, no matter the load order.
|
|
21
|
+
|
|
22
|
+
const TOKENS_INJECTED = '__nkDefaultTokensInjected';
|
|
23
|
+
|
|
24
|
+
function injectDefaultTokens() {
|
|
25
|
+
if (typeof document === 'undefined') return; // SSR / non-DOM
|
|
26
|
+
if (globalThis[TOKENS_INJECTED]) return; // another bundle copy did it
|
|
27
|
+
globalThis[TOKENS_INJECTED] = true;
|
|
28
|
+
|
|
29
|
+
const sheet = new CSSStyleSheet();
|
|
30
|
+
sheet.replaceSync(`@layer notionkit-defaults { ${tokensCss} }`);
|
|
31
|
+
// Append — never assign — so an app's own adopted sheets survive.
|
|
32
|
+
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
injectDefaultTokens();
|
|
36
|
+
|
|
37
|
+
// ── Global theme sync ──
|
|
38
|
+
// data-theme on <html> is the only source of truth. One MutationObserver
|
|
39
|
+
// mirrors it onto every registered instance; no element sets a theme itself.
|
|
40
|
+
|
|
41
|
+
const instances = new Set();
|
|
42
|
+
|
|
43
|
+
function getCurrentTheme() {
|
|
44
|
+
return document.documentElement.getAttribute('data-theme') || 'light';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function syncAllThemes() {
|
|
48
|
+
const theme = getCurrentTheme();
|
|
49
|
+
for (const instance of instances) instance._syncTheme(theme);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') {
|
|
53
|
+
new MutationObserver(syncAllThemes).observe(document.documentElement, {
|
|
54
|
+
attributes: true,
|
|
55
|
+
attributeFilter: ['data-theme']
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Host stylesheets ──
|
|
60
|
+
// Layout plumbing only: how the host box participates in the outer layout, and
|
|
61
|
+
// that `hidden` keeps working. :host([hidden]) has a higher specificity than
|
|
62
|
+
// :host, so it wins regardless of order. The wrapper is layout-transparent.
|
|
63
|
+
|
|
64
|
+
const hostSheets = new Map();
|
|
65
|
+
|
|
66
|
+
function hostSheetFor(display) {
|
|
67
|
+
let sheet = hostSheets.get(display);
|
|
68
|
+
if (!sheet) {
|
|
69
|
+
sheet = new CSSStyleSheet();
|
|
70
|
+
sheet.replaceSync(`
|
|
71
|
+
:host { display: ${display}; }
|
|
72
|
+
:host([hidden]) { display: none; }
|
|
73
|
+
.nk-wrapper { display: contents; }
|
|
74
|
+
`);
|
|
75
|
+
hostSheets.set(display, sheet);
|
|
76
|
+
}
|
|
77
|
+
return sheet;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Base class ──
|
|
81
|
+
|
|
82
|
+
export class NkElement extends HTMLElement {
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* How the host takes part in the outer layout. Default 'contents': the host
|
|
86
|
+
* generates no box, so the inner .nk-* element sits in the parent's layout
|
|
87
|
+
* exactly where the class markup would – a button stays inline, a sidebar
|
|
88
|
+
* is a direct flex child of the app, a tree row and its children box are
|
|
89
|
+
* siblings. That is what makes the pixel parity with the class markup hold.
|
|
90
|
+
* Consequence: style the parent or the tokens, not the host; `hidden` on
|
|
91
|
+
* the host still works. Override with 'block' / 'inline-block' only when a
|
|
92
|
+
* host box is genuinely needed.
|
|
93
|
+
*/
|
|
94
|
+
static get display() { return 'contents'; }
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Optional CSSStyleSheet with component-specific host rules, adopted after
|
|
98
|
+
* the shared sheets. Positioning and sizing of the host only — never colour,
|
|
99
|
+
* spacing or typography; those belong to notionkit.css.
|
|
100
|
+
*/
|
|
101
|
+
static get hostStyles() { return null; }
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Opt in when the component *copies* light-DOM children into its shadow tree
|
|
105
|
+
* (options, breadcrumb crumbs). A MutationObserver then calls
|
|
106
|
+
* projectLightDom() again whenever those children change, so a framework
|
|
107
|
+
* that swaps them keeps the rendered element in step. Components that merely
|
|
108
|
+
* *slot* their children use `slotchange` instead.
|
|
109
|
+
*/
|
|
110
|
+
static get observesLightDom() { return false; }
|
|
111
|
+
|
|
112
|
+
/** What the light-DOM observer watches. Drop `subtree` for shallow copies. */
|
|
113
|
+
static get lightDomObserverInit() {
|
|
114
|
+
return { childList: true, subtree: true, characterData: true, attributes: true };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 'named' (default) or 'manual'. Manual assignment lets a component route
|
|
119
|
+
* light-DOM nodes itself – text nodes into a label slot, nested elements
|
|
120
|
+
* into a children slot – so <nk-tree-item icon="🏠">Home</nk-tree-item>
|
|
121
|
+
* reads naturally. With 'manual' the component calls assignSlots() on
|
|
122
|
+
* every childList change.
|
|
123
|
+
*/
|
|
124
|
+
static get slotAssignment() { return 'named'; }
|
|
125
|
+
|
|
126
|
+
static get observedAttributes() { return []; }
|
|
127
|
+
|
|
128
|
+
constructor() {
|
|
129
|
+
super();
|
|
130
|
+
this._initialized = false;
|
|
131
|
+
this._shadow = this.attachShadow({ mode: 'open', slotAssignment: this.constructor.slotAssignment });
|
|
132
|
+
const sheets = [componentsSheet, hostSheetFor(this.constructor.display)];
|
|
133
|
+
const extra = this.constructor.hostStyles;
|
|
134
|
+
if (extra) sheets.push(extra);
|
|
135
|
+
this._shadow.adoptedStyleSheets = sheets;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
connectedCallback() {
|
|
139
|
+
if (!this._initialized) {
|
|
140
|
+
this._initialized = true;
|
|
141
|
+
|
|
142
|
+
// The wrapper mirrors data-theme from <html> so [data-theme]-keyed
|
|
143
|
+
// component rules apply inside the root. Token *values* need no
|
|
144
|
+
// mirroring — they inherit.
|
|
145
|
+
this._wrapper = document.createElement('div');
|
|
146
|
+
this._wrapper.className = 'nk-wrapper';
|
|
147
|
+
this._wrapper.setAttribute('data-theme', getCurrentTheme());
|
|
148
|
+
this._shadow.appendChild(this._wrapper);
|
|
149
|
+
|
|
150
|
+
this.render();
|
|
151
|
+
} else {
|
|
152
|
+
this._wrapper.setAttribute('data-theme', getCurrentTheme());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Everything below runs on every connect, not just the first. Moving an
|
|
156
|
+
// element in the DOM disconnects and reconnects it, and disconnectedCallback
|
|
157
|
+
// tears all of this down — without re-arming it here a moved element would
|
|
158
|
+
// keep its markup but silently stop reacting.
|
|
159
|
+
this.setupEvents();
|
|
160
|
+
instances.add(this);
|
|
161
|
+
|
|
162
|
+
if (this.constructor.observesLightDom) {
|
|
163
|
+
this._lightDomObserver ??= new MutationObserver(records => this.projectLightDom(records));
|
|
164
|
+
this._lightDomObserver.observe(this, this.constructor.lightDomObserverInit);
|
|
165
|
+
}
|
|
166
|
+
if (this.constructor.slotAssignment === 'manual') {
|
|
167
|
+
this.assignSlots();
|
|
168
|
+
this._slotObserver ??= new MutationObserver(() => this.assignSlots());
|
|
169
|
+
this._slotObserver.observe(this, { childList: true });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
disconnectedCallback() {
|
|
174
|
+
instances.delete(this);
|
|
175
|
+
this._lightDomObserver?.disconnect();
|
|
176
|
+
this._slotObserver?.disconnect();
|
|
177
|
+
this.teardownEvents();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
181
|
+
if (!this._initialized) return;
|
|
182
|
+
if (oldValue === newValue) return;
|
|
183
|
+
this.onAttributeChanged(name, oldValue, newValue);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_syncTheme(theme) {
|
|
187
|
+
this._wrapper?.setAttribute('data-theme', theme);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Subclasses override to build the inner DOM inside this._wrapper. Runs once. */
|
|
191
|
+
render() {}
|
|
192
|
+
|
|
193
|
+
/** Subclasses override to attach event listeners. Runs on every connect. */
|
|
194
|
+
setupEvents() {}
|
|
195
|
+
|
|
196
|
+
/** Subclasses override to remove event listeners. Runs on every disconnect. */
|
|
197
|
+
teardownEvents() {}
|
|
198
|
+
|
|
199
|
+
/** Subclasses override to react to attribute changes after render(). */
|
|
200
|
+
onAttributeChanged(name, oldValue, newValue) {}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Subclasses that set observesLightDom override this to (re-)copy their
|
|
204
|
+
* light-DOM children into the shadow tree. Runs on every change to those
|
|
205
|
+
* children, so it has to be safe to call repeatedly.
|
|
206
|
+
*/
|
|
207
|
+
projectLightDom() {}
|
|
208
|
+
|
|
209
|
+
/** Components with manual slot assignment override this. */
|
|
210
|
+
assignSlots() {}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Escape hatch: re-copy the light-DOM children now. The observer covers the
|
|
214
|
+
* ordinary cases; this is for the ones it cannot see, so nobody has to reach
|
|
215
|
+
* into element.shadowRoot.
|
|
216
|
+
*/
|
|
217
|
+
refresh() { this.projectLightDom(); if (this.constructor.slotAssignment === 'manual') this.assignSlots(); }
|
|
218
|
+
|
|
219
|
+
// ── Utilities ──
|
|
220
|
+
|
|
221
|
+
getBoolAttr(name) {
|
|
222
|
+
return this.hasAttribute(name);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
setBoolAttr(name, value) {
|
|
226
|
+
if (value) this.setAttribute(name, '');
|
|
227
|
+
else this.removeAttribute(name);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
createElement(tag, classes = [], attrs = {}) {
|
|
231
|
+
const el = document.createElement(tag);
|
|
232
|
+
if (classes.length) el.classList.add(...classes);
|
|
233
|
+
for (const [key, val] of Object.entries(attrs)) {
|
|
234
|
+
if (val !== null && val !== undefined && val !== false) el.setAttribute(key, val === true ? '' : val);
|
|
235
|
+
}
|
|
236
|
+
return el;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Dispatches a composed, bubbling CustomEvent — the nk-* event contract. */
|
|
240
|
+
emit(eventName, detail = null) {
|
|
241
|
+
return this.dispatchEvent(new CustomEvent(eventName, {
|
|
242
|
+
bubbles: true,
|
|
243
|
+
composed: true,
|
|
244
|
+
cancelable: true,
|
|
245
|
+
detail
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Form-associated base class ──
|
|
251
|
+
// ElementInternals gives the element a place in the surrounding <form>:
|
|
252
|
+
// FormData, reset, validation and :disabled through <fieldset>.
|
|
253
|
+
|
|
254
|
+
export class NkFormElement extends NkElement {
|
|
255
|
+
|
|
256
|
+
static formAssociated = true;
|
|
257
|
+
|
|
258
|
+
constructor() {
|
|
259
|
+
super();
|
|
260
|
+
this._internals = this.attachInternals();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
get form() { return this._internals.form; }
|
|
264
|
+
get validationMessage() { return this._internals.validationMessage; }
|
|
265
|
+
get validity() { return this._internals.validity; }
|
|
266
|
+
get willValidate() { return this._internals.willValidate; }
|
|
267
|
+
|
|
268
|
+
checkValidity() { return this._internals.checkValidity(); }
|
|
269
|
+
reportValidity() { return this._internals.reportValidity(); }
|
|
270
|
+
|
|
271
|
+
formResetCallback() { this.resetValue(); }
|
|
272
|
+
formStateRestoreCallback(state, mode) { this.restoreValue(state, mode); }
|
|
273
|
+
formDisabledCallback(disabled) { this.onFormDisabled(disabled); }
|
|
274
|
+
|
|
275
|
+
/** Subclasses override. */
|
|
276
|
+
resetValue() {}
|
|
277
|
+
restoreValue(state, mode) {}
|
|
278
|
+
onFormDisabled(disabled) {}
|
|
279
|
+
|
|
280
|
+
setFormValue(value, state) {
|
|
281
|
+
this._internals.setFormValue(value, state);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
setValidity(flags, message, anchor) {
|
|
285
|
+
this._internals.setValidity(flags, message, anchor);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Mirrors the native control's validity onto the host, so required works. */
|
|
289
|
+
syncValidityFrom(control) {
|
|
290
|
+
if (!control) return;
|
|
291
|
+
if (control.validity.valid) this._internals.setValidity({});
|
|
292
|
+
else this._internals.setValidity(control.validity, control.validationMessage, control);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export { getCurrentTheme };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-callout icon="💡">text</nk-callout>
|
|
4
|
+
// <nk-callout><span slot="icon">📌</span>text</nk-callout>
|
|
5
|
+
// The .c-icon rule has ::slotted(.c-icon) / ::slotted([slot="icon"]) twins in
|
|
6
|
+
// notionkit.css – those size an icon passed in from the light DOM. Pass the
|
|
7
|
+
// icon node itself, never wrapped: ::slotted() only matches the assigned node.
|
|
8
|
+
class NkCallout extends NkElement {
|
|
9
|
+
static get observedAttributes() { return ['icon']; }
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
this._box = this.createElement('div', ['nk-callout']);
|
|
13
|
+
this._icon = this.createElement('span', ['c-icon']);
|
|
14
|
+
const iconSlot = this.createElement('slot', [], { name: 'icon' });
|
|
15
|
+
iconSlot.appendChild(this._icon);
|
|
16
|
+
const body = document.createElement('div');
|
|
17
|
+
body.appendChild(document.createElement('slot'));
|
|
18
|
+
this._box.appendChild(iconSlot);
|
|
19
|
+
this._box.appendChild(body);
|
|
20
|
+
this._wrapper.appendChild(this._box);
|
|
21
|
+
this._syncIcon();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onAttributeChanged() { this._syncIcon(); }
|
|
25
|
+
|
|
26
|
+
_syncIcon() {
|
|
27
|
+
if (this._icon) this._icon.textContent = this.getAttribute('icon') || '💡';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get icon() { return this.getAttribute('icon'); }
|
|
31
|
+
set icon(v) { v == null ? this.removeAttribute('icon') : this.setAttribute('icon', v); }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
customElements.define('nk-callout', NkCallout);
|
|
35
|
+
export { NkCallout };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
import { highlightHtml } from '../../util/highlight.js';
|
|
3
|
+
|
|
4
|
+
// <nk-code lang="html"><div class="nk-btn">…</nk-code>
|
|
5
|
+
// The block keeps whatever whitespace it is given (white-space: pre). Text
|
|
6
|
+
// content is slotted as-is; add `highlight` to colour HTML tags/attributes,
|
|
7
|
+
// which copies the text into the shadow tree (light DOM is then ignored).
|
|
8
|
+
class NkCode extends NkElement {
|
|
9
|
+
static get observedAttributes() { return ['lang', 'highlight']; }
|
|
10
|
+
static get observesLightDom() { return true; }
|
|
11
|
+
|
|
12
|
+
render() {
|
|
13
|
+
this._box = this.createElement('div', ['nk-code']);
|
|
14
|
+
this._lang = this.createElement('span', ['lang']);
|
|
15
|
+
this._slot = document.createElement('slot');
|
|
16
|
+
this._pre = this.createElement('span', ['hl']);
|
|
17
|
+
this._box.appendChild(this._lang);
|
|
18
|
+
this._box.appendChild(this._slot);
|
|
19
|
+
this._box.appendChild(this._pre);
|
|
20
|
+
this._wrapper.appendChild(this._box);
|
|
21
|
+
this._sync();
|
|
22
|
+
this.projectLightDom();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onAttributeChanged() { this._sync(); this.projectLightDom(); }
|
|
26
|
+
|
|
27
|
+
_sync() {
|
|
28
|
+
if (!this._lang) return;
|
|
29
|
+
const lang = this.getAttribute('lang');
|
|
30
|
+
this._lang.textContent = lang || '';
|
|
31
|
+
this._lang.style.display = lang ? '' : 'none';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
projectLightDom() {
|
|
35
|
+
if (!this._pre) return;
|
|
36
|
+
const highlight = this.hasAttribute('highlight');
|
|
37
|
+
this._slot.style.display = highlight ? 'none' : '';
|
|
38
|
+
this._pre.style.display = highlight ? '' : 'none';
|
|
39
|
+
if (!highlight) return;
|
|
40
|
+
const text = this.textContent.replace(/^\n/, '');
|
|
41
|
+
if (text === this._lastText) return;
|
|
42
|
+
this._lastText = text;
|
|
43
|
+
this._pre.innerHTML = highlightHtml(text);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get lang() { return this.getAttribute('lang'); }
|
|
47
|
+
set lang(v) { v == null ? this.removeAttribute('lang') : this.setAttribute('lang', v); }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
customElements.define('nk-code', NkCode);
|
|
51
|
+
export { NkCode };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-divider></nk-divider> → <hr class="nk-divider">
|
|
4
|
+
class NkDivider extends NkElement {
|
|
5
|
+
render() {
|
|
6
|
+
this._wrapper.appendChild(this.createElement('hr', ['nk-divider']));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
customElements.define('nk-divider', NkDivider);
|
|
11
|
+
export { NkDivider };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-heading level="2">Section</nk-heading> → <h2 class="nk-heading">
|
|
4
|
+
// The heading level is real markup (h1–h4), so the outline stays honest.
|
|
5
|
+
class NkHeading extends NkElement {
|
|
6
|
+
static get observedAttributes() { return ['level']; }
|
|
7
|
+
|
|
8
|
+
render() {
|
|
9
|
+
this._build();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
_build() {
|
|
13
|
+
const level = Math.min(4, Math.max(1, Number(this.getAttribute('level')) || 2));
|
|
14
|
+
const h = this.createElement(`h${level}`, ['nk-heading']);
|
|
15
|
+
h.appendChild(document.createElement('slot'));
|
|
16
|
+
if (this._heading) this._heading.replaceWith(h);
|
|
17
|
+
else this._wrapper.appendChild(h);
|
|
18
|
+
this._heading = h;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onAttributeChanged() { this._build(); }
|
|
22
|
+
|
|
23
|
+
get level() { return Number(this.getAttribute('level')) || 2; }
|
|
24
|
+
set level(v) { this.setAttribute('level', v); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
customElements.define('nk-heading', NkHeading);
|
|
28
|
+
export { NkHeading };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-kbd>⌘</nk-kbd> <nk-kbd>K</nk-kbd> → <kbd class="nk-kbd">
|
|
4
|
+
class NkKbd extends NkElement {
|
|
5
|
+
|
|
6
|
+
render() {
|
|
7
|
+
const kbd = this.createElement('kbd', ['nk-kbd']);
|
|
8
|
+
kbd.appendChild(document.createElement('slot'));
|
|
9
|
+
this._wrapper.appendChild(kbd);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
customElements.define('nk-kbd', NkKbd);
|
|
14
|
+
export { NkKbd };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-progress value="72" label="72%"></nk-progress>
|
|
4
|
+
// The bar is `.nk-progress > i` — a child combinator, so both nodes live here.
|
|
5
|
+
class NkProgress extends NkElement {
|
|
6
|
+
static get observedAttributes() { return ['value', 'max', 'label']; }
|
|
7
|
+
|
|
8
|
+
render() {
|
|
9
|
+
this._bar = this.createElement('span', ['nk-progress'], { role: 'progressbar' });
|
|
10
|
+
this._fill = document.createElement('i');
|
|
11
|
+
this._bar.appendChild(this._fill);
|
|
12
|
+
this._label = this.createElement('span', ['nk-progress-label']);
|
|
13
|
+
this._wrapper.appendChild(this._bar);
|
|
14
|
+
this._wrapper.appendChild(this._label);
|
|
15
|
+
this._sync();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
onAttributeChanged() { this._sync(); }
|
|
19
|
+
|
|
20
|
+
_sync() {
|
|
21
|
+
if (!this._bar) return;
|
|
22
|
+
const max = Number(this.getAttribute('max')) || 100;
|
|
23
|
+
const value = Math.min(max, Math.max(0, Number(this.getAttribute('value')) || 0));
|
|
24
|
+
this._fill.style.width = `${(value / max) * 100}%`;
|
|
25
|
+
this._bar.setAttribute('aria-valuenow', value);
|
|
26
|
+
this._bar.setAttribute('aria-valuemin', 0);
|
|
27
|
+
this._bar.setAttribute('aria-valuemax', max);
|
|
28
|
+
const label = this.getAttribute('label');
|
|
29
|
+
this._label.textContent = label ?? '';
|
|
30
|
+
this._label.style.display = label === null ? 'none' : '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get value() { return Number(this.getAttribute('value')) || 0; }
|
|
34
|
+
set value(v) { this.setAttribute('value', v); }
|
|
35
|
+
get max() { return Number(this.getAttribute('max')) || 100; }
|
|
36
|
+
set max(v) { this.setAttribute('max', v); }
|
|
37
|
+
get label() { return this.getAttribute('label'); }
|
|
38
|
+
set label(v) { v == null ? this.removeAttribute('label') : this.setAttribute('label', v); }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
customElements.define('nk-progress', NkProgress);
|
|
42
|
+
export { NkProgress };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NkElement } from '../../base.js';
|
|
2
|
+
|
|
3
|
+
// <nk-quote cite="Ada Lovelace">The Analytical Engine weaves…</nk-quote>
|
|
4
|
+
// .q-cite has no ::slotted() twin, so the citation is rendered here.
|
|
5
|
+
class NkQuote extends NkElement {
|
|
6
|
+
static get observedAttributes() { return ['cite']; }
|
|
7
|
+
|
|
8
|
+
render() {
|
|
9
|
+
this._quote = this.createElement('blockquote', ['nk-quote']);
|
|
10
|
+
this._quote.appendChild(document.createElement('slot'));
|
|
11
|
+
this._cite = this.createElement('cite', ['q-cite']);
|
|
12
|
+
this._quote.appendChild(this._cite);
|
|
13
|
+
this._wrapper.appendChild(this._quote);
|
|
14
|
+
this._sync();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
onAttributeChanged() { this._sync(); }
|
|
18
|
+
|
|
19
|
+
_sync() {
|
|
20
|
+
if (!this._cite) return;
|
|
21
|
+
const cite = this.getAttribute('cite');
|
|
22
|
+
this._cite.textContent = cite || '';
|
|
23
|
+
this._cite.style.display = cite ? '' : 'none';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get cite() { return this.getAttribute('cite'); }
|
|
27
|
+
set cite(v) { v == null ? this.removeAttribute('cite') : this.setAttribute('cite', v); }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
customElements.define('nk-quote', NkQuote);
|
|
31
|
+
export { NkQuote };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NkElement } from '../../base.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
|
+
export { NkTag };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { NkFormElement } from '../../base.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
|
+
export { NkTodo };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NkElement } from '../../base.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
|
+
export { NkToggle };
|