@riebeckite/plugin-ux 0.0.31 → 0.0.33
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/README.md +11 -19
- package/README_ja.md +14 -21
- package/dist/client.js +44 -79
- package/dist/index.d.ts +2 -4
- package/dist/index.js +9 -25
- package/dist/src/init.d.ts +1 -1
- package/dist/src/inject.d.ts +0 -9
- package/dist/src/options.d.ts +0 -9
- package/package.json +2 -2
- package/style.css +2 -45
package/README.md
CHANGED
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
Client-side progressive enhancements for reading. The plugin leaves the built
|
|
6
6
|
article HTML untouched and adds a reading progress bar, a back-to-top button,
|
|
7
|
-
table-of-contents scroll-spy
|
|
7
|
+
and table-of-contents scroll-spy at runtime.
|
|
8
8
|
|
|
9
9
|
[日本語](./README_ja.md)
|
|
10
10
|
|
|
11
|
+
## Live example
|
|
12
|
+
|
|
13
|
+
A reading-progress bar runs along the top of this page, and a back-to-top button appears once you scroll. The **Contents** rail also follows the current section.
|
|
14
|
+
|
|
11
15
|
## Features
|
|
12
16
|
|
|
13
17
|
- **Reading progress bar** — a thin fixed bar (`.rb-ux__progress`) that follows
|
|
@@ -17,8 +21,6 @@ table-of-contents scroll-spy, and code copy buttons at runtime.
|
|
|
17
21
|
- **TOC scroll-spy** — observes `a[href^="#"]` links inside the article table of
|
|
18
22
|
contents with `IntersectionObserver` and marks the active link with
|
|
19
23
|
`.rb-ux__toc-active`.
|
|
20
|
-
- **Code copy buttons** — wraps each `pre > code` in `.rb-ux__code` and adds a
|
|
21
|
-
copy button (`.rb-ux__copy`) with a transient "copied" state.
|
|
22
24
|
|
|
23
25
|
Every feature is a no-op when the relevant DOM is absent, and `initUx()` is
|
|
24
26
|
idempotent.
|
|
@@ -27,19 +29,16 @@ idempotent.
|
|
|
27
29
|
|
|
28
30
|
```ts
|
|
29
31
|
import { defineConfig } from "@riebeckite/core";
|
|
30
|
-
import {
|
|
32
|
+
import { ux } from "@riebeckite/plugin-ux";
|
|
31
33
|
|
|
32
34
|
export default defineConfig({
|
|
33
35
|
// ...
|
|
34
36
|
plugins: [
|
|
35
|
-
|
|
37
|
+
ux({
|
|
36
38
|
progress: true,
|
|
37
39
|
backToTop: true,
|
|
38
40
|
tocScrollSpy: true,
|
|
39
|
-
codeCopy: true,
|
|
40
41
|
backToTopLabel: "Back to top",
|
|
41
|
-
copyLabel: "Copy",
|
|
42
|
-
copiedLabel: "Copied",
|
|
43
42
|
}),
|
|
44
43
|
],
|
|
45
44
|
});
|
|
@@ -54,10 +53,7 @@ The factory is also exported as `ux`.
|
|
|
54
53
|
| `progress` | `boolean` | `true` | Show the reading progress bar |
|
|
55
54
|
| `backToTop` | `boolean` | `true` | Show the back-to-top button |
|
|
56
55
|
| `tocScrollSpy` | `boolean` | `true` | Highlight the active table-of-contents link |
|
|
57
|
-
| `codeCopy` | `boolean` | `true` | Add a copy button to each code block |
|
|
58
56
|
| `backToTopLabel` | `string` | `"Back to top"` | `aria-label` for the back-to-top button |
|
|
59
|
-
| `copyLabel` | `string` | `"Copy"` | Label of the copy button |
|
|
60
|
-
| `copiedLabel` | `string` | `"Copied"` | Transient label shown after copying |
|
|
61
57
|
|
|
62
58
|
## How configuration reaches the client
|
|
63
59
|
|
|
@@ -80,9 +76,6 @@ being inserted twice per page.
|
|
|
80
76
|
| `rb-ux__back-to-top` | Back-to-top button |
|
|
81
77
|
| `rb-ux__back-to-top--visible` | Added while the button is visible |
|
|
82
78
|
| `rb-ux__toc-active` | Active table-of-contents link |
|
|
83
|
-
| `rb-ux__code` | Code block wrapper |
|
|
84
|
-
| `rb-ux__copy` | Copy button |
|
|
85
|
-
| `rb-ux__copy--copied` | Transient copied state |
|
|
86
79
|
|
|
87
80
|
Styles ship as `@riebeckite/plugin-ux/style.css` and use the theme's
|
|
88
81
|
`--rb-color-*` tokens so they do not fight the existing theme.
|
|
@@ -95,20 +88,19 @@ Styles ship as `@riebeckite/plugin-ux/style.css` and use the theme's
|
|
|
95
88
|
- The active table-of-contents link receives `aria-current="true"`.
|
|
96
89
|
- Under `prefers-reduced-motion: reduce`, progress and back-to-top transitions
|
|
97
90
|
are disabled and the back-to-top jump becomes an immediate scroll.
|
|
98
|
-
-
|
|
91
|
+
- The progress bar and buttons are hidden when printing.
|
|
99
92
|
|
|
100
93
|
## Limitations
|
|
101
94
|
|
|
102
95
|
- Client-only: without JavaScript nothing is added.
|
|
103
|
-
-
|
|
96
|
+
- With SPA navigation enabled, the enhancements are re-initialized after the
|
|
97
|
+
page root is replaced.
|
|
104
98
|
- The TOC scroll-spy looks for `.rr-table-of-contents` or `[data-rb-toc]` as
|
|
105
99
|
the table-of-contents container.
|
|
106
|
-
- Copy buttons are skipped inside `.rr-code` blocks (managed by the
|
|
107
|
-
code-enhance plugin) to avoid duplicate copy UI.
|
|
108
100
|
|
|
109
101
|
## Exports
|
|
110
102
|
|
|
111
|
-
- `
|
|
103
|
+
- `ux(options?)` / `ux(options?)` — plugin factory
|
|
112
104
|
- `initUx` — browser initializer
|
|
113
105
|
- Types: `UxOptions`, `UxResolvedConfig`
|
|
114
106
|
|
package/README_ja.md
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- Generated from docs/docs/plugins/ux.ja.md. Edit the canonical documentation in docs/docs/plugins and run `pnpm docs:sync`. -->
|
|
4
4
|
|
|
5
|
-
記事の読みやすさを高めるクライアント側のプログレッシブ・エンハンスメントをまとめたプラグインです。ビルド後の記事 HTML
|
|
5
|
+
記事の読みやすさを高めるクライアント側のプログレッシブ・エンハンスメントをまとめたプラグインです。ビルド後の記事 HTML はそのままに、読み進捗バー・トップへ戻るボタン・目次のスクロール連動ハイライトを追加します。
|
|
6
6
|
|
|
7
7
|
[English](./README.md)
|
|
8
8
|
|
|
9
|
+
## 実行例
|
|
10
|
+
|
|
11
|
+
このページの上部には読書プログレスバーが表示され、スクロールするとトップへ戻るボタンが現れます。**Contents** の目次も現在のセクションに追従します。
|
|
12
|
+
|
|
9
13
|
## できること
|
|
10
14
|
|
|
11
15
|
- **読み進捗バー**: 画面上部に固定した細いバー(`.rb-ux__progress`)が記事のスクロール量に追従します。
|
|
12
|
-
- **トップへ戻るボタン**: 一定量スクロールすると表示される button 要素(`.rb-ux__back-to-top`)。`aria-label` を持ち、スムーズスクロールで先頭へ戻ります。
|
|
16
|
+
- **トップへ戻るボタン**: 一定量スクロールすると表示される `button` 要素(`.rb-ux__back-to-top`)。`aria-label` を持ち、スムーズスクロールで先頭へ戻ります。
|
|
13
17
|
- **目次のスクロール連動**: 記事の目次コンテナ内の `a[href^="#"]` を `IntersectionObserver` で監視し、現在位置の見出しリンクに `.rb-ux__toc-active` を付けます。
|
|
14
|
-
- **コードのコピーボタン**: 各 `pre > code`(`.rb-ux__code` でラップ)にコピーボタン(`.rb-ux__copy`)を追加します。コピー直後は一時的に「コピーしました」表示へ切り替わります。
|
|
15
18
|
|
|
16
19
|
いずれも対応する DOM が無いページでは何もしません。`initUx()` は何度呼んでも二重に初期化しません。
|
|
17
20
|
|
|
@@ -19,25 +22,22 @@
|
|
|
19
22
|
|
|
20
23
|
```ts
|
|
21
24
|
import { defineConfig } from "@riebeckite/core";
|
|
22
|
-
import {
|
|
25
|
+
import { ux } from "@riebeckite/plugin-ux";
|
|
23
26
|
|
|
24
27
|
export default defineConfig({
|
|
25
28
|
// ...
|
|
26
29
|
plugins: [
|
|
27
|
-
|
|
30
|
+
ux({
|
|
28
31
|
progress: true,
|
|
29
32
|
backToTop: true,
|
|
30
33
|
tocScrollSpy: true,
|
|
31
|
-
codeCopy: true,
|
|
32
34
|
backToTopLabel: "トップへ戻る",
|
|
33
|
-
copyLabel: "コピー",
|
|
34
|
-
copiedLabel: "コピーしました",
|
|
35
35
|
}),
|
|
36
36
|
],
|
|
37
37
|
});
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
`
|
|
40
|
+
`ux` は `ux` という別名でもエクスポートしています。
|
|
41
41
|
|
|
42
42
|
## オプション
|
|
43
43
|
|
|
@@ -46,16 +46,13 @@ export default defineConfig({
|
|
|
46
46
|
| `progress` | `boolean` | `true` | 読み進捗バーを表示する |
|
|
47
47
|
| `backToTop` | `boolean` | `true` | トップへ戻るボタンを表示する |
|
|
48
48
|
| `tocScrollSpy` | `boolean` | `true` | 目次のスクロール連動ハイライトを有効にする |
|
|
49
|
-
| `codeCopy` | `boolean` | `true` | コードのコピーボタンを追加する |
|
|
50
49
|
| `backToTopLabel` | `string` | `"Back to top"` | トップへ戻るボタンの `aria-label` |
|
|
51
|
-
| `copyLabel` | `string` | `"Copy"` | コピーボタンのラベル |
|
|
52
|
-
| `copiedLabel` | `string` | `"Copied"` | コピー成功時に一時表示するラベル |
|
|
53
50
|
|
|
54
51
|
## 設定がクライアントへ渡る仕組み
|
|
55
52
|
|
|
56
53
|
クライアント側の初期化コードは静的にバンドルされ、プラグインのオプションを直接受け取れません。そのためこのプラグインは、ビルド時に各記事 HTML の先頭へ不活性な JSON 設定要素を挿入します。これは実装用のデータであり、記事本文には表示しません。
|
|
57
54
|
|
|
58
|
-
`initUx()` はこの要素を読み取って設定を復元します。要素が無い場合は全機能が有効な既定値で動作します。挿入は `onPostProcessed` で行い、`onManifestCreated`
|
|
55
|
+
`initUx()` はこの要素を読み取って設定を復元します。要素が無い場合は全機能が有効な既定値で動作します。挿入は `onPostProcessed` で行い、`onManifestCreated` でも未挿入の記事エントリへ反映します。挿入はページごとに 1 回だけで、`data-rb-ux-config` を目印に重複挿入を防ぎます。
|
|
59
56
|
|
|
60
57
|
## 出力される HTML / CSS フック
|
|
61
58
|
|
|
@@ -63,12 +60,9 @@ export default defineConfig({
|
|
|
63
60
|
| --- | --- |
|
|
64
61
|
| `rb-ux__progress` | 進捗バーのトラック(`role="progressbar"`) |
|
|
65
62
|
| `rb-ux__progress-bar` | 進捗に応じて `scaleX` されるバー |
|
|
66
|
-
| `rb-ux__back-to-top` |
|
|
63
|
+
| `rb-ux__back-to-top` | トップへ戻るボタン |
|
|
67
64
|
| `rb-ux__back-to-top--visible` | 表示状態のとき付与 |
|
|
68
65
|
| `rb-ux__toc-active` | 現在位置の目次リンク |
|
|
69
|
-
| `rb-ux__code` | コードブロックのラッパー |
|
|
70
|
-
| `rb-ux__copy` | コピーボタン |
|
|
71
|
-
| `rb-ux__copy--copied` | コピー直後の一時状態 |
|
|
72
66
|
|
|
73
67
|
スタイルは `@riebeckite/plugin-ux/style.css` として配布し、テーマの `--rb-color-*` トークンを参照するため既存テーマと競合しません。
|
|
74
68
|
|
|
@@ -78,18 +72,17 @@ export default defineConfig({
|
|
|
78
72
|
- 進捗バーは `role="progressbar"` と `aria-valuemin` / `aria-valuemax` / `aria-valuenow` を持ちます。
|
|
79
73
|
- 目次の現在位置には `aria-current="true"` を付けます。
|
|
80
74
|
- `prefers-reduced-motion: reduce` では進捗バーとトップへ戻るボタンのアニメーションを無効化し、トップへ戻る動作も即時スクロールへ切り替えます。
|
|
81
|
-
-
|
|
75
|
+
- 印刷時は進捗バーとボタンを非表示にします。
|
|
82
76
|
|
|
83
77
|
## 制限事項
|
|
84
78
|
|
|
85
79
|
- クライアント専用です。JavaScript が無効な環境では何も追加されません。
|
|
86
|
-
- SPA
|
|
80
|
+
- SPA ナビゲーションを有効にした場合は、ページルートの置換後に機能を再初期化します。
|
|
87
81
|
- 目次のスクロール連動は `.rr-table-of-contents` または `[data-rb-toc]` を目次コンテナとして探します。
|
|
88
|
-
- コードのコピーボタンは `.rr-code`(code-enhance プラグインが管理するブロック)には追加しません。二重のコピー UI を避けるためです。
|
|
89
82
|
|
|
90
83
|
## 主なエクスポート
|
|
91
84
|
|
|
92
|
-
- `
|
|
85
|
+
- `ux(options?)` / `ux(options?)`: プラグインを作成する
|
|
93
86
|
- `initUx`: クライアント側の初期化
|
|
94
87
|
- 型: `UxOptions`、`UxResolvedConfig`
|
|
95
88
|
|
package/dist/client.js
CHANGED
|
@@ -4,10 +4,7 @@ var DEFAULT_UX_CONFIG = {
|
|
|
4
4
|
progress: true,
|
|
5
5
|
backToTop: true,
|
|
6
6
|
tocScrollSpy: true,
|
|
7
|
-
|
|
8
|
-
backToTopLabel: "Back to top",
|
|
9
|
-
copyLabel: "Copy",
|
|
10
|
-
copiedLabel: "Copied"
|
|
7
|
+
backToTopLabel: "Back to top"
|
|
11
8
|
};
|
|
12
9
|
|
|
13
10
|
// src/init.ts
|
|
@@ -16,28 +13,27 @@ var PROGRESS_BAR_CLASS = "rb-ux__progress-bar";
|
|
|
16
13
|
var BACK_TO_TOP_CLASS = "rb-ux__back-to-top";
|
|
17
14
|
var BACK_TO_TOP_VISIBLE_CLASS = "rb-ux__back-to-top--visible";
|
|
18
15
|
var TOC_ACTIVE_CLASS = "rb-ux__toc-active";
|
|
19
|
-
var CODE_CLASS = "rb-ux__code";
|
|
20
|
-
var COPY_CLASS = "rb-ux__copy";
|
|
21
|
-
var COPY_COPIED_CLASS = "rb-ux__copy--copied";
|
|
22
16
|
var BACK_TO_TOP_THRESHOLD = 320;
|
|
23
|
-
var COPIED_STATE_DURATION = 1500;
|
|
24
|
-
var started = false;
|
|
25
|
-
var copiedTimers = /* @__PURE__ */ new WeakMap();
|
|
26
17
|
function initUx() {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const config = readUxConfig();
|
|
18
|
+
let cleanup = () => {
|
|
19
|
+
};
|
|
30
20
|
const run = () => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
const config = readUxConfig();
|
|
22
|
+
const cleanups = [
|
|
23
|
+
config.progress ? setupProgress() : void 0,
|
|
24
|
+
config.backToTop ? setupBackToTop(config) : void 0,
|
|
25
|
+
config.tocScrollSpy ? setupTocScrollSpy() : void 0
|
|
26
|
+
].filter((value) => value !== void 0);
|
|
27
|
+
cleanup = () => {
|
|
28
|
+
for (const dispose of cleanups) dispose();
|
|
29
|
+
};
|
|
35
30
|
};
|
|
36
31
|
if (document.readyState === "loading") {
|
|
37
32
|
document.addEventListener("DOMContentLoaded", run, { once: true });
|
|
38
|
-
|
|
39
|
-
run();
|
|
33
|
+
return () => document.removeEventListener("DOMContentLoaded", run);
|
|
40
34
|
}
|
|
35
|
+
run();
|
|
36
|
+
return () => cleanup();
|
|
41
37
|
}
|
|
42
38
|
function readUxConfig() {
|
|
43
39
|
const element = document.getElementById(UX_CONFIG_ELEMENT_ID);
|
|
@@ -50,13 +46,10 @@ function readUxConfig() {
|
|
|
50
46
|
parsed.tocScrollSpy,
|
|
51
47
|
DEFAULT_UX_CONFIG.tocScrollSpy
|
|
52
48
|
),
|
|
53
|
-
codeCopy: readBoolean(parsed.codeCopy, DEFAULT_UX_CONFIG.codeCopy),
|
|
54
49
|
backToTopLabel: readString(
|
|
55
50
|
parsed.backToTopLabel,
|
|
56
51
|
DEFAULT_UX_CONFIG.backToTopLabel
|
|
57
|
-
)
|
|
58
|
-
copyLabel: readString(parsed.copyLabel, DEFAULT_UX_CONFIG.copyLabel),
|
|
59
|
-
copiedLabel: readString(parsed.copiedLabel, DEFAULT_UX_CONFIG.copiedLabel)
|
|
52
|
+
)
|
|
60
53
|
};
|
|
61
54
|
}
|
|
62
55
|
function parseConfig(raw) {
|
|
@@ -79,7 +72,7 @@ function readString(value, fallback) {
|
|
|
79
72
|
}
|
|
80
73
|
function setupProgress() {
|
|
81
74
|
const article = findArticle();
|
|
82
|
-
if (!article || hasElement(PROGRESS_CLASS)) return;
|
|
75
|
+
if (!article || hasElement(PROGRESS_CLASS)) return void 0;
|
|
83
76
|
const track = document.createElement("div");
|
|
84
77
|
track.className = PROGRESS_CLASS;
|
|
85
78
|
track.setAttribute("role", "progressbar");
|
|
@@ -95,7 +88,11 @@ function setupProgress() {
|
|
|
95
88
|
fill.style.transform = `scaleX(${ratio})`;
|
|
96
89
|
track.setAttribute("aria-valuenow", String(Math.round(ratio * 100)));
|
|
97
90
|
};
|
|
98
|
-
scheduleOnScroll(update);
|
|
91
|
+
const stop = scheduleOnScroll(update);
|
|
92
|
+
return () => {
|
|
93
|
+
stop();
|
|
94
|
+
track.remove();
|
|
95
|
+
};
|
|
99
96
|
}
|
|
100
97
|
function readingProgress(article) {
|
|
101
98
|
const top = article.getBoundingClientRect().top + window.scrollY;
|
|
@@ -104,7 +101,7 @@ function readingProgress(article) {
|
|
|
104
101
|
return clamp((window.scrollY - top) / (end - top), 0, 1);
|
|
105
102
|
}
|
|
106
103
|
function setupBackToTop(config) {
|
|
107
|
-
if (hasElement(BACK_TO_TOP_CLASS)) return;
|
|
104
|
+
if (hasElement(BACK_TO_TOP_CLASS)) return void 0;
|
|
108
105
|
const button = document.createElement("button");
|
|
109
106
|
button.type = "button";
|
|
110
107
|
button.className = BACK_TO_TOP_CLASS;
|
|
@@ -124,17 +121,21 @@ function setupBackToTop(config) {
|
|
|
124
121
|
window.scrollY >= threshold()
|
|
125
122
|
);
|
|
126
123
|
};
|
|
127
|
-
scheduleOnScroll(update);
|
|
124
|
+
const stop = scheduleOnScroll(update);
|
|
125
|
+
return () => {
|
|
126
|
+
stop();
|
|
127
|
+
button.remove();
|
|
128
|
+
};
|
|
128
129
|
}
|
|
129
130
|
function setupTocScrollSpy() {
|
|
130
131
|
const container = document.querySelector(
|
|
131
132
|
".rr-table-of-contents, [data-rb-toc]"
|
|
132
133
|
);
|
|
133
|
-
if (!container || container.dataset.rbUxToc === "true") return;
|
|
134
|
+
if (!container || container.dataset.rbUxToc === "true") return void 0;
|
|
134
135
|
const links = Array.from(
|
|
135
136
|
container.querySelectorAll('a[href^="#"]')
|
|
136
137
|
);
|
|
137
|
-
if (links.length === 0) return;
|
|
138
|
+
if (links.length === 0) return void 0;
|
|
138
139
|
const linksById = /* @__PURE__ */ new Map();
|
|
139
140
|
for (const link of links) {
|
|
140
141
|
const id = hashToId(link.getAttribute("href"));
|
|
@@ -144,7 +145,7 @@ function setupTocScrollSpy() {
|
|
|
144
145
|
linksById.set(id, existing);
|
|
145
146
|
}
|
|
146
147
|
const headings = Array.from(linksById.keys()).map((id) => document.getElementById(id)).filter((heading) => heading !== null);
|
|
147
|
-
if (headings.length === 0) return;
|
|
148
|
+
if (headings.length === 0) return void 0;
|
|
148
149
|
container.dataset.rbUxToc = "true";
|
|
149
150
|
const activate = (id) => {
|
|
150
151
|
for (const link of links) {
|
|
@@ -179,8 +180,9 @@ function setupTocScrollSpy() {
|
|
|
179
180
|
activate(nextId);
|
|
180
181
|
}
|
|
181
182
|
};
|
|
183
|
+
let observer;
|
|
182
184
|
if (typeof IntersectionObserver === "function") {
|
|
183
|
-
|
|
185
|
+
observer = new IntersectionObserver(
|
|
184
186
|
(entries) => {
|
|
185
187
|
for (const entry of entries) {
|
|
186
188
|
const id = entry.target.id;
|
|
@@ -197,54 +199,12 @@ function setupTocScrollSpy() {
|
|
|
197
199
|
);
|
|
198
200
|
for (const heading of headings) observer.observe(heading);
|
|
199
201
|
}
|
|
200
|
-
scheduleOnScroll(updateActive);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
for (const code of blocks) {
|
|
207
|
-
const pre = code.parentElement;
|
|
208
|
-
if (!pre || pre.dataset.rbUxCopy === "true") continue;
|
|
209
|
-
if (pre.closest(".rr-code")) continue;
|
|
210
|
-
pre.dataset.rbUxCopy = "true";
|
|
211
|
-
const wrapper = document.createElement("div");
|
|
212
|
-
wrapper.className = CODE_CLASS;
|
|
213
|
-
pre.replaceWith(wrapper);
|
|
214
|
-
wrapper.append(pre);
|
|
215
|
-
const button = document.createElement("button");
|
|
216
|
-
button.type = "button";
|
|
217
|
-
button.className = COPY_CLASS;
|
|
218
|
-
button.setAttribute("aria-label", config.copyLabel);
|
|
219
|
-
button.textContent = config.copyLabel;
|
|
220
|
-
button.addEventListener("click", () => {
|
|
221
|
-
void copyCode(button, code, config);
|
|
222
|
-
});
|
|
223
|
-
wrapper.append(button);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
async function copyCode(button, code, config) {
|
|
227
|
-
const text = code.textContent ?? "";
|
|
228
|
-
const clipboard = navigator.clipboard;
|
|
229
|
-
if (text.length === 0 || !clipboard) return;
|
|
230
|
-
try {
|
|
231
|
-
await clipboard.writeText(text);
|
|
232
|
-
} catch {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
showCopiedState(button, config);
|
|
236
|
-
}
|
|
237
|
-
function showCopiedState(button, config) {
|
|
238
|
-
const previous = copiedTimers.get(button);
|
|
239
|
-
if (previous !== void 0) window.clearTimeout(previous);
|
|
240
|
-
button.textContent = config.copiedLabel;
|
|
241
|
-
button.classList.add(COPY_COPIED_CLASS);
|
|
242
|
-
const timer = window.setTimeout(() => {
|
|
243
|
-
button.textContent = config.copyLabel;
|
|
244
|
-
button.classList.remove(COPY_COPIED_CLASS);
|
|
245
|
-
copiedTimers.delete(button);
|
|
246
|
-
}, COPIED_STATE_DURATION);
|
|
247
|
-
copiedTimers.set(button, timer);
|
|
202
|
+
const stop = scheduleOnScroll(updateActive);
|
|
203
|
+
return () => {
|
|
204
|
+
stop();
|
|
205
|
+
observer?.disconnect();
|
|
206
|
+
delete container.dataset.rbUxToc;
|
|
207
|
+
};
|
|
248
208
|
}
|
|
249
209
|
function findArticle() {
|
|
250
210
|
return document.querySelector(".rb-article-body");
|
|
@@ -280,6 +240,11 @@ function scheduleOnScroll(update) {
|
|
|
280
240
|
window.addEventListener("scroll", schedule, { passive: true });
|
|
281
241
|
window.addEventListener("resize", schedule);
|
|
282
242
|
update();
|
|
243
|
+
return () => {
|
|
244
|
+
window.removeEventListener("scroll", schedule);
|
|
245
|
+
window.removeEventListener("resize", schedule);
|
|
246
|
+
if (frame) window.cancelAnimationFrame(frame);
|
|
247
|
+
};
|
|
283
248
|
}
|
|
284
249
|
export {
|
|
285
250
|
initUx
|
package/dist/index.d.ts
CHANGED
|
@@ -2,11 +2,9 @@ import { type UxOptions } from "./src/options.js";
|
|
|
2
2
|
export type { UxOptions, UxResolvedConfig } from "./src/options.js";
|
|
3
3
|
/**
|
|
4
4
|
* Client-only progressive enhancements: a reading progress bar, a
|
|
5
|
-
* back-to-top button, table-of-contents scroll-spy
|
|
5
|
+
* back-to-top button, and table-of-contents scroll-spy.
|
|
6
6
|
*
|
|
7
7
|
* None of the features change the rendered article markup, so the plugin
|
|
8
8
|
* only injects a JSON config element and registers a browser initializer.
|
|
9
9
|
*/
|
|
10
|
-
export declare function
|
|
11
|
-
/** Alias kept for naming symmetry with the other plugin factories. */
|
|
12
|
-
export declare const ux: typeof uxPlugin;
|
|
10
|
+
export declare function ux(options?: UxOptions): import("@riebeckite/core").RiebeckitePlugin<UxOptions>;
|
package/dist/index.js
CHANGED
|
@@ -15,25 +15,16 @@ var DEFAULT_UX_CONFIG = {
|
|
|
15
15
|
progress: true,
|
|
16
16
|
backToTop: true,
|
|
17
17
|
tocScrollSpy: true,
|
|
18
|
-
|
|
19
|
-
backToTopLabel: "Back to top",
|
|
20
|
-
copyLabel: "Copy",
|
|
21
|
-
copiedLabel: "Copied"
|
|
18
|
+
backToTopLabel: "Back to top"
|
|
22
19
|
};
|
|
23
20
|
function resolveUxConfig(options = {}) {
|
|
24
21
|
return {
|
|
25
22
|
progress: options.progress ?? DEFAULT_UX_CONFIG.progress,
|
|
26
23
|
backToTop: options.backToTop ?? DEFAULT_UX_CONFIG.backToTop,
|
|
27
24
|
tocScrollSpy: options.tocScrollSpy ?? DEFAULT_UX_CONFIG.tocScrollSpy,
|
|
28
|
-
codeCopy: options.codeCopy ?? DEFAULT_UX_CONFIG.codeCopy,
|
|
29
25
|
backToTopLabel: normalizeLabel(
|
|
30
26
|
options.backToTopLabel,
|
|
31
27
|
DEFAULT_UX_CONFIG.backToTopLabel
|
|
32
|
-
),
|
|
33
|
-
copyLabel: normalizeLabel(options.copyLabel, DEFAULT_UX_CONFIG.copyLabel),
|
|
34
|
-
copiedLabel: normalizeLabel(
|
|
35
|
-
options.copiedLabel,
|
|
36
|
-
DEFAULT_UX_CONFIG.copiedLabel
|
|
37
28
|
)
|
|
38
29
|
};
|
|
39
30
|
}
|
|
@@ -52,7 +43,7 @@ function injectUxConfig(html, config) {
|
|
|
52
43
|
}
|
|
53
44
|
|
|
54
45
|
// index.ts
|
|
55
|
-
function
|
|
46
|
+
function ux(options = {}) {
|
|
56
47
|
const config = resolveUxConfig(options);
|
|
57
48
|
return definePlugin({
|
|
58
49
|
name: "ux",
|
|
@@ -78,29 +69,22 @@ function uxPlugin(options = {}) {
|
|
|
78
69
|
}
|
|
79
70
|
});
|
|
80
71
|
}
|
|
81
|
-
var ux = uxPlugin;
|
|
82
72
|
function validateUxOptions(options) {
|
|
83
73
|
if (!options) return [];
|
|
84
74
|
const issues = [];
|
|
85
|
-
for (const key of [
|
|
86
|
-
"progress",
|
|
87
|
-
"backToTop",
|
|
88
|
-
"tocScrollSpy",
|
|
89
|
-
"codeCopy"
|
|
90
|
-
]) {
|
|
75
|
+
for (const key of ["progress", "backToTop", "tocScrollSpy"]) {
|
|
91
76
|
if (options[key] !== void 0 && typeof options[key] !== "boolean") {
|
|
92
77
|
issues.push({ path: key, message: "Expected a boolean." });
|
|
93
78
|
}
|
|
94
79
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
80
|
+
if (options.backToTopLabel !== void 0 && (typeof options.backToTopLabel !== "string" || options.backToTopLabel.trim() === "")) {
|
|
81
|
+
issues.push({
|
|
82
|
+
path: "backToTopLabel",
|
|
83
|
+
message: "Expected a non-empty string."
|
|
84
|
+
});
|
|
100
85
|
}
|
|
101
86
|
return issues;
|
|
102
87
|
}
|
|
103
88
|
export {
|
|
104
|
-
ux
|
|
105
|
-
uxPlugin
|
|
89
|
+
ux
|
|
106
90
|
};
|
package/dist/src/init.d.ts
CHANGED
package/dist/src/inject.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import { type UxResolvedConfig } from "./options.js";
|
|
2
|
-
/**
|
|
3
|
-
* Serializes the resolved options into an inert JSON script element.
|
|
4
|
-
*
|
|
5
|
-
* Client initializers are static and receive no plugin options, so the build
|
|
6
|
-
* injects this element into the article HTML and reads it back at runtime.
|
|
7
|
-
* `escapeScriptJson` neutralizes `<`, `>`, `&`, U+2028 and U+2029 so labels
|
|
8
|
-
* can never terminate the script element.
|
|
9
|
-
*/
|
|
10
|
-
export declare function renderUxConfigElement(config: UxResolvedConfig): string;
|
|
11
2
|
/**
|
|
12
3
|
* Prepends the configuration element exactly once.
|
|
13
4
|
*
|
package/dist/src/options.d.ts
CHANGED
|
@@ -11,24 +11,15 @@ export type UxOptions = {
|
|
|
11
11
|
backToTop?: boolean;
|
|
12
12
|
/** Highlight the active entry in the article table of contents. Defaults to `true`. */
|
|
13
13
|
tocScrollSpy?: boolean;
|
|
14
|
-
/** Add a copy button to every code block. Defaults to `true`. */
|
|
15
|
-
codeCopy?: boolean;
|
|
16
14
|
/** Accessible label for the back-to-top button. */
|
|
17
15
|
backToTopLabel?: string;
|
|
18
|
-
/** Label for the code copy button. */
|
|
19
|
-
copyLabel?: string;
|
|
20
|
-
/** Transient label shown after a successful copy. */
|
|
21
|
-
copiedLabel?: string;
|
|
22
16
|
};
|
|
23
17
|
/** Options after defaults have been applied. Serialized into the page. */
|
|
24
18
|
export type UxResolvedConfig = {
|
|
25
19
|
progress: boolean;
|
|
26
20
|
backToTop: boolean;
|
|
27
21
|
tocScrollSpy: boolean;
|
|
28
|
-
codeCopy: boolean;
|
|
29
22
|
backToTopLabel: string;
|
|
30
|
-
copyLabel: string;
|
|
31
|
-
copiedLabel: string;
|
|
32
23
|
};
|
|
33
24
|
/** Id of the JSON configuration element injected into each article. */
|
|
34
25
|
export declare const UX_CONFIG_ELEMENT_ID = "rb-ux-config";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-ux",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.33",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"./style.css": "./style.css"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@riebeckite/core": "0.0.
|
|
24
|
+
"@riebeckite/core": "0.0.33"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public"
|
package/style.css
CHANGED
|
@@ -65,59 +65,16 @@
|
|
|
65
65
|
font-weight: 600;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
.rb-ux__code {
|
|
69
|
-
position: relative;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.rb-ux__copy {
|
|
73
|
-
position: absolute;
|
|
74
|
-
inset-block-start: 0.5rem;
|
|
75
|
-
inset-inline-end: 0.5rem;
|
|
76
|
-
z-index: 1;
|
|
77
|
-
padding: 0.2rem 0.5rem;
|
|
78
|
-
border: 1px solid
|
|
79
|
-
var(--rb-color-border, color-mix(in srgb, currentColor 20%, transparent));
|
|
80
|
-
border-radius: 4px;
|
|
81
|
-
background: var(--rb-color-surface, var(--rb-color-paper, #ffffff));
|
|
82
|
-
color: var(--rb-color-muted, #69645c);
|
|
83
|
-
font-size: 0.75rem;
|
|
84
|
-
line-height: 1.4;
|
|
85
|
-
cursor: pointer;
|
|
86
|
-
opacity: 0.9;
|
|
87
|
-
transition:
|
|
88
|
-
opacity 120ms ease,
|
|
89
|
-
color 120ms ease,
|
|
90
|
-
border-color 120ms ease;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
.rb-ux__copy:hover,
|
|
94
|
-
.rb-ux__copy:focus-visible {
|
|
95
|
-
opacity: 1;
|
|
96
|
-
color: var(--rb-color-ink, #1c1a17);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.rb-ux__copy:focus-visible {
|
|
100
|
-
outline: 2px solid var(--rb-color-accent, #686868);
|
|
101
|
-
outline-offset: 2px;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.rb-ux__copy--copied {
|
|
105
|
-
color: var(--rb-color-accent, #686868);
|
|
106
|
-
border-color: var(--rb-color-accent, #686868);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
68
|
@media (prefers-reduced-motion: reduce) {
|
|
110
69
|
.rb-ux__progress-bar,
|
|
111
|
-
.rb-ux__back-to-top
|
|
112
|
-
.rb-ux__copy {
|
|
70
|
+
.rb-ux__back-to-top {
|
|
113
71
|
transition: none;
|
|
114
72
|
}
|
|
115
73
|
}
|
|
116
74
|
|
|
117
75
|
@media print {
|
|
118
76
|
.rb-ux__progress,
|
|
119
|
-
.rb-ux__back-to-top
|
|
120
|
-
.rb-ux__copy {
|
|
77
|
+
.rb-ux__back-to-top {
|
|
121
78
|
display: none;
|
|
122
79
|
}
|
|
123
80
|
}
|