@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jungherz-de/notionkit-elements",
3
- "version": "1.17.0",
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.17.0"
66
+ "@jungherz-de/notionkit": ">=1.18.0"
67
67
  },
68
68
  "devDependencies": {
69
- "@jungherz-de/notionkit": "^1.17.0",
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._wrapper.appendChild(this._bar);
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
- this._label.style.display = label === null ? 'none' : '';
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">Show</button><button class="cf-btn">Copy</button></div>
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
- this._show.textContent = this._revealed ? (this.getAttribute('hide-label') || this.str('hide')) : (this.getAttribute('show-label') || this.str('show'));
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.textContent = this._copied ? (this.getAttribute('copied-label') || this.str('copied')) : (this.getAttribute('copy-label') || this.str('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. Exactly one item is `active`; a
17
- // `drawer` item opens the sidebar instead of becoming active.
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 || !items.some(i => this._valueOf(i) === value)) {
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