@haruhimemoe/ui 0.11.1 → 0.11.2

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 CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.11.2] - 2026-10-04
10
+
11
+ ### Fixed
12
+
13
+ - `CopyMarkdownButton` copies on Safari and iOS: where `ClipboardItem` exists, the clipboard write starts inside the click with the fetched Markdown as a promised Blob, instead of after an awaited fetch that loses the user activation. Other browsers keep the fetch-then-`writeText` path.
14
+
9
15
  ## [0.11.1] - 2026-10-04
10
16
 
11
17
  ### Fixed
@@ -159,7 +165,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
159
165
  - `className` on every component, and the extras passed to `buttonClasses` and `fieldClasses`, merge with tailwind-merge: a caller's class replaces a built-in one that sets the same property (`fieldClasses("w-auto")` drops `w-full`).
160
166
  - Shell: `SiteHeader` (brand slot, nav links as data with `aria-current`, actions slot), `NavLinks`, `SiteFooter` (link columns as data, fine print, the haruhime.moe wordmark and a GitHub link) and `PageShell` (skip link, header, main, footer).
161
167
 
162
- [unreleased]: https://github.com/haruhimemoe/ui/compare/v0.11.1...HEAD
168
+ [unreleased]: https://github.com/haruhimemoe/ui/compare/v0.11.2...HEAD
169
+ [0.11.2]: https://github.com/haruhimemoe/ui/compare/v0.11.1...v0.11.2
163
170
  [0.11.1]: https://github.com/haruhimemoe/ui/compare/v0.11.0...v0.11.1
164
171
  [0.11.0]: https://github.com/haruhimemoe/ui/compare/v0.10.0...v0.11.0
165
172
  [0.10.0]: https://github.com/haruhimemoe/ui/compare/v0.9.0...v0.10.0
@@ -2,7 +2,9 @@
2
2
  * @file src/components/content/CopyMarkdownButton.tsx
3
3
  * @desc A button that fetches a page's raw Markdown and copies it to the clipboard, reporting the
4
4
  * result in an `<output>` beside it (a polite live region), for a doc page's "Copy as
5
- * Markdown" action. Any failure (the fetch rejects, the response isn't ok, or the clipboard
5
+ * Markdown" action. Where `ClipboardItem` exists, the clipboard write starts inside the click
6
+ * with the fetch as a promised Blob, since Safari rejects a write that starts after an
7
+ * awaited fetch; elsewhere it fetches, then calls `writeText`. Any failure (the fetch rejects, the response isn't ok, or the clipboard
6
8
  * refuses) announces the same failure message; the button never throws. Its own client file
7
9
  * with finished class strings (no `cx`), so tailwind-merge stays out of the browser bundle
8
10
  * for `ContentPage`, the Server Component that renders it. Not a wrapper around
@@ -2,7 +2,9 @@
2
2
  * @file src/components/content/CopyMarkdownButton.tsx
3
3
  * @desc A button that fetches a page's raw Markdown and copies it to the clipboard, reporting the
4
4
  * result in an `<output>` beside it (a polite live region), for a doc page's "Copy as
5
- * Markdown" action. Any failure (the fetch rejects, the response isn't ok, or the clipboard
5
+ * Markdown" action. Where `ClipboardItem` exists, the clipboard write starts inside the click
6
+ * with the fetch as a promised Blob, since Safari rejects a write that starts after an
7
+ * awaited fetch; elsewhere it fetches, then calls `writeText`. Any failure (the fetch rejects, the response isn't ok, or the clipboard
6
8
  * refuses) announces the same failure message; the button never throws. Its own client file
7
9
  * with finished class strings (no `cx`), so tailwind-merge stays out of the browser bundle
8
10
  * for `ContentPage`, the Server Component that renders it. Not a wrapper around
@@ -28,11 +30,23 @@ export function CopyMarkdownButton({ href, label = "Copy as Markdown", copiedLab
28
30
  const copy = async () => {
29
31
  const run = start();
30
32
  try {
31
- const response = await fetch(href);
32
- if (!response.ok)
33
- throw new Error(String(response.status));
34
- const text = await response.text();
35
- await navigator.clipboard.writeText(text);
33
+ if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
34
+ // Started synchronously in the click so Safari keeps the user activation.
35
+ const blob = fetch(href).then(async (response) => {
36
+ if (!response.ok)
37
+ throw new Error(String(response.status));
38
+ return new Blob([await response.text()], { type: "text/plain" });
39
+ });
40
+ // If the write is refused before reading the blob, its own rejection stays handled.
41
+ blob.catch(() => undefined);
42
+ await navigator.clipboard.write([new ClipboardItem({ "text/plain": blob })]);
43
+ }
44
+ else {
45
+ const response = await fetch(href);
46
+ if (!response.ok)
47
+ throw new Error(String(response.status));
48
+ await navigator.clipboard.writeText(await response.text());
49
+ }
36
50
  settle(run, "copied");
37
51
  }
38
52
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haruhimemoe/ui",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "React components for the haruhime.moe osu! tools on Next.js: the osu!-web-style palette as a Tailwind theme, buttons, cards, form fields and confirms, filter controls, tables, osu! beatmap display pieces and player cards, a command palette, content section tooling, brand pages and the site header and footer.",
5
5
  "keywords": [
6
6
  "osu",