@jungherz-de/notionkit-elements 1.16.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +64 -6
  3. package/SKILL.md +79 -65
  4. package/dist/components/base.js +102 -3
  5. package/dist/components/nk-ai-input-row.js +6 -3
  6. package/dist/components/nk-board-view.js +2 -2
  7. package/dist/components/nk-breadcrumb.js +2 -1
  8. package/dist/components/nk-calendar-view.js +8 -5
  9. package/dist/components/nk-calendar.js +10 -7
  10. package/dist/components/nk-cmdk.js +6 -2
  11. package/dist/components/nk-comments.js +5 -3
  12. package/dist/components/nk-copy-field.js +37 -6
  13. package/dist/components/nk-emoji-picker.js +6 -2
  14. package/dist/components/nk-filter-bar.js +10 -8
  15. package/dist/components/nk-gallery-view.js +29 -7
  16. package/dist/components/nk-image-picker.js +7 -3
  17. package/dist/components/nk-list-view.js +3 -2
  18. package/dist/components/nk-modal.js +3 -1
  19. package/dist/components/nk-page-title.js +2 -1
  20. package/dist/components/nk-page.js +3 -1
  21. package/dist/components/nk-peek.js +6 -3
  22. package/dist/components/nk-progress.js +6 -3
  23. package/dist/components/nk-sidebar.js +5 -2
  24. package/dist/components/nk-steps.js +20 -4
  25. package/dist/components/nk-synced.js +2 -1
  26. package/dist/components/nk-tab-bar.js +10 -4
  27. package/dist/components/nk-table-view.js +3 -2
  28. package/dist/components/nk-theme-toggle.js +5 -2
  29. package/dist/components/nk-tree-item.js +3 -1
  30. package/dist/components/shared/{property-cell-BSVcETss.js → property-cell-DcsfnUNF.js} +15 -5
  31. package/dist/notionkit-elements.esm.js +312 -80
  32. package/dist/notionkit-elements.esm.min.js +1 -1
  33. package/dist/notionkit-elements.esm.min.js.map +1 -1
  34. package/dist/notionkit-elements.js +314 -79
  35. package/dist/notionkit-elements.min.js +1 -1
  36. package/package.json +3 -3
  37. package/src/base.js +32 -2
  38. package/src/components/content/nk-progress.js +6 -3
  39. package/src/components/data/nk-ai-input-row.js +6 -3
  40. package/src/components/data/nk-board-view.js +2 -2
  41. package/src/components/data/nk-calendar-view.js +7 -4
  42. package/src/components/data/nk-comments.js +5 -3
  43. package/src/components/data/nk-filter-bar.js +10 -8
  44. package/src/components/data/nk-gallery-view.js +29 -7
  45. package/src/components/data/nk-list-view.js +2 -1
  46. package/src/components/data/nk-table-view.js +2 -1
  47. package/src/components/forms/nk-calendar.js +10 -7
  48. package/src/components/forms/nk-copy-field.js +37 -6
  49. package/src/components/forms/nk-image-picker.js +7 -3
  50. package/src/components/overlays/nk-cmdk.js +6 -2
  51. package/src/components/overlays/nk-emoji-picker.js +6 -2
  52. package/src/components/overlays/nk-modal.js +3 -1
  53. package/src/components/overlays/nk-peek.js +6 -3
  54. package/src/components/page/nk-page-title.js +2 -1
  55. package/src/components/page/nk-page.js +3 -1
  56. package/src/components/page/nk-steps.js +20 -4
  57. package/src/components/page/nk-synced.js +2 -1
  58. package/src/components/shell/nk-breadcrumb.js +2 -1
  59. package/src/components/shell/nk-sidebar.js +5 -2
  60. package/src/components/shell/nk-tab-bar.js +10 -4
  61. package/src/components/shell/nk-theme-toggle.js +5 -2
  62. package/src/components/shell/nk-tree-item.js +3 -1
  63. package/src/index.js +5 -1
  64. package/src/util/property-cell.js +14 -4
  65. package/src/util/strings.js +73 -0
@@ -1,5 +1,5 @@
1
1
  import { NkElement } from './base.js';
2
- import { a as textOf, f as formatDate, r as renderPropertyCell } from './shared/property-cell-BSVcETss.js';
2
+ import { a as textOf, b as formatDate, r as renderPropertyCell } from './shared/property-cell-DcsfnUNF.js';
3
3
  import '@jungherz-de/notionkit/notionkit-styles.js';
4
4
  import './shared/avatar-CzPZN3uP.js';
5
5
  import './shared/dates-a32DcW1l.js';
@@ -56,7 +56,7 @@ class NkListView extends NkElement {
56
56
  item.append(icon, t, m);
57
57
  this._list.appendChild(item);
58
58
  }
59
- this._newRow.textContent = this.getAttribute('new-row-label') || '+ New page';
59
+ this._newRow.textContent = this.getAttribute('new-row-label') || this.str('newPage');
60
60
  this._newRow.style.display = this.getBoolAttr('new-row') ? '' : 'none';
61
61
  }
62
62
 
@@ -81,6 +81,7 @@ class NkListView extends NkElement {
81
81
 
82
82
  _rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
83
83
  onAttributeChanged() { this._render(); }
84
+ onStringsChanged() { this._render(); }
84
85
 
85
86
  get name() { return this.getAttribute('name') || 'list'; }
86
87
  get columns() { return this._columns; }
@@ -23,7 +23,7 @@ class NkModal extends NkElement {
23
23
  this._backdrop = this.createElement('div', ['nk-modal-backdrop']);
24
24
  this._backdrop.inert = true;
25
25
  this._box = this.createElement('div', ['nk-modal'], { role: 'dialog', 'aria-modal': 'true', tabindex: '-1' });
26
- this._nav = this.createElement('nav', ['nk-settings-nav'], { 'aria-label': 'Settings' });
26
+ this._nav = this.createElement('nav', ['nk-settings-nav'], { 'aria-label': this.str('settings') });
27
27
  this._nav.appendChild(this.createElement('slot', [], { name: 'user' }));
28
28
  this._rows = document.createElement('div');
29
29
  this._nav.appendChild(this._rows);
@@ -141,6 +141,8 @@ class NkModal extends NkElement {
141
141
  if (this._wasOpen) { unlockScroll(); this._undoInert?.(); this._undoInert = null; this._wasOpen = false; }
142
142
  }
143
143
 
144
+ onStringsChanged() { this._nav.setAttribute('aria-label', this.str('settings')); }
145
+
144
146
  onAttributeChanged(name) {
145
147
  if (name === 'open') {
146
148
  this._syncOpen();
@@ -24,7 +24,7 @@ class NkPageTitle extends NkElement {
24
24
  this._h1.setAttribute('contenteditable', 'plaintext-only');
25
25
  this._h1.setAttribute('spellcheck', 'false');
26
26
  this._h1.setAttribute('role', 'textbox');
27
- this._h1.setAttribute('aria-label', 'Title');
27
+ this._h1.setAttribute('aria-label', this.str('title'));
28
28
  const text = this.getAttribute('value') ?? this.textContent.trim();
29
29
  if (this._h1.textContent !== text && document.activeElement !== this) this._h1.textContent = text;
30
30
  this._h1.setAttribute('data-placeholder', this.getAttribute('placeholder') || '');
@@ -59,6 +59,7 @@ class NkPageTitle extends NkElement {
59
59
  }
60
60
 
61
61
  onAttributeChanged() { this._sync(); }
62
+ onStringsChanged() { if (this._h1.hasAttribute('role')) this._h1.setAttribute('aria-label', this.str('title')); }
62
63
 
63
64
  get value() { return this.getBoolAttr('editable') ? (this._h1?.textContent.trim() ?? '') : (this.getAttribute('value') ?? this.textContent.trim()); }
64
65
  set value(v) { this.setAttribute('value', v); }
@@ -20,7 +20,7 @@ class NkPage extends NkElement {
20
20
 
21
21
  render() {
22
22
  this._page = this.createElement('div', ['nk-page']);
23
- this._icon = this.createElement('div', ['nk-page-icon'], { role: 'button', title: 'Change icon' });
23
+ this._icon = this.createElement('div', ['nk-page-icon'], { role: 'button', title: this.str('changeIcon') });
24
24
  this._iconSlot = this.createElement('slot', [], { name: 'icon' });
25
25
  this._iconSlot.appendChild(this._icon);
26
26
  this._page.appendChild(this._iconSlot);
@@ -76,6 +76,8 @@ class NkPage extends NkElement {
76
76
  this._iconSlot?.removeEventListener('slotchange', this._onCover);
77
77
  }
78
78
 
79
+ onStringsChanged() { this._icon.title = this.str('changeIcon'); }
80
+
79
81
  onAttributeChanged(name) {
80
82
  if (name === 'narrow') this._build();
81
83
  else this._sync();
@@ -27,7 +27,8 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
27
27
  // Notion: from `min` (380) to `max` (all but 320px of the window). The width
28
28
  // is the token --nk-peek-width, set on :root; `width` (px) sets it too, and
29
29
  // nk-resize { width } fires when a drag or a key ends – the moment to keep
30
- // it. resize-label names the edge (default "Resize").
30
+ // it. resize-label names the edge (default "Resize", in German "Breite
31
+ // ändern").
31
32
  // `inset`: while it is open on the desktop, the page's <nk-app> gets
32
33
  // `peek-inset` – its main column makes room instead of lying under the peek.
33
34
  const CLOSE_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 17 5-5-5-5M13 17l5-5-5-5"/></svg>';
@@ -74,7 +75,7 @@ class NkPeek extends NkElement {
74
75
 
75
76
  _syncResize() {
76
77
  this._handle.hidden = !this.getBoolAttr('resizable');
77
- this._handle.setAttribute('aria-label', this.getAttribute('resize-label') || 'Resize');
78
+ this._handle.setAttribute('aria-label', this.getAttribute('resize-label') || this.str('resize'));
78
79
  this._handle.setAttribute('aria-valuemin', this._min);
79
80
  this._handle.setAttribute('aria-valuenow', Math.round(this.width));
80
81
  }
@@ -91,7 +92,7 @@ class NkPeek extends NkElement {
91
92
  _syncLabels() {
92
93
  const label = this.getAttribute('label');
93
94
  if (label) this._box.setAttribute('aria-label', label); else this._box.removeAttribute('aria-label');
94
- this._close.setAttribute('aria-label', this.getAttribute('close-label') || 'Close');
95
+ this._close.setAttribute('aria-label', this.getAttribute('close-label') || this.str('close'));
95
96
  }
96
97
 
97
98
  _syncOpen() {
@@ -176,6 +177,8 @@ class NkPeek extends NkElement {
176
177
  this._wasOpen = false;
177
178
  }
178
179
 
180
+ onStringsChanged() { this._syncResize(); this._syncLabels(); }
181
+
179
182
  onAttributeChanged(name, _old, value) {
180
183
  if (name === 'width') { this._setWidth(Number(value)); return; }
181
184
  if (name === 'inset') { this._syncInset(); return; }
@@ -4,6 +4,9 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
4
4
  // <nk-progress value="72" label="72%"></nk-progress>
5
5
  // <nk-progress value="72" label="72 %" wide></nk-progress> → fills its row, the label stays beside it
6
6
  // The bar is `.nk-progress > i` — a child combinator, so both nodes live here.
7
+ // With a label the two form one .nk-progress-row (1.18.0): the label beside
8
+ // the bar in a flex column too – a panel – where the host, display: contents,
9
+ // would let them stand one under the other.
7
10
  class NkProgress extends NkElement {
8
11
  static get observedAttributes() { return ['value', 'max', 'label', 'wide']; }
9
12
 
@@ -12,8 +15,7 @@ class NkProgress extends NkElement {
12
15
  this._fill = document.createElement('i');
13
16
  this._bar.appendChild(this._fill);
14
17
  this._label = this.createElement('span', ['nk-progress-label']);
15
- this._wrapper.appendChild(this._bar);
16
- this._wrapper.appendChild(this._label);
18
+ this._row = this.createElement('span', ['nk-progress-row']);
17
19
  this._sync();
18
20
  }
19
21
 
@@ -30,7 +32,8 @@ class NkProgress extends NkElement {
30
32
  this._bar.setAttribute('aria-valuemax', max);
31
33
  const label = this.getAttribute('label');
32
34
  this._label.textContent = label ?? '';
33
- this._label.style.display = label === null ? 'none' : '';
35
+ if (label === null) { if (this._bar.parentNode !== this._wrapper) this._wrapper.replaceChildren(this._bar); }
36
+ else if (this._bar.parentNode !== this._row) { this._row.replaceChildren(this._bar, this._label); this._wrapper.replaceChildren(this._row); }
34
37
  }
35
38
 
36
39
  get value() { return Number(this.getAttribute('value')) || 0; }
@@ -24,7 +24,8 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
24
24
  // brings it back; ⌘\ or Ctrl+\ toggles it (on a phone the drawer).
25
25
  // `collapsed` is the state, collapse() / expand() / toggleCollapsed() the
26
26
  // API, nk-collapse { collapsed } the moment to keep it. collapse-label names
27
- // the « (default "Close sidebar") and is its tooltip for <nk-tooltip>.
27
+ // the « (default "Close sidebar", in German "Seitenleiste schließen") and is
28
+ // its tooltip for <nk-tooltip>.
28
29
  const COLLAPSE_ICON = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m18 17-5-5 5-5M11 17l-5-5 5-5"/></svg>';
29
30
  const phone = () => matchMedia('(max-width: 860px)').matches;
30
31
 
@@ -54,7 +55,7 @@ class NkSidebar extends NkElement {
54
55
  }
55
56
 
56
57
  _syncCollapse() {
57
- const label = this.getAttribute('collapse-label') || 'Close sidebar';
58
+ const label = this.getAttribute('collapse-label') || this.str('closeSidebar');
58
59
  this._collapse.hidden = !this.getBoolAttr('collapsible');
59
60
  this._collapse.setAttribute('aria-label', label);
60
61
  this._collapse.dataset.tooltip = label;
@@ -106,6 +107,8 @@ class NkSidebar extends NkElement {
106
107
  document.removeEventListener('keydown', this._onKey);
107
108
  }
108
109
 
110
+ onStringsChanged() { this._syncCollapse(); }
111
+
109
112
  onAttributeChanged(name) {
110
113
  if (name === 'open') {
111
114
  this._syncOpen();
@@ -18,6 +18,12 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
18
18
  // nk-select { index, value, step } – index counts from 1, like `current` –
19
19
  // and, unless cancelled, makes that step current. `horizontal` sets the
20
20
  // steps in one row above a wizard.
21
+ // A step with `href` (1.17.0) shows its label as a link, <a class="st-label">,
22
+ // with or without `selectable` – to open a chapter in a new tab or copy its
23
+ // address. A plain click, or Enter, fires the same nk-select; cancelling it
24
+ // keeps the browser from following the link, for a router of your own. A
25
+ // middle click or one with Cmd, Ctrl, Shift or Alt fires nothing and does
26
+ // what a link does.
21
27
  class NkSteps extends NkElement {
22
28
  static get observedAttributes() { return ['steps', 'current', 'label', 'selectable', 'horizontal']; }
23
29
 
@@ -45,7 +51,7 @@ class NkSteps extends NkElement {
45
51
  // Rebuilding drops the focused label; the new one at that place takes it back.
46
52
  const focused = this.shadowRoot?.activeElement?.closest?.('li')?.dataset.index;
47
53
  this._list.replaceChildren(...this._items().map((_, i) => {
48
- const n = i + 1, { label: text, desc, state } = this._step(i);
54
+ const n = i + 1, { label: text, desc, state, href } = this._step(i);
49
55
  // A state of its own wins; without one, the steps before `current` are done.
50
56
  const done = state ? state === 'done' : n < current, skipped = state === 'skipped';
51
57
  const li = this.createElement('li', ['nk-step'], { 'data-index': String(n) });
@@ -55,7 +61,8 @@ class NkSteps extends NkElement {
55
61
  if (n === current) li.setAttribute('aria-current', 'step');
56
62
  const mark = this.createElement('span', ['st-mark'], { 'aria-hidden': 'true' });
57
63
  mark.textContent = done ? '✓' : skipped ? '–' : String(n);
58
- const body = selectable ? this.createElement('button', ['st-label'], { type: 'button' }) : document.createElement('span');
64
+ const body = href != null && href !== '' ? this.createElement('a', ['st-label'], { href: String(href) })
65
+ : selectable ? this.createElement('button', ['st-label'], { type: 'button' }) : document.createElement('span');
59
66
  body.textContent = text ?? '';
60
67
  if (desc) { const d = this.createElement('span', ['st-desc']); d.textContent = desc; body.appendChild(d); }
61
68
  li.append(mark, body);
@@ -66,9 +73,18 @@ class NkSteps extends NkElement {
66
73
 
67
74
  setupEvents() {
68
75
  this._onClick = (e) => {
69
- if (!this.getBoolAttr('selectable')) return;
70
76
  const li = e.target.closest?.('li.nk-step');
71
- if (li && this._list.contains(li)) this.select(Number(li.dataset.index));
77
+ if (!li || !this._list.contains(li)) return;
78
+ const link = li.querySelector('a.st-label');
79
+ if (link) {
80
+ // The link contract: a plain left click is a selection, any other
81
+ // one the browser's. The mark beside the link counts as the link.
82
+ if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
83
+ if (!link.contains(e.target)) { link.click(); return; }
84
+ if (!this.select(Number(li.dataset.index))) e.preventDefault();
85
+ return;
86
+ }
87
+ if (this.getBoolAttr('selectable')) this.select(Number(li.dataset.index));
72
88
  };
73
89
  this._list.addEventListener('click', this._onClick);
74
90
  }
@@ -15,8 +15,9 @@ class NkSynced extends NkElement {
15
15
  this._sync();
16
16
  }
17
17
 
18
- _sync() { this._badge.textContent = this.getAttribute('badge') ?? '⟳ synced'; }
18
+ _sync() { this._badge.textContent = this.getAttribute('badge') ?? this.str('synced'); }
19
19
  onAttributeChanged() { this._sync(); }
20
+ onStringsChanged() { this._sync(); }
20
21
 
21
22
  get badge() { return this.getAttribute('badge'); }
22
23
  set badge(v) { this.setAttribute('badge', v); }
@@ -14,8 +14,12 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
14
14
  // stylesheet hides it above 860px – there the sidebar is the navigation –
15
15
  // and shows it below; `always` shows it at every width (previews, phone
16
16
  // frames), `fixed` pins it to the viewport (standalone PWAs) with a spacer
17
- // holding its place, `floating` makes it a capsule. Exactly one item is `active`; a
18
- // `drawer` item opens the sidebar instead of becoming active.
17
+ // holding its place, `floating` makes it a capsule. The item whose value is
18
+ // the bar's `value` is `active`; a `drawer` item opens the sidebar instead of
19
+ // becoming active when clicked. A value no item has – a page without a tab
20
+ // of its own, opened from the drawer – marks the drawer item, as iOS marks
21
+ // "More" (1.18.0); without one no item is active. Without a value the item
22
+ // marked `active`, else the first, is the one.
19
23
  class NkTabBar extends NkElement {
20
24
  static get observedAttributes() { return ['value', 'always', 'fixed', 'floating', 'label']; }
21
25
 
@@ -46,16 +50,18 @@ class NkTabBar extends NkElement {
46
50
  }
47
51
 
48
52
  _sync() {
49
- const items = this.items.filter(i => !i.hasAttribute('drawer'));
53
+ const items = this.items.filter(i => !i.hasAttribute('drawer')), drawers = this.items.filter(i => i.hasAttribute('drawer'));
50
54
  if (!items.length) return;
51
55
  let value = this.getAttribute('value');
52
- if (value === null || !items.some(i => this._valueOf(i) === value)) {
56
+ if (value === null) {
53
57
  const preset = items.find(i => i.hasAttribute('active')) || items[0];
54
58
  value = this._valueOf(preset);
55
59
  }
60
+ const known = items.some(i => this._valueOf(i) === value);
56
61
  for (const item of items) {
57
62
  if (this._valueOf(item) === value) item.setAttribute('active', ''); else item.removeAttribute('active');
58
63
  }
64
+ drawers.forEach((item, n) => { if (!known && n === 0) item.setAttribute('active', ''); else item.removeAttribute('active'); });
59
65
  this._value = value;
60
66
  }
61
67
 
@@ -1,5 +1,5 @@
1
1
  import { NkElement } from './base.js';
2
- import { c as compareBy, r as renderPropertyCell } from './shared/property-cell-BSVcETss.js';
2
+ import { c as compareBy, r as renderPropertyCell } from './shared/property-cell-DcsfnUNF.js';
3
3
  import '@jungherz-de/notionkit/notionkit-styles.js';
4
4
  import './shared/avatar-CzPZN3uP.js';
5
5
  import './shared/dates-a32DcW1l.js';
@@ -66,7 +66,7 @@ class NkTableView extends NkElement {
66
66
  }
67
67
  this._tbody.appendChild(r);
68
68
  }
69
- this._newRow.textContent = this.getAttribute('new-row-label') || '+ New page';
69
+ this._newRow.textContent = this.getAttribute('new-row-label') || this.str('newPage');
70
70
  this._newRow.style.display = this.getBoolAttr('new-row') ? '' : 'none';
71
71
  }
72
72
 
@@ -116,6 +116,7 @@ class NkTableView extends NkElement {
116
116
 
117
117
  _rowById(id) { return this._rows.find(r => String(r.id) === String(id)); }
118
118
  onAttributeChanged() { this._render(); }
119
+ onStringsChanged() { this._render(); }
119
120
 
120
121
  get name() { return this.getAttribute('name') || 'table'; }
121
122
  get columns() { return this._columns; }
@@ -15,7 +15,7 @@ class NkThemeToggle extends NkElement {
15
15
 
16
16
  render() {
17
17
  this._btn = this.createElement('button', ['nk-topbar-btn', 'nk-theme-toggle'], { type: 'button' });
18
- this._btn.title = this.getAttribute('title') || 'Toggle light / dark';
18
+ this._syncTitle();
19
19
  this._wrapper.appendChild(this._btn);
20
20
  const root = document.documentElement;
21
21
  let stored = null;
@@ -46,8 +46,11 @@ class NkThemeToggle extends NkElement {
46
46
  window.removeEventListener('message', this._onMessage);
47
47
  }
48
48
 
49
+ _syncTitle() { this._btn.title = this.getAttribute('title') || this.str('toggleTheme'); }
50
+ onStringsChanged() { this._syncTitle(); }
51
+
49
52
  onAttributeChanged(name) {
50
- if (name === 'title' && this._btn) this._btn.title = this.getAttribute('title') || 'Toggle light / dark';
53
+ if (name === 'title' && this._btn) this._syncTitle();
51
54
  }
52
55
 
53
56
  /** Sets the theme on <html>, persists it and reports it. */
@@ -34,7 +34,7 @@ class NkTreeItem extends NkElement {
34
34
  this._labelSlot.appendChild(this._labelText);
35
35
  this._label.appendChild(this._labelSlot);
36
36
  this._actions = this.createElement('span', ['actions']);
37
- for (const [action, glyph, title] of [['add', '+', 'Add'], ['more', '⋯', 'More']]) {
37
+ for (const [action, glyph, title] of [['add', '+', this.str('add')], ['more', '⋯', this.str('more')]]) {
38
38
  const b = this.createElement('span', [], { 'data-action': action, role: 'button', title });
39
39
  b.textContent = glyph;
40
40
  this._actions.appendChild(b);
@@ -120,6 +120,8 @@ class NkTreeItem extends NkElement {
120
120
  this._row?.removeEventListener('keydown', this._onKey);
121
121
  }
122
122
 
123
+ onStringsChanged() { for (const b of this._actions.children) b.title = this.str(b.dataset.action); }
124
+
123
125
  onAttributeChanged(name) {
124
126
  if (name === 'label') this.assignSlots();
125
127
  else this._sync();
@@ -16,6 +16,8 @@ import { r as resolveLocale } from './dates-a32DcW1l.js';
16
16
  // text: a string, or { text, desc, color, tooltip, sort } – one of Notion's
17
17
  // nine text colours for a tone, a quiet second line under it, the whole
18
18
  // text in a tooltip (served by <nk-tooltip>), a value to sort by.
19
+ // progress: 0–100, the bar and its percentage as Intl writes it in
20
+ // column.locale, else the page's language ("45 %" in German).
19
21
  // date: the value as given, or with column.format ('short', 'relative' or
20
22
  // Intl.DateTimeFormat options) and column.locale formatted from an ISO date
21
23
  // or date-time, D.M.YYYY, a Date or a timestamp; { start, end } is a range.
@@ -81,7 +83,7 @@ function renderPropertyCell(column, value, row = {}) {
81
83
  if (type === 'actions') return actions(column, value);
82
84
  if (value === undefined || value === null || value === '') {
83
85
  if (type === 'checkbox') return checkbox(column, false, row);
84
- if (type === 'progress') return progress(0);
86
+ if (type === 'progress') return progress(column, 0);
85
87
  return el('span', type === 'person' ? 'person-cell' : null, type === 'text' && column.title ? '' : '—');
86
88
  }
87
89
  switch (type) {
@@ -113,7 +115,7 @@ function renderPropertyCell(column, value, row = {}) {
113
115
  }
114
116
  // column.locale / column.format: Intl.NumberFormat locale and options, e.g. { minimumFractionDigits: 1 }.
115
117
  case 'number': return el('span', null, typeof value === 'number' ? value.toLocaleString(column.locale, column.format) : String(value));
116
- case 'progress': return progress(Number(value) || 0);
118
+ case 'progress': return progress(column, Number(value) || 0);
117
119
  default: {
118
120
  const o = typeof value === 'object' ? value : { text: value };
119
121
  let t;
@@ -159,15 +161,23 @@ function checkbox(column, checked, row) {
159
161
  return label;
160
162
  }
161
163
 
162
- function progress(value) {
164
+ function progress(column, value) {
163
165
  const frag = document.createDocumentFragment();
164
166
  const bar = el('span', 'nk-progress');
165
167
  const fill = el('i'); fill.style.width = `${Math.max(0, Math.min(100, value))}%`;
166
168
  bar.appendChild(fill);
167
- frag.append(bar, el('span', 'nk-progress-label', `${Math.round(value)}%`));
169
+ frag.append(bar, el('span', 'nk-progress-label', formatPercent(column, value)));
168
170
  return frag;
169
171
  }
170
172
 
173
+ /** A progress value of 0–100 as Intl writes a percentage in column.locale, else the page's language: 45 → "45%", in German "45 %". */
174
+ function formatPercent(column, value) {
175
+ const n = Number(value) || 0;
176
+ try {
177
+ return new Intl.NumberFormat(column.locale || resolveLocale(), { style: 'percent', maximumFractionDigits: 0 }).format(n / 100);
178
+ } catch { return `${Math.round(n)}%`; } // a lang Intl does not take
179
+ }
180
+
171
181
  /** A select's place among its options: the order Notion sorts by. */
172
182
  function rankOf(column, value) {
173
183
  const i = (column.options || []).findIndex(o => o.value === value || o.label === value);
@@ -201,4 +211,4 @@ function compareBy(column, dir = 1) {
201
211
  };
202
212
  }
203
213
 
204
- export { textOf as a, compareBy as c, formatDate as f, renderPropertyCell as r, tagFor as t };
214
+ export { textOf as a, formatDate as b, compareBy as c, formatPercent as f, renderPropertyCell as r, tagFor as t };