mosage 0.1.0 → 0.8.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 (146) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +16 -217
  3. package/bin.js +2 -0
  4. package/dist/build-C7NW_3Pk.js +14 -0
  5. package/dist/check-CP4873Wx.js +41 -0
  6. package/dist/cli/bin.d.ts +1 -0
  7. package/dist/cli/bin.js +228 -0
  8. package/dist/config-DPm1BBAb.js +2619 -0
  9. package/dist/config-TlTe7Ona.d.ts +24 -0
  10. package/dist/context-BqsdSrAQ.js +1084 -0
  11. package/dist/dev-Biz42qlu.js +17 -0
  12. package/dist/diagram-xlVDekYk.js +763 -0
  13. package/dist/export-Bi6nuxjT.js +31 -0
  14. package/dist/import-D2jNB07F.js +25 -0
  15. package/dist/index.d.ts +455 -0
  16. package/dist/index.js +693 -0
  17. package/dist/init-Bbtj2pxF.js +262 -0
  18. package/dist/preview-CLm51aRt.js +19 -0
  19. package/dist/sdk-DjpX6mCv.js +51 -0
  20. package/dist/vite/index.d.ts +25 -0
  21. package/dist/vite/index.js +2 -0
  22. package/env.d.ts +83 -0
  23. package/package.json +84 -64
  24. package/skills/apply-comments/SKILL.md +43 -56
  25. package/skills/create-doc/SKILL.md +106 -0
  26. package/skills/create-theme/SKILL.md +184 -0
  27. package/skills/current-doc/SKILL.md +120 -0
  28. package/skills/doc-authoring/SKILL.md +434 -0
  29. package/skills/doc-authoring/references/assets.md +47 -0
  30. package/skills/doc-authoring/references/design-system.md +81 -0
  31. package/skills/doc-authoring/references/long-form.md +131 -0
  32. package/skills/doc-authoring/references/pagination.md +118 -0
  33. package/skills/doc-authoring/references/tables-and-charts.md +161 -0
  34. package/src/app/app.tsx +42 -0
  35. package/src/app/components/data-table.tsx +196 -0
  36. package/src/app/components/design-panel/design-panel.tsx +318 -0
  37. package/src/app/components/design-panel/design-provider.tsx +121 -0
  38. package/src/app/components/design-panel/use-design.ts +85 -0
  39. package/src/app/components/diagram.tsx +76 -0
  40. package/src/app/components/doc-assets.tsx +129 -0
  41. package/src/app/components/doc-search.tsx +248 -0
  42. package/src/app/components/doc-sidebar.tsx +162 -0
  43. package/src/app/components/flow-page.tsx +93 -0
  44. package/src/app/components/footnote.tsx +204 -0
  45. package/src/app/components/image-placeholder.tsx +50 -0
  46. package/src/app/components/inspector/inspector.tsx +518 -0
  47. package/src/app/components/numbering.tsx +224 -0
  48. package/src/app/components/page-frame.tsx +70 -0
  49. package/src/app/components/sidebar/folder-item.tsx +212 -0
  50. package/src/app/components/sidebar/icon-picker.tsx +99 -0
  51. package/src/app/components/sidebar/sidebar.tsx +252 -0
  52. package/src/app/components/table-of-contents.tsx +93 -0
  53. package/src/app/components/theme-toggle.tsx +50 -0
  54. package/src/app/components/themes/markdown.tsx +249 -0
  55. package/src/app/components/themes/theme-preview.tsx +74 -0
  56. package/src/app/components/ui/menu.tsx +143 -0
  57. package/src/app/index.html +12 -0
  58. package/src/app/lib/agent-bridge.ts +140 -0
  59. package/src/app/lib/assets.ts +151 -0
  60. package/src/app/lib/design-presets.ts +109 -0
  61. package/src/app/lib/design.ts +88 -0
  62. package/src/app/lib/diagnostics.ts +282 -0
  63. package/src/app/lib/doc-preview.tsx +29 -0
  64. package/src/app/lib/docs.ts +26 -0
  65. package/src/app/lib/docx/extract.ts +1623 -0
  66. package/src/app/lib/docx/fonts.test.ts +136 -0
  67. package/src/app/lib/docx/fonts.ts +166 -0
  68. package/src/app/lib/docx/media.ts +102 -0
  69. package/src/app/lib/docx/model.ts +206 -0
  70. package/src/app/lib/docx/paragraph.test.ts +92 -0
  71. package/src/app/lib/docx/paragraph.ts +107 -0
  72. package/src/app/lib/docx/props.ts +187 -0
  73. package/src/app/lib/docx/styles.ts +306 -0
  74. package/src/app/lib/docx/units.ts +35 -0
  75. package/src/app/lib/docx/write.test.ts +507 -0
  76. package/src/app/lib/docx/write.ts +581 -0
  77. package/src/app/lib/docx/xml.ts +39 -0
  78. package/src/app/lib/export-docx.ts +289 -0
  79. package/src/app/lib/export-dom.ts +318 -0
  80. package/src/app/lib/export-html.ts +156 -0
  81. package/src/app/lib/export-image.ts +70 -0
  82. package/src/app/lib/export-pdf.ts +165 -0
  83. package/src/app/lib/flow-measure.test.ts +31 -0
  84. package/src/app/lib/flow-measure.ts +183 -0
  85. package/src/app/lib/flow.test.ts +110 -0
  86. package/src/app/lib/flow.ts +136 -0
  87. package/src/app/lib/folders.ts +192 -0
  88. package/src/app/lib/footnotes.test.tsx +102 -0
  89. package/src/app/lib/footnotes.ts +94 -0
  90. package/src/app/lib/inspector/fiber.ts +99 -0
  91. package/src/app/lib/labels.test.ts +18 -0
  92. package/src/app/lib/labels.ts +181 -0
  93. package/src/app/lib/outline.ts +118 -0
  94. package/src/app/lib/page-context.tsx +43 -0
  95. package/src/app/lib/page-range.test.ts +95 -0
  96. package/src/app/lib/page-range.ts +90 -0
  97. package/src/app/lib/print-ready.ts +69 -0
  98. package/src/app/lib/rasterize.ts +173 -0
  99. package/src/app/lib/scan.ts +26 -0
  100. package/src/app/lib/sdk.test.ts +32 -0
  101. package/src/app/lib/sdk.ts +115 -0
  102. package/src/app/lib/themes.ts +31 -0
  103. package/src/app/lib/use-doc-module.ts +53 -0
  104. package/src/app/lib/use-doc-pages.ts +147 -0
  105. package/src/app/lib/utils.ts +6 -0
  106. package/src/app/lib/view-mode.test.ts +91 -0
  107. package/src/app/lib/view-mode.ts +104 -0
  108. package/src/app/main.tsx +14 -0
  109. package/src/app/routes/assets.tsx +257 -0
  110. package/src/app/routes/doc.tsx +877 -0
  111. package/src/app/routes/home-shell.tsx +203 -0
  112. package/src/app/routes/home.tsx +269 -0
  113. package/src/app/routes/themes.tsx +121 -0
  114. package/src/app/styles.css +97 -0
  115. package/src/app/virtual.d.ts +30 -0
  116. package/template/AGENTS.md +27 -0
  117. package/template/README.md +39 -0
  118. package/template/docs/getting-started/index.tsx +230 -0
  119. package/template/mosage.config.ts +5 -0
  120. package/template/package.json +24 -0
  121. package/template/tsconfig.json +17 -0
  122. package/README.en.md +0 -57
  123. package/dist/cli.js +0 -4545
  124. package/dist/web/assets/index-Czg2WeHe.js +0 -182
  125. package/dist/web/assets/index-DKAyt92W.css +0 -1
  126. package/dist/web/index.html +0 -15
  127. package/skills/current-position/SKILL.md +0 -65
  128. package/skills/kickoff/SKILL.md +0 -105
  129. package/skills/mosage-reference/SKILL.md +0 -164
  130. package/skills/outline/SKILL.md +0 -79
  131. package/skills/review/SKILL.md +0 -64
  132. package/skills/write-chapter/SKILL.md +0 -58
  133. package/template/book/STYLE.md +0 -6
  134. package/template/book/assets/.gitkeep +0 -0
  135. package/template/book/book.yaml +0 -42
  136. package/template/book/brief.md +0 -6
  137. package/template/book/chapters/.gitkeep +0 -0
  138. package/template/book/notes/README.md +0 -7
  139. package/template/project/AGENTS.md +0 -82
  140. package/template/project/CLAUDE.md +0 -1
  141. package/template/project/README.md +0 -48
  142. package/template/project/books/.gitkeep +0 -0
  143. package/template/project/gitignore +0 -5
  144. package/template/project/mosage.yaml +0 -23
  145. package/template/project/notes/README.md +0 -8
  146. package/template/project/package.json +0 -14
package/dist/index.js ADDED
@@ -0,0 +1,693 @@
1
+ import { a as defaultDesign, i as cssVarsToString, n as DiagramSyntaxError, o as designToCssVars, r as parseDelimited, t as compileDiagram } from "./diagram-xlVDekYk.js";
2
+ import { a as PAGE_SIZE_NAMES, c as resolvePageGeometry, i as PAGE_SIZES, n as DEFAULT_PAGE_SIZE, o as isOrientation, r as ORIENTATIONS, s as isPageSizeName, t as DEFAULT_ORIENTATION } from "./sdk-DjpX6mCv.js";
3
+ import { Children, Fragment, createContext, isValidElement, useContext, useEffect, useId, useRef, useSyncExternalStore } from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/app/lib/outline.ts
6
+ /** A contents list; headings inside one are never part of the outline. */
7
+ const TOC_ATTR = "data-od-toc";
8
+ const GLOBAL_KEY$3 = "__mosage_outline_store__";
9
+ const g$3 = globalThis;
10
+ if (!g$3[GLOBAL_KEY$3]) g$3[GLOBAL_KEY$3] = {
11
+ snapshot: [],
12
+ listeners: /* @__PURE__ */ new Set()
13
+ };
14
+ const store$1 = g$3[GLOBAL_KEY$3];
15
+ function getOutline() {
16
+ return store$1.snapshot;
17
+ }
18
+ function subscribe$1(listener) {
19
+ store$1.listeners.add(listener);
20
+ return () => store$1.listeners.delete(listener);
21
+ }
22
+ /**
23
+ * The document's headings, filled in after the pages render. Empty on the first
24
+ * pass — a `<TableOfContents>` renders its rows on the second, once the scan
25
+ * has run.
26
+ */
27
+ function useDocOutline() {
28
+ return useSyncExternalStore(subscribe$1, getOutline, getOutline);
29
+ }
30
+ //#endregion
31
+ //#region src/app/lib/labels.ts
32
+ const LABEL_ATTR = "data-od-label";
33
+ const LABEL_ID_ATTR = "data-od-label-id";
34
+ const LABEL_TEXT_ATTR = "data-od-label-text";
35
+ const EMPTY = {
36
+ entries: [],
37
+ vocabulary: {
38
+ figure: "Figure",
39
+ table: "Table",
40
+ footnotes: "",
41
+ onPage: " (p. {page})"
42
+ }
43
+ };
44
+ const GLOBAL_KEY$2 = "__mosage_labels_store__";
45
+ const g$2 = globalThis;
46
+ if (!g$2[GLOBAL_KEY$2]) g$2[GLOBAL_KEY$2] = {
47
+ snapshot: EMPTY,
48
+ listeners: /* @__PURE__ */ new Set()
49
+ };
50
+ const store = g$2[GLOBAL_KEY$2];
51
+ function getLabels() {
52
+ return store.snapshot;
53
+ }
54
+ function subscribe(listener) {
55
+ store.listeners.add(listener);
56
+ return () => store.listeners.delete(listener);
57
+ }
58
+ function useLabelSnapshot() {
59
+ return useSyncExternalStore(subscribe, getLabels, getLabels);
60
+ }
61
+ /** The document's numbered items of one kind, in order. Empty on the first render pass. */
62
+ function useDocLabels(kind) {
63
+ return useLabelSnapshot().entries.filter((entry) => entry.kind === kind);
64
+ }
65
+ /** One numbered item by id, once the scan has run. */
66
+ function useDocLabel(id) {
67
+ return useLabelSnapshot().entries.find((entry) => entry.id === id) ?? null;
68
+ }
69
+ function useLabelVocabulary() {
70
+ return useLabelSnapshot().vocabulary;
71
+ }
72
+ /**
73
+ * The words either side of the page number in a cross-reference's page suffix,
74
+ * or null when the suffix names no page — it is then printed as written.
75
+ */
76
+ function onPageParts(vocabulary) {
77
+ const at = vocabulary.onPage.indexOf("{page}");
78
+ if (at < 0) return null;
79
+ return [vocabulary.onPage.slice(0, at), vocabulary.onPage.slice(at + 6)];
80
+ }
81
+ //#endregion
82
+ //#region src/app/components/footnote.tsx
83
+ /** Stands in for a number the scan has not produced yet, or never will. */
84
+ const UNNUMBERED = "•";
85
+ const GLOBAL_KEY$1 = "__mosage_footnote_context__";
86
+ const g$1 = globalThis;
87
+ if (!g$1[GLOBAL_KEY$1]) g$1[GLOBAL_KEY$1] = createContext(null);
88
+ const FootnoteContext = g$1[GLOBAL_KEY$1];
89
+ const FOOTNOTE_MARKER_FLAG = "__odFootnote";
90
+ function markerStyle() {
91
+ return {
92
+ fontSize: "0.7em",
93
+ lineHeight: 0,
94
+ verticalAlign: "super",
95
+ fontVariantNumeric: "tabular-nums",
96
+ color: "var(--od-accent)",
97
+ padding: "0 1px"
98
+ };
99
+ }
100
+ /**
101
+ * The superscript in the text. Its position in the document decides the number.
102
+ *
103
+ * The placeholder is the one the printed note uses, and deliberately not an
104
+ * empty string: the two describe the same unresolved state, a note whose number
105
+ * never arrives has to be visible in both places rather than silently blank,
106
+ * and the packer measures this glyph — an empty marker is narrower than the
107
+ * digit that replaces it.
108
+ */
109
+ function FootnoteMarker({ id }) {
110
+ const entry = useDocLabel(id);
111
+ return /* @__PURE__ */ jsx("sup", {
112
+ [LABEL_ATTR]: "footnote",
113
+ [LABEL_ID_ATTR]: id,
114
+ style: markerStyle(),
115
+ children: entry?.number ?? UNNUMBERED
116
+ });
117
+ }
118
+ /**
119
+ * A note anchored to this point in the text. Inside a `flow()` section the
120
+ * framework lifts it to the foot of whatever page the marker lands on; on a
121
+ * fixed page, put a `<Footnotes />` where you want them printed.
122
+ */
123
+ function Footnote({ id, children }) {
124
+ const generated = useId();
125
+ const noteId = id ?? generated;
126
+ const collector = useContext(FootnoteContext);
127
+ const latest = useRef(null);
128
+ useEffect(() => {
129
+ latest.current = collector;
130
+ collector?.register(noteId, children);
131
+ });
132
+ useEffect(() => () => latest.current?.unregister(noteId), [noteId]);
133
+ return /* @__PURE__ */ jsx(FootnoteMarker, { id: noteId });
134
+ }
135
+ Footnote[FOOTNOTE_MARKER_FLAG] = true;
136
+ const FOOTNOTE_ROW_ATTR = "data-od-footnote-row";
137
+ const FOOTNOTE_BODY_ATTR = "data-od-footnote-body";
138
+ const FOOTNOTES_ATTR = "data-od-footnotes";
139
+ function footnoteAreaStyle() {
140
+ return {
141
+ borderTop: "1px solid var(--od-rule)",
142
+ paddingTop: 6,
143
+ marginTop: 12,
144
+ fontSize: "var(--od-size-caption)",
145
+ lineHeight: 1.45,
146
+ color: "var(--od-muted)"
147
+ };
148
+ }
149
+ /** One note's row. Shared with the measurement pass so the reserved space is real. */
150
+ function FootnoteRow({ id, content }) {
151
+ const entry = useDocLabel(id);
152
+ return /* @__PURE__ */ jsxs("div", {
153
+ [FOOTNOTE_ROW_ATTR]: id,
154
+ style: {
155
+ display: "flex",
156
+ gap: 5,
157
+ marginBottom: 3
158
+ },
159
+ children: [/* @__PURE__ */ jsx("span", {
160
+ style: {
161
+ flex: "none",
162
+ fontVariantNumeric: "tabular-nums",
163
+ color: "var(--od-accent)"
164
+ },
165
+ children: entry?.number ?? UNNUMBERED
166
+ }), /* @__PURE__ */ jsx("span", {
167
+ [FOOTNOTE_BODY_ATTR]: "",
168
+ style: { minWidth: 0 },
169
+ children: content
170
+ })]
171
+ });
172
+ }
173
+ function Footnotes({ notes, style, className }) {
174
+ const collector = useContext(FootnoteContext);
175
+ const vocabulary = useLabelVocabulary();
176
+ const list = notes ?? collector?.ids.map((id) => ({
177
+ id,
178
+ content: collector.contents.get(id)
179
+ })) ?? [];
180
+ if (list.length === 0) return null;
181
+ return /* @__PURE__ */ jsxs("div", {
182
+ [FOOTNOTES_ATTR]: "",
183
+ className,
184
+ style: {
185
+ ...footnoteAreaStyle(),
186
+ ...style
187
+ },
188
+ children: [vocabulary.footnotes ? /* @__PURE__ */ jsx("div", {
189
+ style: {
190
+ fontWeight: 600,
191
+ marginBottom: 3
192
+ },
193
+ children: vocabulary.footnotes
194
+ }) : null, list.map((note) => /* @__PURE__ */ jsx(FootnoteRow, {
195
+ id: note.id,
196
+ content: note.content
197
+ }, note.id))]
198
+ });
199
+ }
200
+ //#endregion
201
+ //#region src/app/lib/page-context.tsx
202
+ const GLOBAL_KEY = "__mosage_page_context__";
203
+ const g = globalThis;
204
+ if (!g[GLOBAL_KEY]) g[GLOBAL_KEY] = createContext(null);
205
+ const PageContext = g[GLOBAL_KEY];
206
+ /** 1-based page number of the page currently rendering. `0` outside a page. */
207
+ function useDocPageNumber() {
208
+ const ctx = useContext(PageContext);
209
+ return ctx ? ctx.index + 1 : 0;
210
+ }
211
+ /** Total page count of the document being rendered. `0` outside a page. */
212
+ function useDocPageCount() {
213
+ const ctx = useContext(PageContext);
214
+ return ctx ? ctx.total : 0;
215
+ }
216
+ //#endregion
217
+ //#region src/app/components/numbering.tsx
218
+ const REF_ATTR = "data-od-ref";
219
+ const LIST_OF_ATTR = "data-od-list-of";
220
+ function nameFor(kind, vocabulary) {
221
+ return kind === "table" ? vocabulary.table : vocabulary.figure;
222
+ }
223
+ /**
224
+ * A numbered figure or table. The number comes from a scan of the rendered
225
+ * pages, not from a counter in the source, so inserting a figure halfway
226
+ * through a document renumbers everything after it — including the references
227
+ * to it — without anyone editing a number.
228
+ *
229
+ * Caption and content are one block, so a `flow()` section never separates them.
230
+ */
231
+ function Figure({ id, caption, kind = "figure", captionPosition, captionText, style, captionStyle, className, children }) {
232
+ const generated = useId();
233
+ const labelId = id ?? generated;
234
+ const entry = useDocLabel(labelId);
235
+ const vocabulary = useLabelVocabulary();
236
+ const position = captionPosition ?? (kind === "table" ? "above" : "below");
237
+ const text = captionText ?? (typeof caption === "string" || typeof caption === "number" ? String(caption) : "");
238
+ const captionNode = caption ? /* @__PURE__ */ jsxs("figcaption", {
239
+ style: {
240
+ fontSize: "var(--od-size-caption)",
241
+ color: "var(--od-muted)",
242
+ lineHeight: 1.4,
243
+ margin: position === "above" ? "0 0 6px" : "6px 0 0",
244
+ ...captionStyle
245
+ },
246
+ children: [/* @__PURE__ */ jsxs("span", {
247
+ style: {
248
+ fontWeight: 600,
249
+ color: "var(--od-text)"
250
+ },
251
+ children: [
252
+ nameFor(kind, vocabulary),
253
+ " ",
254
+ entry?.number ?? ""
255
+ ]
256
+ }), caption ? /* @__PURE__ */ jsxs("span", { children: [" — ", caption] }) : null]
257
+ }) : null;
258
+ return /* @__PURE__ */ jsxs("figure", {
259
+ [LABEL_ATTR]: kind,
260
+ [LABEL_ID_ATTR]: labelId,
261
+ [LABEL_TEXT_ATTR]: text,
262
+ className,
263
+ style: {
264
+ margin: "0 0 16px",
265
+ ...style
266
+ },
267
+ children: [
268
+ position === "above" && captionNode,
269
+ children,
270
+ position === "below" && captionNode
271
+ ]
272
+ });
273
+ }
274
+ /**
275
+ * A cross-reference that resolves after layout: "Figure 3", plus the page when
276
+ * the target is elsewhere. Never write "see Figure 3 on page 12" by hand — both
277
+ * numbers move.
278
+ */
279
+ function Ref({ to, showPage = "auto", style, className }) {
280
+ const entry = useDocLabel(to);
281
+ const vocabulary = useLabelVocabulary();
282
+ const here = useDocPageNumber();
283
+ if (!entry) return /* @__PURE__ */ jsxs("span", {
284
+ "data-od-ref-unresolved": to,
285
+ className,
286
+ style: {
287
+ color: "var(--od-accent)",
288
+ ...style
289
+ },
290
+ children: [
291
+ "[?",
292
+ to,
293
+ "]"
294
+ ]
295
+ });
296
+ const name = entry.kind === "footnote" ? "" : `${nameFor(entry.kind, vocabulary)} `;
297
+ const withPage = showPage === "auto" ? entry.page !== here : showPage;
298
+ const parts = onPageParts(vocabulary);
299
+ return /* @__PURE__ */ jsxs("span", {
300
+ [REF_ATTR]: to,
301
+ className,
302
+ style,
303
+ children: [
304
+ name,
305
+ entry.number,
306
+ withPage && parts && /* @__PURE__ */ jsxs(Fragment$1, { children: [
307
+ parts[0],
308
+ /* @__PURE__ */ jsx("span", {
309
+ ["data-od-ref-page"]: "",
310
+ children: entry.page
311
+ }),
312
+ parts[1]
313
+ ] }),
314
+ withPage && !parts && vocabulary.onPage
315
+ ]
316
+ });
317
+ }
318
+ /**
319
+ * The document's figures or tables as a contents list. Same lifecycle as
320
+ * `<TableOfContents>`: empty on the first render pass, filled once the scan has
321
+ * run — including before an export serializes.
322
+ */
323
+ function ListOf({ kind = "figure", showPageNumbers = true, style, className }) {
324
+ const entries = useDocLabels(kind);
325
+ const vocabulary = useLabelVocabulary();
326
+ return /* @__PURE__ */ jsx("div", {
327
+ [LIST_OF_ATTR]: kind,
328
+ className,
329
+ style: {
330
+ fontFamily: "var(--od-font-body)",
331
+ fontSize: "var(--od-size-body)",
332
+ color: "var(--od-text)",
333
+ ...style
334
+ },
335
+ children: entries.map((entry) => /* @__PURE__ */ jsxs("div", {
336
+ [REF_ATTR]: entry.id,
337
+ style: {
338
+ display: "flex",
339
+ alignItems: "baseline",
340
+ gap: 8,
341
+ marginBottom: 6
342
+ },
343
+ children: [
344
+ /* @__PURE__ */ jsxs("span", {
345
+ style: {
346
+ flex: "none",
347
+ color: "var(--od-muted)"
348
+ },
349
+ children: [
350
+ nameFor(kind, vocabulary),
351
+ " ",
352
+ entry.number
353
+ ]
354
+ }),
355
+ /* @__PURE__ */ jsx("span", { children: entry.text }),
356
+ showPageNumbers && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
357
+ "aria-hidden": true,
358
+ style: {
359
+ flex: 1,
360
+ borderBottom: "1px dotted var(--od-rule)",
361
+ transform: "translateY(-3px)"
362
+ }
363
+ }), /* @__PURE__ */ jsx("span", {
364
+ ["data-od-ref-page"]: "",
365
+ style: { fontVariantNumeric: "tabular-nums" },
366
+ children: entry.page
367
+ })] })
368
+ ]
369
+ }, entry.id))
370
+ });
371
+ }
372
+ function ListOfFigures(props) {
373
+ return /* @__PURE__ */ jsx(ListOf, {
374
+ ...props,
375
+ kind: "figure"
376
+ });
377
+ }
378
+ function ListOfTables(props) {
379
+ return /* @__PURE__ */ jsx(ListOf, {
380
+ ...props,
381
+ kind: "table"
382
+ });
383
+ }
384
+ //#endregion
385
+ //#region src/app/components/data-table.tsx
386
+ function normalizeColumn(column) {
387
+ return typeof column === "string" ? { key: column } : column;
388
+ }
389
+ function isNumber(value) {
390
+ return typeof value === "number" && Number.isFinite(value);
391
+ }
392
+ /** A column of numbers aligns right; anything mixed stays left. */
393
+ function inferAlign(rows, key) {
394
+ let seen = 0;
395
+ for (const row of rows) {
396
+ const value = row[key];
397
+ if (value === null || value === void 0 || value === "") continue;
398
+ if (!isNumber(value)) return "left";
399
+ seen++;
400
+ }
401
+ return seen > 0 ? "right" : "left";
402
+ }
403
+ function formatValue(value, format, row, emptyValue) {
404
+ if (typeof format === "function") return format(value, row);
405
+ if (value === null || value === void 0 || value === "") return emptyValue;
406
+ if (isNumber(value)) {
407
+ if (format === "integer") return Math.round(value).toLocaleString();
408
+ if (format === "percent") return `${(value * 100).toLocaleString(void 0, { maximumFractionDigits: 1 })}%`;
409
+ if (format === "text") return String(value);
410
+ return value.toLocaleString(void 0, { maximumFractionDigits: 2 });
411
+ }
412
+ return String(value);
413
+ }
414
+ /**
415
+ * A print-shaped table from data, so the numbers in a report come from a file
416
+ * instead of being retyped into JSX. Alignment and grouping follow the column's
417
+ * contents, which is the part people get wrong by hand.
418
+ */
419
+ function DataTable({ rows, columns, caption, id, limit, compact = false, emptyValue = "—", style, className }) {
420
+ const resolved = (columns ?? Object.keys(rows[0] ?? {})).map(normalizeColumn);
421
+ const shown = limit !== void 0 ? rows.slice(0, limit) : rows;
422
+ const hidden = rows.length - shown.length;
423
+ const cellPadding = compact ? "4px 8px" : "7px 8px";
424
+ const table = /* @__PURE__ */ jsxs("table", {
425
+ className,
426
+ style: {
427
+ width: "100%",
428
+ borderCollapse: "collapse",
429
+ tableLayout: "fixed",
430
+ margin: 0,
431
+ ...style
432
+ },
433
+ children: [
434
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: resolved.map((column) => {
435
+ const align = column.align ?? inferAlign(rows, column.key);
436
+ return /* @__PURE__ */ jsx("th", {
437
+ style: {
438
+ textAlign: align,
439
+ width: column.width,
440
+ fontFamily: "var(--od-font-heading)",
441
+ fontSize: 11,
442
+ fontWeight: 600,
443
+ letterSpacing: "0.04em",
444
+ textTransform: "uppercase",
445
+ color: "var(--od-muted)",
446
+ borderBottom: "1px solid var(--od-rule)",
447
+ padding: "0 8px 6px"
448
+ },
449
+ children: column.label ?? column.key
450
+ }, column.key);
451
+ }) }) }),
452
+ /* @__PURE__ */ jsx("tbody", { children: shown.map((row, rowIndex) => /* @__PURE__ */ jsx("tr", { children: resolved.map((column) => {
453
+ const align = column.align ?? inferAlign(rows, column.key);
454
+ return /* @__PURE__ */ jsx("td", {
455
+ style: {
456
+ textAlign: align,
457
+ fontSize: compact ? 11 : 12,
458
+ padding: cellPadding,
459
+ borderBottom: "1px solid var(--od-rule)",
460
+ fontVariantNumeric: align === "right" ? "tabular-nums" : void 0,
461
+ overflowWrap: "anywhere"
462
+ },
463
+ children: formatValue(row[column.key], column.format, row, emptyValue)
464
+ }, column.key);
465
+ }) }, rowIndex)) }),
466
+ hidden > 0 && /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsxs("td", {
467
+ colSpan: resolved.length,
468
+ style: {
469
+ fontSize: "var(--od-size-caption)",
470
+ color: "var(--od-muted)",
471
+ padding: "6px 8px 0"
472
+ },
473
+ children: [
474
+ hidden,
475
+ " more row",
476
+ hidden === 1 ? "" : "s",
477
+ " not shown"
478
+ ]
479
+ }) }) })
480
+ ]
481
+ });
482
+ if (!caption) return table;
483
+ return /* @__PURE__ */ jsx(Figure, {
484
+ kind: "table",
485
+ caption,
486
+ ...id !== void 0 ? { id } : {},
487
+ children: table
488
+ });
489
+ }
490
+ //#endregion
491
+ //#region src/app/components/diagram.tsx
492
+ /**
493
+ * A diagram compiled from Mermaid-flavoured text at build time. The SVG is
494
+ * inlined rather than referenced through `<img>` so it inherits the document's
495
+ * theme variables — an `<img src="data:…">` would be painted in a document of
496
+ * its own, with none of this one's colours or faces.
497
+ */
498
+ function Diagram({ chart, caption, captionText, kind = "figure", id, width, align = "center", style, className }) {
499
+ const drawing = /* @__PURE__ */ jsx("div", {
500
+ className: caption ? void 0 : className,
501
+ style: {
502
+ width: width ?? chart.width,
503
+ maxWidth: "100%",
504
+ marginLeft: align === "center" ? "auto" : void 0,
505
+ marginRight: align === "center" ? "auto" : void 0,
506
+ ...caption ? void 0 : style
507
+ },
508
+ dangerouslySetInnerHTML: { __html: chart.svg }
509
+ });
510
+ if (!caption) return drawing;
511
+ return /* @__PURE__ */ jsx(Figure, {
512
+ ...id ? { id } : {},
513
+ caption,
514
+ ...captionText ? { captionText } : {},
515
+ kind,
516
+ className,
517
+ ...style ? { style } : {},
518
+ children: drawing
519
+ });
520
+ }
521
+ //#endregion
522
+ //#region src/app/components/image-placeholder.tsx
523
+ /**
524
+ * Stands in for an image the user has to supply (a chart from their data, a
525
+ * product screenshot). Use it instead of inventing filler — never for
526
+ * decoration.
527
+ */
528
+ function ImagePlaceholder({ hint, width = "100%", height = 200, style, className }) {
529
+ return /* @__PURE__ */ jsx("div", {
530
+ "data-od-image-placeholder": "",
531
+ className,
532
+ style: {
533
+ width,
534
+ height,
535
+ display: "flex",
536
+ alignItems: "center",
537
+ justifyContent: "center",
538
+ textAlign: "center",
539
+ padding: 16,
540
+ boxSizing: "border-box",
541
+ border: "1px dashed var(--od-rule, #d4d4d8)",
542
+ borderRadius: "var(--od-radius, 6px)",
543
+ background: "repeating-linear-gradient(45deg, rgba(0,0,0,.02) 0 8px, transparent 8px 16px)",
544
+ color: "var(--od-muted, #6b7280)",
545
+ fontFamily: "var(--od-font-body, system-ui, sans-serif)",
546
+ fontSize: "var(--od-size-caption, 10px)",
547
+ lineHeight: 1.4,
548
+ ...style
549
+ },
550
+ children: hint
551
+ });
552
+ }
553
+ //#endregion
554
+ //#region src/app/components/table-of-contents.tsx
555
+ const TOC_ENTRY_ATTR = "data-od-toc-entry";
556
+ /** Read by the Word export, which rebuilds the list as a contents field. */
557
+ const TOC_DEPTH_ATTR = "data-od-toc-depth";
558
+ const TOC_LEVEL_ATTR = "data-od-toc-level";
559
+ /**
560
+ * Renders the document's headings as a contents list. The outline is filled in
561
+ * by a DOM scan after the pages render, so this component is empty on the very
562
+ * first pass and populated on the next — including during PDF/HTML export,
563
+ * which scans before serializing.
564
+ */
565
+ function TableOfContents({ maxLevel = 2, showPageNumbers = true, includeOwnPage = false, indent = 20, style, className }) {
566
+ const outline = useDocOutline();
567
+ const ownPage = useDocPageNumber();
568
+ const entries = outline.filter((entry) => entry.level <= maxLevel && (includeOwnPage || entry.page !== ownPage));
569
+ return /* @__PURE__ */ jsx("div", {
570
+ [TOC_ATTR]: "",
571
+ [TOC_DEPTH_ATTR]: maxLevel,
572
+ className,
573
+ style: {
574
+ fontFamily: "var(--od-font-body)",
575
+ fontSize: "var(--od-size-body)",
576
+ color: "var(--od-text)",
577
+ ...style
578
+ },
579
+ children: entries.map((entry) => /* @__PURE__ */ jsxs("div", {
580
+ [TOC_ENTRY_ATTR]: entry.id,
581
+ [TOC_LEVEL_ATTR]: entry.level,
582
+ style: {
583
+ display: "flex",
584
+ alignItems: "baseline",
585
+ gap: 8,
586
+ paddingLeft: (entry.level - 1) * indent,
587
+ marginBottom: entry.level === 1 ? 10 : 6,
588
+ fontWeight: entry.level === 1 ? 600 : 400,
589
+ color: entry.level === 1 ? "var(--od-text)" : "var(--od-muted)"
590
+ },
591
+ children: [/* @__PURE__ */ jsx("span", { children: entry.text }), showPageNumbers && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
592
+ "aria-hidden": true,
593
+ style: {
594
+ flex: 1,
595
+ borderBottom: "1px dotted var(--od-rule)",
596
+ transform: "translateY(-3px)"
597
+ }
598
+ }), /* @__PURE__ */ jsx("span", {
599
+ ["data-od-toc-page"]: "",
600
+ style: { fontVariantNumeric: "tabular-nums" },
601
+ children: entry.page
602
+ })] })]
603
+ }, entry.id))
604
+ });
605
+ }
606
+ //#endregion
607
+ //#region src/app/lib/flow.ts
608
+ /**
609
+ * Flattens fragments so `flow(<>…</>)` yields one block per element the author
610
+ * wrote, not a single block containing the whole fragment.
611
+ */
612
+ function toBlocks(node) {
613
+ const out = [];
614
+ for (const child of Children.toArray(node)) {
615
+ if (!isValidElement(child)) continue;
616
+ if (child.type === Fragment) {
617
+ out.push(...toBlocks(child.props.children));
618
+ continue;
619
+ }
620
+ out.push(child);
621
+ }
622
+ return out;
623
+ }
624
+ /**
625
+ * Wraps continuous content so the framework paginates it, instead of the author
626
+ * hand-splitting sections into fixed pages. Each direct child is one atomic
627
+ * block — a heading, a paragraph, a table — that never straddles a page break.
628
+ */
629
+ function flow(children, opts = {}) {
630
+ return {
631
+ __odFlow: true,
632
+ blocks: toBlocks(children),
633
+ ...opts
634
+ };
635
+ }
636
+ function isFlowSection(entry) {
637
+ return typeof entry === "object" && entry !== null && entry.__odFlow === true;
638
+ }
639
+ /**
640
+ * Greedy top-to-bottom packing: fill a page until the next block would cross
641
+ * the bottom edge, then push that block — plus anything glued to it — onto the
642
+ * next page. Blocks are atomic; nothing is split mid-block.
643
+ *
644
+ * Footnotes are packed from the same budget: a block that carries notes brings
645
+ * their height with it, because those notes print at the foot of whichever page
646
+ * the block lands on.
647
+ */
648
+ function paginateBlocks(blocks, available, opts = {}) {
649
+ const overhead = opts.footnoteOverhead ?? 0;
650
+ const pages = [];
651
+ const overflowing = [];
652
+ let current = [];
653
+ let used = 0;
654
+ let notes = 0;
655
+ const heightOf = (indices) => indices.reduce((sum, index) => sum + (blocks[index]?.height ?? 0), 0);
656
+ const notesOf = (indices) => indices.reduce((sum, index) => sum + (blocks[index]?.footnoteHeight ?? 0), 0);
657
+ /** A page with no notes pays nothing; the first note also pays for the rule. */
658
+ const reserve = (total) => total > 0 ? total + overhead : 0;
659
+ const closePage = () => {
660
+ if (current.length === 0) return;
661
+ pages.push(current);
662
+ current = [];
663
+ used = 0;
664
+ notes = 0;
665
+ };
666
+ blocks.forEach((block, index) => {
667
+ const own = block.footnoteHeight ?? 0;
668
+ if (block.height + reserve(own) > available) overflowing.push(index);
669
+ if (block.breakBefore) closePage();
670
+ if (current.length > 0 && used + block.height + reserve(notes + own) > available) {
671
+ let splitAt = current.length;
672
+ while (splitAt > 0 && blocks[current[splitAt - 1]]?.keepWithNext) splitAt--;
673
+ if (block.keepWithPrevious && splitAt === current.length) splitAt--;
674
+ if (splitAt > 0 && splitAt < current.length) {
675
+ const moved = current.slice(splitAt);
676
+ pages.push(current.slice(0, splitAt));
677
+ current = moved;
678
+ used = heightOf(moved);
679
+ notes = notesOf(moved);
680
+ } else closePage();
681
+ }
682
+ current.push(index);
683
+ used += block.height;
684
+ notes += own;
685
+ });
686
+ closePage();
687
+ return {
688
+ pages,
689
+ overflowing
690
+ };
691
+ }
692
+ //#endregion
693
+ export { DEFAULT_ORIENTATION, DEFAULT_PAGE_SIZE, DataTable, Diagram, DiagramSyntaxError, Figure, Footnote, Footnotes, ImagePlaceholder, ListOf, ListOfFigures, ListOfTables, ORIENTATIONS, PAGE_SIZES, PAGE_SIZE_NAMES, Ref, TableOfContents, compileDiagram, cssVarsToString, defaultDesign, designToCssVars, flow, isFlowSection, isOrientation, isPageSizeName, paginateBlocks, parseDelimited, resolvePageGeometry, useDocLabel, useDocLabels, useDocOutline, useDocPageCount, useDocPageNumber };