@stll/folio-core 0.66.1 → 0.66.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 (80) hide show
  1. package/dist/ai-edits/apply.d.ts +13 -1
  2. package/dist/ai-edits/apply.js +533 -136
  3. package/dist/ai-edits/batch-claims.d.ts +26 -0
  4. package/dist/ai-edits/batch-claims.js +30 -5
  5. package/dist/ai-edits/headless.d.ts +6 -0
  6. package/dist/ai-edits/headless.js +25 -3
  7. package/dist/ai-edits/newListNumbering.d.ts +4 -1
  8. package/dist/ai-edits/newListNumbering.js +4 -2
  9. package/dist/ai-edits/types.d.ts +1 -1
  10. package/dist/document-operations.d.ts +13 -1
  11. package/dist/document-operations.js +11 -4
  12. package/dist/docx/listNumberingInstances.d.ts +6 -4
  13. package/dist/docx/listNumberingInstances.js +12 -11
  14. package/dist/docx/numberingIds.d.ts +10 -0
  15. package/dist/docx/numberingIds.js +5 -1
  16. package/dist/docx/paragraphParser.js +16 -3
  17. package/dist/docx/paragraphProperties.js +2 -1
  18. package/dist/docx/serializer/paragraphSerializer.js +86 -66
  19. package/dist/i18n/messages/ar.gen.js +1 -0
  20. package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
  21. package/dist/i18n/messages/catalogs.gen.js +17 -0
  22. package/dist/i18n/messages/cs.gen.js +1 -0
  23. package/dist/i18n/messages/de.gen.js +1 -0
  24. package/dist/i18n/messages/en.gen.js +1 -0
  25. package/dist/i18n/messages/es.gen.js +1 -0
  26. package/dist/i18n/messages/et.gen.js +1 -0
  27. package/dist/i18n/messages/fr.gen.js +1 -0
  28. package/dist/i18n/messages/he.gen.js +1 -0
  29. package/dist/i18n/messages/hi.gen.js +1 -0
  30. package/dist/i18n/messages/hu.gen.js +1 -0
  31. package/dist/i18n/messages/lt.gen.js +1 -0
  32. package/dist/i18n/messages/lv.gen.js +1 -0
  33. package/dist/i18n/messages/messages.gen.d.ts +1 -0
  34. package/dist/i18n/messages/pl.gen.js +1 -0
  35. package/dist/i18n/messages/pt-BR.gen.js +1 -0
  36. package/dist/i18n/messages/sk.gen.js +1 -0
  37. package/dist/i18n/messages/tr.gen.js +1 -0
  38. package/dist/i18n/messages/zh-CN.gen.js +1 -0
  39. package/dist/internal/wholeStoryRevisionResolution.d.ts +1 -1
  40. package/dist/internal/wholeStoryRevisionResolution.js +24 -9
  41. package/dist/layout-bridge/convert/runMarkFormatting.js +1 -0
  42. package/dist/markdown/renderRuns.js +68 -12
  43. package/dist/prosemirror/commands/comments.js +2 -0
  44. package/dist/prosemirror/commands/resolveParagraphProperties.js +15 -2
  45. package/dist/prosemirror/conversion/fromProseDoc.js +55 -18
  46. package/dist/prosemirror/conversion/hyphenTextCarriers.d.ts +23 -1
  47. package/dist/prosemirror/conversion/hyphenTextCarriers.js +46 -1
  48. package/dist/prosemirror/conversion/toProseDoc.js +15 -9
  49. package/dist/prosemirror/extensions/StarterKit.js +1 -1
  50. package/dist/prosemirror/extensions/features/pasteCleanup.js +6 -0
  51. package/dist/prosemirror/extensions/features/pastedHtmlLists.js +2 -1
  52. package/dist/prosemirror/extensions/markRegistry.d.ts +3 -1
  53. package/dist/prosemirror/extensions/markRegistry.js +3 -0
  54. package/dist/prosemirror/extensions/marks/HyphenElementExtension.d.ts +6 -0
  55. package/dist/prosemirror/extensions/marks/HyphenElementExtension.js +54 -0
  56. package/dist/prosemirror/fieldRepresentation.js +2 -1
  57. package/dist/prosemirror/hyphenElementAttrs.d.ts +10 -0
  58. package/dist/prosemirror/hyphenElementAttrs.js +32 -0
  59. package/dist/prosemirror/keptRangeBoundaries.d.ts +2 -0
  60. package/dist/prosemirror/keptRangeBoundaries.js +1 -1
  61. package/dist/prosemirror/listNumbering.js +3 -2
  62. package/dist/prosemirror/paragraphIndentation.js +2 -1
  63. package/dist/prosemirror/paragraphPropertyCarry.d.ts +1 -14
  64. package/dist/prosemirror/paragraphPropertyCarry.js +4 -28
  65. package/dist/prosemirror/plugins/suggestionMode.js +19 -6
  66. package/dist/prosemirror/replacedAnnotations.d.ts +1 -0
  67. package/dist/prosemirror/replacedAnnotations.js +1 -0
  68. package/dist/prosemirror/sectionCarrier.d.ts +8 -0
  69. package/dist/prosemirror/sectionCarrier.js +29 -1
  70. package/dist/prosemirror/styles/paragraphStyleCascade.d.ts +10 -0
  71. package/dist/prosemirror/styles/paragraphStyleCascade.js +12 -2
  72. package/dist/prosemirror/tableRunIn.d.ts +24 -9
  73. package/dist/prosemirror/tableRunIn.js +107 -21
  74. package/dist/prosemirror/textInput.js +3 -1
  75. package/dist/prosemirror/validation.js +6 -0
  76. package/dist/utils/createDocument.js +11 -8
  77. package/dist/utils/formatToStyle.js +2 -2
  78. package/dist/utils/units.d.ts +5 -0
  79. package/dist/utils/units.js +6 -1
  80. package/package.json +3 -3
@@ -1,11 +1,12 @@
1
1
  import { getProseParagraphPropertySourceToken, recreateProseNodeWithParagraphPropertySource } from "../docx/paragraphPropertySource.js";
2
2
  import { expectParagraphAttrs } from "./attrs/index.js";
3
- import { paragraphRemainsOf } from "./keptRangeBoundaries.js";
3
+ import { REVISION_MARK_NAMES, paragraphRemainsOf, revisionLayerRemovesContent } from "./keptRangeBoundaries.js";
4
4
  import { rebaseParagraphRuns } from "./rebaseParagraphRunFormatting.js";
5
5
  import { paragraphRunStyleContext } from "./runStyleFormatting.js";
6
+ import { trackedRevisionPathOf } from "./trackedRevisionPath.js";
6
7
  import { paragraphContentOf } from "./zeroWidthAnchors.js";
7
8
  import { Fragment, Slice } from "prosemirror-model";
8
- import { ReplaceAroundStep, StepMap } from "prosemirror-transform";
9
+ import { ReplaceAroundStep, ReplaceStep, StepMap } from "prosemirror-transform";
9
10
  import { paragraphMarkOutcome } from "@stll/docx-core/ops";
10
11
  // src/prosemirror/tableRunIn.ts
11
12
  /**
@@ -19,35 +20,53 @@ import { paragraphMarkOutcome } from "@stll/docx-core/ops";
19
20
  *
20
21
  * A paragraph with nothing left to carry is simply removed by the resolvers;
21
22
  * one that ends a section keeps its place, as does one before a table whose
22
- * rows or cells are themselves pending.
23
+ * first row or cell may not stay. Rejecting every change gives the document
24
+ * before back, so a deleted row stays for it. A table that resolving every
25
+ * change removes never stood in the way: the paragraph runs on into what follows
26
+ * that table.
23
27
  */
24
28
  /**
25
29
  * Whether the cell a paragraph's words would run into is itself pending: its
26
30
  * row or the cell (through nested tables' first cells) is inserted, deleted
27
- * or merged, so resolving may not keep it. Pending rows or cells elsewhere in
28
- * the table leave that cell where it is.
31
+ * or merged, so resolving may not keep it. A deletion is not pending for a
32
+ * rejection of every change. Pending rows or cells elsewhere in the table leave
33
+ * that cell where it is.
29
34
  */
30
- const runInCellPending = (table) => {
35
+ const runInCellPending = (table, everyChange) => {
36
+ const deletionStays = everyChange === "reject";
31
37
  let node = table;
32
38
  while (node?.type.spec["tableRole"] === "table") {
33
39
  const row = node.firstChild;
34
40
  if (!row) return false;
35
- if (row.attrs["trIns"] != null || row.attrs["trDel"] != null) return true;
41
+ if (row.attrs["trIns"] != null) return true;
42
+ if (row.attrs["trDel"] != null && !deletionStays) return true;
36
43
  const cell = row.firstChild;
37
44
  if (!cell) return false;
38
- if (cell.attrs["cellMarker"] != null) return true;
45
+ const marker = cell.attrs["cellMarker"];
46
+ if (marker != null && !(deletionStays && marker.kind === "del")) return true;
39
47
  node = cell.firstChild;
40
48
  }
41
49
  return false;
42
50
  };
51
+ /**
52
+ * Whether resolving every change takes this row out with what it records. The
53
+ * table it stands in goes when all its rows do.
54
+ */
55
+ const rowGoes = (row, mode) => row.attrs[mode === "accept" ? "trDel" : "trIns"] != null;
56
+ /** Whether resolving every change in `mode` removes the whole table. */
57
+ const tableGoes = (table, mode) => {
58
+ if (table.type.spec["tableRole"] !== "table" || table.childCount === 0) return false;
59
+ for (let index = 0; index < table.childCount; index++) if (!rowGoes(table.child(index), mode)) return false;
60
+ return true;
61
+ };
43
62
  /** The first paragraph of a table's first cell, descending into nested tables. */
44
- const runInTarget = (doc, tablePos) => {
63
+ const runInTarget = (doc, tablePos, everyChange) => {
45
64
  let node = doc.nodeAt(tablePos);
46
65
  let pos = tablePos;
47
66
  const table = node;
48
67
  const firstRow = table?.firstChild;
49
68
  if (!table || table.type.spec["tableRole"] !== "table" || !firstRow) return null;
50
- if (runInCellPending(table)) return null;
69
+ if (runInCellPending(table, everyChange)) return null;
51
70
  while (node && node.type.name !== "paragraph") {
52
71
  const child = node.firstChild;
53
72
  if (!child || child.isInline) return null;
@@ -59,22 +78,22 @@ const runInTarget = (doc, tablePos) => {
59
78
  node
60
79
  } : null;
61
80
  };
62
- const paragraphAfterContentRemoval = (paragraph, removedMark) => removedMark ? paragraph.copy(Fragment.fromArray(paragraph.children.filter((child) => !removedMark.isInSet(child.marks)))) : paragraph;
81
+ const paragraphAfterContentRemoval = (paragraph, everyChange) => everyChange ? paragraph.copy(Fragment.fromArray(paragraph.children.filter((child) => !child.marks.some((mark) => REVISION_MARK_NAMES.has(mark.type.name) && trackedRevisionPathOf(mark).some((layer) => revisionLayerRemovesContent(layer, everyChange)))))) : paragraph;
63
82
  /**
64
83
  * Run the paragraph at `paragraphPos` on into the table right after it.
65
84
  * Returns where the paragraph that took its words now starts and the
66
85
  * paragraph-property source it hands over, or null when it did not run in and
67
86
  * the caller keeps its own handling.
68
87
  */
69
- const runParagraphIntoTable = ({ tr, paragraphPos, styleResolver, removedMark }) => {
88
+ const runParagraphIntoTable = ({ tr, paragraphPos, styleResolver, everyChange }) => {
70
89
  const paragraph = tr.doc.nodeAt(paragraphPos);
71
90
  if (paragraph?.type.name !== "paragraph") return null;
72
91
  if (expectParagraphAttrs(paragraph)._sectionProperties !== void 0) return null;
73
- const left = paragraphAfterContentRemoval(paragraph, removedMark);
92
+ const left = paragraphAfterContentRemoval(paragraph, everyChange);
74
93
  const remains = paragraphRemainsOf(left);
75
94
  if (!paragraphContentOf(left) && (remains.blocks.length > 0 || remains.comments.length === 0)) return null;
76
95
  const tablePos = paragraphPos + paragraph.nodeSize;
77
- const target = runInTarget(tr.doc, tablePos);
96
+ const target = runInTarget(tr.doc, tablePos, everyChange);
78
97
  if (!target) return null;
79
98
  const $target = tr.doc.resolve(target.pos + 1);
80
99
  const containerDepth = tr.doc.resolve(paragraphPos).depth;
@@ -118,6 +137,36 @@ const markGoes = (paragraph, mode) => {
118
137
  if (!mark) return false;
119
138
  return (mark.kind === "ins" || mark.kind === "moveTo") !== (mode === "accept");
120
139
  };
140
+ /** Whether resolving every change in `mode` leaves nothing of this paragraph. */
141
+ const paragraphGoes = (paragraph, mode) => {
142
+ if (paragraph.type.name !== "paragraph" || !markGoes(paragraph, mode) || expectParagraphAttrs(paragraph)._sectionProperties != null) return false;
143
+ const left = paragraphAfterContentRemoval(paragraph, mode);
144
+ if (paragraphContentOf(left)) return false;
145
+ const remains = paragraphRemainsOf(left);
146
+ return remains.blocks.length === 0 && remains.comments.length === 0 && remains.inline.length === 0;
147
+ };
148
+ /**
149
+ * Whether what stands right after a paragraph whose mark goes is itself removed
150
+ * by this resolution, so the paragraph runs on into what stands after that, as
151
+ * it would had the removed block never been there: a table it removes, or, when
152
+ * rejecting, an inserted paragraph it empties with a table after it. (Accepted,
153
+ * a deleted paragraph did stand there, and the join ended at it. Paragraphs join
154
+ * among themselves without help.)
155
+ */
156
+ const paragraphBeforeRemovedBlock = ({ parent, index, mode }) => {
157
+ const paragraph = parent.child(index);
158
+ const next = parent.maybeChild(index + 1);
159
+ if (paragraph.type.name !== "paragraph" || !next || !markGoes(paragraph, mode)) return false;
160
+ if (tableGoes(next, mode)) return true;
161
+ if (mode !== "reject" || paragraphGoes(paragraph, mode)) return false;
162
+ let following = index + 1;
163
+ while (following < parent.childCount) {
164
+ const block = parent.child(following);
165
+ if (!paragraphGoes(block, mode)) break;
166
+ following += 1;
167
+ }
168
+ return following > index + 1 && parent.maybeChild(following)?.type.spec["tableRole"] === "table";
169
+ };
121
170
  const runsIntoFollowingTable = ({ parent, index, mode }) => {
122
171
  const paragraph = parent.child(index);
123
172
  const next = parent.maybeChild(index + 1);
@@ -125,7 +174,6 @@ const runsIntoFollowingTable = ({ parent, index, mode }) => {
125
174
  const remains = paragraphRemainsOf(paragraph);
126
175
  const holdsContent = paragraphContentOf(paragraph);
127
176
  const needsCommentHost = !holdsContent && remains.blocks.length === 0 && remains.comments.length > 0;
128
- const removedMark = paragraph.type.schema.marks[mode === "accept" ? "deletion" : "insertion"];
129
177
  let hasPrecedingCommentHost = false;
130
178
  for (let previousIndex = needsCommentHost ? index - 1 : -1; previousIndex >= 0; previousIndex -= 1) {
131
179
  const previous = parent.child(previousIndex);
@@ -134,7 +182,7 @@ const runsIntoFollowingTable = ({ parent, index, mode }) => {
134
182
  hasPrecedingCommentHost = true;
135
183
  break;
136
184
  }
137
- const retained = paragraphAfterContentRemoval(previous, removedMark);
185
+ const retained = paragraphAfterContentRemoval(previous, mode);
138
186
  if (paragraphContentOf(retained)) break;
139
187
  if (paragraphRemainsOf(retained).blocks.length > 0) {
140
188
  hasPrecedingCommentHost = true;
@@ -146,22 +194,60 @@ const runsIntoFollowingTable = ({ parent, index, mode }) => {
146
194
  holdsContent,
147
195
  markWasAdded: expectParagraphAttrs(paragraph).pPrMark?.kind === "ins" || expectParagraphAttrs(paragraph).pPrMark?.kind === "moveTo",
148
196
  containerStillEndsWithParagraph: false,
149
- followingTableCanReceiveRunIn: expectParagraphAttrs(paragraph)._sectionProperties === void 0 && !runInCellPending(next),
197
+ followingTableCanReceiveRunIn: expectParagraphAttrs(paragraph)._sectionProperties === void 0 && !runInCellPending(next, mode),
150
198
  needsFollowingCommentHost: needsCommentHost && !hasPrecedingCommentHost
151
199
  }).kind === "run-in";
152
200
  };
153
201
  /**
202
+ * Stand each block this resolution removes before the paragraph whose mark goes
203
+ * right before it, so that paragraph meets what it will run on into. The block
204
+ * is going, so where it goes from does not show; the paragraph's words keep
205
+ * mapping to themselves.
206
+ */
207
+ const moveRemovedBlocksBeforeParagraphs = (tr, mode) => {
208
+ const maps = [];
209
+ for (;;) {
210
+ let move = null;
211
+ tr.doc.descendants((node, position, parent, index) => {
212
+ if (move !== null) return false;
213
+ if (node.type.name !== "paragraph") return !node.isTextblock;
214
+ const block = parent?.maybeChild(index + 1);
215
+ if (parent && block && paragraphBeforeRemovedBlock({
216
+ parent,
217
+ index,
218
+ mode
219
+ })) move = {
220
+ position,
221
+ paragraph: node,
222
+ block
223
+ };
224
+ return false;
225
+ });
226
+ if (move === null) return maps;
227
+ const { position, paragraph, block } = move;
228
+ const blockPosition = position + paragraph.nodeSize;
229
+ tr.step(new ReplaceStep(position, blockPosition + block.nodeSize, new Slice(Fragment.fromArray([block, paragraph]), 0, 0)));
230
+ maps.push(new StepMap([
231
+ position,
232
+ 0,
233
+ block.nodeSize,
234
+ blockPosition,
235
+ block.nodeSize,
236
+ 0
237
+ ]));
238
+ }
239
+ };
240
+ /**
154
241
  * Before every change in a story is resolved: run each paragraph whose mark
155
242
  * goes and that sits right before a table on into that table. Returns each
156
243
  * paragraph that took words (its position after the step count given) and
157
244
  * the paragraph-property sources handed over.
158
245
  */
159
246
  const runParagraphsIntoTables = (tr, mode, styleResolver) => {
160
- const removedMark = tr.doc.type.schema.marks[mode === "accept" ? "deletion" : "insertion"];
161
247
  const tried = /* @__PURE__ */ new Set();
162
248
  const targets = [];
163
249
  const transfers = [];
164
- const maps = [];
250
+ const maps = moveRemovedBlocksBeforeParagraphs(tr, mode);
165
251
  for (;;) {
166
252
  let candidate = null;
167
253
  tr.doc.descendants((node, pos, parent, index) => {
@@ -183,7 +269,7 @@ const runParagraphsIntoTables = (tr, mode, styleResolver) => {
183
269
  tr,
184
270
  paragraphPos: candidate,
185
271
  styleResolver,
186
- removedMark
272
+ everyChange: mode
187
273
  });
188
274
  if (ranIn !== null) {
189
275
  targets.push({
@@ -196,4 +282,4 @@ const runParagraphsIntoTables = (tr, mode, styleResolver) => {
196
282
  }
197
283
  }
198
284
  };
199
- export { runInCellPending, runParagraphIntoTable, runParagraphsIntoTables, runsIntoFollowingTable };
285
+ export { paragraphBeforeRemovedBlock, runInCellPending, runParagraphIntoTable, runParagraphsIntoTables, runsIntoFollowingTable };
@@ -30,8 +30,10 @@ const dispatchEditorTextInput = (view, input) => {
30
30
  });
31
31
  const tr = view.state.tr;
32
32
  if (formattingMarks !== void 0) tr.setStoredMarks(formattingMarks);
33
+ const typesOverSelection = !selection.empty && selection.from === from && selection.to === to;
34
+ if (typesOverSelection && !selection.$from.parent.inlineContent) return tr.insertText(text);
33
35
  tr.insertText(text, from, to);
34
- return !selection.empty && selection.from === from && selection.to === to ? tr.setSelection(TextSelection.create(tr.doc, from + text.length)) : tr;
36
+ return typesOverSelection ? tr.setSelection(TextSelection.create(tr.doc, from + text.length)) : tr;
35
37
  }
36
38
  const start = Selection.atStart(view.state.doc);
37
39
  const end = Selection.atEnd(view.state.doc);
@@ -3,7 +3,9 @@ import { readBlockCustomXmlAttrs, readBlockSdtAttrs, readCharacterSpacingMarkAtt
3
3
  import { readBookmarkBoundaryAttrs } from "./bookmarkBoundaryAttrs.js";
4
4
  import { readCommentRangeBoundaryAttrs } from "./commentRangeBoundaryAttrs.js";
5
5
  import { readCommentReferenceAttrs } from "./commentReferenceAttrs.js";
6
+ import { HYPHEN_ELEMENT_MARK_NAME } from "./conversion/hyphenTextCarriers.js";
6
7
  import { fieldRequiresStructuredContent } from "./fieldRepresentation.js";
8
+ import { readHyphenElementMarkAttrs } from "./hyphenElementAttrs.js";
7
9
  import { readMoveRangeBoundaryAttrs } from "./moveRangeBoundaryAttrs.js";
8
10
  import { readRangeAnchorAttrs } from "./rangeAnchorAttrs.js";
9
11
  import { isRunContentInlineNodeName } from "./runContentProjection.js";
@@ -15,6 +17,7 @@ import { readTextBoxAnchorAttrs } from "./textBoxAnchorAttrs.js";
15
17
  const COMPLEX_FIELD_RESULT_MARK_NAMES = /* @__PURE__ */ new Set([
16
18
  ...RUN_FORMATTING_MARK_NAMES,
17
19
  RUN_IDENTITY_MARK_NAME,
20
+ HYPHEN_ELEMENT_MARK_NAME,
18
21
  "runPropertyChange",
19
22
  "footnoteRef"
20
23
  ]);
@@ -352,6 +355,9 @@ const validateMark = (mark, markPath, issues) => {
352
355
  case "textShadow":
353
356
  case "textOutline":
354
357
  case "rtl": return;
358
+ case "hyphenElement":
359
+ appendAttrIssues(markPath, readHyphenElementMarkAttrs(mark), issues);
360
+ return;
355
361
  case "textEffect":
356
362
  appendAttrIssues(markPath, readTextEffectMarkAttrs(mark), issues);
357
363
  return;
@@ -5,6 +5,7 @@ import { formatParseWarnings } from "../docx/parseWarningMessage.js";
5
5
  import { DEFAULT_TAB_STOP_TWIPS as DEFAULT_TAB_STOP_TWIPS$1 } from "../docx/settingsParser.js";
6
6
  import { STYLE_SET_PART, normalizeDocumentStyleSet } from "../style-sets/styleSetNormalization.js";
7
7
  import "../style-sets/types.js";
8
+ import { generateHexId } from "./hexId.js";
8
9
  import { panic } from "better-result";
9
10
  import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
10
11
  // src/utils/createDocument.ts
@@ -368,17 +369,19 @@ function createEmptyDocument(options = {}) {
368
369
  if (options.marginLeft !== void 0) sectionProps.marginLeft = Math.round(options.marginLeft);
369
370
  if (options.marginRight !== void 0) sectionProps.marginRight = Math.round(options.marginRight);
370
371
  const initialText = options.initialText ?? "";
372
+ const run = {
373
+ type: "run",
374
+ content: initialText ? [{
375
+ type: "text",
376
+ text: initialText
377
+ }] : [],
378
+ formatting: {}
379
+ };
371
380
  const documentBody = {
372
381
  content: [{
373
382
  type: "paragraph",
374
- content: [{
375
- type: "run",
376
- content: initialText ? [{
377
- type: "text",
378
- text: initialText
379
- }] : [],
380
- formatting: {}
381
- }],
383
+ paraId: generateHexId(),
384
+ content: [run],
382
385
  formatting: { styleId: styleSet?.initialParagraphStyleId ?? "Normal" }
383
386
  }],
384
387
  finalSectionProperties: sectionProps
@@ -6,7 +6,7 @@ import { resolveColor, resolveHighlightToCss, resolveShadingColor } from "./colo
6
6
  import { resolveFontFamily, resolveThemeFont } from "./fontResolver.js";
7
7
  import { getHorizontalScaleFactor, normalizeHorizontalScalePercent } from "./horizontalScale.js";
8
8
  import { textFlowCss } from "./textDirectionFlow.js";
9
- import { AUTO_PARAGRAPH_SPACING_PX, eighthsToPixels, formatPx, halfPointsToPixels, halfPointsToPoints, twipsToPixels } from "./units.js";
9
+ import { AUTO_PARAGRAPH_SPACING_PX, eighthsToPixels, formatPx, halfPointsToPixels, halfPointsToPoints, hangingFirstLineIndent, twipsToPixels } from "./units.js";
10
10
  import { statesNoBorder } from "@stll/docx-core/model";
11
11
  // src/utils/formatToStyle.ts
12
12
  /**
@@ -119,7 +119,7 @@ function paragraphToStyle(formatting, theme) {
119
119
  if (formatting.indentLeft !== void 0) style.marginLeft = formatPx(twipsToPixels(formatting.indentLeft));
120
120
  if (formatting.indentRight !== void 0) style.marginRight = formatPx(twipsToPixels(formatting.indentRight));
121
121
  if (formatting.indentFirstLine !== void 0) {
122
- const textIndent = formatting.hangingIndent ? -Math.abs(formatting.indentFirstLine) : formatting.indentFirstLine;
122
+ const textIndent = formatting.hangingIndent ? hangingFirstLineIndent(formatting.indentFirstLine) : formatting.indentFirstLine;
123
123
  style.textIndent = formatPx(twipsToPixels(textIndent));
124
124
  }
125
125
  if (formatting.borders) {
@@ -22,6 +22,11 @@ export declare const PIXELS_PER_INCH: 96;
22
22
  * twips conversion. See eigenpal/docx-editor#823.
23
23
  */
24
24
  export declare const AUTO_PARAGRAPH_SPACING_PX: number;
25
+ /**
26
+ * The `indentFirstLine` a hanging indent of `magnitude` twips stores: negated,
27
+ * but never as `-0`, which compares unequal to the `0` a model holds elsewhere.
28
+ */
29
+ export declare const hangingFirstLineIndent: (magnitude: number) => number;
25
30
  /**
26
31
  * Convert twips to pixels (at 96 DPI)
27
32
  *
@@ -34,6 +34,11 @@ const PIXELS_PER_INCH = STANDARD_DPI;
34
34
  */
35
35
  const AUTO_PARAGRAPH_SPACING_PX = pointsToPixels(14);
36
36
  /**
37
+ * The `indentFirstLine` a hanging indent of `magnitude` twips stores: negated,
38
+ * but never as `-0`, which compares unequal to the `0` a model holds elsewhere.
39
+ */
40
+ const hangingFirstLineIndent = (magnitude) => magnitude === 0 ? 0 : -Math.abs(magnitude);
41
+ /**
37
42
  * Convert twips to pixels (at 96 DPI)
38
43
  *
39
44
  * 1 inch = 1440 twips = 96 pixels
@@ -146,4 +151,4 @@ function clamp(value, min, max) {
146
151
  function formatPx(px) {
147
152
  return `${roundPixels(px)}px`;
148
153
  }
149
- export { AUTO_PARAGRAPH_SPACING_PX, PIXELS_PER_INCH, TWIPS_PER_INCH, clamp, eighthsToPixels, emuToPixels, emuToStrokePixels, emuToTwips, formatPx, halfPointsToPixels, halfPointsToPoints, pixelsToEmu, pixelsToTwips, pointsToHalfPoints, pointsToPixels, roundPixels, twipsToEmu, twipsToPixels };
154
+ export { AUTO_PARAGRAPH_SPACING_PX, PIXELS_PER_INCH, TWIPS_PER_INCH, clamp, eighthsToPixels, emuToPixels, emuToStrokePixels, emuToTwips, formatPx, halfPointsToPixels, halfPointsToPoints, hangingFirstLineIndent, pixelsToEmu, pixelsToTwips, pointsToHalfPoints, pointsToPixels, roundPixels, twipsToEmu, twipsToPixels };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.66.1",
3
+ "version": "0.66.2",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",
@@ -173,7 +173,7 @@
173
173
  "perf": "bun scripts/profile-editor.ts"
174
174
  },
175
175
  "dependencies": {
176
- "@stll/docx-core": "^0.41.0",
176
+ "@stll/docx-core": "^0.42.0",
177
177
  "@stll/docx-utils": "^0.2.0",
178
178
  "@stll/template-conditions": "^0.6.2",
179
179
  "@stll/utf8-boundaries": "0.2.0",
@@ -198,7 +198,7 @@
198
198
  "prosemirror-state": "^1.4.4",
199
199
  "prosemirror-tables": "^1.8.5",
200
200
  "prosemirror-transform": "^1.12.4",
201
- "prosemirror-view": "^1.42.7",
201
+ "prosemirror-view": "^1.42.8",
202
202
  "y-prosemirror": "^1.3.7",
203
203
  "y-protocols": "^1.0.6",
204
204
  "yjs": "^13.6.33"