@21stware/rpui 0.4.4 → 0.5.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.
@@ -0,0 +1,6 @@
1
+ export interface LiveRenderOpts {
2
+ scroller?: Element | null;
3
+ preserve?: boolean;
4
+ onError?: (msg: string | null) => void;
5
+ }
6
+ export declare function liveRender(host: HTMLElement, source: string, opts?: LiveRenderOpts): void;
package/dist/gallery.d.ts CHANGED
@@ -2,4 +2,7 @@ export interface RpmlDoc {
2
2
  path: string;
3
3
  source: string;
4
4
  }
5
- export declare function mountGallery(docs: RpmlDoc[], host?: HTMLElement): void;
5
+ export interface GalleryController {
6
+ update(newDocs: RpmlDoc[]): void;
7
+ }
8
+ export declare function mountGallery(docs: RpmlDoc[], host?: HTMLElement): GalleryController;
package/dist/gallery.js CHANGED
@@ -1,6 +1,59 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ function attr(el, name, fallback = "") {
5
+ return el.getAttribute(name) ?? fallback;
6
+ }
7
+ function intAttr(el, name, fallback) {
8
+ const raw = el.getAttribute(name);
9
+ const value = raw === null || raw === "" ? NaN : Number(raw);
10
+ return Number.isFinite(value) ? value : fallback;
11
+ }
12
+ function escapeHtml(value) {
13
+ return value.replace(/[&<>'"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;" })[c] || c);
14
+ }
15
+ function csv(el, name, fallback) {
16
+ return attr(el, name, fallback).split(",").map((s) => s.trim()).filter(Boolean);
17
+ }
18
+ const deviceWidths = { web: 1440, ipad: 834, mobile: 390 };
19
+ function resolveWidth(el, fallback) {
20
+ const raw = el.getAttribute("width");
21
+ const width = raw === null || raw === "" ? NaN : Number(raw);
22
+ if (Number.isFinite(width)) return width;
23
+ return deviceWidths[attr(el, "device")] ?? fallback;
24
+ }
25
+ function hasExplicitNumericHeight(el) {
26
+ const raw = el.getAttribute("height");
27
+ return raw !== null && raw !== "" && Number.isFinite(Number(raw));
28
+ }
29
+ function usesAutoHeight(el) {
30
+ const raw = el.getAttribute("height");
31
+ return raw === "auto" || el.hasAttribute("auto-height") || !!el.getAttribute("device") && !hasExplicitNumericHeight(el);
32
+ }
33
+ function resolveHeight(el, fallback) {
34
+ const raw = el.getAttribute("height");
35
+ const height = raw === null || raw === "" ? NaN : Number(raw);
36
+ return Number.isFinite(height) ? height : fallback;
37
+ }
38
+ function isTopAnnotation(node) {
39
+ if (!(node instanceof HTMLElement)) return false;
40
+ const tag = node.tagName.toLowerCase();
41
+ return tag === "annotation-el" || tag === "annotation-el";
42
+ }
43
+ function isGlobalAnnotation(node) {
44
+ return node instanceof HTMLElement && node.tagName.toLowerCase() === "annotation-global-el";
45
+ }
46
+ function isViewportNode(node) {
47
+ if (!(node instanceof HTMLElement)) return false;
48
+ const tag = node.tagName.toLowerCase();
49
+ return tag === "viewport-el" || tag === "viewport-el";
50
+ }
51
+ function define(name, ctor) {
52
+ if (customElements.get(name)) return;
53
+ const Alias = class extends ctor {
54
+ };
55
+ customElements.define(name, Alias);
56
+ }
4
57
  const PRIMITIVES = [
5
58
  // layout
6
59
  "viewport",
@@ -187,59 +240,6 @@ function parseToPage(source) {
187
240
  if (!root) throw new Error("RPML parse error: no <page> root element found");
188
241
  return root;
189
242
  }
190
- function attr(el, name, fallback = "") {
191
- return el.getAttribute(name) ?? fallback;
192
- }
193
- function intAttr(el, name, fallback) {
194
- const raw = el.getAttribute(name);
195
- const value = raw === null || raw === "" ? NaN : Number(raw);
196
- return Number.isFinite(value) ? value : fallback;
197
- }
198
- function escapeHtml(value) {
199
- return value.replace(/[&<>'"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;" })[c] || c);
200
- }
201
- function csv(el, name, fallback) {
202
- return attr(el, name, fallback).split(",").map((s) => s.trim()).filter(Boolean);
203
- }
204
- const deviceWidths = { web: 1440, ipad: 834, mobile: 390 };
205
- function resolveWidth(el, fallback) {
206
- const raw = el.getAttribute("width");
207
- const width = raw === null || raw === "" ? NaN : Number(raw);
208
- if (Number.isFinite(width)) return width;
209
- return deviceWidths[attr(el, "device")] ?? fallback;
210
- }
211
- function hasExplicitNumericHeight(el) {
212
- const raw = el.getAttribute("height");
213
- return raw !== null && raw !== "" && Number.isFinite(Number(raw));
214
- }
215
- function usesAutoHeight(el) {
216
- const raw = el.getAttribute("height");
217
- return raw === "auto" || el.hasAttribute("auto-height") || !!el.getAttribute("device") && !hasExplicitNumericHeight(el);
218
- }
219
- function resolveHeight(el, fallback) {
220
- const raw = el.getAttribute("height");
221
- const height = raw === null || raw === "" ? NaN : Number(raw);
222
- return Number.isFinite(height) ? height : fallback;
223
- }
224
- function isTopAnnotation(node) {
225
- if (!(node instanceof HTMLElement)) return false;
226
- const tag = node.tagName.toLowerCase();
227
- return tag === "annotation-el" || tag === "annotation-el";
228
- }
229
- function isGlobalAnnotation(node) {
230
- return node instanceof HTMLElement && node.tagName.toLowerCase() === "annotation-global-el";
231
- }
232
- function isViewportNode(node) {
233
- if (!(node instanceof HTMLElement)) return false;
234
- const tag = node.tagName.toLowerCase();
235
- return tag === "viewport-el" || tag === "viewport-el";
236
- }
237
- function define(name, ctor) {
238
- if (customElements.get(name)) return;
239
- const Alias = class extends ctor {
240
- };
241
- customElements.define(name, Alias);
242
- }
243
243
  const RPUI_STYLE_ID = "rpui-runtime-style";
244
244
  const style = `
245
245
  :root { --rp-bg:#f0f2f5; --rp-surface:#fff; --rp-surface-soft:#f9fafb; --rp-text:#111827; --rp-muted:#6b7280; --rp-border:#e5e7eb; --rp-border-strong:#d1d5db; --rp-primary:#2563eb; --rp-success:#059669; --rp-warning:#d97706; --rp-danger:#dc2626; --rp-purple:#7c3aed; --rp-radius-sm:4px; --rp-radius-md:8px; --rp-radius-lg:12px; --rp-shadow:0 8px 28px rgba(15,23,42,.08); --rp-font:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
@@ -99584,6 +99584,30 @@ function registerAll() {
99584
99584
  define(toComponentTag(suffix), ctor);
99585
99585
  }
99586
99586
  }
99587
+ function liveRender(host, source, opts = {}) {
99588
+ const { scroller, preserve = true, onError } = opts;
99589
+ const sc = scroller ?? document.scrollingElement ?? document.documentElement;
99590
+ const pane = preserve ? host.querySelector(".annotation-el-pane") : null;
99591
+ const pos = preserve ? { x: sc.scrollLeft, y: sc.scrollTop, px: (pane == null ? void 0 : pane.scrollLeft) ?? 0, py: (pane == null ? void 0 : pane.scrollTop) ?? 0 } : null;
99592
+ try {
99593
+ host.replaceChildren(parseToPage(source));
99594
+ onError == null ? void 0 : onError(null);
99595
+ } catch (e) {
99596
+ onError == null ? void 0 : onError(e.message ?? String(e));
99597
+ return;
99598
+ }
99599
+ if (pos) {
99600
+ requestAnimationFrame(() => requestAnimationFrame(() => {
99601
+ sc.scrollLeft = pos.x;
99602
+ sc.scrollTop = pos.y;
99603
+ const newPane = host.querySelector(".annotation-el-pane");
99604
+ if (newPane) {
99605
+ newPane.scrollLeft = pos.px;
99606
+ newPane.scrollTop = pos.py;
99607
+ }
99608
+ }));
99609
+ }
99610
+ }
99587
99611
  registerAll();
99588
99612
  const THEME_STYLE_ID = "rpml-theme-style";
99589
99613
  const ATTR = "data-rpml-theme";
@@ -99721,14 +99745,13 @@ function resolveAnchorTarget(to, fromPath, paths) {
99721
99745
  }
99722
99746
  function mountGallery(docs, host = document.body) {
99723
99747
  injectChrome();
99724
- const byPath = new Map(docs.map((d) => [d.path, d]));
99725
- const tree = buildTree(docs);
99748
+ let byPath = new Map(docs.map((d) => [d.path, d]));
99749
+ let tree = buildTree(docs);
99726
99750
  const root = document.createElement("div");
99727
99751
  root.className = "rpml-gallery";
99728
99752
  const side = document.createElement("aside");
99729
99753
  side.className = "rpml-gx-side";
99730
- const count = docs.length;
99731
- side.innerHTML = `<div class="rpml-gx-head"><span>RPML 文档<small>${count} 个文件</small></span><div class="rpml-gx-head-actions"><button class="rpml-gx-btn rpml-gx-theme" type="button" title="切换亮色/暗色" aria-label="切换亮色/暗色">◑</button><button class="rpml-gx-btn rpml-gx-toggle" type="button" title="收起侧边栏" aria-label="收起侧边栏">«</button></div></div>`;
99754
+ side.innerHTML = `<div class="rpml-gx-head"><span>RPML 文档<small>${docs.length} 个文件</small></span><div class="rpml-gx-head-actions"><button class="rpml-gx-btn rpml-gx-theme" type="button" title="切换亮色/暗色" aria-label="切换亮色/暗色">◑</button><button class="rpml-gx-btn rpml-gx-toggle" type="button" title="收起侧边栏" aria-label="收起侧边栏">«</button></div></div>`;
99732
99755
  const nav = document.createElement("nav");
99733
99756
  nav.className = "rpml-gx-nav";
99734
99757
  side.appendChild(nav);
@@ -99781,25 +99804,34 @@ function mountGallery(docs, host = document.body) {
99781
99804
  }
99782
99805
  }
99783
99806
  renderNav(tree, 0);
99807
+ function rebuildNav(newDocs) {
99808
+ nav.innerHTML = "";
99809
+ links.clear();
99810
+ tree = buildTree(newDocs);
99811
+ renderNav(tree, 0);
99812
+ side.querySelector(".rpml-gx-head small").textContent = `${newDocs.length} 个文件`;
99813
+ }
99784
99814
  function pickDefault() {
99785
- const idx = docs.find((d) => basename(d.path).replace(/\.rpml$/i, "").toLowerCase() === "index");
99786
- return (idx ?? docs[0]).path;
99815
+ const cur = [...byPath.values()];
99816
+ const idx = cur.find((d) => basename(d.path).replace(/\.rpml$/i, "").toLowerCase() === "index");
99817
+ return (idx ?? cur[0]).path;
99787
99818
  }
99788
99819
  let currentPath = "";
99789
- function show(path, section) {
99820
+ function show(path, section, preserve = false) {
99790
99821
  const doc = byPath.get(path);
99791
99822
  if (!doc) {
99792
99823
  main.innerHTML = `<div class="rpml-gx-err">未找到文档:${path}</div>`;
99793
99824
  return;
99794
99825
  }
99795
- try {
99796
- main.innerHTML = "";
99797
- main.appendChild(parseToPage(doc.source));
99798
- currentPath = path;
99799
- } catch (e) {
99800
- main.innerHTML = `<div class="rpml-gx-err">RPML 解析错误:${e.message}</div>`;
99801
- }
99802
- links.forEach((a, p) => a.classList.toggle("active", p === path));
99826
+ liveRender(main, doc.source, {
99827
+ scroller: main,
99828
+ preserve,
99829
+ onError: (msg) => {
99830
+ if (msg) main.innerHTML = `<div class="rpml-gx-err">RPML 解析错误:${msg}</div>`;
99831
+ }
99832
+ });
99833
+ currentPath = path;
99834
+ links.forEach((row, p) => row.classList.toggle("active", p === path));
99803
99835
  if (section) {
99804
99836
  requestAnimationFrame(() => requestAnimationFrame(() => window.dispatchEvent(new CustomEvent("rp-section", { detail: section }))));
99805
99837
  }
@@ -99843,11 +99875,46 @@ function mountGallery(docs, host = document.body) {
99843
99875
  });
99844
99876
  window.addEventListener("popstate", route);
99845
99877
  route();
99878
+ const controller = {
99879
+ update(newDocs) {
99880
+ var _a2;
99881
+ const prevSource = (_a2 = byPath.get(currentPath)) == null ? void 0 : _a2.source;
99882
+ byPath = new Map(newDocs.map((d) => [d.path, d]));
99883
+ const newPaths = newDocs.map((d) => d.path).sort().join("\0");
99884
+ const oldPaths = [...links.keys()].sort().join("\0");
99885
+ if (newPaths !== oldPaths) rebuildNav(newDocs);
99886
+ const curr = byPath.get(currentPath);
99887
+ if (curr) {
99888
+ if (curr.source !== prevSource) show(currentPath, void 0, true);
99889
+ else links.forEach((row, p) => row.classList.toggle("active", p === currentPath));
99890
+ } else if (newDocs.length) {
99891
+ const def = pickDefault();
99892
+ history.replaceState(null, "", `#${def}`);
99893
+ show(def);
99894
+ }
99895
+ }
99896
+ };
99897
+ globalThis.__RPML_GALLERY__ = controller;
99898
+ return controller;
99846
99899
  }
99847
99900
  const inlined = globalThis.__RPML_DOCS__;
99848
99901
  if (inlined && Array.isArray(inlined) && inlined.length) {
99849
- if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", () => mountGallery(inlined));
99850
- else mountGallery(inlined);
99902
+ const mount = () => {
99903
+ mountGallery(inlined);
99904
+ if (globalThis.__RPML_LIVE__) {
99905
+ const es = new EventSource("/~live");
99906
+ es.onmessage = (ev) => {
99907
+ var _a2;
99908
+ try {
99909
+ const docs = JSON.parse(ev.data);
99910
+ (_a2 = globalThis.__RPML_GALLERY__) == null ? void 0 : _a2.update(docs);
99911
+ } catch {
99912
+ }
99913
+ };
99914
+ }
99915
+ };
99916
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", mount);
99917
+ else mount();
99851
99918
  }
99852
99919
  export {
99853
99920
  mountGallery