@jungherz-de/notionkit-elements 1.17.0 → 1.18.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/CHANGELOG.md +15 -0
- package/README.md +6 -6
- package/SKILL.md +41 -33
- package/dist/components/nk-copy-field.js +33 -5
- package/dist/components/nk-progress.js +6 -3
- package/dist/components/nk-tab-bar.js +10 -4
- package/dist/notionkit-elements.esm.js +51 -14
- package/dist/notionkit-elements.esm.min.js +1 -1
- package/dist/notionkit-elements.esm.min.js.map +1 -1
- package/dist/notionkit-elements.js +51 -14
- package/dist/notionkit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/content/nk-progress.js +6 -3
- package/src/components/forms/nk-copy-field.js +33 -5
- package/src/components/shell/nk-tab-bar.js +10 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungherz-de/notionkit-elements",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Vanilla JavaScript Web Components wrapping the NotionKit CSS foundation – <nk-*> custom elements with Shadow DOM, automatic theme sync and native form participation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jungherz GmbH <info@jungherz.com> (https://www.jungherz.com)",
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
"dark-mode"
|
|
64
64
|
],
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@jungherz-de/notionkit": ">=1.
|
|
66
|
+
"@jungherz-de/notionkit": ">=1.18.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@jungherz-de/notionkit": "^1.
|
|
69
|
+
"@jungherz-de/notionkit": "^1.18.0",
|
|
70
70
|
"@playwright/test": "^1.50.0",
|
|
71
71
|
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
72
72
|
"@rollup/plugin-terser": "^0.4.0",
|
|
@@ -3,6 +3,9 @@ import { NkElement } from '../../base.js';
|
|
|
3
3
|
// <nk-progress value="72" label="72%"></nk-progress>
|
|
4
4
|
// <nk-progress value="72" label="72 %" wide></nk-progress> → fills its row, the label stays beside it
|
|
5
5
|
// The bar is `.nk-progress > i` — a child combinator, so both nodes live here.
|
|
6
|
+
// With a label the two form one .nk-progress-row (1.18.0): the label beside
|
|
7
|
+
// the bar in a flex column too – a panel – where the host, display: contents,
|
|
8
|
+
// would let them stand one under the other.
|
|
6
9
|
class NkProgress extends NkElement {
|
|
7
10
|
static get observedAttributes() { return ['value', 'max', 'label', 'wide']; }
|
|
8
11
|
|
|
@@ -11,8 +14,7 @@ class NkProgress extends NkElement {
|
|
|
11
14
|
this._fill = document.createElement('i');
|
|
12
15
|
this._bar.appendChild(this._fill);
|
|
13
16
|
this._label = this.createElement('span', ['nk-progress-label']);
|
|
14
|
-
this.
|
|
15
|
-
this._wrapper.appendChild(this._label);
|
|
17
|
+
this._row = this.createElement('span', ['nk-progress-row']);
|
|
16
18
|
this._sync();
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -29,7 +31,8 @@ class NkProgress extends NkElement {
|
|
|
29
31
|
this._bar.setAttribute('aria-valuemax', max);
|
|
30
32
|
const label = this.getAttribute('label');
|
|
31
33
|
this._label.textContent = label ?? '';
|
|
32
|
-
|
|
34
|
+
if (label === null) { if (this._bar.parentNode !== this._wrapper) this._wrapper.replaceChildren(this._bar); }
|
|
35
|
+
else if (this._bar.parentNode !== this._row) { this._row.replaceChildren(this._bar, this._label); this._wrapper.replaceChildren(this._row); }
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
get value() { return Number(this.getAttribute('value')) || 0; }
|
|
@@ -3,7 +3,8 @@ import { NkElement } from '../../base.js';
|
|
|
3
3
|
// <nk-copy-field value="https://monahilft.notionkit.app"></nk-copy-field>
|
|
4
4
|
// <nk-copy-field value="ntn_4f2…" secret mono></nk-copy-field>
|
|
5
5
|
// → <div class="nk-copy-field mono"><span class="cf-value">…</span>
|
|
6
|
-
// <button class="cf-btn"
|
|
6
|
+
// <button class="cf-btn" aria-label="Show"><svg>…</svg><span>Show</span></button>
|
|
7
|
+
// <button class="cf-btn" aria-label="Copy"><svg>…</svg><span>Copy</span></button></div>
|
|
7
8
|
// A value to take along – a link, an address, a key (NotionKit 1.8.0). Copy
|
|
8
9
|
// writes it to the clipboard and says "Copied" in green for a moment; where
|
|
9
10
|
// the clipboard is not allowed, the value is shown and selected instead, for
|
|
@@ -12,8 +13,21 @@ import { NkElement } from '../../base.js';
|
|
|
12
13
|
// lands in the markup. Fires nk-action { action: 'copy', value, ok }. The
|
|
13
14
|
// texts come in English or German after the language (1.17.0, setStrings()
|
|
14
15
|
// for others); copy-label, copied-label, show-label, hide-label set them here.
|
|
16
|
+
// Each button carries an icon beside its word (1.18.0): on a phone, and with
|
|
17
|
+
// `icons` anywhere, the icon stands in for the word – two overlapping squares,
|
|
18
|
+
// an eye, a check in green for the moment after – and a masked key keeps a
|
|
19
|
+
// dozen characters more. The word stays the button's name, and while the
|
|
20
|
+
// icons show, its tooltip for <nk-tooltip>.
|
|
21
|
+
const ICONS = {
|
|
22
|
+
copy: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 9V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h4"/><rect x="9" y="9" width="12" height="12" rx="2"/></svg>',
|
|
23
|
+
copied: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg>',
|
|
24
|
+
show: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>',
|
|
25
|
+
hide: '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/><path d="m3 3 18 18"/></svg>',
|
|
26
|
+
};
|
|
27
|
+
const PHONE = typeof matchMedia === 'function' ? matchMedia('(max-width: 860px)') : null;
|
|
28
|
+
|
|
15
29
|
class NkCopyField extends NkElement {
|
|
16
|
-
static get observedAttributes() { return ['value', 'mono', 'wrap', 'wide', 'secret', 'copy-label', 'copied-label', 'show-label', 'hide-label']; }
|
|
30
|
+
static get observedAttributes() { return ['value', 'mono', 'wrap', 'wide', 'secret', 'icons', 'copy-label', 'copied-label', 'show-label', 'hide-label']; }
|
|
17
31
|
|
|
18
32
|
render() {
|
|
19
33
|
this._val ??= this.getAttribute('value') ?? '';
|
|
@@ -28,29 +42,41 @@ class NkCopyField extends NkElement {
|
|
|
28
42
|
|
|
29
43
|
_sync() {
|
|
30
44
|
if (!this._box) return;
|
|
31
|
-
for (const m of ['mono', 'wrap', 'wide']) this._box.classList.toggle(m, this.getBoolAttr(m));
|
|
45
|
+
for (const m of ['mono', 'wrap', 'wide', 'icons']) this._box.classList.toggle(m, this.getBoolAttr(m));
|
|
32
46
|
const secret = this.getBoolAttr('secret');
|
|
33
47
|
this._text.textContent = secret && !this._revealed ? '•'.repeat(Math.min(this._val.length, 24)) : this._val;
|
|
34
48
|
// Not `hidden`: .cf-btn sets no display of its own that could lose to it,
|
|
35
49
|
// but a button that is not there cannot be tabbed to either.
|
|
36
50
|
if (secret && !this._show.isConnected) this._copy.before(this._show);
|
|
37
51
|
if (!secret) this._show.remove();
|
|
38
|
-
|
|
52
|
+
const tip = this.getBoolAttr('icons') || !!PHONE?.matches;
|
|
53
|
+
this._button(this._show, this._revealed ? 'hide' : 'show', this._revealed ? (this.getAttribute('hide-label') || this.str('hide')) : (this.getAttribute('show-label') || this.str('show')), tip);
|
|
39
54
|
this._show.setAttribute('aria-pressed', String(!!this._revealed));
|
|
40
|
-
this._copy.
|
|
55
|
+
this._button(this._copy, this._copied ? 'copied' : 'copy', this._copied ? (this.getAttribute('copied-label') || this.str('copied')) : (this.getAttribute('copy-label') || this.str('copy')), tip);
|
|
41
56
|
this._copy.classList.toggle('copied', !!this._copied);
|
|
42
57
|
}
|
|
43
58
|
|
|
59
|
+
/** The icon and the word of a button; the word is its name, and its tooltip while the icon stands in for it. */
|
|
60
|
+
_button(btn, icon, word, tip) {
|
|
61
|
+
if (btn.dataset.icon !== icon) { btn.innerHTML = `${ICONS[icon]}<span></span>`; btn.dataset.icon = icon; }
|
|
62
|
+
btn.lastChild.textContent = word;
|
|
63
|
+
btn.setAttribute('aria-label', word);
|
|
64
|
+
if (tip) btn.setAttribute('data-tooltip', word); else btn.removeAttribute('data-tooltip');
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
setupEvents() {
|
|
45
68
|
this._onCopy = () => this.copy();
|
|
46
69
|
this._onShow = () => { this._revealed = !this._revealed; this._sync(); };
|
|
70
|
+
this._onPhone = () => this._sync();
|
|
47
71
|
this._copy.addEventListener('click', this._onCopy);
|
|
48
72
|
this._show.addEventListener('click', this._onShow);
|
|
73
|
+
PHONE?.addEventListener('change', this._onPhone);
|
|
49
74
|
}
|
|
50
75
|
|
|
51
76
|
teardownEvents() {
|
|
52
77
|
this._copy?.removeEventListener('click', this._onCopy);
|
|
53
78
|
this._show?.removeEventListener('click', this._onShow);
|
|
79
|
+
PHONE?.removeEventListener('change', this._onPhone);
|
|
54
80
|
clearTimeout(this._timer);
|
|
55
81
|
}
|
|
56
82
|
|
|
@@ -83,6 +109,8 @@ class NkCopyField extends NkElement {
|
|
|
83
109
|
set value(v) { this._val = v == null ? '' : String(v); this._sync(); }
|
|
84
110
|
get secret() { return this.getBoolAttr('secret'); }
|
|
85
111
|
set secret(v) { this.setBoolAttr('secret', v); }
|
|
112
|
+
get icons() { return this.getBoolAttr('icons'); }
|
|
113
|
+
set icons(v) { this.setBoolAttr('icons', v); }
|
|
86
114
|
}
|
|
87
115
|
|
|
88
116
|
customElements.define('nk-copy-field', NkCopyField);
|
|
@@ -13,8 +13,12 @@ import { NkElement } from '../../base.js';
|
|
|
13
13
|
// stylesheet hides it above 860px – there the sidebar is the navigation –
|
|
14
14
|
// and shows it below; `always` shows it at every width (previews, phone
|
|
15
15
|
// frames), `fixed` pins it to the viewport (standalone PWAs) with a spacer
|
|
16
|
-
// holding its place, `floating` makes it a capsule.
|
|
17
|
-
// `drawer` item opens the sidebar instead of
|
|
16
|
+
// holding its place, `floating` makes it a capsule. The item whose value is
|
|
17
|
+
// the bar's `value` is `active`; a `drawer` item opens the sidebar instead of
|
|
18
|
+
// becoming active when clicked. A value no item has – a page without a tab
|
|
19
|
+
// of its own, opened from the drawer – marks the drawer item, as iOS marks
|
|
20
|
+
// "More" (1.18.0); without one no item is active. Without a value the item
|
|
21
|
+
// marked `active`, else the first, is the one.
|
|
18
22
|
class NkTabBar extends NkElement {
|
|
19
23
|
static get observedAttributes() { return ['value', 'always', 'fixed', 'floating', 'label']; }
|
|
20
24
|
|
|
@@ -45,16 +49,18 @@ class NkTabBar extends NkElement {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
_sync() {
|
|
48
|
-
const items = this.items.filter(i => !i.hasAttribute('drawer'));
|
|
52
|
+
const items = this.items.filter(i => !i.hasAttribute('drawer')), drawers = this.items.filter(i => i.hasAttribute('drawer'));
|
|
49
53
|
if (!items.length) return;
|
|
50
54
|
let value = this.getAttribute('value');
|
|
51
|
-
if (value === null
|
|
55
|
+
if (value === null) {
|
|
52
56
|
const preset = items.find(i => i.hasAttribute('active')) || items[0];
|
|
53
57
|
value = this._valueOf(preset);
|
|
54
58
|
}
|
|
59
|
+
const known = items.some(i => this._valueOf(i) === value);
|
|
55
60
|
for (const item of items) {
|
|
56
61
|
if (this._valueOf(item) === value) item.setAttribute('active', ''); else item.removeAttribute('active');
|
|
57
62
|
}
|
|
63
|
+
drawers.forEach((item, n) => { if (!known && n === 0) item.setAttribute('active', ''); else item.removeAttribute('active'); });
|
|
58
64
|
this._value = value;
|
|
59
65
|
}
|
|
60
66
|
|