@jungherz-de/notionkit-elements 1.14.0 → 1.15.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 +18 -0
- package/README.md +6 -6
- package/SKILL.md +25 -25
- package/dist/components/nk-board-view.js +4 -3
- package/dist/components/nk-calendar-view.js +3 -1
- package/dist/components/nk-filter-bar.js +3 -2
- package/dist/components/nk-gallery-view.js +4 -3
- package/dist/components/nk-list-view.js +4 -3
- package/dist/components/nk-table-view.js +2 -1
- package/dist/components/shared/property-cell-BSVcETss.js +204 -0
- package/dist/notionkit-elements.esm.js +102 -24
- package/dist/notionkit-elements.js +104 -23
- package/dist/notionkit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/data/nk-board-view.js +3 -3
- package/src/components/data/nk-calendar-view.js +2 -1
- package/src/components/data/nk-filter-bar.js +3 -2
- package/src/components/data/nk-gallery-view.js +3 -3
- package/src/components/data/nk-list-view.js +3 -3
- package/src/index.js +1 -1
- package/src/util/property-cell.js +90 -12
- package/dist/components/shared/property-cell-DKvv6TIG.js +0 -126
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ All notable changes to NotionKit Elements are documented here. The format follow
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.15.0] – 2026-09-25
|
|
8
|
+
|
|
9
|
+
Built against NotionKit CSS 1.15.0 (peer `>= 1.15.0`). Auxdesk's findings 22 and 23 from its jobs, messages and tickets in `<nk-table-view>`.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **`sortable` sorts by the value, not by the text shown.** Every column but number, progress and checkbox compared its text: dates as text, so 13.09.2026 came before 24.08.2026, and a select by its key – `failed`, `ignored`, `posted` – instead of the order of its options. Dates now sort by their time (D.M.YYYY, ISO dates and date-times, ranges by their start), selects by their options as Notion does, and empty cells come last in both directions. `compareBy()` does the same for tables of one's own.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **A value to sort by.** `sortKey` on a column sorts by another field of the row – a “Time” column that shows “4 min ago” sorts by the ISO time next to it – and a `sort` in a cell's object sorts that cell by its own value.
|
|
16
|
+
- **Date formats.** A `date` column with `format` – `'short'` (day and month, the year when it is not this one), `'relative'` (minutes or hours ago or ahead within a day, else the short date) or the options of Intl.DateTimeFormat – shows ISO dates and date-times formatted in its `locale`, and sorts by their time. Without `format` a date stands as given, as before. Board, list and gallery show the same format; a range reads start → end.
|
|
17
|
+
- **Text cells with a tone, a second line and a tooltip.** A text value may be `{ text, desc, color, tooltip }`: `color` one of Notion's nine text colours – orange for an error –, `desc` a quiet second line under it – the next run under an error, the sender under a subject –, `tooltip` the whole text, which `<nk-tooltip>` shows. A title cell takes `desc` and `tooltip` too. The other views and the filter bar's search read the text of such an object.
|
|
18
|
+
- Exported next to `renderPropertyCell()`: `formatDate()`, `timeOf()` and `textOf()`.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- The docs example of `<nk-table-view>` shows a quiet line under the first title.
|
|
22
|
+
- Peer `@jungherz-de/notionkit >= 1.15.0`.
|
|
23
|
+
|
|
7
24
|
## [1.14.0] – 2026-09-25
|
|
8
25
|
|
|
9
26
|
Built against NotionKit CSS 1.14.0 (peer `>= 1.14.0`). The gallery view – 88 elements now – for LearnHub's course catalog.
|
|
@@ -667,6 +684,7 @@ built and tested against NotionKit CSS 1.1.1 (peer range `>= 1.0.0`).
|
|
|
667
684
|
import, never in the core bundle – shadow-less, adding `nk-block-host` to
|
|
668
685
|
itself so the foundation's editor adapter rules apply.
|
|
669
686
|
|
|
687
|
+
[1.15.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.15.0
|
|
670
688
|
[1.14.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.14.0
|
|
671
689
|
[1.13.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.13.0
|
|
672
690
|
[1.12.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.12.0
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<h1 align="center">🧩 NotionKit Elements</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="https://www.npmjs.com/package/@jungherz-de/notionkit-elements"><img src="https://img.shields.io/badge/version-1.
|
|
4
|
+
<a href="https://www.npmjs.com/package/@jungherz-de/notionkit-elements"><img src="https://img.shields.io/badge/version-1.15.0-2383e2?style=flat-square" alt="Version"></a>
|
|
5
5
|
<a href="#"><img src="https://img.shields.io/badge/vanilla_JS-no_dependencies-448361?style=flat-square" alt="Vanilla JS"></a>
|
|
6
6
|
<a href="#"><img src="https://img.shields.io/badge/elements-88-529cca?style=flat-square" alt="88 Elements"></a>
|
|
7
7
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-9065b0?style=flat-square" alt="MIT License"></a>
|
|
8
|
-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.
|
|
8
|
+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v1.15.0-lightgrey?style=flat-square" alt="Changelog"></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/@jungherz-de/notionkit-elements"><img src="https://img.shields.io/badge/npm-%40jungherz--de%2Fnotionkit--elements-cb3837?style=flat-square&logo=npm" alt="npm"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
@@ -59,7 +59,7 @@ It is the **app layer** of the NotionKit family – three layers, one design lan
|
|
|
59
59
|
| 🧩 **88 Elements** | Forms with copy field, image picker and date picker, content blocks with bookmarks, app shell & tree with the ☰ drawer and the collapsing sidebar, page shell with properties, panels, avatars and steps, overlays (modal, sheet, dialog, resizable side peek, tooltip, ⌘K palette, menus that float in and become sheets on a phone, popover, emoji picker, toast), database table, board, list, calendar & gallery with toolbar and filter pills, comments, AI thread |
|
|
60
60
|
| 🎛️ **Form participation** | Input, textarea, select, switch, check, radio, slider, segmented and model card work natively with `<form>` via `ElementInternals`; radios group across shadow roots |
|
|
61
61
|
| 🌗 **Theme sync** | One observer mirrors `data-theme` on `<html>` into every element |
|
|
62
|
-
| 🪶 **Lightweight** |
|
|
62
|
+
| 🪶 **Lightweight** | 280 KB minified / 56 KB gzipped (IIFE, foundation sheet included), no external dependencies |
|
|
63
63
|
| 📦 **Four bundle shapes** | IIFE, minified IIFE, ESM, and per-component ESM entries on a stable `base.js`, which import NotionKit's sheet instead of carrying a copy |
|
|
64
64
|
| 🤖 **AI-ready** | `SKILL.md` with copy-paste markup, six app skeletons and the rules – an agent given only that file built a working app in the release test |
|
|
65
65
|
|
|
@@ -71,8 +71,8 @@ It is the **app layer** of the NotionKit family – three layers, one design lan
|
|
|
71
71
|
<!DOCTYPE html>
|
|
72
72
|
<html lang="en" data-theme="light">
|
|
73
73
|
<head>
|
|
74
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
75
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
74
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
75
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
76
76
|
</head>
|
|
77
77
|
<body class="nk-body">
|
|
78
78
|
<nk-field label="Display name" desc="Shown next to your comments">
|
|
@@ -102,7 +102,7 @@ The per-component files import NotionKit's sheet as `@jungherz-de/notionkit/noti
|
|
|
102
102
|
|
|
103
103
|
Do not mix the full bundle with the per-component files: each brings its own `NkElement`. With the bundle, take `componentsSheet` and `NkElement` from the bundle (`NotionKitElements.componentsSheet` from the `<script>` build).
|
|
104
104
|
|
|
105
|
-
Two prerequisites, always: `notionkit.css` on the document (the peer dependency, `>= 1.
|
|
105
|
+
Two prerequisites, always: `notionkit.css` on the document (the peer dependency, `>= 1.15.0`; 1.15.0 brings table text with a tone and a second line, 1.14.0 brings the gallery view and a banner action that stands at the right edge again, 1.13.0 keeps fields inside narrow panels, leaves a page without icon its padding under the cover and makes cover heights tokens, 1.12.0 brings a tag or button beside a panel's title, lists and grids without outer margin, text values, steps with states in any order and in a row, scrolling tabs, a cap for full pages and buttons per table row, 1.11.0 keeps a floating menu inside an open modal or dialog in place and inside the window and brings the banner's action as a button, fields that share a row and tooltips of several lines, 1.10.1 lets a disabled button show its tooltip, 1.10.0 brings the collapsing sidebar, the resizable side peek, the dialog and the tooltip, 1.9.0 the date picker and the calendar view, 1.8.0 the side peek, the bookmark, the copy field and pictures on the profile row, 1.7.0 the sheet, menus that float in and become sheets on a phone, the sidebar drawer, the database toolbar with filter pills and the steps, 1.7.1 minified files that carry all of it, 1.6.0 page properties, page options, list view, panels, prose and the avatar, 1.5.1 the safe areas, 1.5.0 the Notion-2025 tokens, the nine tag colours and the filled inputs; from 1.5.0 on, Elements and the foundation share one version number – the elements are built and tested against the same release) and `class="nk-body"` on `<body>`. The elements ship no visual CSS of their own – every rule comes from the foundation. The bundle injects the design tokens once as `@layer notionkit-defaults`, so any plain `:root { --nk-accent: … }` of yours re-brands every element in both themes.
|
|
106
106
|
|
|
107
107
|
## Elements
|
|
108
108
|
|
package/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: notionkit-elements
|
|
3
|
-
description: NotionKit Elements is a vanilla-JS Web Components library (v1.
|
|
3
|
+
description: NotionKit Elements is a vanilla-JS Web Components library (v1.15.0) wrapping NotionKit CSS v1.15.0 – the calm, document-centric design system in the Notion idiom. 88 custom elements with the `nk-` prefix, Shadow DOM, automatic light/dark sync via data-theme on <html>, and form-associated controls. Use this reference whenever generating HTML that uses <nk-*> tags to get attributes, slots, events and composition right.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# NotionKit Elements – AI Component Reference
|
|
@@ -21,8 +21,8 @@ description: NotionKit Elements is a vanilla-JS Web Components library (v1.14.0)
|
|
|
21
21
|
<head>
|
|
22
22
|
<meta charset="UTF-8">
|
|
23
23
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
24
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
25
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
24
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
25
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
26
26
|
</head>
|
|
27
27
|
<body class="nk-body">
|
|
28
28
|
<nk-btn variant="primary">Save</nk-btn>
|
|
@@ -2650,7 +2650,7 @@ The view switcher with Notion’s toolbar: child views (`nk-table-view`, `nk-boa
|
|
|
2650
2650
|
|
|
2651
2651
|
### 3.78 `<nk-table-view>` – Table view
|
|
2652
2652
|
|
|
2653
|
-
Renders `columns` × `rows` as the NotionKit table. Cells are polymorphic (`text`, `select`, `multi-select`, `date`, `person`, `checkbox`, `url`, `number`, `progress`) and rendered as plain markup by the exported `renderPropertyCell()` – every cell rule starts with `.nk-table`, so a cell element of its own would never be styled. Header clicks sort with `sortable`. A `number` column stands right-aligned in figures of equal width, formatted by its `locale` and `format` (Intl.NumberFormat options); a person’s `color` takes one of the nine names. A column of type `actions` (NotionKit 1.12.0) sets buttons in each row from `column.actions` – `[{ action, label, icon, danger, disabled, tooltip }]` –, and a row’s value – a list of action names – picks which of them it shows; a click fires `nk-action { action, row, id, anchor }` instead of selecting the row. A `url` value may be `{ href, label, target }`: a link of your own, to another page of the app, with its own text.
|
|
2653
|
+
Renders `columns` × `rows` as the NotionKit table. Cells are polymorphic (`text`, `select`, `multi-select`, `date`, `person`, `checkbox`, `url`, `number`, `progress`) and rendered as plain markup by the exported `renderPropertyCell()` – every cell rule starts with `.nk-table`, so a cell element of its own would never be styled. Header clicks sort with `sortable`. A `number` column stands right-aligned in figures of equal width, formatted by its `locale` and `format` (Intl.NumberFormat options); a person’s `color` takes one of the nine names. A column of type `actions` (NotionKit 1.12.0) sets buttons in each row from `column.actions` – `[{ action, label, icon, danger, disabled, tooltip }]` –, and a row’s value – a list of action names – picks which of them it shows; a click fires `nk-action { action, row, id, anchor }` instead of selecting the row. A `url` value may be `{ href, label, target }`: a link of your own, to another page of the app, with its own text. A text value may be `{ text, desc, color, tooltip }` (NotionKit 1.15.0): a quiet second line, one of Notion’s nine text colours – `orange` for an error –, the whole text in a tooltip. Sorting goes by the value, not the text shown: dates by their time, selects by the order of their options, empty cells last; `sortKey` on a column sorts by another field of the row, a `sort` in a cell’s object by that value. A `date` column with `format` – `'short'`, `'relative'` or the options of Intl.DateTimeFormat – shows ISO dates and date-times formatted in its `locale`.
|
|
2654
2654
|
|
|
2655
2655
|
```html
|
|
2656
2656
|
<nk-table-view new-row sortable></nk-table-view>
|
|
@@ -2667,7 +2667,7 @@ Renders `columns` × `rows` as the NotionKit table. Cells are polymorphic (`text
|
|
|
2667
2667
|
{ key: 'actions', type: 'actions', actions: [{ action: 'open', label: 'Open' }, { action: 'archive', label: 'Archive', danger: true }] },
|
|
2668
2668
|
];
|
|
2669
2669
|
db.rows = [
|
|
2670
|
-
{ id: 1, icon: '🧭', name: 'App shell & sidebar', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '08.05.2026', progress: 100, effort: 6, cover: '/covers/aurora.svg' },
|
|
2670
|
+
{ id: 1, icon: '🧭', name: { text: 'App shell & sidebar', desc: 'Three open questions' }, status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '08.05.2026', progress: 100, effort: 6, cover: '/covers/aurora.svg' },
|
|
2671
2671
|
{ id: 2, icon: '📄', name: 'Page shell & typography', status: 'done', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '10.05.2026', progress: 100, effort: 4.5, cover: '/covers/dunes.svg' },
|
|
2672
2672
|
{ id: 3, icon: '🗃️', name: 'Database table view', status: 'progress', owner: { name: 'Marcel', initials: 'MK', color: 'purple' }, due: '20.05.2026', progress: 65, effort: 12.5, cover: '/covers/meadow.svg' },
|
|
2673
2673
|
{ id: 4, icon: '▤', name: 'Board view & drag-and-drop', status: 'planned', due: '02.06.2026', progress: 0, effort: 8, actions: ['open'], cover: '/covers/tide.svg' },
|
|
@@ -2692,14 +2692,14 @@ Renders `columns` × `rows` as the NotionKit table. Cells are polymorphic (`text
|
|
|
2692
2692
|
|
|
2693
2693
|
**Properties:** `columns`, `rows`, `data` · **Methods:** `refresh()`
|
|
2694
2694
|
|
|
2695
|
-
**Replaces:** `.nk-table-wrap`, `.nk-table`, `.wrap`, `.th-icon`, `.row-title`, `.date-cell`, `.person-cell`, `.nk-avatar`, `.num`, `.row-actions`, `.actions`, `.nk-new-row`
|
|
2695
|
+
**Replaces:** `.nk-table-wrap`, `.nk-table`, `.wrap`, `.th-icon`, `.row-title`, `.date-cell`, `.person-cell`, `.nk-avatar`, `.num`, `.row-actions`, `.actions`, `.td-text`, `.td-desc`, `.nk-new-row`
|
|
2696
2696
|
|
|
2697
2697
|
```html
|
|
2698
2698
|
<!-- equivalent class markup -->
|
|
2699
2699
|
<div class="nk-table-wrap"><table class="nk-table">
|
|
2700
2700
|
<thead><tr><th><span class="th-icon">📄</span>Name</th><th><span class="th-icon">◉</span>Status</th><th><span class="th-icon">👤</span>Owner</th><th><span class="th-icon">📅</span>Due</th><th><span class="th-icon">▰</span>Progress</th><th><span class="th-icon">#</span>Effort (h)</th><th class="actions"></th></tr></thead>
|
|
2701
2701
|
<tbody>
|
|
2702
|
-
<tr><td><span class="row-title">🧭 App shell & sidebar</span></td><td><span class="nk-tag green">Done</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">08.05.2026</span></td><td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100%</span></td><td class="num"><span>6.0</span></td><td><span class="row-actions"><button class="nk-btn secondary small">Open</button><button class="nk-btn danger small">Archive</button></span></td></tr>
|
|
2702
|
+
<tr><td><span class="row-title">🧭 App shell & sidebar</span><span class="td-desc">Three open questions</span></td><td><span class="nk-tag green">Done</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">08.05.2026</span></td><td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100%</span></td><td class="num"><span>6.0</span></td><td><span class="row-actions"><button class="nk-btn secondary small">Open</button><button class="nk-btn danger small">Archive</button></span></td></tr>
|
|
2703
2703
|
<tr><td><span class="row-title">📄 Page shell & typography</span></td><td><span class="nk-tag green">Done</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">10.05.2026</span></td><td><span class="nk-progress"><i style="width:100%"></i></span><span class="nk-progress-label">100%</span></td><td class="num"><span>4.5</span></td><td><span class="row-actions"><button class="nk-btn secondary small">Open</button><button class="nk-btn danger small">Archive</button></span></td></tr>
|
|
2704
2704
|
<tr><td><span class="row-title">🗃️ Database table view</span></td><td><span class="nk-tag blue">In progress</span></td><td><span class="person-cell"><span class="nk-avatar small purple">MK</span> Marcel</span></td><td><span class="date-cell">20.05.2026</span></td><td><span class="nk-progress"><i style="width:65%"></i></span><span class="nk-progress-label">65%</span></td><td class="num"><span>12.5</span></td><td><span class="row-actions"><button class="nk-btn secondary small">Open</button><button class="nk-btn danger small">Archive</button></span></td></tr>
|
|
2705
2705
|
<tr><td><span class="row-title">▤ Board view & drag-and-drop</span></td><td><span class="nk-tag orange">Planned</span></td><td><span class="person-cell">—</span></td><td><span class="date-cell">02.06.2026</span></td><td><span class="nk-progress"><i style="width:0%"></i></span><span class="nk-progress-label">0%</span></td><td class="num"><span>8.0</span></td><td><span class="row-actions"><button class="nk-btn secondary small">Open</button></span></td></tr>
|
|
@@ -3142,8 +3142,8 @@ Eight skeletons, one per app shape, mirroring the NotionKit CSS SKILL.md. Copy o
|
|
|
3142
3142
|
<html lang="en" data-theme="light">
|
|
3143
3143
|
<head>
|
|
3144
3144
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3145
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3146
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3145
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3146
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3147
3147
|
</head>
|
|
3148
3148
|
<body class="nk-body">
|
|
3149
3149
|
<nk-app>
|
|
@@ -3224,8 +3224,8 @@ Rules of the shell: `nk-sidebar`, `nk-topbar` and `nk-page` are `display: conten
|
|
|
3224
3224
|
<html lang="en" data-theme="light">
|
|
3225
3225
|
<head>
|
|
3226
3226
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3227
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3228
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3227
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3228
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3229
3229
|
</head>
|
|
3230
3230
|
<body class="nk-body">
|
|
3231
3231
|
<nk-app>
|
|
@@ -3332,8 +3332,8 @@ Data contract: `columns` describe the properties (`type`: text | select | multi-
|
|
|
3332
3332
|
<html lang="en" data-theme="light">
|
|
3333
3333
|
<head>
|
|
3334
3334
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3335
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3336
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3335
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3336
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3337
3337
|
</head>
|
|
3338
3338
|
<body class="nk-body">
|
|
3339
3339
|
<nk-app>
|
|
@@ -3392,8 +3392,8 @@ Data contract: `columns` describe the properties (`type`: text | select | multi-
|
|
|
3392
3392
|
<html lang="en" data-theme="light">
|
|
3393
3393
|
<head>
|
|
3394
3394
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3395
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3396
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3395
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3396
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3397
3397
|
</head>
|
|
3398
3398
|
<body class="nk-body">
|
|
3399
3399
|
<!-- your app -->
|
|
@@ -3470,8 +3470,8 @@ The open/close contract is one attribute: `settings.open = true`, `settings.show
|
|
|
3470
3470
|
<html lang="en" data-theme="light">
|
|
3471
3471
|
<head>
|
|
3472
3472
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3473
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3474
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3473
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3474
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3475
3475
|
</head>
|
|
3476
3476
|
<body class="nk-body">
|
|
3477
3477
|
<div class="nk-page" style="padding-top:48px">
|
|
@@ -3517,8 +3517,8 @@ The open/close contract is one attribute: `settings.open = true`, `settings.show
|
|
|
3517
3517
|
<html lang="en" data-theme="light">
|
|
3518
3518
|
<head>
|
|
3519
3519
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3520
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3521
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3520
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3521
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3522
3522
|
</head>
|
|
3523
3523
|
<body class="nk-body">
|
|
3524
3524
|
<nk-page narrow icon="📘" cover>
|
|
@@ -3562,8 +3562,8 @@ Note `narrow`: the page is the document, so there is no inner scroll wrapper –
|
|
|
3562
3562
|
<html lang="en" data-theme="light">
|
|
3563
3563
|
<head>
|
|
3564
3564
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3565
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3566
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3565
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3566
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3567
3567
|
</head>
|
|
3568
3568
|
<body class="nk-body">
|
|
3569
3569
|
<nk-app>
|
|
@@ -3617,8 +3617,8 @@ Note `narrow`: the page is the document, so there is no inner scroll wrapper –
|
|
|
3617
3617
|
<html lang="en" data-theme="light">
|
|
3618
3618
|
<head>
|
|
3619
3619
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3620
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3621
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3620
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.15.0/notionkit.min.css">
|
|
3621
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.15.0/dist/notionkit-elements.min.js"></script>
|
|
3622
3622
|
</head>
|
|
3623
3623
|
<body class="nk-body">
|
|
3624
3624
|
<!-- A narrow column, centred: layout is yours, so it is inline. -->
|
|
@@ -3818,10 +3818,10 @@ Form controls additionally re-dispatch a native, bubbling `change` event, so `fo
|
|
|
3818
3818
|
| Theme sync | one `MutationObserver` on `<html>[data-theme]`, a `Set` of instances, `.nk-wrapper[data-theme]` inside each root |
|
|
3819
3819
|
| Components | `src/components/{forms,content,shell,page,overlays,data}/nk-*.js`, one tag per file, `customElements.define` at the bottom |
|
|
3820
3820
|
| Build | Rollup: IIFE, minified IIFE, ESM, and per-component ESM entries on a stable `dist/components/base.js` that import NotionKit's sheet (`@jungherz-de/notionkit/notionkit-styles.js`) instead of inlining it; the full bundles inline it and export `componentsSheet` |
|
|
3821
|
-
| Peer | `@jungherz-de/notionkit >= 1.
|
|
3821
|
+
| Peer | `@jungherz-de/notionkit >= 1.15.0` – from 1.5.0 on the elements and the foundation share one version number; the bundle embeds that release's stylesheet, so keep them in step |
|
|
3822
3822
|
|
|
3823
3823
|
Lifecycle: construct (attach shadow, adopt sheets) → first connect (wrapper + `render()`) → every connect (`setupEvents()`, theme registration, light-DOM observer) → `attributeChangedCallback` → `onAttributeChanged` → disconnect (`teardownEvents()`, unregister).
|
|
3824
3824
|
|
|
3825
3825
|
|
|
3826
3826
|
---
|
|
3827
|
-
*NotionKit Elements v1.
|
|
3827
|
+
*NotionKit Elements v1.15.0 · wrapping NotionKit CSS v1.15.0 · MIT · Jungherz GmbH*
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { t as tagFor, r as renderPropertyCell } from './shared/property-cell-
|
|
2
|
+
import { t as tagFor, a as textOf, f as formatDate, r as renderPropertyCell } from './shared/property-cell-BSVcETss.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
4
|
import './shared/avatar-CzPZN3uP.js';
|
|
5
|
+
import './shared/dates-a32DcW1l.js';
|
|
5
6
|
|
|
6
7
|
// <nk-board-view name="board" label="▤ Board" group-by="status" new-row></nk-board-view>
|
|
7
8
|
// Groups rows by a select column (default: the first select column); one
|
|
@@ -51,14 +52,14 @@ class NkBoardView extends NkElement {
|
|
|
51
52
|
for (const row of cards) {
|
|
52
53
|
const card = this.createElement('div', ['nk-card'], { draggable: 'true', 'data-id': row.id ?? '', tabindex: '0' });
|
|
53
54
|
const t = this.createElement('div', ['card-title']);
|
|
54
|
-
t.textContent = `${row.icon ? row.icon + ' ' : ''}${title ? row[title.key]
|
|
55
|
+
t.textContent = `${row.icon ? row.icon + ' ' : ''}${title ? textOf(row[title.key]) : ''}`;
|
|
55
56
|
const meta = this.createElement('div', ['card-meta']);
|
|
56
57
|
for (const c of this._metaColumns()) {
|
|
57
58
|
const v = row[c.key];
|
|
58
59
|
if (v === undefined || v === null || v === '') continue;
|
|
59
60
|
const s = document.createElement('span');
|
|
60
61
|
if (c.type === 'progress') s.textContent = `▰ ${v}%`;
|
|
61
|
-
else if (c.type === 'date') s.textContent = `📅 ${v}`;
|
|
62
|
+
else if (c.type === 'date') s.textContent = `📅 ${formatDate(c, v)}`;
|
|
62
63
|
else s.appendChild(renderPropertyCell(c, v, row));
|
|
63
64
|
meta.appendChild(s);
|
|
64
65
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
+
import { a as textOf } from './shared/property-cell-BSVcETss.js';
|
|
2
3
|
import { C as CHEVRONS, i as isoOf, r as resolveLocale, f as firstWeekday, m as monthGrid, p as parseDay, w as weekLabel, a as weekdayNames, b as isoWeek, s as stepMonth } from './shared/dates-a32DcW1l.js';
|
|
3
4
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
5
|
+
import './shared/avatar-CzPZN3uP.js';
|
|
4
6
|
|
|
5
7
|
// <nk-calendar-view name="calendar" label="📅 Calendar" date-key="due" weeks></nk-calendar-view>
|
|
6
8
|
// The fourth database view (NotionKit 1.9.0): a month, the rows as cards on
|
|
@@ -83,7 +85,7 @@ class NkCalendarView extends NkElement {
|
|
|
83
85
|
day.appendChild(num);
|
|
84
86
|
for (const row of byDay[iso] || []) {
|
|
85
87
|
const item = this.createElement('button', ['cv-item'], { type: 'button', 'data-id': row.id ?? '' });
|
|
86
|
-
const title = titleCol ?
|
|
88
|
+
const title = titleCol ? textOf(row[titleCol.key]) : '';
|
|
87
89
|
item.textContent = row.icon ? `${row.icon} ${title}` : title;
|
|
88
90
|
day.appendChild(item);
|
|
89
91
|
}
|
|
@@ -88,12 +88,13 @@ class NkFilterBar extends NkElement {
|
|
|
88
88
|
/**
|
|
89
89
|
* Applies filters and search to rows: a filter keeps rows where row[key]
|
|
90
90
|
* equals value – or differs from it with op: 'is-not'; the search looks
|
|
91
|
-
* for the text in every string field (a person's name
|
|
91
|
+
* for the text in every string field (a person's name, a text or link
|
|
92
|
+
* object's text).
|
|
92
93
|
*/
|
|
93
94
|
apply(rows) {
|
|
94
95
|
const q = this.value.trim().toLowerCase();
|
|
95
96
|
return rows.filter(r => this._filters.every(f => (f.op === 'is-not' ? r[f.key] !== f.value : r[f.key] === f.value))
|
|
96
|
-
&& (!q || Object.values(r).some(v => String(typeof v === 'object' && v ? v.name ?? '' : v ?? '').toLowerCase().includes(q))));
|
|
97
|
+
&& (!q || Object.values(r).some(v => String(typeof v === 'object' && v ? v.name ?? v.text ?? v.label ?? '' : v ?? '').toLowerCase().includes(q))));
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { r as renderPropertyCell } from './shared/property-cell-
|
|
2
|
+
import { a as textOf, f as formatDate, r as renderPropertyCell } from './shared/property-cell-BSVcETss.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
4
|
import './shared/avatar-CzPZN3uP.js';
|
|
5
|
+
import './shared/dates-a32DcW1l.js';
|
|
5
6
|
|
|
6
7
|
// <nk-gallery-view name="gallery" label="🖼 Gallery" meta-keys="status,due" new-row></nk-gallery-view>
|
|
7
8
|
// The fifth database view (NotionKit 1.14.0): the rows as cards with a
|
|
@@ -56,14 +57,14 @@ class NkGalleryView extends NkElement {
|
|
|
56
57
|
card.appendChild(cover);
|
|
57
58
|
}
|
|
58
59
|
const t = this.createElement('div', ['card-title']);
|
|
59
|
-
t.textContent = `${row.icon ? row.icon + ' ' : ''}${title ? row[title.key]
|
|
60
|
+
t.textContent = `${row.icon ? row.icon + ' ' : ''}${title ? textOf(row[title.key]) : ''}`;
|
|
60
61
|
const m = this.createElement('div', ['card-meta']);
|
|
61
62
|
for (const c of meta) {
|
|
62
63
|
const v = row[c.key];
|
|
63
64
|
if (v === undefined || v === null || v === '') continue;
|
|
64
65
|
const s = document.createElement('span');
|
|
65
66
|
if (c.type === 'progress') s.textContent = `▰ ${v}%`;
|
|
66
|
-
else if (c.type === 'date') s.textContent = `📅 ${v}`;
|
|
67
|
+
else if (c.type === 'date') s.textContent = `📅 ${formatDate(c, v)}`;
|
|
67
68
|
else s.appendChild(renderPropertyCell(c, v, row));
|
|
68
69
|
m.appendChild(s);
|
|
69
70
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { NkElement } from './base.js';
|
|
2
|
-
import { r as renderPropertyCell } from './shared/property-cell-
|
|
2
|
+
import { a as textOf, f as formatDate, r as renderPropertyCell } from './shared/property-cell-BSVcETss.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
4
|
import './shared/avatar-CzPZN3uP.js';
|
|
5
|
+
import './shared/dates-a32DcW1l.js';
|
|
5
6
|
|
|
6
7
|
// <nk-list-view name="list" label="☰ List" meta-keys="due,status" new-row></nk-list-view>
|
|
7
8
|
// The third database view: one line per row – icon and title, the `meta-keys`
|
|
@@ -45,12 +46,12 @@ class NkListView extends NkElement {
|
|
|
45
46
|
icon.textContent = row.icon || '';
|
|
46
47
|
if (!row.icon) icon.style.display = 'none';
|
|
47
48
|
const t = this.createElement('span', ['l-title']);
|
|
48
|
-
t.textContent = title ?
|
|
49
|
+
t.textContent = title ? textOf(row[title.key]) : '';
|
|
49
50
|
const m = this.createElement('span', ['l-meta']);
|
|
50
51
|
for (const c of meta) {
|
|
51
52
|
const v = row[c.key];
|
|
52
53
|
if (v === undefined || v === null || v === '') continue;
|
|
53
|
-
m.appendChild(c.type === 'date'
|
|
54
|
+
m.appendChild(c.type === 'date' ? document.createTextNode(formatDate(c, v)) : c.type === 'text' || !c.type ? document.createTextNode(textOf(v)) : renderPropertyCell(c, v, row));
|
|
54
55
|
}
|
|
55
56
|
item.append(icon, t, m);
|
|
56
57
|
this._list.appendChild(item);
|
|
@@ -1,7 +1,8 @@
|
|
|
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-BSVcETss.js';
|
|
3
3
|
import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
4
4
|
import './shared/avatar-CzPZN3uP.js';
|
|
5
|
+
import './shared/dates-a32DcW1l.js';
|
|
5
6
|
|
|
6
7
|
// <nk-table-view name="table" label="▦ Table" count new-row sortable wrap></nk-table-view>
|
|
7
8
|
// `wrap` lets cell text break (`.nk-table.wrap`), like Notion's "wrap column".
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { i as initialsOf, p as paintAvatar } from './avatar-CzPZN3uP.js';
|
|
2
|
+
import { r as resolveLocale } from './dates-a32DcW1l.js';
|
|
3
|
+
|
|
4
|
+
// Polymorphic property renderer for database views. Returns plain DOM built
|
|
5
|
+
// from NotionKit classes – deliberately not a custom element: every cell rule
|
|
6
|
+
// starts with `.nk-table`, so a cell inside its own shadow root would never
|
|
7
|
+
// be styled. Exported for consumers who render tables themselves.
|
|
8
|
+
//
|
|
9
|
+
// column: { key, label, type, icon, options: [{ value, label, color }], title }
|
|
10
|
+
// types: text | select | multi-select | date | person | checkbox | url | number | progress | actions
|
|
11
|
+
// url: a string is an address outside, opened in a new tab; { href, label,
|
|
12
|
+
// target } a link of your own – to another page of the app, with its text.
|
|
13
|
+
// actions: buttons for the row from column.actions [{ action, label, icon,
|
|
14
|
+
// danger, disabled, tooltip }]; the row's value – action names or objects –
|
|
15
|
+
// picks which of them it shows, all of them without one.
|
|
16
|
+
// text: a string, or { text, desc, color, tooltip, sort } – one of Notion's
|
|
17
|
+
// nine text colours for a tone, a quiet second line under it, the whole
|
|
18
|
+
// text in a tooltip (served by <nk-tooltip>), a value to sort by.
|
|
19
|
+
// date: the value as given, or with column.format ('short', 'relative' or
|
|
20
|
+
// Intl.DateTimeFormat options) and column.locale formatted from an ISO date
|
|
21
|
+
// or date-time, D.M.YYYY, a Date or a timestamp; { start, end } is a range.
|
|
22
|
+
// Sorting (compareBy) goes by column.sortKey – another field of the row –
|
|
23
|
+
// when there is one, else by a value's `sort`, a date's time, a select's
|
|
24
|
+
// place among its options; empty cells come last in both directions.
|
|
25
|
+
|
|
26
|
+
const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; };
|
|
27
|
+
const COLORS = ['gray', 'brown', 'orange', 'yellow', 'green', 'blue', 'purple', 'pink', 'red'];
|
|
28
|
+
|
|
29
|
+
function optionFor(column, value) {
|
|
30
|
+
const opts = column.options || [];
|
|
31
|
+
return opts.find(o => o.value === value) || opts.find(o => o.label === value) || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function tagFor(column, value) {
|
|
35
|
+
const opt = optionFor(column, value);
|
|
36
|
+
const color = opt?.color && COLORS.includes(opt.color) ? opt.color : 'gray'; // Notion's default option colour
|
|
37
|
+
return el('span', `nk-tag ${color}`, opt?.label ?? String(value));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The text of a value: a string as it is, a text, link or person object's text, label or name. */
|
|
41
|
+
const textOf = v => (v && typeof v === 'object' ? v.text ?? v.label ?? v.name ?? '' : String(v ?? ''));
|
|
42
|
+
|
|
43
|
+
/** The time of a date value in ms: an ISO date – local midnight – or date-time, D.M.YYYY, a Date, a timestamp, a range's start; NaN when it is none. */
|
|
44
|
+
function timeOf(v) {
|
|
45
|
+
if (v instanceof Date) return v.getTime();
|
|
46
|
+
if (typeof v === 'number') return v;
|
|
47
|
+
if (v && typeof v === 'object') return timeOf(v.start);
|
|
48
|
+
const s = String(v ?? '').trim();
|
|
49
|
+
let m = s.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
|
|
50
|
+
if (m) return new Date(+m[3], +m[2] - 1, +m[1]).getTime();
|
|
51
|
+
m = s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
52
|
+
if (m) return new Date(+m[1], +m[2] - 1, +m[3]).getTime();
|
|
53
|
+
return /^\d{4}-\d{2}-\d{2}T/.test(s) ? Date.parse(s) : NaN;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A date as its column shows it: as given without column.format; 'short' –
|
|
58
|
+
* day and month, the year when it is not this one; 'relative' – minutes or
|
|
59
|
+
* hours ago or ahead within a day, else the short date; or the options of
|
|
60
|
+
* Intl.DateTimeFormat. A range { start, end } stands as start → end.
|
|
61
|
+
*/
|
|
62
|
+
function formatDate(column, value) {
|
|
63
|
+
if (value && typeof value === 'object' && !(value instanceof Date)) return [value.start, value.end].filter(Boolean).map(v => formatDate(column, v)).join(' → ');
|
|
64
|
+
const format = column.format, t = timeOf(value);
|
|
65
|
+
if (!format || Number.isNaN(t)) return String(value ?? '');
|
|
66
|
+
const d = new Date(t), locale = column.locale || resolveLocale();
|
|
67
|
+
if (format === 'relative') {
|
|
68
|
+
const minutes = Math.round((t - Date.now()) / 60000), rel = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'short' });
|
|
69
|
+
if (Math.abs(minutes) < 60) return rel.format(minutes, 'minute');
|
|
70
|
+
if (Math.abs(minutes) < 24 * 60) return rel.format(Math.round(minutes / 60), 'hour');
|
|
71
|
+
}
|
|
72
|
+
if (format === 'short' || format === 'relative') {
|
|
73
|
+
const year = d.getFullYear() === new Date().getFullYear() ? {} : { year: 'numeric' };
|
|
74
|
+
return d.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', ...year });
|
|
75
|
+
}
|
|
76
|
+
return typeof format === 'object' ? new Intl.DateTimeFormat(locale, format).format(d) : String(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function renderPropertyCell(column, value, row = {}) {
|
|
80
|
+
const type = column.type || 'text';
|
|
81
|
+
if (type === 'actions') return actions(column, value);
|
|
82
|
+
if (value === undefined || value === null || value === '') {
|
|
83
|
+
if (type === 'checkbox') return checkbox(column, false, row);
|
|
84
|
+
if (type === 'progress') return progress(0);
|
|
85
|
+
return el('span', type === 'person' ? 'person-cell' : null, type === 'text' && column.title ? '' : '—');
|
|
86
|
+
}
|
|
87
|
+
switch (type) {
|
|
88
|
+
case 'select': return tagFor(column, value);
|
|
89
|
+
case 'multi-select': {
|
|
90
|
+
const wrap = el('span');
|
|
91
|
+
wrap.style.display = 'inline-flex'; wrap.style.gap = '4px'; wrap.style.flexWrap = 'wrap';
|
|
92
|
+
for (const v of Array.isArray(value) ? value : String(value).split(',').map(s => s.trim()).filter(Boolean)) wrap.appendChild(tagFor(column, v));
|
|
93
|
+
return wrap;
|
|
94
|
+
}
|
|
95
|
+
case 'date': return el('span', 'date-cell', formatDate(column, value));
|
|
96
|
+
case 'person': {
|
|
97
|
+
const cell = el('span', 'person-cell');
|
|
98
|
+
const p = typeof value === 'string' ? { name: value } : value;
|
|
99
|
+
// color: one of Notion's nine names, or any CSS background; none – the avatar gradient.
|
|
100
|
+
const avatar = el('span', 'nk-avatar small', p.initials || p.avatar || initialsOf(p.name));
|
|
101
|
+
paintAvatar(avatar, p.color);
|
|
102
|
+
cell.append(avatar, document.createTextNode(' ' + (p.name || '')));
|
|
103
|
+
return cell;
|
|
104
|
+
}
|
|
105
|
+
case 'checkbox': return checkbox(column, !!value, row);
|
|
106
|
+
case 'url': {
|
|
107
|
+
const link = typeof value === 'object' ? value : { href: String(value), label: String(value).replace(/^https?:\/\//, ''), target: '_blank' };
|
|
108
|
+
const a = el('a', null, link.label ?? link.href);
|
|
109
|
+
a.href = link.href ?? '';
|
|
110
|
+
if (link.target) a.target = link.target;
|
|
111
|
+
if (link.target === '_blank') a.rel = 'noopener';
|
|
112
|
+
return a;
|
|
113
|
+
}
|
|
114
|
+
// column.locale / column.format: Intl.NumberFormat locale and options, e.g. { minimumFractionDigits: 1 }.
|
|
115
|
+
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);
|
|
117
|
+
default: {
|
|
118
|
+
const o = typeof value === 'object' ? value : { text: value };
|
|
119
|
+
let t;
|
|
120
|
+
if (column.title) {
|
|
121
|
+
t = el('span', 'row-title');
|
|
122
|
+
if (row.icon) t.append(document.createTextNode(row.icon + ' '));
|
|
123
|
+
t.append(document.createTextNode(String(o.text ?? '')));
|
|
124
|
+
} else if (typeof value === 'object') {
|
|
125
|
+
t = el('span', o.color && COLORS.includes(o.color) ? `td-text ${o.color}` : 'td-text', String(o.text ?? ''));
|
|
126
|
+
} else return el('span', null, String(value));
|
|
127
|
+
if (o.tooltip) t.dataset.tooltip = o.tooltip;
|
|
128
|
+
if (!o.desc) return t;
|
|
129
|
+
const frag = document.createDocumentFragment();
|
|
130
|
+
frag.append(t, el('span', 'td-desc', String(o.desc)));
|
|
131
|
+
return frag;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function actions(column, value) {
|
|
137
|
+
const all = column.actions || [];
|
|
138
|
+
const list = value == null ? all
|
|
139
|
+
: (Array.isArray(value) ? value : [value]).map(v => typeof v === 'string' ? all.find(a => a.action === v) ?? { action: v, label: v } : v);
|
|
140
|
+
const wrap = el('span', 'row-actions');
|
|
141
|
+
for (const a of list) {
|
|
142
|
+
const b = el('button', `nk-btn ${a.danger ? 'danger' : 'secondary'} small`, [a.icon, a.label ?? a.action].filter(Boolean).join(' '));
|
|
143
|
+
b.type = 'button';
|
|
144
|
+
b.dataset.action = a.action;
|
|
145
|
+
b.disabled = !!a.disabled;
|
|
146
|
+
if (a.tooltip) b.dataset.tooltip = a.tooltip;
|
|
147
|
+
wrap.appendChild(b);
|
|
148
|
+
}
|
|
149
|
+
return wrap;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function checkbox(column, checked, row) {
|
|
153
|
+
const label = el('label', 'nk-check');
|
|
154
|
+
const input = el('input');
|
|
155
|
+
input.type = 'checkbox'; input.checked = checked;
|
|
156
|
+
input.dataset.key = column.key;
|
|
157
|
+
if (row.id != null) input.dataset.rowId = row.id;
|
|
158
|
+
label.appendChild(input);
|
|
159
|
+
return label;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function progress(value) {
|
|
163
|
+
const frag = document.createDocumentFragment();
|
|
164
|
+
const bar = el('span', 'nk-progress');
|
|
165
|
+
const fill = el('i'); fill.style.width = `${Math.max(0, Math.min(100, value))}%`;
|
|
166
|
+
bar.appendChild(fill);
|
|
167
|
+
frag.append(bar, el('span', 'nk-progress-label', `${Math.round(value)}%`));
|
|
168
|
+
return frag;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** A select's place among its options: the order Notion sorts by. */
|
|
172
|
+
function rankOf(column, value) {
|
|
173
|
+
const i = (column.options || []).findIndex(o => o.value === value || o.label === value);
|
|
174
|
+
return i < 0 ? (column.options || []).length : i;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** What a column sorts a row by – see the head of this file. */
|
|
178
|
+
function sortValue(column, row) {
|
|
179
|
+
if (column.sortKey) return row[column.sortKey];
|
|
180
|
+
const v = row[column.key];
|
|
181
|
+
if (v === undefined || v === null || v === '') return null;
|
|
182
|
+
if (typeof v === 'object' && !Array.isArray(v) && 'sort' in v) return v.sort;
|
|
183
|
+
switch (column.type || 'text') {
|
|
184
|
+
case 'number': case 'progress': return Number(v);
|
|
185
|
+
case 'checkbox': return v ? 1 : 0;
|
|
186
|
+
case 'date': { const t = timeOf(v); return Number.isNaN(t) ? String(v) : t; }
|
|
187
|
+
case 'select': return rankOf(column, v);
|
|
188
|
+
case 'multi-select': { const first = (Array.isArray(v) ? v : String(v).split(','))[0]; return first == null ? null : rankOf(column, String(first).trim()); }
|
|
189
|
+
default: return textOf(v);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Sort comparator for a column; empty cells come last whichever the direction. */
|
|
194
|
+
function compareBy(column, dir = 1) {
|
|
195
|
+
const empty = v => v === null || v === undefined || v === '' || Number.isNaN(v);
|
|
196
|
+
return (a, b) => {
|
|
197
|
+
const x = sortValue(column, a), y = sortValue(column, b);
|
|
198
|
+
if (empty(x) || empty(y)) return empty(x) === empty(y) ? 0 : empty(x) ? 1 : -1;
|
|
199
|
+
if (typeof x === 'number' && typeof y === 'number') return (x - y) * dir;
|
|
200
|
+
return String(x).localeCompare(String(y), undefined, { numeric: true }) * dir;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export { textOf as a, compareBy as c, formatDate as f, renderPropertyCell as r, tagFor as t };
|