@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 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, and code copy buttons at runtime.
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 { uxPlugin } from "@riebeckite/plugin-ux";
32
+ import { ux } from "@riebeckite/plugin-ux";
31
33
 
32
34
  export default defineConfig({
33
35
  // ...
34
36
  plugins: [
35
- uxPlugin({
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
- - Progress bar, buttons, and copy buttons are hidden when printing.
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
- - No SPA support; a full page load re-initializes the enhancements.
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
- - `uxPlugin(options?)` / `ux(options?)` — plugin factory
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 { uxPlugin } from "@riebeckite/plugin-ux";
25
+ import { ux } from "@riebeckite/plugin-ux";
23
26
 
24
27
  export default defineConfig({
25
28
  // ...
26
29
  plugins: [
27
- uxPlugin({
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
- `uxPlugin` は `ux` という別名でもエクスポートしています。
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` でも未挿入の記事エントリへ反映します。挿入はページごとに一度だけで、`data-rb-ux-config` を目印に重複挿入を防ぎます。
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` | トップへ戻る button |
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
- - `uxPlugin(options?)` / `ux(options?)`: プラグインを作成する
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
- codeCopy: true,
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
- if (started) return;
28
- started = true;
29
- const config = readUxConfig();
18
+ let cleanup = () => {
19
+ };
30
20
  const run = () => {
31
- if (config.progress) setupProgress();
32
- if (config.backToTop) setupBackToTop(config);
33
- if (config.tocScrollSpy) setupTocScrollSpy();
34
- if (config.codeCopy) setupCodeCopy(config);
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
- } else {
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
- const observer = new IntersectionObserver(
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
- function setupCodeCopy(config) {
203
- const blocks = Array.from(
204
- document.querySelectorAll("pre > code")
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, and code copy buttons.
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 uxPlugin(options?: UxOptions): import("@riebeckite/core").RiebeckitePlugin<UxOptions>;
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
- codeCopy: true,
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 uxPlugin(options = {}) {
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
- for (const key of ["backToTopLabel", "copyLabel", "copiedLabel"]) {
96
- const value = options[key];
97
- if (value !== void 0 && (typeof value !== "string" || value.trim() === "")) {
98
- issues.push({ path: key, message: "Expected a non-empty string." });
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
  };
@@ -2,4 +2,4 @@
2
2
  * Initializes every enabled UX enhancement. Safe to call more than once and a
3
3
  * no-op when the relevant DOM elements are absent.
4
4
  */
5
- export declare function initUx(): void;
5
+ export declare function initUx(): () => void;
@@ -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
  *
@@ -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.31",
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.31"
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
  }