@portone/docx-editor 0.6.0 → 0.6.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.
Files changed (146) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/DocxEditor.js +84 -26
  3. package/dist/core.d.ts +4 -3
  4. package/dist/docx/exportDocx.d.ts +3 -2
  5. package/dist/docx/exportDocx.js +6 -10
  6. package/dist/docx/headersFooters.js +35 -23
  7. package/dist/docx/identities.d.ts +30 -0
  8. package/dist/docx/identities.js +43 -10
  9. package/dist/docx/importDocx.js +25 -26
  10. package/dist/docx/importParagraph.d.ts +1 -4
  11. package/dist/docx/importParagraph.js +4 -18
  12. package/dist/docx/invariants.d.ts +13 -10
  13. package/dist/docx/invariants.js +300 -116
  14. package/dist/docx/newLists.js +12 -3
  15. package/dist/docx/notes/newNote.d.ts +39 -0
  16. package/dist/docx/notes/newNote.js +122 -0
  17. package/dist/docx/notes/numbering.d.ts +27 -0
  18. package/dist/docx/notes/numbering.js +113 -0
  19. package/dist/docx/notes/reading.d.ts +58 -0
  20. package/dist/docx/notes/reading.js +98 -0
  21. package/dist/docx/notes/references.d.ts +33 -0
  22. package/dist/docx/notes/references.js +37 -0
  23. package/dist/docx/notes/writing.d.ts +19 -0
  24. package/dist/docx/notes/writing.js +95 -0
  25. package/dist/docx/partPlan.d.ts +3 -0
  26. package/dist/docx/partPlanners.d.ts +13 -0
  27. package/dist/docx/partPlanners.js +48 -0
  28. package/dist/docx/sections.d.ts +5 -0
  29. package/dist/docx/sections.js +7 -2
  30. package/dist/docx/serializeParagraph.js +7 -2
  31. package/dist/docx/serializePreserved.d.ts +5 -1
  32. package/dist/docx/serializePreserved.js +24 -7
  33. package/dist/docx/session.d.ts +5 -0
  34. package/dist/docx/session.js +6 -0
  35. package/dist/docx/storyParts.d.ts +133 -0
  36. package/dist/docx/storyParts.js +314 -0
  37. package/dist/editor/clipboard/htmlReader.d.ts +24 -4
  38. package/dist/editor/clipboard/htmlReader.js +39 -4
  39. package/dist/editor/clipboard/internalChannel.d.ts +30 -3
  40. package/dist/editor/clipboard/internalChannel.js +43 -3
  41. package/dist/editor/clipboard/normalizers.d.ts +35 -8
  42. package/dist/editor/clipboard/normalizers.js +60 -11
  43. package/dist/editor/clipboard/plugin.js +42 -22
  44. package/dist/editor/commands/exportQueries.d.ts +4 -3
  45. package/dist/editor/commands/index.d.ts +2 -1
  46. package/dist/editor/commands/index.js +18 -0
  47. package/dist/editor/commands/noteCommands.d.ts +47 -0
  48. package/dist/editor/commands/noteCommands.js +95 -0
  49. package/dist/editor/commands/noteQueries.d.ts +22 -3
  50. package/dist/editor/commands/noteQueries.js +21 -13
  51. package/dist/editor/createEditor.js +21 -3
  52. package/dist/editor/documentStyles.d.ts +3 -0
  53. package/dist/editor/documentStyles.js +2 -1
  54. package/dist/editor/editorDocument.d.ts +24 -0
  55. package/dist/editor/editorDocument.js +34 -4
  56. package/dist/editor/notes/noteSurface.d.ts +53 -0
  57. package/dist/editor/notes/noteSurface.js +246 -0
  58. package/dist/editor/plugins/compositionSelection.d.ts +20 -0
  59. package/dist/editor/plugins/compositionSelection.js +19 -0
  60. package/dist/editor/plugins/keymap.d.ts +14 -0
  61. package/dist/editor/plugins/keymap.js +13 -1
  62. package/dist/editor/plugins/noteLifecycle.d.ts +18 -0
  63. package/dist/editor/plugins/noteLifecycle.js +117 -0
  64. package/dist/editor/plugins/noteNavigation.d.ts +45 -0
  65. package/dist/editor/plugins/noteNavigation.js +86 -0
  66. package/dist/editor/plugins/noteNumbering.d.ts +13 -0
  67. package/dist/editor/plugins/noteNumbering.js +58 -0
  68. package/dist/editor/plugins/rowResize.js +2 -7
  69. package/dist/editor/plugins/tabLayout.js +2 -7
  70. package/dist/editor/stories/storyMarkup.d.ts +24 -0
  71. package/dist/editor/stories/storyMarkup.js +35 -0
  72. package/dist/editor/stories/storyState.d.ts +34 -0
  73. package/dist/editor/stories/storyState.js +74 -0
  74. package/dist/editor/stories/storyView.d.ts +115 -0
  75. package/dist/editor/stories/storyView.js +144 -0
  76. package/dist/index.d.ts +2 -2
  77. package/dist/numbering/spellers.d.ts +1 -1
  78. package/dist/numbering/spellers.js +6 -1
  79. package/dist/ooxml/errors.d.ts +135 -2
  80. package/dist/ooxml/errors.js +6 -0
  81. package/dist/page/blockKinds.d.ts +23 -4
  82. package/dist/page/demands/footnoteDemands.d.ts +18 -0
  83. package/dist/page/demands/footnoteDemands.js +51 -0
  84. package/dist/page/demands/index.d.ts +43 -0
  85. package/dist/page/demands/index.js +8 -0
  86. package/dist/page/kinds/paragraphKind.js +7 -1
  87. package/dist/page/kinds/tableKind.js +9 -1
  88. package/dist/page/measureBlocks.d.ts +2 -0
  89. package/dist/page/measureBlocks.js +31 -10
  90. package/dist/page/pageDecorations.d.ts +9 -2
  91. package/dist/page/pageDecorations.js +18 -5
  92. package/dist/page/pageLayout.d.ts +77 -1
  93. package/dist/page/pageLayout.js +186 -9
  94. package/dist/page/usePageLayout.d.ts +57 -2
  95. package/dist/page/usePageLayout.js +50 -5
  96. package/dist/schema/attrRoles.js +3 -2
  97. package/dist/schema/docxSchema.js +5 -2
  98. package/dist/schema/editGuard.d.ts +1 -1
  99. package/dist/schema/guards.js +1 -2
  100. package/dist/schema/preservedGuards.d.ts +4 -6
  101. package/dist/schema/preservedGuards.js +0 -9
  102. package/dist/schema/stories.d.ts +42 -2
  103. package/dist/schema/stories.js +28 -5
  104. package/dist/styles/classNames.d.ts +21 -6
  105. package/dist/styles/classNames.js +21 -6
  106. package/dist/styles/inlineStyle.d.ts +7 -0
  107. package/dist/styles/inlineStyle.js +11 -5
  108. package/dist/styles/visualScale.d.ts +19 -0
  109. package/dist/styles/visualScale.js +26 -0
  110. package/dist/styles.css +89 -32
  111. package/dist/ui/InsertImageButton.d.ts +3 -1
  112. package/dist/ui/InsertImageButton.js +3 -2
  113. package/dist/ui/TableMenu.d.ts +5 -2
  114. package/dist/ui/TableMenu.js +18 -1
  115. package/dist/ui/TextMenu.d.ts +5 -2
  116. package/dist/ui/TextMenu.js +21 -6
  117. package/dist/ui/Toolbar.d.ts +17 -2
  118. package/dist/ui/Toolbar.js +23 -10
  119. package/dist/ui/comments/useCommentRailLayout.js +2 -4
  120. package/dist/ui/noteItems.d.ts +21 -0
  121. package/dist/ui/noteItems.js +30 -0
  122. package/dist/ui/notes/NoteAreas.d.ts +73 -0
  123. package/dist/ui/notes/NoteAreas.js +131 -0
  124. package/dist/ui/notes/NoteList.d.ts +25 -0
  125. package/dist/ui/notes/NoteList.js +58 -0
  126. package/dist/ui/notes/StoryRow.d.ts +75 -0
  127. package/dist/ui/notes/StoryRow.js +122 -0
  128. package/dist/ui/notes/noteBands.d.ts +100 -0
  129. package/dist/ui/notes/noteBands.js +142 -0
  130. package/dist/ui/notes/noteHeights.d.ts +10 -0
  131. package/dist/ui/notes/noteHeights.js +24 -0
  132. package/dist/ui/notes/noteSeparator.d.ts +12 -0
  133. package/dist/ui/notes/noteSeparator.js +9 -0
  134. package/dist/ui/notes/useStorySurface.d.ts +68 -0
  135. package/dist/ui/notes/useStorySurface.js +95 -0
  136. package/dist/ui/runCommand.d.ts +8 -1
  137. package/dist/ui/runCommand.js +2 -2
  138. package/dist/ui/shortcutLabels.d.ts +16 -0
  139. package/dist/ui/shortcutLabels.js +9 -0
  140. package/dist/ui/usePageRoom.d.ts +13 -0
  141. package/dist/ui/usePageRoom.js +31 -0
  142. package/package.json +1 -1
  143. package/dist/docx/notes.d.ts +0 -27
  144. package/dist/docx/notes.js +0 -55
  145. package/dist/ui/NotesPanel.d.ts +0 -6
  146. package/dist/ui/NotesPanel.js +0 -33
@@ -0,0 +1,122 @@
1
+ // src/ui/notes/StoryRow.tsx
2
+ import {
3
+ memo,
4
+ useEffect,
5
+ useLayoutEffect,
6
+ useRef
7
+ } from "react";
8
+ import { noteNodeSpecs } from "../../editor/notes/noteSurface.js";
9
+ import {
10
+ storyMarkup
11
+ } from "../../editor/stories/storyMarkup.js";
12
+ import {
13
+ caretOf,
14
+ createStoryView
15
+ } from "../../editor/stories/storyView.js";
16
+ import { editorClassNames } from "../../styles/classNames.js";
17
+ import { jsx } from "react/jsx-runtime";
18
+ var drawn = /* @__PURE__ */ new WeakMap();
19
+ function markupOf(story, label, fontFallbacks, draw) {
20
+ const held = drawn.get(story);
21
+ if (held?.label === label && held.fontFallbacks === fontFallbacks && held.draw === draw) {
22
+ return held.markup;
23
+ }
24
+ const markup = draw(story, {
25
+ fontFallbacks,
26
+ nodeSpecs: noteNodeSpecs(() => label)
27
+ });
28
+ drawn.set(story, { label, fontFallbacks, draw, markup });
29
+ return markup;
30
+ }
31
+ function pressedTheNumber(target) {
32
+ return target instanceof Element && target.closest(`.${editorClassNames.noteMark}`) !== null;
33
+ }
34
+ var StoryRow = memo(function StoryRow2({
35
+ noteKey,
36
+ story,
37
+ label,
38
+ fontFallbacks,
39
+ name,
40
+ hidden = false,
41
+ onHeight,
42
+ zoom = 1,
43
+ draw = storyMarkup,
44
+ editing = null,
45
+ revision,
46
+ readOnly = false,
47
+ onPress,
48
+ onReturn
49
+ }) {
50
+ const box = useRef(null);
51
+ const scale = useRef(zoom);
52
+ const live = useRef(null);
53
+ useEffect(() => {
54
+ scale.current = zoom;
55
+ });
56
+ useLayoutEffect(() => {
57
+ if (editing) return;
58
+ box.current?.replaceChildren(
59
+ markupOf(story, label, fontFallbacks, draw).cloneNode(true)
60
+ );
61
+ }, [editing, story, label, fontFallbacks, draw]);
62
+ useLayoutEffect(() => {
63
+ const row = box.current;
64
+ if (!row || !editing) return;
65
+ const mount = document.createElement("div");
66
+ row.replaceChildren(mount);
67
+ const opened = createStoryView({
68
+ mount,
69
+ host: editing.host,
70
+ key: noteKey,
71
+ document: editing.document,
72
+ fontFallbacks,
73
+ extensions: editing.extensions,
74
+ caret: editing.caret.take(),
75
+ onStateChange: editing.onStateChange
76
+ });
77
+ live.current = opened;
78
+ opened.view.focus();
79
+ return () => {
80
+ editing.caret.keep(caretOf(opened.view.state));
81
+ live.current = null;
82
+ opened.destroy();
83
+ };
84
+ }, [editing, noteKey, fontFallbacks]);
85
+ useLayoutEffect(() => {
86
+ live.current?.sync();
87
+ }, [revision]);
88
+ useEffect(() => {
89
+ const row = box.current;
90
+ if (!row || !onHeight || typeof ResizeObserver === "undefined") return;
91
+ const observer = new ResizeObserver(() => {
92
+ onHeight(noteKey, row.getBoundingClientRect().height / scale.current);
93
+ });
94
+ observer.observe(row);
95
+ return () => observer.disconnect();
96
+ }, [noteKey, onHeight]);
97
+ return /* @__PURE__ */ jsx(
98
+ "div",
99
+ {
100
+ ref: box,
101
+ className: [
102
+ editorClassNames.noteRow,
103
+ editing ? editorClassNames.noteRowOpen : null
104
+ ].filter(Boolean).join(" "),
105
+ role: name === void 0 ? void 0 : "group",
106
+ "aria-label": name,
107
+ "aria-readonly": editing && readOnly ? true : void 0,
108
+ style: hidden ? { visibility: "hidden" } : void 0,
109
+ onMouseDown: editing || !onPress && !onReturn ? void 0 : (event) => {
110
+ event.preventDefault();
111
+ if (onReturn && pressedTheNumber(event.target)) {
112
+ onReturn();
113
+ return;
114
+ }
115
+ onPress?.({ left: event.clientX, top: event.clientY });
116
+ }
117
+ }
118
+ );
119
+ });
120
+ export {
121
+ StoryRow
122
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The footnote band: what a page keeps room for at its foot, and what is drawn in that room and
3
+ * after the last page.
4
+ *
5
+ * A band is two halves that have to agree. The layout is told a name, a place at the foot, the
6
+ * height a page adds once for the band and the height of each thing kept in it (`page/demands`),
7
+ * and something has to draw exactly that much. Both halves live here so a page's room and the
8
+ * notes drawn in it cannot drift apart, and so the component that mounts the editor asks only for
9
+ * the bands a document keeps and renders what it is handed.
10
+ *
11
+ * A document referring to no footnote keeps no band at all, so it is laid out through the same
12
+ * pass it took before a page kept room for anything.
13
+ */
14
+ import type { EditorState } from "prosemirror-state";
15
+ import type { CSSProperties, ReactElement } from "react";
16
+ import { type NoteRow } from "../../editor/commands/noteQueries";
17
+ import type { DemandBand } from "../../page/demands";
18
+ import type { PagePixels, TrailingRows } from "../../page/pageLayout";
19
+ import type { PageOverlay } from "../../page/usePageLayout";
20
+ import type { StoryKey } from "../../schema/stories";
21
+ import { type FontFallbacks } from "../../styles/fontStack";
22
+ import type { NoteHeightReport, RowEditing } from "./StoryRow";
23
+ /** What a document's notes ask of the page layout, and what is needed to draw them */
24
+ export interface NoteBands {
25
+ /**
26
+ * The bands to lay the pages out with, by name, or undefined where the document asks for no
27
+ * room at all. A new value lays the pages out again, so it changes only when a height does
28
+ */
29
+ readonly bands: ReadonlyMap<string, DemandBand> | undefined;
30
+ /**
31
+ * The endnotes as rows laid after the last block of the document, or undefined where it refers
32
+ * to none. A new value lays the pages out again, so it changes only when a height does
33
+ */
34
+ readonly trailing: TrailingRows | undefined;
35
+ /**
36
+ * Every note the document refers to, by story key, in first-reference order.
37
+ *
38
+ * Which one a caret stands in is the mounting component's to hold, so this is the one half of
39
+ * the notes it reads; everything else about how they are drawn stays in `drawn`.
40
+ */
41
+ readonly rows: ReadonlyMap<StoryKey, NoteRow>;
42
+ /** Held for `NotesAroundPage`; nothing else reads it */
43
+ readonly drawn: DrawnNotes;
44
+ }
45
+ /** What `NotesAroundPage` draws with, kept opaque so a caller cannot take the two halves apart */
46
+ interface DrawnNotes {
47
+ /** The footnotes, which are the notes drawn at the foot of the page their reference stands on */
48
+ readonly footnotes: ReadonlyMap<StoryKey, NoteRow>;
49
+ readonly endnotes: readonly NoteRow[];
50
+ readonly anyNotes: boolean;
51
+ readonly heights: ReadonlyMap<StoryKey, number>;
52
+ readonly onHeight: NoteHeightReport;
53
+ readonly fontFallbacks: FontFallbacks;
54
+ readonly textStyle: CSSProperties;
55
+ }
56
+ export interface NoteBandsOptions {
57
+ /** The live document's state, or null before one is opened */
58
+ readonly state: EditorState | null;
59
+ /** A value that differs for every document opened, which the measured heights are held against */
60
+ readonly of: unknown;
61
+ readonly fontFallbacks: FontFallbacks | undefined;
62
+ }
63
+ export declare function useNoteBands({ state, of, fontFallbacks, }: NoteBandsOptions): NoteBands;
64
+ export interface NotesAroundPageProps {
65
+ readonly notes: NoteBands;
66
+ /**
67
+ * Where the pages stand, or null while none are measured, which is what decides where the notes
68
+ * go: a page keeps room for them only once it has been laid out
69
+ */
70
+ readonly overlay: PageOverlay | null;
71
+ /** The paper the first section is drawn on, which the list under the sheet is as wide as */
72
+ readonly page: PagePixels;
73
+ readonly zoom: number;
74
+ /** The note the caret is in, which is the one row an editing view stands over */
75
+ readonly open?: StoryKey | null;
76
+ /** What mounts that view. Null while no note is open */
77
+ readonly editing?: RowEditing | null;
78
+ /** Whether the open note stands where nothing may be edited */
79
+ readonly readOnly?: boolean;
80
+ /** A value that differs every time the main document changes, which the open row syncs on */
81
+ readonly revision?: unknown;
82
+ /** Called for a press on a note no view stands over yet */
83
+ readonly onOpen?: (key: StoryKey, at: {
84
+ left: number;
85
+ top: number;
86
+ }) => void;
87
+ /** Called for a press on the number a note is drawn by, which is the way back to its reference */
88
+ readonly onReturn?: (key: StoryKey) => void;
89
+ }
90
+ /**
91
+ * The notes around the paper: each footnote over the room its page keeps, and the endnotes over
92
+ * the room kept after the last paragraph.
93
+ *
94
+ * Until a page has been measured there is no room to stand in - the guides may be off, or the
95
+ * first frame may not have been laid out yet - and a note drawn nowhere is a note a reader cannot
96
+ * read, so both kinds are listed under the sheet until there is. Only a note standing in its own
97
+ * room is edited in place.
98
+ */
99
+ export declare function NotesAroundPage({ notes, overlay, page, zoom, open, editing, readOnly, revision, onOpen, onReturn, }: NotesAroundPageProps): ReactElement | null;
100
+ export {};
@@ -0,0 +1,142 @@
1
+ // src/ui/notes/noteBands.tsx
2
+ import { useMemo } from "react";
3
+ import {
4
+ noteProjection
5
+ } from "../../editor/commands/noteQueries.js";
6
+ import { documentOf } from "../../editor/editorDocument.js";
7
+ import {
8
+ FOOTNOTE_BAND,
9
+ FOOTNOTE_BAND_ORDER
10
+ } from "../../page/demands/footnoteDemands.js";
11
+ import { editorClassNames } from "../../styles/classNames.js";
12
+ import {
13
+ DEFAULT_FONT_FALLBACKS
14
+ } from "../../styles/fontStack.js";
15
+ import { documentDefaultsVariables } from "../../styles/inlineStyle.js";
16
+ import { endnoteAreasOf, footnoteAreasOf, NoteAreas } from "./NoteAreas.js";
17
+ import { NoteList } from "./NoteList.js";
18
+ import { useNoteHeights } from "./noteHeights.js";
19
+ import { NOTE_SEPARATOR_HEIGHT } from "./noteSeparator.js";
20
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
21
+ var NO_ROWS = /* @__PURE__ */ new Map();
22
+ var NO_NOTE_ROWS = [];
23
+ function useNoteBands({
24
+ state,
25
+ of,
26
+ fontFallbacks
27
+ }) {
28
+ const notes = state === null ? null : noteProjection.read(state);
29
+ const footnotes = notes?.footnotes ?? NO_ROWS;
30
+ const hasFootnotes = footnotes.size > 0;
31
+ const [heights, onHeight] = useNoteHeights(of);
32
+ const bands = useMemo(
33
+ () => hasFootnotes ? /* @__PURE__ */ new Map([
34
+ [
35
+ FOOTNOTE_BAND,
36
+ {
37
+ order: FOOTNOTE_BAND_ORDER,
38
+ overhead: NOTE_SEPARATOR_HEIGHT,
39
+ heights
40
+ }
41
+ ]
42
+ ]) : void 0,
43
+ [hasFootnotes, heights]
44
+ );
45
+ const endnotes = notes?.endnotes ?? NO_NOTE_ROWS;
46
+ const trailing = useMemo(
47
+ () => endnotes.length === 0 ? void 0 : {
48
+ overhead: NOTE_SEPARATOR_HEIGHT,
49
+ rows: endnotes.map((row) => ({
50
+ id: row.key,
51
+ height: heights.get(row.key) ?? 0
52
+ }))
53
+ },
54
+ [endnotes, heights]
55
+ );
56
+ const snapshot = state === null ? null : documentOf(state);
57
+ const noteFontFallbacks = fontFallbacks ?? DEFAULT_FONT_FALLBACKS;
58
+ const textStyle = useMemo(
59
+ () => snapshot === null ? {} : {
60
+ ...documentDefaultsVariables(snapshot.defaults, noteFontFallbacks),
61
+ tabSize: `${snapshot.defaultTabStopPt}pt`
62
+ },
63
+ [snapshot, noteFontFallbacks]
64
+ );
65
+ return {
66
+ bands,
67
+ trailing,
68
+ rows: notes?.rows ?? NO_ROWS,
69
+ drawn: {
70
+ footnotes,
71
+ endnotes,
72
+ anyNotes: notes !== null,
73
+ heights,
74
+ onHeight,
75
+ fontFallbacks: noteFontFallbacks,
76
+ textStyle
77
+ }
78
+ };
79
+ }
80
+ function NotesAroundPage({
81
+ notes,
82
+ overlay,
83
+ page,
84
+ zoom,
85
+ open,
86
+ editing,
87
+ readOnly,
88
+ revision,
89
+ onOpen,
90
+ onReturn
91
+ }) {
92
+ const { footnotes, endnotes, anyNotes, heights, onHeight } = notes.drawn;
93
+ const drawing = {
94
+ heights,
95
+ onHeight,
96
+ fontFallbacks: notes.drawn.fontFallbacks,
97
+ textStyle: notes.drawn.textStyle,
98
+ zoom,
99
+ open,
100
+ editing,
101
+ readOnly,
102
+ revision,
103
+ onOpen,
104
+ onReturn
105
+ };
106
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
107
+ overlay !== null && footnotes.size > 0 && /* @__PURE__ */ jsx(
108
+ NoteAreas,
109
+ {
110
+ overlay,
111
+ areas: footnoteAreasOf(overlay),
112
+ areaClassName: editorClassNames.footnoteArea,
113
+ rows: footnotes,
114
+ ...drawing
115
+ }
116
+ ),
117
+ overlay !== null && endnotes.length > 0 && /* @__PURE__ */ jsx(
118
+ NoteAreas,
119
+ {
120
+ overlay,
121
+ areas: endnoteAreasOf(overlay),
122
+ areaClassName: editorClassNames.endnoteArea,
123
+ rows: notes.rows,
124
+ ...drawing
125
+ }
126
+ ),
127
+ anyNotes && overlay === null && /* @__PURE__ */ jsx(
128
+ NoteList,
129
+ {
130
+ footnotes: [...footnotes.values()],
131
+ endnotes,
132
+ page,
133
+ fontFallbacks: notes.drawn.fontFallbacks,
134
+ textStyle: notes.drawn.textStyle
135
+ }
136
+ )
137
+ ] });
138
+ }
139
+ export {
140
+ NotesAroundPage,
141
+ useNoteBands
142
+ };
@@ -0,0 +1,10 @@
1
+ import type { StoryKey } from "../../schema/stories";
2
+ import type { NoteHeightReport } from "./StoryRow";
3
+ /**
4
+ * The height each note stands at, as its row last reported it, for the document `of` names.
5
+ *
6
+ * A height is a fact about what the browser drew, so it can only arrive after a row is drawn. The
7
+ * map handed back is the same object until a height really changes, which is what lets the page
8
+ * layout that reads it run again only then, and a document swapped in starts from none.
9
+ */
10
+ export declare function useNoteHeights(of: unknown): readonly [ReadonlyMap<StoryKey, number>, NoteHeightReport];
@@ -0,0 +1,24 @@
1
+ // src/ui/notes/noteHeights.ts
2
+ import { useCallback, useState } from "react";
3
+ var NO_HEIGHTS = /* @__PURE__ */ new Map();
4
+ function round(value) {
5
+ return Math.round(value * 100) / 100;
6
+ }
7
+ function useNoteHeights(of) {
8
+ const [held, setHeld] = useState({ of, heights: NO_HEIGHTS });
9
+ const report = useCallback(
10
+ (key, height) => {
11
+ const rounded = round(height);
12
+ setHeld((previous) => {
13
+ const heights = previous.of === of ? previous.heights : NO_HEIGHTS;
14
+ if (heights.get(key) === rounded && previous.of === of) return previous;
15
+ return { of, heights: new Map(heights).set(key, rounded) };
16
+ });
17
+ },
18
+ [of]
19
+ );
20
+ return [held.of === of ? held.heights : NO_HEIGHTS, report];
21
+ }
22
+ export {
23
+ useNoteHeights
24
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The rule notes are set under, wherever they are drawn: in the room a page keeps for them
3
+ * (`./NoteAreas`) and at the head of the list under the sheet (`./NoteList`).
4
+ *
5
+ * Its height is also what the page layout is told a page adds once when it holds any footnote
6
+ * (`DemandBand.overhead`), and what the endnotes are given once where they begin
7
+ * (`TrailingRows.overhead`), so the room kept and the notes drawn in it are the same size.
8
+ */
9
+ /** The height a page's notes are set under, the rule drawn across the middle of it */
10
+ export declare const NOTE_SEPARATOR_HEIGHT = 16;
11
+ /** Word's rule runs a third of the body and no further than two inches */
12
+ export declare function noteSeparatorWidth(bodyWidth: number): number;
@@ -0,0 +1,9 @@
1
+ // src/ui/notes/noteSeparator.ts
2
+ var NOTE_SEPARATOR_HEIGHT = 16;
3
+ function noteSeparatorWidth(bodyWidth) {
4
+ return Math.min(bodyWidth / 3, 192);
5
+ }
6
+ export {
7
+ NOTE_SEPARATOR_HEIGHT,
8
+ noteSeparatorWidth
9
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Which side story the caret is in, and what the one view over it is built from.
3
+ *
4
+ * One story is open at a time (`editor/stories/storyView`), and this is what holds that true: the
5
+ * state here names one story key. Everything the view is built from arrives as one binding, so a
6
+ * second kind of story is another binding rather than more of the component that renders this.
7
+ * Where a story stands on the screen is the drawing side's (`./noteBands`).
8
+ */
9
+ import type { EditorState } from "prosemirror-state";
10
+ import type { EditorView } from "prosemirror-view";
11
+ import type { NoteRow } from "../../editor/commands/noteQueries";
12
+ import { type EditorDocument } from "../../editor/editorDocument";
13
+ import { type ActiveSurface, type StoryExtensions, type StoryHost } from "../../editor/stories/storyView";
14
+ import type { StoryKey } from "../../schema/stories";
15
+ import type { RowEditing } from "./StoryRow";
16
+ /** A view and the state on screen, which is what the mounting component holds as React state */
17
+ export interface LiveSurface {
18
+ readonly view: EditorView;
19
+ readonly state: EditorState;
20
+ }
21
+ /** What one kind of story hands the view over one of its rows, which is all that differs per kind */
22
+ export interface StorySurfaceBinding {
23
+ /** The host every edit in this kind of story leaves through */
24
+ hostOf(main: EditorView, activate: StoryHost["activate"]): StoryHost;
25
+ /** What the view over this row adds, and null for a row this binding does not answer for */
26
+ extensionsOf(main: EditorView, row: NoteRow): StoryExtensions | null;
27
+ /** The values this row's story is edited against, worked out from the document's own snapshot */
28
+ documentFor(main: EditorState, snapshot: EditorDocument, row: NoteRow): EditorDocument;
29
+ /**
30
+ * Which story the document is asking to have opened, read off the state the mounting component
31
+ * holds. A fresh value for every request, so asking twice for one story opens it twice.
32
+ */
33
+ requestedIn(main: EditorState): {
34
+ readonly key: StoryKey;
35
+ } | null;
36
+ /** Puts the caret in this row's story, which is what opens it */
37
+ openIn(main: EditorView, row: NoteRow): void;
38
+ /** Hands the caret back to the text this row's story is called from, which is what closes it */
39
+ returnFrom(main: EditorView, row: NoteRow): void;
40
+ }
41
+ export interface StorySurfaceOptions {
42
+ /** The body's view and the state on screen, or null before a document is opened */
43
+ readonly main: LiveSurface | null;
44
+ /** The rows one of which a view may stand over, by the key naming each one's story */
45
+ readonly rows: ReadonlyMap<StoryKey, NoteRow>;
46
+ /** The kind of story these rows hold. A new value takes an open view down, so keep one */
47
+ readonly binding: StorySurfaceBinding;
48
+ }
49
+ export interface StorySurface {
50
+ /** Where the caret is, and null before a document is opened */
51
+ readonly active: ActiveSurface | null;
52
+ /** The story a view stands over, and null while the caret is in the body */
53
+ readonly open: StoryKey | null;
54
+ /** What mounts that view, which only the open row is given. Null while none is open */
55
+ readonly editing: RowEditing | null;
56
+ /** Whether the open story stands where nothing may be edited */
57
+ readonly readOnly: boolean;
58
+ /** Whether the open view is composing, which a page laid out again would take down */
59
+ composing(): boolean;
60
+ /** Opens the story of this row, with the caret where the press landed */
61
+ onOpen(key: StoryKey, at: {
62
+ left: number;
63
+ top: number;
64
+ }): void;
65
+ /** Hands the caret back to the text this row's story is called from */
66
+ onReturn(key: StoryKey): void;
67
+ }
68
+ export declare function useStorySurface({ main, rows, binding, }: StorySurfaceOptions): StorySurface;
@@ -0,0 +1,95 @@
1
+ // src/ui/notes/useStorySurface.ts
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { documentOf } from "../../editor/editorDocument.js";
4
+ import {
5
+ EVERY_CAPABILITY
6
+ } from "../../editor/stories/storyView.js";
7
+ function useStorySurface({
8
+ main,
9
+ rows,
10
+ binding
11
+ }) {
12
+ const [asked, setAsked] = useState(null);
13
+ const [live, setLive] = useState(null);
14
+ const held = useRef(null);
15
+ const requested = main === null ? null : binding.requestedIn(main.state);
16
+ useEffect(() => {
17
+ if (requested !== null) setAsked(requested.key);
18
+ }, [requested]);
19
+ useEffect(() => {
20
+ const body = main?.view;
21
+ if (!body) return;
22
+ const leave = () => setAsked(null);
23
+ body.dom.addEventListener("focus", leave);
24
+ return () => body.dom.removeEventListener("focus", leave);
25
+ }, [main?.view]);
26
+ const open = asked !== null && rows.has(asked) ? asked : null;
27
+ const row = open === null ? void 0 : rows.get(open);
28
+ const activate = useCallback((_key, view) => {
29
+ setLive(view === null ? null : { view, state: view.state });
30
+ }, []);
31
+ const onStateChange = useCallback((state) => {
32
+ setLive(
33
+ (current) => current === null ? current : { view: current.view, state }
34
+ );
35
+ }, []);
36
+ const caret = useMemo(
37
+ () => ({
38
+ take: () => held.current?.key === open ? held.current.caret : null,
39
+ keep: (at) => {
40
+ if (open !== null) held.current = { key: open, caret: at };
41
+ }
42
+ }),
43
+ [open]
44
+ );
45
+ const host = useMemo(
46
+ () => main === null ? null : binding.hostOf(main.view, activate),
47
+ [main?.view, activate, binding]
48
+ );
49
+ const extensions = useMemo(
50
+ () => main === null || row === void 0 ? null : binding.extensionsOf(main.view, row),
51
+ [main?.view, row?.key, row?.label, binding]
52
+ );
53
+ const snapshot = main === null ? null : documentOf(main.state);
54
+ const document = useMemo(
55
+ () => main === null || snapshot === null || row === void 0 ? null : binding.documentFor(main.state, snapshot, row),
56
+ [main?.view, snapshot, row?.referencePos, binding]
57
+ );
58
+ const editing = useMemo(
59
+ () => host === null || extensions === null || document === null ? null : { host, document, extensions, caret, onStateChange },
60
+ [host, extensions, document, caret, onStateChange]
61
+ );
62
+ const active = main === null ? null : open !== null && live !== null && extensions !== null ? {
63
+ surface: "story",
64
+ key: open,
65
+ view: live.view,
66
+ state: live.state,
67
+ takes: extensions.takes
68
+ } : {
69
+ surface: "body",
70
+ view: main.view,
71
+ state: main.state,
72
+ takes: EVERY_CAPABILITY
73
+ };
74
+ return {
75
+ active,
76
+ open,
77
+ editing,
78
+ readOnly: open !== null && host !== null && host.shut(open),
79
+ composing: () => live?.view.composing === true,
80
+ onOpen(key, at) {
81
+ const pressed = rows.get(key);
82
+ if (main === null || pressed === void 0) return;
83
+ held.current = { key, caret: { kind: "point", ...at } };
84
+ binding.openIn(main.view, pressed);
85
+ },
86
+ onReturn(key) {
87
+ const pressed = rows.get(key);
88
+ if (main === null || pressed === void 0) return;
89
+ binding.returnFrom(main.view, pressed);
90
+ }
91
+ };
92
+ }
93
+ export {
94
+ useStorySurface
95
+ };
@@ -2,4 +2,11 @@
2
2
  import type { Command } from "prosemirror-state";
3
3
  import type { EditorView } from "prosemirror-view";
4
4
  export type RunCommand = (command: Command) => void;
5
- export declare function commandRunner(view: EditorView): RunCommand;
5
+ /**
6
+ * Runs commands on `view` and leaves the focus on `focus`, which is `view` itself unless the
7
+ * command acts somewhere other than where the caret stands.
8
+ *
9
+ * Undo and redo are the one case: they run on the document while the caret is in a story, and
10
+ * handing the focus to the document is what closes an open story (`DocxEditor`).
11
+ */
12
+ export declare function commandRunner(view: EditorView, focus?: EditorView): RunCommand;
@@ -2,10 +2,10 @@
2
2
  function apply(view, command) {
3
3
  command(view.state, (tr) => view.dispatch(tr), view);
4
4
  }
5
- function commandRunner(view) {
5
+ function commandRunner(view, focus = view) {
6
6
  return (command) => {
7
7
  apply(view, command);
8
- view.focus();
8
+ focus.focus();
9
9
  };
10
10
  }
11
11
  export {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * How a menu row spells the modifiers of a key binding.
3
+ *
4
+ * `Mod` is Command on Apple platforms and Control elsewhere, and which one a binding answers to is
5
+ * decided by `prosemirror-keymap` off the platform name. A label is read off the same platform
6
+ * test, so a row cannot promise a key the bindings do not hold - an iPad keyboard sends Command
7
+ * for every `Mod` binding the editor declares (`editor/clipboard/plugin` reads it for the same
8
+ * reason).
9
+ */
10
+ export interface ModifierLabels {
11
+ /** `Mod`, as a row writes it before the key */
12
+ readonly mod: string;
13
+ readonly alt: string;
14
+ }
15
+ /** The spellings this platform's bindings are labelled with */
16
+ export declare function modifierLabels(): ModifierLabels;
@@ -0,0 +1,9 @@
1
+ // src/ui/shortcutLabels.ts
2
+ var APPLE = /Mac|iP(hone|[oa]d)/;
3
+ function modifierLabels() {
4
+ const platform = typeof navigator === "undefined" ? "" : navigator.platform;
5
+ return APPLE.test(platform) ? { mod: "\u2318", alt: "\u2325" } : { mod: "Ctrl+", alt: "Alt+" };
6
+ }
7
+ export {
8
+ modifierLabels
9
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Holds the room the page layer takes in the scroll box.
3
+ *
4
+ * The layer is scaled with a transform and stands outside the flow (`styles/editor.css`), so
5
+ * nothing in the flow reaches down to where the paper ends and the scroll box would stop short of
6
+ * it. The box the layer stands in is given the height the layer is drawn at, and the scroll box
7
+ * then reaches the paper and keeps its own padding below it.
8
+ *
9
+ * The two elements are taken rather than refs to them, so that a surface which drew neither - a
10
+ * file that was refused, before the reader opens one that is not - is measured the moment they
11
+ * arrive rather than never again.
12
+ */
13
+ export declare function usePageRoom(box: HTMLElement | null, layer: HTMLElement | null, scale: number): void;