@jungherz-de/notionkit-elements 1.19.0 → 1.20.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 +33 -0
- package/README.md +8 -4
- package/SKILL.md +30 -28
- package/dist/components/base.js +2 -0
- package/dist/components/nk-theme-toggle.js +163 -22
- package/dist/notionkit-elements.esm.js +167 -24
- package/dist/notionkit-elements.esm.min.js +1 -1
- package/dist/notionkit-elements.esm.min.js.map +1 -1
- package/dist/notionkit-elements.js +167 -24
- package/dist/notionkit-elements.min.js +1 -1
- package/package.json +3 -3
- package/src/components/shell/nk-theme-toggle.js +163 -22
- package/src/util/strings.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,38 @@ 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.20.0] – 2026-10-12
|
|
8
|
+
|
|
9
|
+
Built against NotionKit CSS 1.20.0 (peer `>= 1.20.0`).
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **`<nk-theme-toggle modes="auto light dark">` – a third state that follows
|
|
13
|
+
the system.** `auto` is what no stored value means, so the documented
|
|
14
|
+
inline script before first paint keeps working and a choice made without
|
|
15
|
+
`modes` still counts; while it is in force the element follows
|
|
16
|
+
`prefers-color-scheme` live, which the toggle never did. A click goes from
|
|
17
|
+
`auto` to the theme *not* on screen, then to the other one, then back, so
|
|
18
|
+
every click but the last changes something visibly. The glyph shows the
|
|
19
|
+
mode – `.nk-theme-glyph`, sun and moon diagonally for `auto` – instead of
|
|
20
|
+
the next theme, and the title names the mode and the next step (four new
|
|
21
|
+
texts: `appearance`, `themeAuto`, `themeLight`, `themeDark`). A choice made
|
|
22
|
+
in another tab arrives through the `storage` event. `value` returns
|
|
23
|
+
`auto | light | dark`, the new `theme` what is on screen, `nk-change`
|
|
24
|
+
reports both; `nextMode()` and `themeFor(mode)` are public.
|
|
25
|
+
**Two states stay the default**, where nothing about this element changes.
|
|
26
|
+
Requested by Mailverse (#2).
|
|
27
|
+
- `theme-color` keeps `<meta name="theme-color">` in step: while a theme is
|
|
28
|
+
fixed every bar carries that theme's colour, back on `auto` each one
|
|
29
|
+
carries its own again. The colours are the page's, never invented.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- The toggle added its system-preference listener to a throwaway
|
|
33
|
+
`matchMedia()` object and removed it from a second one, so it was never
|
|
34
|
+
released and could be collected with its query. One query per element now.
|
|
35
|
+
- A mode whose write failed (Safari's private mode refuses to store but
|
|
36
|
+
reads) is kept in the element until the page reloads, instead of falling
|
|
37
|
+
back to `auto` on the next read.
|
|
38
|
+
|
|
7
39
|
## [1.19.0] – 2026-09-25
|
|
8
40
|
|
|
9
41
|
Built against NotionKit CSS 1.19.1 (peer `>= 1.19.1`). Auxdesk's wish 24 and what SupaGantt's mock-ups found – 14, 15 and 16 – together with a toast that never hid.
|
|
@@ -746,6 +778,7 @@ built and tested against NotionKit CSS 1.1.1 (peer range `>= 1.0.0`).
|
|
|
746
778
|
import, never in the core bundle – shadow-less, adding `nk-block-host` to
|
|
747
779
|
itself so the foundation's editor adapter rules apply.
|
|
748
780
|
|
|
781
|
+
[1.20.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.20.0
|
|
749
782
|
[1.19.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.19.0
|
|
750
783
|
[1.18.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.18.0
|
|
751
784
|
[1.17.0]: https://github.com/JUNGHERZ/NotionKit-Elements/releases/tag/v1.17.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.20.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.20.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
|
|
|
@@ -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.20.0/notionkit.min.css">
|
|
75
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.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">
|
|
@@ -161,6 +161,10 @@ The elements on the page take new texts at once, after `setStrings()` and when `
|
|
|
161
161
|
| `commandPlaceholder` | `nk-cmdk` | `placeholder` | Search or type a command … | Suchen oder Befehl eingeben … |
|
|
162
162
|
| `emojiPlaceholder` | `nk-emoji-picker` | `placeholder` | Search… | Suchen … |
|
|
163
163
|
| `toggleTheme` | `nk-theme-toggle` | `title` | Toggle light / dark | Hell / Dunkel |
|
|
164
|
+
| `appearance` | `nk-theme-toggle` (`modes` with `auto`) | `title` | Appearance: {mode} – click: {next} | Darstellung: {mode} – Klick: {next} |
|
|
165
|
+
| `themeAuto` | `nk-theme-toggle` (`modes` with `auto`) | – | automatic (follows the system) | automatisch (wie das System) |
|
|
166
|
+
| `themeLight` | `nk-theme-toggle` (`modes` with `auto`) | – | light | hell |
|
|
167
|
+
| `themeDark` | `nk-theme-toggle` (`modes` with `auto`) | – | dark | dunkel |
|
|
164
168
|
| `synced` | `nk-synced` | `badge` | ⟳ synced | ⟳ synchronisiert |
|
|
165
169
|
| `message` | `nk-ai-input-row` | – | Message | Nachricht |
|
|
166
170
|
| `comment` | `nk-comments` | – | Comment | Kommentar |
|
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.20.0) wrapping NotionKit CSS v1.20.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.19.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.20.0/notionkit.min.css">
|
|
25
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.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>
|
|
@@ -1177,7 +1177,7 @@ Give it plain `<span>` or `<a>` children; they are cloned into the bar with sepa
|
|
|
1177
1177
|
|
|
1178
1178
|
### 3.33 `<nk-theme-toggle>` – Theme toggle
|
|
1179
1179
|
|
|
1180
|
-
The ☀️/🌙 button. Flips `data-theme` on `<html>`, remembers the choice in `localStorage`, applies a stored or system preference on first connect when `<html>` has no theme yet, and accepts `postMessage({ nkTheme })` from a parent page. `apply(
|
|
1180
|
+
The ☀️/🌙 button. Flips `data-theme` on `<html>`, remembers the choice in `localStorage`, applies a stored or system preference on first connect when `<html>` has no theme yet, and accepts `postMessage({ nkTheme })` from a parent page. `apply(mode)` does everything a click does: sets, persists and fires `nk-change`.`modes="auto light dark"` adds a third state: auto follows `prefers-color-scheme`, live, and is what no stored value means – so the inline script before first paint keeps working and a choice made without `modes` still counts. A click then goes from auto to the theme not on screen, to the other one, back to auto, so every click but the last changes something. The glyph shows the mode (`nk-theme-glyph`, sun and moon diagonally for auto) instead of the next theme, the title names the mode and the next one, and another tab's choice arrives through the `storage` event. Two states stay the default, where nothing about this element changes.
|
|
1181
1181
|
|
|
1182
1182
|
```html
|
|
1183
1183
|
<nk-theme-toggle></nk-theme-toggle>
|
|
@@ -1185,14 +1185,16 @@ The ☀️/🌙 button. Flips `data-theme` on `<html>`, remembers the choice in
|
|
|
1185
1185
|
|
|
1186
1186
|
| Attribute | Type | Default | Description |
|
|
1187
1187
|
|---|---|---|---|
|
|
1188
|
-
| `
|
|
1189
|
-
| `
|
|
1188
|
+
| `modes` | string | `light dark` | The states offered, e.g. `auto light dark`; without `auto` among them nothing changes. |
|
|
1189
|
+
| `storage-key` | string | `nk-theme` | localStorage key; `auto` is no entry. |
|
|
1190
|
+
| `theme-color` | boolean | – | Keeps `<meta name="theme-color">` in step: while a theme is fixed every bar carries that theme's colour – the page's own, never invented. |
|
|
1191
|
+
| `title` | string | – | Tooltip; replaces the built-in text. |
|
|
1190
1192
|
|
|
1191
|
-
**Events:** `nk-change` `{ value: 'light' | 'dark' }` –
|
|
1193
|
+
**Events:** `nk-change` `{ value: 'auto' | 'light' | 'dark', theme: 'light' | 'dark' }` – Mode applied; `theme` is what is on screen (without `auto` both are the theme).
|
|
1192
1194
|
|
|
1193
|
-
**Properties:** `value` · **Methods:** `apply(
|
|
1195
|
+
**Properties:** `value`, `theme`, `mode`, `modes` · **Methods:** `apply(mode)`, `nextMode()`, `themeFor(mode)`
|
|
1194
1196
|
|
|
1195
|
-
**Replaces:** `.nk-theme-toggle`
|
|
1197
|
+
**Replaces:** `.nk-theme-toggle`, `.nk-theme-glyph`
|
|
1196
1198
|
|
|
1197
1199
|
```html
|
|
1198
1200
|
<!-- equivalent class markup -->
|
|
@@ -3150,8 +3152,8 @@ Eight skeletons, one per app shape, mirroring the NotionKit CSS SKILL.md. Copy o
|
|
|
3150
3152
|
<html lang="en" data-theme="light">
|
|
3151
3153
|
<head>
|
|
3152
3154
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3153
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3154
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3155
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3156
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3155
3157
|
</head>
|
|
3156
3158
|
<body class="nk-body">
|
|
3157
3159
|
<nk-app>
|
|
@@ -3232,8 +3234,8 @@ Rules of the shell: `nk-sidebar`, `nk-topbar` and `nk-page` are `display: conten
|
|
|
3232
3234
|
<html lang="en" data-theme="light">
|
|
3233
3235
|
<head>
|
|
3234
3236
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3235
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3236
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3237
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3238
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3237
3239
|
</head>
|
|
3238
3240
|
<body class="nk-body">
|
|
3239
3241
|
<nk-app>
|
|
@@ -3340,8 +3342,8 @@ Data contract: `columns` describe the properties (`type`: text | select | multi-
|
|
|
3340
3342
|
<html lang="en" data-theme="light">
|
|
3341
3343
|
<head>
|
|
3342
3344
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3343
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3344
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3345
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3346
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3345
3347
|
</head>
|
|
3346
3348
|
<body class="nk-body">
|
|
3347
3349
|
<nk-app>
|
|
@@ -3400,8 +3402,8 @@ Data contract: `columns` describe the properties (`type`: text | select | multi-
|
|
|
3400
3402
|
<html lang="en" data-theme="light">
|
|
3401
3403
|
<head>
|
|
3402
3404
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3403
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3404
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3405
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3406
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3405
3407
|
</head>
|
|
3406
3408
|
<body class="nk-body">
|
|
3407
3409
|
<!-- your app -->
|
|
@@ -3478,8 +3480,8 @@ The open/close contract is one attribute: `settings.open = true`, `settings.show
|
|
|
3478
3480
|
<html lang="en" data-theme="light">
|
|
3479
3481
|
<head>
|
|
3480
3482
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3481
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3482
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3483
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3484
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3483
3485
|
</head>
|
|
3484
3486
|
<body class="nk-body">
|
|
3485
3487
|
<div class="nk-page" style="padding-top:48px">
|
|
@@ -3525,8 +3527,8 @@ The open/close contract is one attribute: `settings.open = true`, `settings.show
|
|
|
3525
3527
|
<html lang="en" data-theme="light">
|
|
3526
3528
|
<head>
|
|
3527
3529
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3528
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3529
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3530
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3531
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3530
3532
|
</head>
|
|
3531
3533
|
<body class="nk-body">
|
|
3532
3534
|
<nk-page narrow icon="📘" cover>
|
|
@@ -3570,8 +3572,8 @@ Note `narrow`: the page is the document, so there is no inner scroll wrapper –
|
|
|
3570
3572
|
<html lang="en" data-theme="light">
|
|
3571
3573
|
<head>
|
|
3572
3574
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3573
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3574
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3575
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3576
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3575
3577
|
</head>
|
|
3576
3578
|
<body class="nk-body">
|
|
3577
3579
|
<nk-app>
|
|
@@ -3625,8 +3627,8 @@ Note `narrow`: the page is the document, so there is no inner scroll wrapper –
|
|
|
3625
3627
|
<html lang="en" data-theme="light">
|
|
3626
3628
|
<head>
|
|
3627
3629
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3628
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.
|
|
3629
|
-
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.
|
|
3630
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit@1.20.0/notionkit.min.css">
|
|
3631
|
+
<script src="https://cdn.jsdelivr.net/npm/@jungherz-de/notionkit-elements@1.20.0/dist/notionkit-elements.min.js"></script>
|
|
3630
3632
|
</head>
|
|
3631
3633
|
<body class="nk-body">
|
|
3632
3634
|
<!-- A narrow column, centred: layout is yours, so it is inline. -->
|
|
@@ -3761,7 +3763,7 @@ Form controls additionally re-dispatch a native, bubbling `change` event, so `fo
|
|
|
3761
3763
|
| `<nk-tree-item>` | shell | `icon`, `label`, `value`, `href` | `(default)`, `icon`, `end` | `nk-select`, `nk-toggle`, `nk-action` |
|
|
3762
3764
|
| `<nk-topbar>` | shell | – | `(default)`, `actions` | – |
|
|
3763
3765
|
| `<nk-breadcrumb>` | shell | `separator` | `(default)` | `nk-select` |
|
|
3764
|
-
| `<nk-theme-toggle>` | shell | `storage-key`, `title` | – | `nk-change` |
|
|
3766
|
+
| `<nk-theme-toggle>` | shell | `modes`, `storage-key`, `theme-color`, `title` | – | `nk-change` |
|
|
3765
3767
|
| `<nk-tab-bar>` | shell | `value`, `always`, `fixed`, `floating` | `(default)` | `nk-change`, `nk-select` |
|
|
3766
3768
|
| `<nk-tab-bar-item>` | shell | `icon`, `value`, `label`, `href` | `(default)`, `icon` | `nk-select` |
|
|
3767
3769
|
| `<nk-page>` | page | `icon`, `full`, `small`, `cover` | `(default)`, `cover`, `icon` | `nk-action` |
|
|
@@ -3837,10 +3839,10 @@ Form controls additionally re-dispatch a native, bubbling `change` event, so `fo
|
|
|
3837
3839
|
| Texts | `src/util/strings.js` – the English and German dictionary and `setStrings()`; `NkElement#str(key)` reads it in the element's language, `onStringsChanged()` runs after `setStrings()` and when `<html lang>` changes |
|
|
3838
3840
|
| Components | `src/components/{forms,content,shell,page,overlays,data}/nk-*.js`, one tag per file, `customElements.define` at the bottom |
|
|
3839
3841
|
| 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` |
|
|
3840
|
-
| Peer | `@jungherz-de/notionkit >= 1.
|
|
3842
|
+
| Peer | `@jungherz-de/notionkit >= 1.20.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 |
|
|
3841
3843
|
|
|
3842
3844
|
Lifecycle: construct (attach shadow, adopt sheets) → first connect (wrapper + `render()`) → every connect (`setupEvents()`, theme registration, light-DOM observer) → `attributeChangedCallback` → `onAttributeChanged` → disconnect (`teardownEvents()`, unregister).
|
|
3843
3845
|
|
|
3844
3846
|
|
|
3845
3847
|
---
|
|
3846
|
-
*NotionKit Elements v1.
|
|
3848
|
+
*NotionKit Elements v1.20.0 · wrapping NotionKit CSS v1.20.0 · MIT · Jungherz GmbH*
|
package/dist/components/base.js
CHANGED
|
@@ -18,6 +18,7 @@ const BUILT_IN = {
|
|
|
18
18
|
commentPlaceholder: 'Comment …', send: 'Send', askPlaceholder: 'Ask something …',
|
|
19
19
|
commandPlaceholder: 'Search or type a command …', emojiPlaceholder: 'Search…',
|
|
20
20
|
toggleTheme: 'Toggle light / dark', synced: '⟳ synced',
|
|
21
|
+
appearance: 'Appearance: {mode} – click: {next}', themeAuto: 'automatic (follows the system)', themeLight: 'light', themeDark: 'dark',
|
|
21
22
|
message: 'Message', breadcrumb: 'Breadcrumb', commandPalette: 'Command palette', comment: 'Comment',
|
|
22
23
|
searchEmoji: 'Search emoji', settings: 'Settings', title: 'Title', changeIcon: 'Change icon',
|
|
23
24
|
add: 'Add', more: 'More',
|
|
@@ -32,6 +33,7 @@ const BUILT_IN = {
|
|
|
32
33
|
commentPlaceholder: 'Kommentieren …', send: 'Senden', askPlaceholder: 'Frag etwas …',
|
|
33
34
|
commandPlaceholder: 'Suchen oder Befehl eingeben …', emojiPlaceholder: 'Suchen …',
|
|
34
35
|
toggleTheme: 'Hell / Dunkel', synced: '⟳ synchronisiert',
|
|
36
|
+
appearance: 'Darstellung: {mode} – Klick: {next}', themeAuto: 'automatisch (wie das System)', themeLight: 'hell', themeDark: 'dunkel',
|
|
35
37
|
message: 'Nachricht', breadcrumb: 'Pfad', commandPalette: 'Befehlspalette', comment: 'Kommentar',
|
|
36
38
|
searchEmoji: 'Emoji suchen', settings: 'Einstellungen', title: 'Titel', changeIcon: 'Symbol ändern',
|
|
37
39
|
add: 'Hinzufügen', more: 'Mehr',
|
|
@@ -8,62 +8,203 @@ import '@jungherz-de/notionkit/notionkit-styles.js';
|
|
|
8
8
|
// system preference on first connect when <html> carries no theme yet, and
|
|
9
9
|
// accepts postMessage({ nkTheme }) from a parent page (the landing page's
|
|
10
10
|
// embedded demo uses that).
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
//
|
|
12
|
+
// <nk-theme-toggle modes="auto light dark"> adds a third state (1.20.0):
|
|
13
|
+
// `auto` follows prefers-color-scheme, live, and is what no stored value
|
|
14
|
+
// means – so the documented inline script before first paint keeps working
|
|
15
|
+
// and a choice made without `modes` still counts. The glyph then shows the
|
|
16
|
+
// mode (☀️, 🌙, both for auto) instead of the next theme, the title names
|
|
17
|
+
// the mode and the next one, other tabs follow through the storage event,
|
|
18
|
+
// and `theme-color` keeps <meta name="theme-color"> in step. Two states stay
|
|
19
|
+
// the default, where nothing about this element changes.
|
|
20
|
+
const GLYPH = { light: '☀️', dark: '🌙' };
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
class NkThemeToggle extends NkElement {
|
|
23
|
+
static get observedAttributes() { return ['storage-key', 'title', 'modes', 'theme-color']; }
|
|
15
24
|
|
|
16
25
|
render() {
|
|
17
26
|
this._btn = this.createElement('button', ['nk-topbar-btn', 'nk-theme-toggle'], { type: 'button' });
|
|
18
|
-
this._syncTitle();
|
|
19
27
|
this._wrapper.appendChild(this._btn);
|
|
20
|
-
|
|
28
|
+
this._readThemeColors();
|
|
29
|
+
if (this.auto) {
|
|
30
|
+
// With `auto` this element owns the theme: a stored choice, else the
|
|
31
|
+
// system. The inline script before first paint has set the same value.
|
|
32
|
+
this._applyMode();
|
|
33
|
+
} else {
|
|
34
|
+
const root = document.documentElement;
|
|
35
|
+
let stored = null;
|
|
36
|
+
try { stored = localStorage.getItem(this.storageKey); } catch {}
|
|
37
|
+
if (stored === 'light' || stored === 'dark') root.setAttribute('data-theme', stored);
|
|
38
|
+
else if (!root.hasAttribute('data-theme')) root.setAttribute('data-theme', this.systemTheme);
|
|
39
|
+
this._syncThemeColor();
|
|
40
|
+
this._syncGlyph();
|
|
41
|
+
this._syncTitle();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Modes ──
|
|
46
|
+
|
|
47
|
+
/** The states offered, in the author's order; without a theme among them the default two. */
|
|
48
|
+
get modes() {
|
|
49
|
+
const list = [...new Set((this.getAttribute('modes') || '').toLowerCase().split(/\s+/))].filter(m => m === 'auto' || m === 'light' || m === 'dark');
|
|
50
|
+
return list.some(m => m !== 'auto') ? list : ['light', 'dark'];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Whether `auto` is offered – the only thing that changes this element's behaviour. */
|
|
54
|
+
get auto() { return this.modes.includes('auto'); }
|
|
55
|
+
|
|
56
|
+
/** One query object per element: a listener on a throwaway one is neither removable nor safe from collection. */
|
|
57
|
+
get systemQuery() { return (this._query ??= matchMedia('(prefers-color-scheme: dark)')); }
|
|
58
|
+
|
|
59
|
+
get systemTheme() { return this.systemQuery.matches ? 'dark' : 'light'; }
|
|
60
|
+
|
|
61
|
+
/** The mode in force: 'auto' (nothing stored), 'light' or 'dark'. Without `auto` the theme on screen. */
|
|
62
|
+
get mode() {
|
|
63
|
+
if (!this.auto) return getCurrentTheme();
|
|
21
64
|
let stored = null;
|
|
22
|
-
try { stored = localStorage.getItem(this.storageKey); } catch {}
|
|
23
|
-
if (stored === 'light' || stored === 'dark')
|
|
24
|
-
|
|
65
|
+
try { stored = localStorage.getItem(this.storageKey); } catch { return this._unsaved || 'auto'; }
|
|
66
|
+
if (stored === 'light' || stored === 'dark') return stored;
|
|
67
|
+
return this._unsaved || 'auto';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The theme on screen for a mode: `auto` is whatever the system says. */
|
|
71
|
+
themeFor(mode = this.mode) { return mode === 'auto' ? this.systemTheme : mode; }
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The mode a click moves to. From `auto` to the theme that is *not* on
|
|
75
|
+
* screen, then to the other one, then back to `auto` – so every click but
|
|
76
|
+
* the last changes something visibly. Without `auto` it cycles the themes.
|
|
77
|
+
*/
|
|
78
|
+
nextMode(mode = this.mode) {
|
|
79
|
+
const modes = this.modes, themes = modes.filter(m => m !== 'auto'), system = this.systemTheme;
|
|
80
|
+
if (!modes.includes('auto')) return themes[(themes.indexOf(mode) + 1) % themes.length];
|
|
81
|
+
if (mode === 'auto') return themes.find(m => m !== system) ?? themes[0];
|
|
82
|
+
if (mode !== system) return themes.includes(system) ? system : 'auto';
|
|
83
|
+
return 'auto';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Applying ──
|
|
87
|
+
|
|
88
|
+
_applyMode(mode = this.mode) {
|
|
89
|
+
const theme = this.themeFor(mode);
|
|
90
|
+
const root = document.documentElement;
|
|
91
|
+
// Only on a change: the observer in base.js mirrors data-theme into every
|
|
92
|
+
// element, and auto re-applies the same value on every system change.
|
|
93
|
+
if (root.getAttribute('data-theme') !== theme) root.setAttribute('data-theme', theme);
|
|
94
|
+
this._syncThemeColor(mode);
|
|
25
95
|
this._syncGlyph();
|
|
96
|
+
this._syncTitle();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The page's own theme-color values, per medium, before this element touched them. */
|
|
100
|
+
_readThemeColors() {
|
|
101
|
+
this._bars = [...document.querySelectorAll('meta[name="theme-color"]')].map(meta => ({
|
|
102
|
+
meta, dark: (meta.getAttribute('media') || '').includes('dark'), content: meta.getAttribute('content'),
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* `theme-color`: while a theme is fixed every meta carries the colour of
|
|
108
|
+
* that theme, so the browser bar follows the choice; back on `auto` each
|
|
109
|
+
* one carries its own again. The colours are the page's – never invented.
|
|
110
|
+
*/
|
|
111
|
+
_syncThemeColor(mode = this.mode) {
|
|
112
|
+
if (!this.hasAttribute('theme-color') || !this._bars?.length) return;
|
|
113
|
+
const auto = mode === 'auto', dark = this.themeFor(mode) === 'dark';
|
|
114
|
+
const fixed = (this._bars.find(bar => bar.dark === dark) ?? this._bars[0]).content;
|
|
115
|
+
for (const bar of this._bars) if (bar.content !== null) bar.meta.setAttribute('content', auto ? bar.content : fixed);
|
|
26
116
|
}
|
|
27
117
|
|
|
28
118
|
_syncGlyph() {
|
|
29
|
-
if (this._btn)
|
|
119
|
+
if (!this._btn) return;
|
|
120
|
+
if (!this.auto) {
|
|
121
|
+
// Two states: the glyph is the next theme, as it has always been.
|
|
122
|
+
this._btn.replaceChildren(getCurrentTheme() === 'dark' ? GLYPH.light : GLYPH.dark);
|
|
123
|
+
this._btn.removeAttribute('aria-label');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const mode = this.mode;
|
|
127
|
+
const glyph = this.createElement('span', ['nk-theme-glyph'], { 'aria-hidden': 'true' });
|
|
128
|
+
if (mode === 'auto') glyph.classList.add('auto');
|
|
129
|
+
for (const text of mode === 'auto' ? [GLYPH.light, GLYPH.dark] : [GLYPH[mode]]) {
|
|
130
|
+
glyph.appendChild(Object.assign(document.createElement('span'), { textContent: text }));
|
|
131
|
+
}
|
|
132
|
+
this._btn.replaceChildren(glyph);
|
|
133
|
+
// Two emojis are no accessible name; the title's text is.
|
|
134
|
+
this._btn.setAttribute('aria-label', this._titleText());
|
|
30
135
|
}
|
|
31
136
|
|
|
32
137
|
_syncTheme(theme) {
|
|
33
138
|
super._syncTheme(theme);
|
|
34
139
|
this._syncGlyph();
|
|
140
|
+
this._syncTitle();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_titleText() {
|
|
144
|
+
const own = this.getAttribute('title');
|
|
145
|
+
if (own) return own;
|
|
146
|
+
if (!this.auto) return this.str('toggleTheme');
|
|
147
|
+
const name = (mode) => this.str(mode === 'auto' ? 'themeAuto' : mode === 'dark' ? 'themeDark' : 'themeLight');
|
|
148
|
+
return this.str('appearance').replace('{mode}', name(this.mode)).replace('{next}', name(this.nextMode()));
|
|
35
149
|
}
|
|
36
150
|
|
|
151
|
+
_syncTitle() { if (this._btn) this._btn.title = this._titleText(); }
|
|
152
|
+
onStringsChanged() { this._syncTitle(); if (this.auto) this._syncGlyph(); }
|
|
153
|
+
|
|
37
154
|
setupEvents() {
|
|
38
|
-
this._onClick = () => this.apply(
|
|
39
|
-
this._onMessage = (e) => { const t = e.data?.nkTheme; if (t === 'light' || t === 'dark') this.apply(t); };
|
|
155
|
+
this._onClick = () => this.apply(this.nextMode());
|
|
156
|
+
this._onMessage = (e) => { const t = e.data?.nkTheme; if (t === 'light' || t === 'dark' || (t === 'auto' && this.auto)) this.apply(t); };
|
|
157
|
+
// `auto` has to notice a system switch while the page is open, and a
|
|
158
|
+
// choice made in another tab. Both only matter where `auto` is offered.
|
|
159
|
+
this._onSystem = () => { if (this.mode === 'auto') this._applyMode(); };
|
|
160
|
+
this._onStorage = (e) => { if (e.key === this.storageKey || e.key === null) this._applyMode(); };
|
|
40
161
|
this._btn.addEventListener('click', this._onClick);
|
|
41
162
|
window.addEventListener('message', this._onMessage);
|
|
163
|
+
if (this.auto) {
|
|
164
|
+
this.systemQuery.addEventListener('change', this._onSystem);
|
|
165
|
+
window.addEventListener('storage', this._onStorage);
|
|
166
|
+
}
|
|
42
167
|
}
|
|
43
168
|
|
|
44
169
|
teardownEvents() {
|
|
45
170
|
this._btn?.removeEventListener('click', this._onClick);
|
|
46
171
|
window.removeEventListener('message', this._onMessage);
|
|
172
|
+
if (this._onSystem) this.systemQuery.removeEventListener('change', this._onSystem);
|
|
173
|
+
window.removeEventListener('storage', this._onStorage);
|
|
47
174
|
}
|
|
48
175
|
|
|
49
|
-
_syncTitle() { this._btn.title = this.getAttribute('title') || this.str('toggleTheme'); }
|
|
50
|
-
onStringsChanged() { this._syncTitle(); }
|
|
51
|
-
|
|
52
176
|
onAttributeChanged(name) {
|
|
53
|
-
if (
|
|
177
|
+
if (!this._btn) return;
|
|
178
|
+
if (name === 'modes') {
|
|
179
|
+
// Re-arm: the listeners above hang on whether `auto` is offered.
|
|
180
|
+
if (this.isConnected) { this.teardownEvents(); this.setupEvents(); }
|
|
181
|
+
this.auto ? this._applyMode() : this._syncGlyph();
|
|
182
|
+
}
|
|
183
|
+
this._syncTitle();
|
|
54
184
|
}
|
|
55
185
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Sets the mode on <html>, persists it and reports it. `light` / `dark`
|
|
188
|
+
* are stored, `auto` (only where it is offered) clears the entry – that is
|
|
189
|
+
* what the inline script before first paint reads.
|
|
190
|
+
*/
|
|
191
|
+
apply(mode) {
|
|
192
|
+
const wanted = mode === 'auto' && this.auto ? 'auto' : mode === 'light' || mode === 'dark' ? mode : this.themeFor(mode);
|
|
193
|
+
try {
|
|
194
|
+
if (wanted === 'auto') localStorage.removeItem(this.storageKey);
|
|
195
|
+
else localStorage.setItem(this.storageKey, wanted);
|
|
196
|
+
this._unsaved = null;
|
|
197
|
+
} catch { this._unsaved = wanted; }
|
|
198
|
+
this._applyMode(wanted);
|
|
199
|
+
this.emit('nk-change', { value: this.auto ? wanted : this.themeFor(wanted), theme: this.themeFor(wanted) });
|
|
62
200
|
}
|
|
63
201
|
|
|
64
202
|
get storageKey() { return this.getAttribute('storage-key') || 'nk-theme'; }
|
|
65
|
-
|
|
203
|
+
/** The mode: 'auto' | 'light' | 'dark'. Without `auto` the theme, as before. */
|
|
204
|
+
get value() { return this.auto ? this.mode : getCurrentTheme(); }
|
|
66
205
|
set value(v) { this.apply(v); }
|
|
206
|
+
/** The theme on screen – 'light' or 'dark', whatever the mode. */
|
|
207
|
+
get theme() { return this.themeFor(); }
|
|
67
208
|
}
|
|
68
209
|
|
|
69
210
|
customElements.define('nk-theme-toggle', NkThemeToggle);
|