@21stware/rpui 0.4.3 → 0.4.4

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.
@@ -1,4 +1,73 @@
1
1
  import { registerAll, parseToPage } from "./rpui.js";
2
+ const THEME_STYLE_ID = "rpml-theme-style";
3
+ const ATTR = "data-rpml-theme";
4
+ const THEME_CSS = `
5
+ :root {
6
+ --rpml-gx-border:#e5e7eb; --rpml-gx-side-bg:#fff; --rpml-gx-main-bg:#f4f6f8;
7
+ --rpml-gx-fg:#111827; --rpml-gx-muted:#6b7280; --rpml-gx-group:#9ca3af;
8
+ --rpml-gx-hover:#f3f4f6; --rpml-gx-item:#374151; --rpml-gx-active-bg:#eff6ff;
9
+ --rpml-gx-active-fg:#1d4ed8; --rpml-gx-copy-hover:#e5e7eb; --rpml-gx-ok:#059669;
10
+ }
11
+ html[${ATTR}="dark"] {
12
+ --rpml-gx-border:#2a3344; --rpml-gx-side-bg:#0f172a; --rpml-gx-main-bg:#0b1120;
13
+ --rpml-gx-fg:#e2e8f0; --rpml-gx-muted:#94a3b8; --rpml-gx-group:#64748b;
14
+ --rpml-gx-hover:#1e293b; --rpml-gx-item:#cbd5e1; --rpml-gx-active-bg:#1e3a5f;
15
+ --rpml-gx-active-fg:#93c5fd; --rpml-gx-copy-hover:#334155; --rpml-gx-ok:#34d399;
16
+ }
17
+ html[${ATTR}="dark"] body { background:#0b1120; }
18
+ /* Invert the rendered prototype as a whole; hue-rotate keeps colors recognizable. */
19
+ html[${ATTR}="dark"] page-el { filter:invert(0.92) hue-rotate(180deg); }
20
+
21
+ .rpml-theme-fab {
22
+ position:fixed; top:12px; right:12px; z-index:50; display:flex; align-items:center;
23
+ justify-content:center; width:34px; height:34px; padding:0; border:1px solid var(--rpml-gx-border);
24
+ border-radius:9px; background:var(--rpml-gx-side-bg); color:var(--rpml-gx-fg); font-size:16px;
25
+ line-height:1; cursor:pointer; box-shadow:0 1px 3px rgba(0,0,0,.12);
26
+ }
27
+ .rpml-theme-fab:hover { background:var(--rpml-gx-hover); }
28
+ `;
29
+ function injectThemeStyle() {
30
+ if (document.getElementById(THEME_STYLE_ID)) return;
31
+ const s = document.createElement("style");
32
+ s.id = THEME_STYLE_ID;
33
+ s.textContent = THEME_CSS;
34
+ document.head.appendChild(s);
35
+ }
36
+ function themeFromUrl() {
37
+ const t = new URLSearchParams(location.search).get("theme");
38
+ return t === "dark" || t === "light" ? t : null;
39
+ }
40
+ function currentTheme() {
41
+ return document.documentElement.getAttribute(ATTR) === "dark" ? "dark" : "light";
42
+ }
43
+ function setTheme(theme) {
44
+ document.documentElement.setAttribute(ATTR, theme);
45
+ const url = new URL(location.href);
46
+ url.searchParams.set("theme", theme);
47
+ history.replaceState(history.state, "", url);
48
+ }
49
+ function initTheme() {
50
+ const fromUrl = themeFromUrl();
51
+ if (fromUrl) {
52
+ document.documentElement.setAttribute(ATTR, fromUrl);
53
+ return;
54
+ }
55
+ const prefersDark = typeof matchMedia === "function" && matchMedia("(prefers-color-scheme: dark)").matches;
56
+ document.documentElement.setAttribute(ATTR, prefersDark ? "dark" : "light");
57
+ }
58
+ function mountThemeFab(host = document.body) {
59
+ injectThemeStyle();
60
+ initTheme();
61
+ if (document.querySelector(".rpml-theme-fab")) return;
62
+ const btn = document.createElement("button");
63
+ btn.className = "rpml-theme-fab";
64
+ btn.type = "button";
65
+ btn.title = "切换亮色/暗色";
66
+ btn.setAttribute("aria-label", "切换亮色/暗色");
67
+ btn.textContent = "◑";
68
+ btn.addEventListener("click", () => setTheme(currentTheme() === "dark" ? "light" : "dark"));
69
+ host.appendChild(btn);
70
+ }
2
71
  registerAll();
3
72
  class RpmlApp extends HTMLElement {
4
73
  async connectedCallback() {
@@ -8,6 +77,7 @@ class RpmlApp extends HTMLElement {
8
77
  const res = await fetch(src);
9
78
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
10
79
  this.replaceWith(parseToPage(await res.text()));
80
+ mountThemeFab();
11
81
  const section = new URLSearchParams(location.search).get("section");
12
82
  if (section) requestAnimationFrame(() => requestAnimationFrame(() => window.dispatchEvent(new CustomEvent("rp-section", { detail: section }))));
13
83
  } catch (e) {
@@ -1 +1 @@
1
- {"version":3,"file":"rpml-loader.js","sources":["../src/rpml-loader.ts"],"sourcesContent":["import { registerAll, parseToPage } from './rpui';\n\nexport { parseToPage };\nregisterAll();\n\nclass RpmlApp extends HTMLElement {\n async connectedCallback() {\n const src = this.getAttribute('src');\n if (!src) return;\n try {\n const res = await fetch(src);\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n this.replaceWith(parseToPage(await res.text()));\n // Honor a ?section= deep-link (e.g. arriving from an <anchor> fallback).\n const section = new URLSearchParams(location.search).get('section');\n if (section) requestAnimationFrame(() => requestAnimationFrame(() =>\n window.dispatchEvent(new CustomEvent('rp-section', { detail: section }))));\n } catch (e) {\n this.textContent = `RPML load error: ${e}`;\n console.error(e);\n }\n }\n}\n\ncustomElements.define('rpml-app', RpmlApp);\n\nconst rpmlSrc = new URLSearchParams(location.search).get('rpml');\nif (rpmlSrc && !document.querySelector('rpml-app')) {\n const app = document.createElement('rpml-app') as HTMLElement;\n app.setAttribute('src', rpmlSrc);\n document.body.appendChild(app);\n}\n"],"names":[],"mappings":";AAGA,YAAA;AAEA,MAAM,gBAAgB,YAAY;AAAA,EAChC,MAAM,oBAAoB;AACxB,UAAM,MAAM,KAAK,aAAa,KAAK;AACnC,QAAI,CAAC,IAAK;AACV,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,QAAQ,IAAI,MAAM,EAAE;AACjD,WAAK,YAAY,YAAY,MAAM,IAAI,KAAA,CAAM,CAAC;AAE9C,YAAM,UAAU,IAAI,gBAAgB,SAAS,MAAM,EAAE,IAAI,SAAS;AAClE,UAAI,QAAS,uBAAsB,MAAM,sBAAsB,MAC7D,OAAO,cAAc,IAAI,YAAY,cAAc,EAAE,QAAQ,QAAA,CAAS,CAAC,CAAC,CAAC;AAAA,IAC7E,SAAS,GAAG;AACV,WAAK,cAAc,oBAAoB,CAAC;AACxC,cAAQ,MAAM,CAAC;AAAA,IACjB;AAAA,EACF;AACF;AAEA,eAAe,OAAO,YAAY,OAAO;AAEzC,MAAM,UAAU,IAAI,gBAAgB,SAAS,MAAM,EAAE,IAAI,MAAM;AAC/D,IAAI,WAAW,CAAC,SAAS,cAAc,UAAU,GAAG;AAClD,QAAM,MAAM,SAAS,cAAc,UAAU;AAC7C,MAAI,aAAa,OAAO,OAAO;AAC/B,WAAS,KAAK,YAAY,GAAG;AAC/B;"}
1
+ {"version":3,"file":"rpml-loader.js","sources":["../src/core/theme.ts","../src/rpml-loader.ts"],"sourcesContent":["/**\n * Light/dark theme, shared by the gallery (sidebar present) and the single-file\n * loader (no sidebar). The chosen theme lives in the `theme` URL param so it\n * survives reloads and can be deep-linked; on first load with no param we fall\n * back to the OS `prefers-color-scheme`.\n *\n * Strategy:\n * - Chrome (sidebar/header/main bg) is themed with CSS variables (--rpml-gx-*).\n * - The rendered prototype (page-el) carries 256+ hardcoded colors, so instead\n * of remapping each one we invert it as a whole with `invert + hue-rotate`,\n * which flips light/dark while keeping hues (blue stays blue) — guaranteed\n * readable without touching the runtime stylesheet.\n */\n\nconst THEME_STYLE_ID = 'rpml-theme-style';\nconst ATTR = 'data-rpml-theme';\n\nexport const THEME_CSS = `\n:root {\n --rpml-gx-border:#e5e7eb; --rpml-gx-side-bg:#fff; --rpml-gx-main-bg:#f4f6f8;\n --rpml-gx-fg:#111827; --rpml-gx-muted:#6b7280; --rpml-gx-group:#9ca3af;\n --rpml-gx-hover:#f3f4f6; --rpml-gx-item:#374151; --rpml-gx-active-bg:#eff6ff;\n --rpml-gx-active-fg:#1d4ed8; --rpml-gx-copy-hover:#e5e7eb; --rpml-gx-ok:#059669;\n}\nhtml[${ATTR}=\"dark\"] {\n --rpml-gx-border:#2a3344; --rpml-gx-side-bg:#0f172a; --rpml-gx-main-bg:#0b1120;\n --rpml-gx-fg:#e2e8f0; --rpml-gx-muted:#94a3b8; --rpml-gx-group:#64748b;\n --rpml-gx-hover:#1e293b; --rpml-gx-item:#cbd5e1; --rpml-gx-active-bg:#1e3a5f;\n --rpml-gx-active-fg:#93c5fd; --rpml-gx-copy-hover:#334155; --rpml-gx-ok:#34d399;\n}\nhtml[${ATTR}=\"dark\"] body { background:#0b1120; }\n/* Invert the rendered prototype as a whole; hue-rotate keeps colors recognizable. */\nhtml[${ATTR}=\"dark\"] page-el { filter:invert(0.92) hue-rotate(180deg); }\n\n.rpml-theme-fab {\n position:fixed; top:12px; right:12px; z-index:50; display:flex; align-items:center;\n justify-content:center; width:34px; height:34px; padding:0; border:1px solid var(--rpml-gx-border);\n border-radius:9px; background:var(--rpml-gx-side-bg); color:var(--rpml-gx-fg); font-size:16px;\n line-height:1; cursor:pointer; box-shadow:0 1px 3px rgba(0,0,0,.12);\n}\n.rpml-theme-fab:hover { background:var(--rpml-gx-hover); }\n`;\n\nexport function injectThemeStyle(): void {\n if (document.getElementById(THEME_STYLE_ID)) return;\n const s = document.createElement('style');\n s.id = THEME_STYLE_ID;\n s.textContent = THEME_CSS;\n document.head.appendChild(s);\n}\n\nfunction themeFromUrl(): 'light' | 'dark' | null {\n const t = new URLSearchParams(location.search).get('theme');\n return t === 'dark' || t === 'light' ? t : null;\n}\n\nexport function currentTheme(): 'light' | 'dark' {\n return document.documentElement.getAttribute(ATTR) === 'dark' ? 'dark' : 'light';\n}\n\n/** Apply a theme to <html> and reflect it in the `theme` URL param. */\nexport function setTheme(theme: 'light' | 'dark'): void {\n document.documentElement.setAttribute(ATTR, theme);\n const url = new URL(location.href);\n url.searchParams.set('theme', theme);\n history.replaceState(history.state, '', url);\n}\n\n/** Seed the theme from the URL param, falling back to the OS preference.\n * Does not write the URL on the OS-preference path, keeping links clean until\n * the user explicitly toggles. */\nexport function initTheme(): void {\n const fromUrl = themeFromUrl();\n if (fromUrl) { document.documentElement.setAttribute(ATTR, fromUrl); return; }\n const prefersDark = typeof matchMedia === 'function' && matchMedia('(prefers-color-scheme: dark)').matches;\n document.documentElement.setAttribute(ATTR, prefersDark ? 'dark' : 'light');\n}\n\n/** A floating top-right toggle, mirroring the sidebar-collapse FAB, so the theme\n * switch is reachable even when there is no sidebar (single-file preview) or it\n * is collapsed. Idempotent. */\nexport function mountThemeFab(host: HTMLElement = document.body): void {\n injectThemeStyle();\n initTheme();\n if (document.querySelector('.rpml-theme-fab')) return;\n const btn = document.createElement('button');\n btn.className = 'rpml-theme-fab';\n btn.type = 'button';\n btn.title = '切换亮色/暗色';\n btn.setAttribute('aria-label', '切换亮色/暗色');\n btn.textContent = '◑';\n btn.addEventListener('click', () => setTheme(currentTheme() === 'dark' ? 'light' : 'dark'));\n host.appendChild(btn);\n}\n","import { registerAll, parseToPage } from './rpui';\nimport { mountThemeFab } from './core/theme';\n\nexport { parseToPage };\nregisterAll();\n\nclass RpmlApp extends HTMLElement {\n async connectedCallback() {\n const src = this.getAttribute('src');\n if (!src) return;\n try {\n const res = await fetch(src);\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n this.replaceWith(parseToPage(await res.text()));\n // A single .rpml preview has no sidebar, so surface the theme toggle as a\n // floating button (mirrors the gallery's collapse FAB).\n mountThemeFab();\n // Honor a ?section= deep-link (e.g. arriving from an <anchor> fallback).\n const section = new URLSearchParams(location.search).get('section');\n if (section) requestAnimationFrame(() => requestAnimationFrame(() =>\n window.dispatchEvent(new CustomEvent('rp-section', { detail: section }))));\n } catch (e) {\n this.textContent = `RPML load error: ${e}`;\n console.error(e);\n }\n }\n}\n\ncustomElements.define('rpml-app', RpmlApp);\n\nconst rpmlSrc = new URLSearchParams(location.search).get('rpml');\nif (rpmlSrc && !document.querySelector('rpml-app')) {\n const app = document.createElement('rpml-app') as HTMLElement;\n app.setAttribute('src', rpmlSrc);\n document.body.appendChild(app);\n}\n"],"names":[],"mappings":";AAcA,MAAM,iBAAiB;AACvB,MAAM,OAAO;AAEN,MAAM,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAOlB,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAMJ,IAAI;AAAA;AAAA,OAEJ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWJ,SAAS,mBAAyB;AACvC,MAAI,SAAS,eAAe,cAAc,EAAG;AAC7C,QAAM,IAAI,SAAS,cAAc,OAAO;AACxC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,WAAS,KAAK,YAAY,CAAC;AAC7B;AAEA,SAAS,eAAwC;AAC/C,QAAM,IAAI,IAAI,gBAAgB,SAAS,MAAM,EAAE,IAAI,OAAO;AAC1D,SAAO,MAAM,UAAU,MAAM,UAAU,IAAI;AAC7C;AAEO,SAAS,eAAiC;AAC/C,SAAO,SAAS,gBAAgB,aAAa,IAAI,MAAM,SAAS,SAAS;AAC3E;AAGO,SAAS,SAAS,OAA+B;AACtD,WAAS,gBAAgB,aAAa,MAAM,KAAK;AACjD,QAAM,MAAM,IAAI,IAAI,SAAS,IAAI;AACjC,MAAI,aAAa,IAAI,SAAS,KAAK;AACnC,UAAQ,aAAa,QAAQ,OAAO,IAAI,GAAG;AAC7C;AAKO,SAAS,YAAkB;AAChC,QAAM,UAAU,aAAA;AAChB,MAAI,SAAS;AAAE,aAAS,gBAAgB,aAAa,MAAM,OAAO;AAAG;AAAA,EAAQ;AAC7E,QAAM,cAAc,OAAO,eAAe,cAAc,WAAW,8BAA8B,EAAE;AACnG,WAAS,gBAAgB,aAAa,MAAM,cAAc,SAAS,OAAO;AAC5E;AAKO,SAAS,cAAc,OAAoB,SAAS,MAAY;AACrE,mBAAA;AACA,YAAA;AACA,MAAI,SAAS,cAAc,iBAAiB,EAAG;AAC/C,QAAM,MAAM,SAAS,cAAc,QAAQ;AAC3C,MAAI,YAAY;AAChB,MAAI,OAAO;AACX,MAAI,QAAQ;AACZ,MAAI,aAAa,cAAc,SAAS;AACxC,MAAI,cAAc;AAClB,MAAI,iBAAiB,SAAS,MAAM,SAAS,mBAAmB,SAAS,UAAU,MAAM,CAAC;AAC1F,OAAK,YAAY,GAAG;AACtB;ACzFA,YAAA;AAEA,MAAM,gBAAgB,YAAY;AAAA,EAChC,MAAM,oBAAoB;AACxB,UAAM,MAAM,KAAK,aAAa,KAAK;AACnC,QAAI,CAAC,IAAK;AACV,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,QAAQ,IAAI,MAAM,EAAE;AACjD,WAAK,YAAY,YAAY,MAAM,IAAI,KAAA,CAAM,CAAC;AAG9C,oBAAA;AAEA,YAAM,UAAU,IAAI,gBAAgB,SAAS,MAAM,EAAE,IAAI,SAAS;AAClE,UAAI,QAAS,uBAAsB,MAAM,sBAAsB,MAC7D,OAAO,cAAc,IAAI,YAAY,cAAc,EAAE,QAAQ,QAAA,CAAS,CAAC,CAAC,CAAC;AAAA,IAC7E,SAAS,GAAG;AACV,WAAK,cAAc,oBAAoB,CAAC;AACxC,cAAQ,MAAM,CAAC;AAAA,IACjB;AAAA,EACF;AACF;AAEA,eAAe,OAAO,YAAY,OAAO;AAEzC,MAAM,UAAU,IAAI,gBAAgB,SAAS,MAAM,EAAE,IAAI,MAAM;AAC/D,IAAI,WAAW,CAAC,SAAS,cAAc,UAAU,GAAG;AAClD,QAAM,MAAM,SAAS,cAAc,UAAU;AAC7C,MAAI,aAAa,OAAO,OAAO;AAC/B,WAAS,KAAK,YAAY,GAAG;AAC/B;"}
package/dist/rpui.js CHANGED
@@ -868,6 +868,10 @@ class RpAnnotationGlobal extends HTMLElement {
868
868
  this.dataset.rpReady = "true";
869
869
  const existing = Array.from(this.childNodes);
870
870
  const label = attr(this, "label", "全局说明");
871
+ const globalSiblings = this.parentElement ? Array.from(this.parentElement.children).filter(
872
+ (el) => el.tagName.toLowerCase() === "annotation-global-el"
873
+ ) : [];
874
+ this.dataset.rpSection = `global-${globalSiblings.indexOf(this) + 1}`;
871
875
  const marker = document.createElement("span");
872
876
  marker.className = "annotation-el-marker global";
873
877
  marker.innerHTML = "<span>★</span>";