@jungherz-de/notionkit-elements 1.10.1 → 1.11.1
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/CHANGELOG.md +641 -0
- package/README.md +6 -6
- package/SKILL.md +3748 -0
- package/dist/components/nk-ai-msg.js +6 -4
- package/dist/components/nk-avatar.js +1 -1
- package/dist/components/nk-banner.js +5 -2
- package/dist/components/nk-board-view.js +2 -2
- package/dist/components/nk-calendar.js +2 -2
- package/dist/components/nk-comment.js +1 -1
- package/dist/components/nk-dialog.js +14 -8
- package/dist/components/nk-fields.js +13 -3
- package/dist/components/nk-filter-bar.js +5 -3
- package/dist/components/nk-list-view.js +2 -2
- package/dist/components/nk-member-row.js +1 -1
- package/dist/components/nk-menu.js +2 -2
- package/dist/components/nk-page.js +3 -2
- package/dist/components/nk-select.js +15 -7
- package/dist/components/nk-switch.js +5 -1
- package/dist/components/nk-table-view.js +2 -2
- package/dist/components/nk-tooltip.js +17 -7
- package/dist/components/nk-tree-item.js +10 -4
- package/dist/components/shared/{avatar-BiqCaHOt.js → avatar-CzPZN3uP.js} +2 -2
- package/dist/components/shared/floating-CtqV6MBj.js +66 -0
- package/dist/components/shared/{property-cell-o7N30kAQ.js → property-cell-Cddz5n2O.js} +1 -1
- package/dist/notionkit-elements.esm.js +137 -56
- package/dist/notionkit-elements.js +137 -56
- package/dist/notionkit-elements.min.js +1 -1
- package/package.json +7 -4
- package/src/components/data/nk-ai-msg.js +6 -4
- package/src/components/data/nk-filter-bar.js +5 -3
- package/src/components/forms/nk-calendar.js +1 -1
- package/src/components/forms/nk-fields.js +13 -3
- package/src/components/forms/nk-select.js +15 -7
- package/src/components/forms/nk-switch.js +5 -1
- package/src/components/overlays/nk-dialog.js +14 -8
- package/src/components/overlays/nk-menu.js +1 -1
- package/src/components/overlays/nk-tooltip.js +16 -6
- package/src/components/page/nk-banner.js +5 -2
- package/src/components/page/nk-page.js +3 -2
- package/src/components/shell/nk-tree-item.js +10 -4
- package/src/util/avatar.js +2 -2
- package/src/util/floating.js +39 -10
- package/dist/components/shared/floating-CxHxy-Cb.js +0 -37
|
@@ -38,13 +38,15 @@ class NkAiMsg extends NkElement {
|
|
|
38
38
|
this._box.classList.toggle('user', user);
|
|
39
39
|
this._box.classList.toggle('bubble', bubble);
|
|
40
40
|
const name = this.getAttribute('name');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.
|
|
41
|
+
// Runs on slotchange: write text on a change only (see nk-switch).
|
|
42
|
+
if (this._nameText.data !== (name || '')) this._nameText.data = name || '';
|
|
43
|
+
const badge = this.getAttribute('badge'), badgeText = badge ? ` ${badge}` : '';
|
|
44
|
+
if (this._badge.textContent !== badgeText) this._badge.textContent = badgeText;
|
|
44
45
|
// A bubble stands without avatar and name; the text says who asked.
|
|
45
46
|
this._name.style.display = name && !bubble ? '' : 'none';
|
|
46
47
|
this._avatarSlot.style.display = bubble ? 'none' : '';
|
|
47
|
-
|
|
48
|
+
const avatar = this.getAttribute('avatar') || (user ? (name || 'U').slice(0, 2).toUpperCase() : '✨');
|
|
49
|
+
if (this._avatar.textContent !== avatar) this._avatar.textContent = avatar;
|
|
48
50
|
const color = this.getAttribute('color');
|
|
49
51
|
this._avatar.style.background = color || '';
|
|
50
52
|
this._actions.style.display = this._actionSlot.assignedElements().length ? '' : 'none';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { p as paintAvatar, i as initialsOf } from './shared/avatar-
|
|
2
|
+
import { p as paintAvatar, i as initialsOf } from './shared/avatar-CzPZN3uP.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
4
|
|
|
5
5
|
// <nk-avatar>MK</nk-avatar> → <span class="nk-avatar">MK</span>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
2
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
3
3
|
|
|
4
|
-
// <nk-banner variant="info">ℹ️ <span>Text</span><
|
|
5
|
-
// → <div class="nk-banner info">…<
|
|
4
|
+
// <nk-banner variant="info">ℹ️ <span>Text</span><button slot="action" type="button">Open</button></nk-banner>
|
|
5
|
+
// → <div class="nk-banner info">…<button class="b-action" type="button">Open</button></div>
|
|
6
|
+
//
|
|
7
|
+
// The action is a <button>, which NotionKit (1.11.0) strips down to
|
|
8
|
+
// underlined text; below 860px it moves under the text.
|
|
6
9
|
const VARIANTS = ['info', 'success', 'warning', 'danger'];
|
|
7
10
|
|
|
8
11
|
class NkBanner extends NkElement {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { t as tagFor, r as renderPropertyCell } from './shared/property-cell-
|
|
2
|
+
import { t as tagFor, r as renderPropertyCell } from './shared/property-cell-Cddz5n2O.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
|
-
import './shared/avatar-
|
|
4
|
+
import './shared/avatar-CzPZN3uP.js';
|
|
5
5
|
|
|
6
6
|
// <nk-board-view name="board" label="▤ Board" group-by="status" new-row></nk-board-view>
|
|
7
7
|
// Groups rows by a select column (default: the first select column); one
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NkFormElement } from './base.js';
|
|
2
2
|
import { p as parseDay, i as isoOf, C as CHEVRONS, r as resolveLocale, f as firstWeekday, m as monthGrid, w as weekLabel, a as weekdayNames, b as isoWeek, s as stepMonth, c as addDays } from './shared/dates-a32DcW1l.js';
|
|
3
|
-
import { p as placeUnder } from './shared/floating-
|
|
3
|
+
import { p as placeUnder } from './shared/floating-CtqV6MBj.js';
|
|
4
4
|
import { d as deepActiveElement } from './shared/focus-BNAChOXO.js';
|
|
5
5
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
6
6
|
|
|
@@ -44,7 +44,7 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
44
44
|
// outside does. Put a floating calendar directly under <body>.
|
|
45
45
|
const STEPS = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };
|
|
46
46
|
const floatSheet = new CSSStyleSheet();
|
|
47
|
-
floatSheet.replaceSync(`:host([floating]) { display: block; position: fixed; z-index: 60; pointer-events: none; }`);
|
|
47
|
+
floatSheet.replaceSync(`:host([floating]) { display: block; position: fixed; top: 0; left: 0; z-index: 60; pointer-events: none; }`);
|
|
48
48
|
|
|
49
49
|
class NkCalendar extends NkFormElement {
|
|
50
50
|
static get hostStyles() { return floatSheet; }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { i as initialsOf, p as paintAvatar } from './shared/avatar-
|
|
2
|
+
import { i as initialsOf, p as paintAvatar } from './shared/avatar-CzPZN3uP.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
4
|
|
|
5
5
|
// <nk-comment author="Sara Lindt" time="1 hr ago" avatar="SL" color="green">The board view feels close.</nk-comment>
|
|
@@ -18,10 +18,13 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
18
18
|
// scroll-locked. It lies above the modal and the sheet, so a question can
|
|
19
19
|
// come from either. 440px, `wide` 560px; below 860px a bottom sheet with the
|
|
20
20
|
// buttons stacked, the confirming one on top.
|
|
21
|
-
// A button in slot="actions" with a `value` closes it with that value
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
21
|
+
// A button in slot="actions" with a `value` closes it with that value, and
|
|
22
|
+
// one anywhere inside with `data-close` with that attribute's value – a
|
|
23
|
+
// button with a value elsewhere, an option of <nk-segmented> say, leaves it
|
|
24
|
+
// open. The submit of a <form method="dialog"> inside closes it too, with
|
|
25
|
+
// its submitter's value. Before it closes, nk-close { value } fires and can
|
|
26
|
+
// be cancelled – the place to check an input dialog; Escape and the
|
|
27
|
+
// backdrop close with ''.
|
|
25
28
|
// `returnValue` keeps the last value. `alert` makes it an alertdialog, named
|
|
26
29
|
// and described by its title and text. `title` is the heading, never a
|
|
27
30
|
// tooltip – see NkElement.takeTitle(). Put it directly under <body>.
|
|
@@ -91,11 +94,14 @@ class NkDialog extends NkElement {
|
|
|
91
94
|
setupEvents() {
|
|
92
95
|
this._onBackdrop = (e) => { if (e.target === this._backdrop) this.close(''); };
|
|
93
96
|
this._onKey = (e) => { if (e.key === 'Escape' && this.getBoolAttr('open')) { e.stopPropagation(); this.close(''); } };
|
|
94
|
-
// A button with a value closes it
|
|
97
|
+
// A button in the actions with a value closes it, and one with data-close
|
|
98
|
+
// anywhere inside; a submit button is the form's.
|
|
95
99
|
this._onClick = (e) => {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
const path = e.composedPath(), own = path.slice(0, Math.max(0, path.indexOf(this))).filter(n => n instanceof Element && this.contains(n));
|
|
101
|
+
const button = own.find(n => n.localName === 'nk-btn' || n.localName === 'button');
|
|
102
|
+
if (!button || button.getAttribute('type') === 'submit' || button.hasAttribute('disabled')) return;
|
|
103
|
+
if (button.hasAttribute('data-close')) { this.close(button.getAttribute('data-close')); return; }
|
|
104
|
+
if (button.hasAttribute('value') && own.some(n => n.getAttribute('slot') === 'actions')) this.close(button.getAttribute('value'));
|
|
99
105
|
};
|
|
100
106
|
this._onSubmit = (e) => {
|
|
101
107
|
if ((e.target.getAttribute('method') || '').toLowerCase() !== 'dialog') return;
|
|
@@ -10,12 +10,22 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
10
10
|
// nk-field inside renders itself stacked and compact (12px label above a
|
|
11
11
|
// full-width control) – the class markup does the same through
|
|
12
12
|
// `.nk-fields > .nk-field`, which cannot cross the shadow boundary here.
|
|
13
|
+
// `fit` (NotionKit 1.11.0) lets the fields share the row instead of keeping
|
|
14
|
+
// 150px columns: two fields in a wide dialog are two halves.
|
|
13
15
|
class NkFields extends NkElement {
|
|
16
|
+
static get observedAttributes() { return ['fit']; }
|
|
17
|
+
|
|
14
18
|
render() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
this._wrapper.appendChild(
|
|
19
|
+
this._grid = this.createElement('div', ['nk-fields']);
|
|
20
|
+
this._grid.appendChild(document.createElement('slot'));
|
|
21
|
+
this._wrapper.appendChild(this._grid);
|
|
22
|
+
this.onAttributeChanged();
|
|
18
23
|
}
|
|
24
|
+
|
|
25
|
+
onAttributeChanged() { this._grid?.classList.toggle('fit', this.getBoolAttr('fit')); }
|
|
26
|
+
|
|
27
|
+
get fit() { return this.getBoolAttr('fit'); }
|
|
28
|
+
set fit(v) { this.setBoolAttr('fit', v); }
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
customElements.define('nk-fields', NkFields);
|
|
@@ -59,7 +59,7 @@ class NkFilterBar extends NkElement {
|
|
|
59
59
|
setupEvents() {
|
|
60
60
|
this._onClick = (e) => {
|
|
61
61
|
const remove = e.target.closest('[data-remove]');
|
|
62
|
-
if (remove) {
|
|
62
|
+
if (remove) { const i = Number(remove.dataset.remove); this._filters = this._filters.filter((_, j) => j !== i); this._sync(); this._emitChange(); return; }
|
|
63
63
|
const edit = e.target.closest('[data-edit]');
|
|
64
64
|
if (edit) { const index = Number(edit.dataset.edit); this.emit('nk-action', { action: 'edit', index, filter: this._filters[index], anchor: edit }); return; }
|
|
65
65
|
const btn = e.target.closest('[data-action]');
|
|
@@ -79,8 +79,10 @@ class NkFilterBar extends NkElement {
|
|
|
79
79
|
_emitChange() { this.emit('nk-change', { filters: [...this._filters], search: this._input.value }); }
|
|
80
80
|
onAttributeChanged() { this._sync(); }
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
// A copy each way: the caller's array is never changed – removing a pill
|
|
83
|
+
// only reports the new list in nk-change.
|
|
84
|
+
get filters() { return [...this._filters]; }
|
|
85
|
+
set filters(v) { this._filters = Array.isArray(v) ? [...v] : []; if (this._row) this._sync(); }
|
|
84
86
|
get value() { return this._input?.value ?? ''; }
|
|
85
87
|
set value(v) { if (this._input) this._input.value = v ?? ''; }
|
|
86
88
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { r as renderPropertyCell } from './shared/property-cell-
|
|
2
|
+
import { r as renderPropertyCell } from './shared/property-cell-Cddz5n2O.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
|
-
import './shared/avatar-
|
|
4
|
+
import './shared/avatar-CzPZN3uP.js';
|
|
5
5
|
|
|
6
6
|
// <nk-list-view name="list" label="☰ List" meta-keys="due,status" new-row></nk-list-view>
|
|
7
7
|
// The third database view: one line per row – icon and title, the `meta-keys`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { i as initialsOf, p as paintAvatar } from './shared/avatar-
|
|
2
|
+
import { i as initialsOf, p as paintAvatar } from './shared/avatar-CzPZN3uP.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
4
|
|
|
5
5
|
// <nk-member-row name="Sara Lindt" mail="sara@…" avatar="SL" color="green">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
2
|
import { d as deepActiveElement } from './shared/focus-BNAChOXO.js';
|
|
3
|
-
import { p as placeUnder } from './shared/floating-
|
|
3
|
+
import { p as placeUnder } from './shared/floating-CtqV6MBj.js';
|
|
4
4
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
5
5
|
|
|
6
6
|
// <nk-menu>
|
|
@@ -20,7 +20,7 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
20
20
|
// close it too, a switch row does not. Opened from the keyboard, focus moves
|
|
21
21
|
// to the first item; closing returns it to where it was.
|
|
22
22
|
const floatSheet = new CSSStyleSheet();
|
|
23
|
-
floatSheet.replaceSync(`:host([floating]) { display: block; position: fixed; z-index: 60; pointer-events: none; }`);
|
|
23
|
+
floatSheet.replaceSync(`:host([floating]) { display: block; position: fixed; top: 0; left: 0; z-index: 60; pointer-events: none; }`);
|
|
24
24
|
|
|
25
25
|
class NkMenu extends NkElement {
|
|
26
26
|
static get hostStyles() { return floatSheet; }
|
|
@@ -44,8 +44,9 @@ class NkPage extends NkElement {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
_sync() {
|
|
47
|
-
const icon = this.getAttribute('icon');
|
|
48
|
-
|
|
47
|
+
const icon = this.getAttribute('icon') || '';
|
|
48
|
+
// Fallback content: write on a change only (see nk-switch).
|
|
49
|
+
if (this._icon.textContent !== icon) this._icon.textContent = icon;
|
|
49
50
|
this._icon.style.display = icon ? '' : 'none';
|
|
50
51
|
this._cover.style.display = this.getBoolAttr('cover') ? '' : 'none';
|
|
51
52
|
// `covered` is the stylesheet's twin of `.nk-cover + .nk-page`: the icon
|
|
@@ -8,7 +8,9 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
8
8
|
//
|
|
9
9
|
// The light-DOM <option>s are copied into the shadow <select>. A
|
|
10
10
|
// MutationObserver keeps the copy in step when a framework swaps them; the
|
|
11
|
-
// empty string is a valid value; the live selection survives a rebuild.
|
|
11
|
+
// empty string is a valid value; the live selection survives a rebuild. A
|
|
12
|
+
// value set before its option exists – a framework sets properties before
|
|
13
|
+
// children – waits and is applied once the option arrives.
|
|
12
14
|
class NkSelect extends NkFormElement {
|
|
13
15
|
static get observedAttributes() { return ['value', 'name', 'disabled', 'required', 'compact', 'wide', 'aria-label']; }
|
|
14
16
|
static get observesLightDom() { return true; }
|
|
@@ -51,9 +53,11 @@ class NkSelect extends NkFormElement {
|
|
|
51
53
|
this._select.innerHTML = '';
|
|
52
54
|
for (const node of nodes) this._select.appendChild(node.cloneNode(true));
|
|
53
55
|
|
|
54
|
-
//
|
|
55
|
-
// otherwise the
|
|
56
|
-
|
|
56
|
+
// A value that waited for its option first; then the live selection when
|
|
57
|
+
// it survived; otherwise the value attribute; otherwise the browser
|
|
58
|
+
// default (first option / `selected`).
|
|
59
|
+
if (this._pending != null && this._applyValue(this._pending)) this._pending = null;
|
|
60
|
+
else if (!this._applyValue(previous)) this._applyValue(this.getAttribute('value'));
|
|
57
61
|
this._syncFormValue();
|
|
58
62
|
}
|
|
59
63
|
|
|
@@ -67,6 +71,7 @@ class NkSelect extends NkFormElement {
|
|
|
67
71
|
|
|
68
72
|
setupEvents() {
|
|
69
73
|
this._onChange = () => {
|
|
74
|
+
this._pending = null;
|
|
70
75
|
this._syncFormValue();
|
|
71
76
|
this.emit('nk-change', { value: this._select.value, name: this.name });
|
|
72
77
|
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
@@ -107,10 +112,13 @@ class NkSelect extends NkFormElement {
|
|
|
107
112
|
|
|
108
113
|
focus(options) { this._select?.focus(options); }
|
|
109
114
|
|
|
110
|
-
get value() { return this._select?.value ?? ''; }
|
|
115
|
+
get value() { return this._pending ?? this._select?.value ?? ''; }
|
|
111
116
|
set value(v) {
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
const value = v == null ? '' : String(v);
|
|
118
|
+
if (!this._select) { this.setAttribute('value', value); return; }
|
|
119
|
+
// No option carries it yet: keep it until one does.
|
|
120
|
+
this._pending = this._applyValue(value) ? null : value;
|
|
121
|
+
this._syncFormValue();
|
|
114
122
|
}
|
|
115
123
|
get selectedIndex() { return this._select?.selectedIndex ?? -1; }
|
|
116
124
|
get options() { return this._select?.options ?? []; }
|
|
@@ -37,7 +37,11 @@ class NkSwitch extends NkFormElement {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
_syncText() {
|
|
40
|
-
|
|
40
|
+
// Only on a change: WebKit fires slotchange for a write to the slot's
|
|
41
|
+
// fallback text, even the same text, and this runs on slotchange – an
|
|
42
|
+
// endless loop that froze Safari with an empty <nk-switch>.
|
|
43
|
+
const text = this.getAttribute('text') ?? '';
|
|
44
|
+
if (this._textNode.data !== text) this._textNode.data = text;
|
|
41
45
|
const hasText = !!(this.getAttribute('text') || this._slot.assignedNodes().some(n => n.nodeType === Node.ELEMENT_NODE || n.data.trim()));
|
|
42
46
|
this._text.hidden = !hasText;
|
|
43
47
|
this._row.style.display = hasText ? '' : 'contents';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { c as compareBy, r as renderPropertyCell } from './shared/property-cell-
|
|
2
|
+
import { c as compareBy, r as renderPropertyCell } from './shared/property-cell-Cddz5n2O.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
|
-
import './shared/avatar-
|
|
4
|
+
import './shared/avatar-CzPZN3uP.js';
|
|
5
5
|
|
|
6
6
|
// <nk-table-view name="table" label="▦ Table" count new-row sortable wrap></nk-table-view>
|
|
7
7
|
// `wrap` lets cell text break (`.nk-table.wrap`), like Notion's "wrap column".
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { a as placeNear } from './shared/floating-
|
|
2
|
+
import { a as placeNear } from './shared/floating-CtqV6MBj.js';
|
|
3
3
|
import { d as deepActiveElement } from './shared/focus-BNAChOXO.js';
|
|
4
4
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
5
5
|
|
|
@@ -15,12 +15,15 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
15
15
|
// element. With `for` it belongs to the element with that id and shows its
|
|
16
16
|
// own content and `shortcut`. It appears after `delay` ms (400) under the
|
|
17
17
|
// pointer and at once on keyboard focus, never on touch; leaving, a press,
|
|
18
|
-
// blur, scrolling
|
|
19
|
-
// centred, above it where the window
|
|
20
|
-
// and 8px inside the window
|
|
21
|
-
// text, shortcut) takes an
|
|
22
|
-
//
|
|
23
|
-
//
|
|
18
|
+
// blur, scrolling – the wheel too, inside a shadow root – and Escape hide
|
|
19
|
+
// it. It sits 6px below its target and centred, above it where the window
|
|
20
|
+
// ends – placement="top" prefers above – and 8px inside the window
|
|
21
|
+
// (placeNear in util/floating.js). show(target, text, shortcut) takes an
|
|
22
|
+
// element or a rect, for what has no element of its own: the bars of a
|
|
23
|
+
// Gantt chart; such a tooltip stays until hide(), the pointer on another
|
|
24
|
+
// [data-tooltip], a press or Escape. A line break in the text is kept.
|
|
25
|
+
// While it shows, the target's aria-describedby names it (in the same
|
|
26
|
+
// document or shadow root).
|
|
24
27
|
const hostSheet = new CSSStyleSheet();
|
|
25
28
|
hostSheet.replaceSync(`:host { display: block; position: fixed; top: 0; left: 0; z-index: 130; pointer-events: none; }`);
|
|
26
29
|
let count = 0;
|
|
@@ -45,6 +48,7 @@ class NkTooltip extends NkElement {
|
|
|
45
48
|
_fill(text, key = this.getAttribute('shortcut')) {
|
|
46
49
|
this._slot.style.display = text == null ? '' : 'none';
|
|
47
50
|
this._text.data = text ?? '';
|
|
51
|
+
this._bubble.classList.toggle('lines', (text ?? '').includes('\n'));
|
|
48
52
|
this._key.textContent = key || '';
|
|
49
53
|
this._key.hidden = !key;
|
|
50
54
|
}
|
|
@@ -53,6 +57,9 @@ class NkTooltip extends NkElement {
|
|
|
53
57
|
show(target, text, shortcut) {
|
|
54
58
|
clearTimeout(this._timer);
|
|
55
59
|
if (!target) return;
|
|
60
|
+
// What the pointer is on now: a rect has no element, so moving over the
|
|
61
|
+
// page leaves a tooltip shown for one alone.
|
|
62
|
+
this._target = target instanceof Element ? target : null;
|
|
56
63
|
this._fill(text ?? null, shortcut);
|
|
57
64
|
this._describe(target);
|
|
58
65
|
placeNear(this, target, this.getAttribute('placement') === 'top' ? 'top' : 'bottom');
|
|
@@ -117,6 +124,8 @@ class NkTooltip extends NkElement {
|
|
|
117
124
|
document.addEventListener('focusout', this._onHide);
|
|
118
125
|
document.addEventListener('pointerdown', this._onHide, true);
|
|
119
126
|
document.addEventListener('scroll', this._onHide, true);
|
|
127
|
+
// Scroll events do not leave a shadow root; the wheel does.
|
|
128
|
+
document.addEventListener('wheel', this._onHide, { capture: true, passive: true });
|
|
120
129
|
document.addEventListener('keydown', this._onKey);
|
|
121
130
|
}
|
|
122
131
|
|
|
@@ -126,6 +135,7 @@ class NkTooltip extends NkElement {
|
|
|
126
135
|
document.removeEventListener('focusout', this._onHide);
|
|
127
136
|
document.removeEventListener('pointerdown', this._onHide, true);
|
|
128
137
|
document.removeEventListener('scroll', this._onHide, true);
|
|
138
|
+
document.removeEventListener('wheel', this._onHide, { capture: true });
|
|
129
139
|
document.removeEventListener('keydown', this._onKey);
|
|
130
140
|
this.hide();
|
|
131
141
|
}
|
|
@@ -60,7 +60,10 @@ class NkTreeItem extends NkElement {
|
|
|
60
60
|
else if (slot === 'end') end.push(node);
|
|
61
61
|
else if (node.localName === 'nk-tree-item') children.push(node);
|
|
62
62
|
else label.push(node);
|
|
63
|
-
} else if (node.nodeType === Node.TEXT_NODE
|
|
63
|
+
} else if (node.nodeType === Node.TEXT_NODE) {
|
|
64
|
+
// Every text node, empty and blank ones too: a template engine fills
|
|
65
|
+
// an expression's node after the item is built ("${i} ${title}"),
|
|
66
|
+
// and the blank node between two expressions is the space.
|
|
64
67
|
label.push(node);
|
|
65
68
|
}
|
|
66
69
|
}
|
|
@@ -83,9 +86,12 @@ class NkTreeItem extends NkElement {
|
|
|
83
86
|
if (this._hasChildren) this._row.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
84
87
|
else this._row.removeAttribute('aria-expanded');
|
|
85
88
|
this._children.classList.toggle('collapsed', !open || !this._hasChildren);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
this.
|
|
89
|
+
// Fallback content: write on a change only (see nk-switch).
|
|
90
|
+
const icon = this.getAttribute('icon') || '';
|
|
91
|
+
if (this._icon.textContent !== icon) this._icon.textContent = icon;
|
|
92
|
+
this._icon.style.display = icon ? '' : 'none';
|
|
93
|
+
const label = this.getAttribute('label') || '';
|
|
94
|
+
if (this._labelText.data !== label) this._labelText.data = label;
|
|
89
95
|
// .actions is display:none until hover (a hover-parent rule); `hidden`
|
|
90
96
|
// would lose to that rule, so the block is detached instead.
|
|
91
97
|
const wantActions = !this.getBoolAttr('no-actions') && !this._hasEnd;
|
|
@@ -13,7 +13,7 @@ function paintAvatar(el, color) {
|
|
|
13
13
|
el.style.background = color && !AVATAR_COLORS.includes(color) ? color : '';
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** "Sara Lindt" → "SL". */
|
|
17
|
-
const initialsOf = name => String(name || '').split(/\s+/).map(w => w[0]).filter(Boolean).join('').slice(0, 2).toUpperCase();
|
|
16
|
+
/** "Sara Lindt" → "SL", "Planer (Dev)" → "PD", "Anna-Lena Groß" → "AG": the first letter or digit of each word; a word without one is skipped. */
|
|
17
|
+
const initialsOf = name => String(name || '').split(/\s+/).map(w => w.match(/[\p{L}\p{N}]/u)?.[0]).filter(Boolean).join('').slice(0, 2).toUpperCase();
|
|
18
18
|
|
|
19
19
|
export { initialsOf as i, paintAvatar as p };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Placement for elements that float over the page – <nk-menu floating>,
|
|
2
|
+
// <nk-calendar floating>, <nk-tooltip>: the host is a fixed box put under
|
|
3
|
+
// its anchor. A transformed ancestor – an open nk-modal or nk-dialog –
|
|
4
|
+
// becomes the containing block of a fixed box, so the frame it really sits
|
|
5
|
+
// in is measured and taken off.
|
|
6
|
+
|
|
7
|
+
/** The border box of an element, or of the first descendant with one (display: contents hosts). */
|
|
8
|
+
function boxOf(el) {
|
|
9
|
+
if (el.getClientRects().length) return el.getBoundingClientRect();
|
|
10
|
+
for (const child of [...(el.shadowRoot?.children ?? []), ...el.children]) {
|
|
11
|
+
const r = boxOf(child);
|
|
12
|
+
if (r.width || r.height) return r;
|
|
13
|
+
}
|
|
14
|
+
return el.getBoundingClientRect();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Where a fixed `host` really sits: the viewport position of its containing
|
|
19
|
+
* block's left, top and right edges – the viewport's own, unless a
|
|
20
|
+
* transformed ancestor takes its place – and the host's size.
|
|
21
|
+
*/
|
|
22
|
+
function frameOf(host) {
|
|
23
|
+
const s = host.style;
|
|
24
|
+
s.top = '0px'; s.left = '0px'; s.right = 'auto';
|
|
25
|
+
const a = host.getBoundingClientRect();
|
|
26
|
+
s.left = 'auto'; s.right = '0px';
|
|
27
|
+
const b = host.getBoundingClientRect();
|
|
28
|
+
return { x: a.left, y: a.top, right: b.right, width: a.width, height: a.height };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Puts `host` 6px below `target` – an element or a rect ({ left, top, right,
|
|
33
|
+
* bottom }, a DOMRect) – centred on it: above with side="top", or where the
|
|
34
|
+
* window ends below; kept 8px inside the window. For <nk-tooltip>.
|
|
35
|
+
*/
|
|
36
|
+
function placeNear(host, target, side = 'bottom', gap = 6) {
|
|
37
|
+
const r = target instanceof Element ? boxOf(target) : target;
|
|
38
|
+
const f = frameOf(host), w = f.width, h = f.height;
|
|
39
|
+
const below = r.bottom + gap, above = r.top - gap - h;
|
|
40
|
+
const top = side === 'top' ? (above < 8 ? below : above) : (below + h > innerHeight - 8 ? above : below);
|
|
41
|
+
const left = Math.min(Math.max(8, (r.left + r.right) / 2 - w / 2), innerWidth - 8 - w);
|
|
42
|
+
host.style.top = `${Math.round(top - f.y)}px`;
|
|
43
|
+
host.style.left = `${Math.round(left - f.x)}px`;
|
|
44
|
+
host.style.right = 'auto';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Puts `host` 6px under `anchor`, right edges aligned – left edges with
|
|
49
|
+
* align="start". Where the window ends below and there is more room above,
|
|
50
|
+
* it opens upwards; either way --_nk-float-max caps its height to the room
|
|
51
|
+
* there, and a longer menu scrolls.
|
|
52
|
+
*/
|
|
53
|
+
function placeUnder(host, anchor, align) {
|
|
54
|
+
const r = boxOf(anchor), gap = 6, edge = 8;
|
|
55
|
+
host.style.removeProperty('--_nk-float-max');
|
|
56
|
+
const f = frameOf(host);
|
|
57
|
+
const roomBelow = innerHeight - r.bottom - gap - edge, roomAbove = r.top - gap - edge;
|
|
58
|
+
const up = f.height > roomBelow && roomAbove > roomBelow;
|
|
59
|
+
const room = Math.max(0, Math.floor(up ? roomAbove : roomBelow));
|
|
60
|
+
if (f.height > room) host.style.setProperty('--_nk-float-max', `${room}px`);
|
|
61
|
+
host.style.top = `${(up ? r.top - gap - Math.min(f.height, room) : r.bottom + gap) - f.y}px`;
|
|
62
|
+
if (align === 'start') { host.style.left = `${r.left - f.x}px`; host.style.right = 'auto'; }
|
|
63
|
+
else { host.style.right = `${f.right - Math.min(r.right, innerWidth - edge)}px`; host.style.left = 'auto'; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { placeNear as a, placeUnder as p };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as initialsOf, p as paintAvatar } from './avatar-
|
|
1
|
+
import { i as initialsOf, p as paintAvatar } from './avatar-CzPZN3uP.js';
|
|
2
2
|
|
|
3
3
|
// Polymorphic property renderer for database views. Returns plain DOM built
|
|
4
4
|
// from NotionKit classes – deliberately not a custom element: every cell rule
|