weifuwu 0.84.0 → 0.86.0

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.
Files changed (44) hide show
  1. package/README.md +5 -5
  2. package/dist/components/Editor/Editor.d.ts +8 -7
  3. package/dist/components/Editor/edit-events.d.ts +27 -0
  4. package/dist/components/Editor/model/apply.d.ts +23 -0
  5. package/dist/components/Editor/model/diff.d.ts +25 -0
  6. package/dist/components/Editor/model/dom.d.ts +27 -0
  7. package/dist/components/Editor/model/history.d.ts +21 -0
  8. package/dist/components/Editor/model/html.d.ts +16 -0
  9. package/dist/components/Editor/model/inverse.d.ts +20 -0
  10. package/dist/components/Editor/model/types.d.ts +116 -0
  11. package/dist/components/Editor/tools/table.d.ts +5 -2
  12. package/dist/components/Editor/tools/toolbar.d.ts +1 -1
  13. package/dist/components/Editor/tools/types.d.ts +25 -0
  14. package/dist/components/FilePreview/FilePreview.d.ts +42 -0
  15. package/dist/components/FilePreview/markdown.d.ts +23 -0
  16. package/dist/components/OfficeEditor/ai/ai-bridge.d.ts +39 -0
  17. package/dist/components/OfficeEditor/model/apply.d.ts +25 -0
  18. package/dist/components/OfficeEditor/model/types.d.ts +224 -0
  19. package/dist/components/SheetGrid/SheetGrid.d.ts +30 -0
  20. package/dist/components/SlideCanvas/SlideCanvas.d.ts +28 -0
  21. package/dist/components/index.d.ts +7 -0
  22. package/dist/components/index.js +54 -14
  23. package/dist/components/style.css +679 -4
  24. package/dist/index.js +128 -17
  25. package/dist/office/docx.d.ts +33 -0
  26. package/dist/office/pptx.d.ts +24 -0
  27. package/dist/office/xlsx.d.ts +25 -0
  28. package/dist/office/xml-serialize.d.ts +18 -0
  29. package/dist/office/xml.d.ts +21 -0
  30. package/dist/office/zip.d.ts +21 -0
  31. package/dist/ui-dom/index.js +8 -7
  32. package/dist/ui-dom/locale/en_US.d.ts +14 -0
  33. package/dist/ui-dom/locale/zh_CN.d.ts +14 -0
  34. package/dist/ui-dom/middleware/ws.d.ts +8 -0
  35. package/dist/ui-dom/testing.js +5 -5
  36. package/dist/ui-dom/types.d.ts +3 -1
  37. package/dist/ui-dom/vdom3/delegate.d.ts +9 -0
  38. package/dist/ui-dom/vdom3/types.d.ts +27 -1
  39. package/dist/ui-dom/vdom3.js +10 -9
  40. package/docs/components-map.md +12 -0
  41. package/docs/components.md +124 -2
  42. package/docs/frontend-ui-dom.md +1 -0
  43. package/package.json +1 -1
  44. package/dist/components/Editor/tools/format.d.ts +0 -11
package/dist/index.js CHANGED
@@ -3316,7 +3316,6 @@ var HtmlSafe = class {
3316
3316
 
3317
3317
  // src/ui-dom/vdom3/types.ts
3318
3318
  function flatten(c) {
3319
- if (c == null || typeof c === "boolean") return [];
3320
3319
  if (Array.isArray(c)) return c.flatMap((x) => flatten(x));
3321
3320
  return [c];
3322
3321
  }
@@ -3368,8 +3367,19 @@ function createEventStream(max = 2e4, opts) {
3368
3367
  }
3369
3368
  }
3370
3369
  };
3370
+ let currentSession = null;
3371
+ let sessionUid = 0;
3371
3372
  return {
3373
+ /** 新渲染会话开始(每次渲染入口调用——同一次渲染的事件共享 session id) */
3374
+ setSession() {
3375
+ currentSession = `s${++sessionUid}`;
3376
+ return currentSession;
3377
+ },
3378
+ currentSession() {
3379
+ return currentSession;
3380
+ },
3372
3381
  emit(evt) {
3382
+ if (currentSession != null && evt.session == null) evt.session = currentSession;
3373
3383
  emitOne(evt, false);
3374
3384
  if (watermark > 0 && !watermarkFired && len >= max * watermark) {
3375
3385
  watermarkFired = true;
@@ -3417,6 +3427,10 @@ function createEventStream(max = 2e4, opts) {
3417
3427
  for (let i = 0; i < len; i++) out[i] = at(i);
3418
3428
  return out;
3419
3429
  },
3430
+ /** 按渲染会话过滤(阶段 5——session 字段的消费 API——一次渲染的事件全量) */
3431
+ eventsBySession(sessionId) {
3432
+ return this.events().filter((e) => e.session === sessionId);
3433
+ },
3420
3434
  inverse(evt) {
3421
3435
  switch (evt.entity + ":" + evt.action) {
3422
3436
  case "node:insert":
@@ -3444,6 +3458,35 @@ function createEventStream(max = 2e4, opts) {
3444
3458
  };
3445
3459
  }
3446
3460
  var stream = createEventStream();
3461
+ if (typeof globalThis !== "undefined") {
3462
+ const w = globalThis;
3463
+ if (!w.__wf_tail) {
3464
+ w.__wf_tail = (n = 200, filter) => {
3465
+ const evs = stream.events().slice(-n);
3466
+ if (!filter) return evs;
3467
+ return evs.filter((e) => {
3468
+ if (filter.comp && e.payload?.name !== filter.comp) return false;
3469
+ if (filter.decision && e.payload?.reason !== filter.decision) return false;
3470
+ if (filter.causeId && e.payload?.causeId !== filter.causeId) return false;
3471
+ return true;
3472
+ });
3473
+ };
3474
+ w.__wf_builds = (comp) => {
3475
+ const evs = stream.events().filter((e) => e.entity === "comp" && e.action === "build");
3476
+ if (!comp) return evs.map((e) => ({ name: e.payload?.name, reason: e.payload?.reason, changedKeys: e.payload?.changedKeys }));
3477
+ return evs.filter((e) => e.payload?.name === comp).map((e) => ({ name: e.payload?.name, reason: e.payload?.reason, changedKeys: e.payload?.changedKeys }));
3478
+ };
3479
+ w.__wf_recent = (n = 50) => stream.events().slice(-n);
3480
+ w.__wf_comp = (compId) => {
3481
+ const evs = stream.events().filter((e) => {
3482
+ if (e.target === compId) return true;
3483
+ if (e.payload?.name && !compId) return true;
3484
+ return false;
3485
+ });
3486
+ return evs.map((e) => ({ entity: e.entity, action: e.action, target: e.target, payload: e.payload, session: e.session }));
3487
+ };
3488
+ }
3489
+ }
3447
3490
  var nodeUid = 0;
3448
3491
  function nextNodeId() {
3449
3492
  return `n${++nodeUid}`;
@@ -3564,6 +3607,17 @@ function createClientBrowser() {
3564
3607
  if (typeof window !== "undefined") window.location.hash = h2;
3565
3608
  },
3566
3609
  timeout: (fn, ms) => typeof window !== "undefined" ? window.setTimeout(fn, ms) : 0,
3610
+ /** 浏览器表单状态恢复(load/pageshow)监听——SSR 安全 no-op */
3611
+ onFormRestore: (cb) => {
3612
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {
3613
+ };
3614
+ window.addEventListener("load", cb, { once: true });
3615
+ window.addEventListener("pageshow", cb);
3616
+ return () => {
3617
+ window.removeEventListener("load", cb);
3618
+ window.removeEventListener("pageshow", cb);
3619
+ };
3620
+ },
3567
3621
  rootElement: () => typeof document !== "undefined" ? document.documentElement : null,
3568
3622
  storageGet: (key) => {
3569
3623
  try {
@@ -3727,7 +3781,15 @@ var globalHandlers = /* @__PURE__ */ new Map();
3727
3781
  var globalRoots = /* @__PURE__ */ new Map();
3728
3782
  var EVENT_MAP = {
3729
3783
  focus: "focusin",
3730
- blur: "focusout"
3784
+ blur: "focusout",
3785
+ mouseenter: "mouseover",
3786
+ mouseleave: "mouseout"
3787
+ };
3788
+ var REVERSE_MAP = {
3789
+ focusin: "focus",
3790
+ focusout: "blur",
3791
+ mouseover: "mouseenter",
3792
+ mouseout: "mouseleave"
3731
3793
  };
3732
3794
  var NON_BUBBLING = /* @__PURE__ */ new Set(["error", "load", "loadstart", "loadend", "abort", "unload", "resize", "message"]);
3733
3795
  var TEXT_INPUT_EVENTS = /* @__PURE__ */ new Set(["input", "change", "compositionstart", "compositionend"]);
@@ -3762,11 +3824,11 @@ function rootOf(node) {
3762
3824
  return null;
3763
3825
  }
3764
3826
  function ensureRootEvent(root, event) {
3827
+ const realEvent = EVENT_MAP[event] ?? event;
3765
3828
  const set = registered.get(root) ?? /* @__PURE__ */ new Set();
3766
- if (set.has(event)) return;
3767
- set.add(event);
3829
+ if (set.has(realEvent)) return;
3830
+ set.add(realEvent);
3768
3831
  registered.set(root, set);
3769
- const realEvent = EVENT_MAP[event] ?? event;
3770
3832
  const fn = (e) => dispatch(e);
3771
3833
  const useCapture = NON_BUBBLING.has(realEvent);
3772
3834
  root.addEventListener(realEvent, fn, useCapture ? { capture: true } : void 0);
@@ -3814,8 +3876,11 @@ function addGlobalListener(target, event, handler, opts) {
3814
3876
  function dispatch(e) {
3815
3877
  let el = e.target;
3816
3878
  if (el && el.nodeType === 3) el = el.parentElement;
3817
- const m = handlers.get(e.type);
3818
- if (!m) return;
3879
+ const keys = [];
3880
+ if (handlers.has(e.type)) keys.push(e.type);
3881
+ const mapped = REVERSE_MAP[e.type];
3882
+ if (mapped && mapped !== e.type && handlers.has(mapped)) keys.push(mapped);
3883
+ if (keys.length === 0) return;
3819
3884
  while (el) {
3820
3885
  if (!el.hasAttribute?.("data-v3-id")) {
3821
3886
  const idEl = el.closest?.("[data-v3-id]");
@@ -3826,8 +3891,9 @@ function dispatch(e) {
3826
3891
  }
3827
3892
  const id = el.getAttribute?.("data-v3-id");
3828
3893
  if (id) {
3829
- const entry = m.get(id);
3830
- if (entry) {
3894
+ for (const k of keys) {
3895
+ const entry = handlers.get(k)?.get(id);
3896
+ if (!entry) continue;
3831
3897
  try {
3832
3898
  Object.defineProperty(e, "currentTarget", { value: el, configurable: true });
3833
3899
  } catch {
@@ -3841,11 +3907,12 @@ function dispatch(e) {
3841
3907
  stream.emit(ev("text", "input", id, { event: e.type, value: typeof value === "string" ? value.slice(0, 100) : null }));
3842
3908
  }
3843
3909
  if (entry.once) {
3844
- m.delete(id);
3910
+ handlers.get(k).delete(id);
3845
3911
  stream.emit(ev("event", "unbind", id, { event: e.type }));
3846
3912
  }
3847
3913
  if (e.cancelBubble) break;
3848
3914
  }
3915
+ if (e.cancelBubble) break;
3849
3916
  }
3850
3917
  el = el.parentElement;
3851
3918
  }
@@ -4359,7 +4426,8 @@ function usePopup(env, options) {
4359
4426
  prevOpen = true;
4360
4427
  lastEl = el;
4361
4428
  }
4362
- const props = content.props ?? {};
4429
+ const props = (content ?? h("span")).props ?? {};
4430
+ if (!content) return null;
4363
4431
  latestContentRef = props.ref ?? null;
4364
4432
  const cls = options.positioning === "none" ? props.class ?? "" : ["wf-popup", props.class].filter(Boolean).join(" ");
4365
4433
  const style = options.positioning === "none" ? { ...props.style ?? {}, position: "fixed" } : {
@@ -5367,6 +5435,21 @@ var unmountHooks = /* @__PURE__ */ new Map();
5367
5435
  function isVNode(v) {
5368
5436
  return v != null && typeof v === "object" && !Array.isArray(v) && "type" in v;
5369
5437
  }
5438
+ function propsSnap(props) {
5439
+ try {
5440
+ const picked = {};
5441
+ for (const [k, v] of Object.entries(props)) {
5442
+ if (v != null && typeof v === "object") {
5443
+ const s = JSON.stringify(v);
5444
+ if (s != null && s.length <= 1e4) picked[k] = s;
5445
+ }
5446
+ }
5447
+ return Object.keys(picked).length > 0 ? JSON.stringify(picked) : null;
5448
+ } catch {
5449
+ return null;
5450
+ }
5451
+ }
5452
+ var warnedContentChange = /* @__PURE__ */ new Set();
5370
5453
  function propsEqual(a, b) {
5371
5454
  const ka = Object.keys(a);
5372
5455
  const kb = Object.keys(b);
@@ -5387,17 +5470,33 @@ async function buildVNode(vnode, ctx, oldV, isRoot = false) {
5387
5470
  v2._render = reuse._render;
5388
5471
  v2._id = reuse._id;
5389
5472
  indexComponent(v2);
5390
- stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true }));
5391
5473
  if (!isRoot && propsEqual(reuse.props, v2.props)) {
5392
5474
  v2._child = reuse._child;
5475
+ if (reuse.el != null) v2.el = reuse.el;
5476
+ if (globalThis.__WF_V3_AUDIT !== "0" && reuse._propsSnap != null) {
5477
+ const cur = propsSnap(v2.props);
5478
+ if (cur != null && cur !== reuse._propsSnap) {
5479
+ const key = `${compName(v2.type)}:${cur.slice(0, 40)}`;
5480
+ if (!warnedContentChange.has(key)) {
5481
+ warnedContentChange.add(key);
5482
+ console.warn(
5483
+ `[vdom3/audit] \u7EC4\u4EF6 ${compName(v2.type)} \u7684 props \u5BF9\u8C61\u5185\u5BB9\u5DF2\u53D8\u4F46\u5F15\u7528\u672A\u53D8\uFF08\u539F\u5730\u4FEE\u6539\u5BF9\u8C61\uFF1F\uFF09\u2014\u2014\u526A\u679D\u5C06\u8DF3\u8FC7\u91CD\u6E32\u67D3\u2014\u2014\u8BF7\u65B0\u5EFA\u5BF9\u8C61\u4F20 props\uFF08props \u4E0D\u53EF\u53D8\u5951\u7EA6\uFF09`
5484
+ );
5485
+ }
5486
+ }
5487
+ }
5488
+ stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true, reason: "reuse-skip", propsKeys: Object.keys(v2.props) }));
5489
+ v2._propsSnap = propsSnap(v2.props);
5393
5490
  return v2;
5394
5491
  }
5395
5492
  const changedKeys = Object.keys({ ...reuse.props, ...v2.props }).filter((k) => reuse.props[k] !== v2.props[k]);
5396
5493
  stream.emit(ev("props", "update", v2._id, { name: compName(v2.type), keys: changedKeys }));
5494
+ v2._propsSnap = propsSnap(v2.props);
5495
+ stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true, reason: isRoot ? "root-render" : "props-changed", changedKeys }));
5397
5496
  } else {
5398
5497
  v2._id = nextNodeId();
5399
5498
  indexComponent(v2);
5400
- stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: false, index: true }));
5499
+ stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: false, index: true, reason: "mount" }));
5401
5500
  stream.emit(ev("comp", "mount", v2._id, { name: compName(v2.type) }));
5402
5501
  const compId = v2._id;
5403
5502
  const componentRender = () => {
@@ -5423,7 +5522,9 @@ async function buildVNode(vnode, ctx, oldV, isRoot = false) {
5423
5522
  // SSR 下 document undefined → null/no-op——组件 36 处 ctx.browser 消费
5424
5523
  // 无需各自 fallback(Tour 曾因 ctx.browser 缺失 → query 返回 undefined →
5425
5524
  // targetEl 永不赋值 → rect 恒 0 → 引导高亮定位失效)
5426
- browser: createClientBrowser(),
5525
+ // 注入优先(测试/宿主可覆盖——真实事故:ctx.browser 注入被覆盖——
5526
+ // Editor draftKey 的 storageSet 走真实 browser 而非 fake)
5527
+ browser: ctx.browser ?? createClientBrowser(),
5427
5528
  ui: createV3Ui(compId, componentRender, (fn) => {
5428
5529
  unmountHooks.set(compId, fn);
5429
5530
  })
@@ -5437,6 +5538,7 @@ async function buildVNode(vnode, ctx, oldV, isRoot = false) {
5437
5538
  throw e;
5438
5539
  }
5439
5540
  v2._render = renderFn;
5541
+ v2._propsSnap = propsSnap(v2.props);
5440
5542
  }
5441
5543
  stream.emit(ev("comp", "render", v2._id, { name: compName(v2.type) }));
5442
5544
  let output = null;
@@ -5535,7 +5637,11 @@ async function renderToEvents(vnode) {
5535
5637
  }
5536
5638
  events.push(ev("node", "insert", id, { parent: parentId, ref: null }));
5537
5639
  for (const c of childrenOf(v)) {
5538
- if (typeof c === "string" || typeof c === "number") {
5640
+ if (c == null || c === false || c === true) {
5641
+ const hid = nextId();
5642
+ events.push(ev("node", "create", hid, { kind: "hole" }));
5643
+ events.push(ev("node", "insert", hid, { parent: id, ref: null }));
5644
+ } else if (typeof c === "string" || typeof c === "number") {
5539
5645
  const tid = nextId();
5540
5646
  events.push(ev("text", "create", tid, { value: String(c) }));
5541
5647
  events.push(ev("node", "insert", tid, { parent: id, ref: null }));
@@ -5551,9 +5657,13 @@ function eventsToHtml(events) {
5551
5657
  const tags = /* @__PURE__ */ new Map();
5552
5658
  const attrs = /* @__PURE__ */ new Map();
5553
5659
  const texts = /* @__PURE__ */ new Map();
5660
+ const holes = /* @__PURE__ */ new Set();
5554
5661
  for (const e of events) {
5555
- if (e.entity === "node" && e.action === "create") tags.set(e.target, e.payload.tag);
5556
- else if (e.entity === "prop" && e.action === "update") {
5662
+ if (e.entity === "node" && e.action === "create") {
5663
+ const pl = e.payload;
5664
+ if (pl.kind === "hole") holes.add(e.target);
5665
+ else tags.set(e.target, pl.tag);
5666
+ } else if (e.entity === "prop" && e.action === "update") {
5557
5667
  const pl = e.payload;
5558
5668
  const m = attrs.get(e.target) ?? /* @__PURE__ */ new Map();
5559
5669
  if (pl.value == null || pl.value === false) m.delete(pl.key);
@@ -5572,6 +5682,7 @@ function eventsToHtml(events) {
5572
5682
  }
5573
5683
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
5574
5684
  const emit = (id) => {
5685
+ if (holes.has(id)) return "<!--wf-hole-->";
5575
5686
  const tag = tags.get(id);
5576
5687
  if (tag) {
5577
5688
  const a = attrs.get(id);
@@ -0,0 +1,33 @@
1
+ /**
2
+ * weifuwu/office/docx — docx ↔ ODES DocState 双向转换(前端/服务端通用——零依赖)
3
+ *
4
+ * 参考算法:office2json(unzip → XML 树 → 路径提取 w:body → w:p → w:r → w:t),
5
+ * 增强提取到 ODES IR(design/office-events-plan.md):
6
+ * - 段落属性:pStyle → h1/h2/h3、jc → 对齐(裁剪:样式名映射子集)
7
+ * - 内联格式:rPr b/i/u → marks(区间)
8
+ * - 表格:w:tbl → Editor 表格 embed 快照(HTML 复用现有模型)
9
+ * - 图片:w:drawing r:embed → rels → media → data url → img embed
10
+ *
11
+ * 导出(DocState → minimal docx):VNode 组件化(OOXML 也是 VNode——与前端
12
+ * "VNode → DOM"同构)→ vnodeToXml → store ZIP。块级 embed(pre/hr)打断段落。
13
+ *
14
+ * 前端可用(无需后端):ZIP 解压走 DecompressionStream(浏览器/Node 18+);
15
+ * base64 跨环境(Buffer 守卫/atob)。诚实裁剪:分节/页眉页脚/域代码/批注
16
+ * 不导入;http 图片导出裁剪(warning)。
17
+ */
18
+ import type { DocState } from '../components/Editor/model/types.ts';
19
+ /** base64 → Uint8Array(跨环境——浏览器 atob / Node Buffer) */
20
+ export declare function base64ToBytes(b64: string): Uint8Array;
21
+ /** Uint8Array → base64(跨环境) */
22
+ export declare function bytesToBase64(u8: Uint8Array): string;
23
+ export interface DocxImportResult {
24
+ doc: DocState;
25
+ /** 裁剪提示(不可映射内容——诚实不静默) */
26
+ warnings: string[];
27
+ }
28
+ export declare function docxToDoc(u8: Uint8Array): Promise<DocxImportResult>;
29
+ export interface DocxExportResult {
30
+ data: Uint8Array;
31
+ warnings: string[];
32
+ }
33
+ export declare function docToDocx(doc: DocState): DocxExportResult;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * weifuwu/office/pptx — pptx ↔ ODES DeckState 双向转换(前端/服务端通用——零依赖)
3
+ *
4
+ * 参考算法:office2json(ppt/slides/slideN.xml → p:sld → p:cSld → p:spTree →
5
+ * p:sp → p:txBody → a:p → a:r → a:t),增强提取到 ODES IR:
6
+ * - 多幻灯片:slideN.xml 全量(office2json 只取文本——ODES 保留结构)
7
+ * - shape 几何:p:xfrm(a:off x/y + a:ext cx/cy——EMU → px:/9525)
8
+ * - shape 类型:p:sp(文本/图形——p:prstGeom prst)/ p:pic(图片)
9
+ * - 文本:txBody → 段落拼接(a:br 换行)
10
+ *
11
+ * 导出(DeckState → minimal pptx):slideN.xml(spTree 结构)+ presentation.xml
12
+ * + rels + contentTypes——store ZIP(VNode 组件化)。裁剪:动画/母版/备注/主题。
13
+ */
14
+ import type { DeckState } from '../components/OfficeEditor/model/types.ts';
15
+ export interface PptxImportResult {
16
+ deck: DeckState;
17
+ warnings: string[];
18
+ }
19
+ export declare function pptxToDeck(u8: Uint8Array): Promise<PptxImportResult>;
20
+ export interface PptxExportResult {
21
+ data: Uint8Array;
22
+ warnings: string[];
23
+ }
24
+ export declare function deckToPptx(deck: DeckState): PptxExportResult;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * weifuwu/office/xlsx — xlsx ↔ ODES WorkbookState 双向转换(前端/服务端通用——零依赖)
3
+ *
4
+ * 参考算法:office2json(sheet1.xml → sheetData → row → c{t,v} + sharedStrings),
5
+ * 增强提取到 ODES IR(design/office-events-plan.md):
6
+ * - 多 sheet:workbook.xml(sheet 名 + rId)→ rels → 各 sheetN.xml
7
+ * - 单元格引用:c@r(A1——office2json 丢弃——ODES 必须)
8
+ * - 类型:c@t(s=shared/n=number/b=bool/f=formula)→ SheetCell.kind
9
+ * - 公式:f 元素(公式字符串保留——不计算——服务端导出重算)
10
+ * - 行高/列宽/样式索引:裁剪(v1——诚实:不读不写)
11
+ *
12
+ * 导出(WorkbookState → minimal xlsx):共享字符串表去重 + sheetData XML +
13
+ * store ZIP(VNode 组件化——OOXML 也是 VNode)。
14
+ */
15
+ import type { WorkbookState } from '../components/OfficeEditor/model/types.ts';
16
+ export interface XlsxImportResult {
17
+ workbook: WorkbookState;
18
+ warnings: string[];
19
+ }
20
+ export declare function xlsxToWorkbook(u8: Uint8Array): Promise<XlsxImportResult>;
21
+ export interface XlsxExportResult {
22
+ data: Uint8Array;
23
+ warnings: string[];
24
+ }
25
+ export declare function workbookToXlsx(wb: WorkbookState): XlsxExportResult;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * weifuwu/office/xml-serialize — VNode 树 → XML 字符串(服务端 OOXML 生成)
3
+ *
4
+ * 架构(与前端 "VNode → DOM" 同构——"OOXML 也是写 VNode"):
5
+ * DocState → VNode 树(h()——vdom3 纯数据结构,零 DOM 依赖)
6
+ * → vnodeToXml(本模块)→ ZIP 打包
7
+ *
8
+ * 序列化约定(仅输出 XML 子集——与 vdom3 渲染管线解耦):
9
+ * - type: string = 元素名(含命名空间前缀——w:p、a:graphic 等原样输出)
10
+ * - props: 字符串值 = XML 属性;number/boolean 字符串化;key/children 排除
11
+ * - children: 字符串 = 文本(转义);VNode = 子元素;数组 = 序列
12
+ * - 空元素 → 自闭合 <w:b/>(OOXML 合法——MS Word 接受)
13
+ * - 函数/symbol type、事件 props(on*)→ 抛错(诚实——序列化器不支持)
14
+ */
15
+ import type { VNode, VNodeChild } from '../ui-dom/vdom3/types.ts';
16
+ export declare function vnodeToXml(v: VNode | VNodeChild, xmlDecl?: boolean): string;
17
+ /** 便捷:把 VNode 树序列化为 OOXML 部件 XML(名称检查由调用方保证) */
18
+ export declare function vnodeToXmlDecl(v: VNode): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * weifuwu/office/xml — 自研轻量 XML 解析(零 npm 依赖)
3
+ *
4
+ * 参考算法:office2json(@xmldom/xmldom → { name, attrs, children } 树)。
5
+ * OOXML 是规整 XML——只做元素树 + 文本;裁剪:DTD/实体展开(除内建五实体)/
6
+ * 命名空间处理(前缀保留原名——w:p 等)。解析失败抛错(诚实——不静默)。
7
+ */
8
+ export interface XmlNode {
9
+ name: string;
10
+ attrs: Record<string, string>;
11
+ children: XmlNode[];
12
+ /** 直接文本内容(子元素外的文本) */
13
+ text: string;
14
+ }
15
+ /** 手写扫描器:<name attrs>...</name> / <name/> / 文本 / 指令 / 注释 / CDATA */
16
+ export declare function parseXml(xml: string): XmlNode;
17
+ /** 便捷:找子元素(按名字——含命名空间前缀匹配) */
18
+ export declare function child(el: XmlNode, name: string): XmlNode | null;
19
+ export declare function children(el: XmlNode, name: string): XmlNode[];
20
+ /** 全部后代文本(拼接) */
21
+ export declare function allText(el: XmlNode): string;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * weifuwu/office/zip — 自研 ZIP 容器读写(零 npm 依赖)
3
+ *
4
+ * 参考算法:office2json(unzip 结构——EOCD → central directory → local header)。
5
+ * 实现取舍:
6
+ * - 读:EOCD(尾部扫描)→ central directory → local header offset 取数据;
7
+ * 压缩方法 0(store)/ 8(deflate——Node 内置 zlib.inflateRawSync)
8
+ * - 写:store(无压缩——合法 ZIP,文件较大但零依赖正确性优先);
9
+ * CRC32 自研查表
10
+ * - 裁剪:ZIP64/加密/数据描述符(flags bit3)/多盘——不支持(诚实——读到即抛错)
11
+ */
12
+ export interface ZipEntry {
13
+ name: string;
14
+ data: Uint8Array;
15
+ }
16
+ export declare function crc32(buf: Uint8Array): number;
17
+ /** inflate(ZIP method 8)——跨环境:浏览器 DecompressionStream / Node 18+(同 API)。
18
+ * 老环境无 DecompressionStream → 抛错(诚实裁剪:OOXML 必用 deflate)。 */
19
+ export declare function inflateRaw(data: Uint8Array): Promise<Uint8Array>;
20
+ export declare function readZip(u8: Uint8Array): Promise<Map<string, Uint8Array>>;
21
+ export declare function writeZip(files: Map<string, Uint8Array>): Uint8Array;