@portone/docx-editor 0.3.0 → 0.4.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 (90) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/DocxEditor.js +1 -0
  3. package/dist/core.d.ts +1 -1
  4. package/dist/docx/comments/writing.js +14 -8
  5. package/dist/docx/exportDocx.js +20 -4
  6. package/dist/docx/formatting/attrs.d.ts +20 -2
  7. package/dist/docx/formatting/attrs.js +57 -2
  8. package/dist/docx/formatting/context.d.ts +10 -2
  9. package/dist/docx/formatting/context.js +23 -5
  10. package/dist/docx/formatting/resolve.d.ts +10 -4
  11. package/dist/docx/formatting/resolve.js +31 -11
  12. package/dist/docx/formatting/styles.d.ts +25 -2
  13. package/dist/docx/formatting/styles.js +84 -3
  14. package/dist/docx/hyperlink.d.ts +14 -4
  15. package/dist/docx/hyperlink.js +4 -1
  16. package/dist/docx/importDocx.js +46 -37
  17. package/dist/docx/importTable.d.ts +2 -2
  18. package/dist/docx/importTable.js +52 -55
  19. package/dist/docx/invariants.js +26 -13
  20. package/dist/docx/newLists.d.ts +30 -0
  21. package/dist/docx/newLists.js +24 -1
  22. package/dist/docx/numberingPlanner.d.ts +6 -3
  23. package/dist/docx/numberingPlanner.js +38 -7
  24. package/dist/docx/session.d.ts +1 -1
  25. package/dist/docx/session.js +11 -1
  26. package/dist/docx/tableFormatting/conditions.d.ts +76 -0
  27. package/dist/docx/tableFormatting/conditions.js +196 -0
  28. package/dist/docx/tableFormatting/editing.d.ts +7 -3
  29. package/dist/docx/tableFormatting/editing.js +4 -5
  30. package/dist/docx/tableFormatting/reading.d.ts +49 -19
  31. package/dist/docx/tableFormatting/reading.js +109 -35
  32. package/dist/docx/tableFormatting.d.ts +1 -0
  33. package/dist/docx/tableFormatting.js +1 -0
  34. package/dist/docx/tableTemplate.js +12 -12
  35. package/dist/editor/commands/formatting/propertyCommands.js +6 -1
  36. package/dist/editor/commands/listCommands.d.ts +6 -3
  37. package/dist/editor/commands/listCommands.js +73 -28
  38. package/dist/editor/commands/paragraphCommands.js +6 -1
  39. package/dist/editor/createEditor.js +1 -1
  40. package/dist/editor/editorDocument.d.ts +7 -6
  41. package/dist/editor/editorDocument.js +18 -6
  42. package/dist/editor/externalClipboard.d.ts +13 -2
  43. package/dist/editor/externalClipboard.js +64 -34
  44. package/dist/editor/paragraphEdits.d.ts +16 -1
  45. package/dist/editor/paragraphEdits.js +13 -5
  46. package/dist/editor/paragraphPlacement.d.ts +11 -0
  47. package/dist/editor/paragraphPlacement.js +18 -0
  48. package/dist/editor/plugins/imagePaste.js +7 -4
  49. package/dist/editor/plugins/numberingDecorations.d.ts +10 -4
  50. package/dist/editor/plugins/numberingDecorations.js +43 -2
  51. package/dist/editor/plugins/paragraphDisplay.js +9 -3
  52. package/dist/editor/plugins/tableDisplay.js +19 -5
  53. package/dist/model/format.d.ts +50 -0
  54. package/dist/model/format.js +65 -0
  55. package/dist/numbering/listRegistry.d.ts +37 -0
  56. package/dist/numbering/listRegistry.js +112 -0
  57. package/dist/numbering/listTemplate.d.ts +26 -17
  58. package/dist/numbering/listTemplate.js +66 -20
  59. package/dist/numbering/markers.d.ts +15 -6
  60. package/dist/numbering/markers.js +26 -59
  61. package/dist/numbering/parseNumbering.d.ts +70 -5
  62. package/dist/numbering/parseNumbering.js +100 -26
  63. package/dist/numbering/spellers.d.ts +15 -0
  64. package/dist/numbering/spellers.js +96 -0
  65. package/dist/numbering/writeNumbering.d.ts +5 -3
  66. package/dist/numbering/writeNumbering.js +15 -15
  67. package/dist/ooxml/conformance.d.ts +28 -0
  68. package/dist/ooxml/conformance.js +20 -0
  69. package/dist/ooxml/errors.d.ts +5 -5
  70. package/dist/ooxml/names.d.ts +9 -0
  71. package/dist/ooxml/names.js +8 -1
  72. package/dist/ooxml/partSplice.d.ts +1 -1
  73. package/dist/ooxml/partSplice.js +10 -3
  74. package/dist/ooxml/xml.d.ts +8 -9
  75. package/dist/ooxml/xml.js +6 -6
  76. package/dist/page/PageGuides.d.ts +1 -1
  77. package/dist/page/PageGuides.js +0 -13
  78. package/dist/page/pageLayout.d.ts +0 -10
  79. package/dist/page/pageLayout.js +3 -10
  80. package/dist/page/usePageLayout.d.ts +0 -7
  81. package/dist/page/usePageLayout.js +2 -9
  82. package/dist/schema/attrRoles.js +8 -1
  83. package/dist/schema/docxSchema.js +32 -3
  84. package/dist/styles/classNames.d.ts +13 -2
  85. package/dist/styles/classNames.js +13 -2
  86. package/dist/styles.css +16 -18
  87. package/dist/table/cellFormatting.js +1 -2
  88. package/dist/table/gridBorders.d.ts +11 -15
  89. package/dist/table/gridBorders.js +32 -18
  90. package/package.json +1 -1
@@ -5,12 +5,24 @@ import {
5
5
  Slice
6
6
  } from "prosemirror-model";
7
7
  import { Plugin } from "prosemirror-state";
8
- import { paragraphAttrsFor, styleIdOf } from "../docx/formatting.js";
8
+ import {
9
+ paragraphAttrsFor,
10
+ styleIdOf
11
+ } from "../docx/formatting.js";
9
12
  import {
10
13
  withListNumbering,
11
14
  withParagraphStyle
12
15
  } from "../docx/paraProps.js";
13
- import { MAX_ILVL, nextNumId } from "../numbering/listTemplate.js";
16
+ import {
17
+ listsWorn,
18
+ NEW_LISTS_ATTR,
19
+ newListsValue
20
+ } from "../numbering/listRegistry.js";
21
+ import {
22
+ allocateList,
23
+ MAX_ILVL,
24
+ templateList
25
+ } from "../numbering/listTemplate.js";
14
26
  import { docxSchema, isPageBreak } from "../schema/index.js";
15
27
  import { editorClassNames } from "../styles/classNames.js";
16
28
  import { PASTED_IMAGE_ATTRIBUTE } from "./clipboard/images.js";
@@ -21,14 +33,11 @@ import {
21
33
  safeHref,
22
34
  withInlineStyle
23
35
  } from "./clipboard/inlineFormatting.js";
24
- import { listRefOf } from "./commands/listCommands.js";
36
+ import { numIdsIn } from "./commands/listCommands.js";
25
37
  import { documentFormatting, documentParagraphStyles } from "./documentStyles.js";
26
38
  import { insertPlainText } from "./plainText.js";
27
39
  import { moveCaretToDrop } from "./plugins/dropCaret.js";
28
- import {
29
- canStartNewList,
30
- documentNumbering
31
- } from "./plugins/numberingDecorations.js";
40
+ import { canStartNewList } from "./plugins/numberingDecorations.js";
32
41
  var BLOCK_TAGS = /* @__PURE__ */ new Set([
33
42
  "ADDRESS",
34
43
  "ARTICLE",
@@ -256,29 +265,17 @@ function listParagraphProps(list) {
256
265
  indent: { kind: "keep" }
257
266
  });
258
267
  }
259
- function paragraphAttrs(state, list, paragraph) {
268
+ function paragraphAttrs(formatting, list, paragraph) {
260
269
  const props = listParagraphProps(list) ?? paragraph;
261
- return props ? {
262
- pPr: props.pPr,
263
- ...paragraphAttrsFor(props.pPr, documentFormatting(state))
264
- } : null;
265
- }
266
- function usedNumIds(state) {
267
- const used = new Set(documentNumbering(state).lists.keys());
268
- state.doc.descendants((node) => {
269
- if (node.type !== docxSchema.nodes.paragraph) return true;
270
- const ref = listRefOf(node);
271
- if (ref) used.add(ref.numId);
272
- return false;
273
- });
274
- return used;
270
+ return props ? { pPr: props.pPr, ...paragraphAttrsFor(props.pPr, formatting) } : null;
275
271
  }
276
272
  var HtmlReader = class {
277
273
  constructor(state, preserveParagraphStyles, images) {
278
274
  this.state = state;
279
275
  this.preserveParagraphStyles = preserveParagraphStyles;
280
276
  this.images = images;
281
- this.used = usedNumIds(state);
277
+ this.formatting = documentFormatting(state);
278
+ this.used = numIdsIn(state.doc);
282
279
  this.canCreateLists = canStartNewList(state);
283
280
  }
284
281
  state;
@@ -287,6 +284,16 @@ var HtmlReader = class {
287
284
  blocks = [];
288
285
  used;
289
286
  canCreateLists;
287
+ /**
288
+ * What the pasted paragraphs are resolved against: the document's context, holding the
289
+ * definition of each pasted list as it is read, so that an item is drawn against the list it
290
+ * is joining rather than against a list nothing yet defines.
291
+ */
292
+ formatting;
293
+ /** The definitions of every list started while editing, the pasted ones among them */
294
+ get newLists() {
295
+ return this.formatting.numbering.added;
296
+ }
290
297
  read(root) {
291
298
  this.readFlow(root, { style: {}, href: null, preserveWhitespace: false });
292
299
  return this.blocks;
@@ -318,7 +325,7 @@ var HtmlReader = class {
318
325
  }
319
326
  this.blocks.push(
320
327
  docxSchema.nodes.paragraph.create(
321
- paragraphAttrs(this.state, list, paragraph),
328
+ paragraphAttrs(this.formatting, list, paragraph),
322
329
  content
323
330
  )
324
331
  );
@@ -354,11 +361,20 @@ var HtmlReader = class {
354
361
  }
355
362
  flush();
356
363
  }
364
+ /**
365
+ * The number a pasted list takes, registered with the definition that list is drawn and written
366
+ * with. A pasted list is started here just as the list button starts one.
367
+ */
357
368
  takeNumId(kind) {
358
369
  if (!this.canCreateLists) return null;
359
- const id = nextNumId(this.used, kind);
360
- this.used.add(id);
361
- return id;
370
+ const started = allocateList(
371
+ this.formatting.numbering,
372
+ this.used,
373
+ templateList(kind)
374
+ );
375
+ this.formatting = { ...this.formatting, numbering: started.numbering };
376
+ this.used.add(started.numId);
377
+ return started.numId;
362
378
  }
363
379
  readList(list, context, level, inherited) {
364
380
  const kind = list.tagName === "UL" ? "bullet" : "numbered";
@@ -407,15 +423,28 @@ function richHtmlSlice(state, document, source, images = /* @__PURE__ */ new Map
407
423
  const template = document.createElement("template");
408
424
  template.innerHTML = source;
409
425
  const open = sliceDepth(template.content);
410
- const blocks = new HtmlReader(state, open === 0, images).read(
411
- template.content
426
+ const reader = new HtmlReader(state, open === 0, images);
427
+ const blocks = reader.read(template.content);
428
+ return blocks.length === 0 ? null : {
429
+ slice: new Slice(Fragment.fromArray([...blocks]), open, open),
430
+ newLists: reader.newLists
431
+ };
432
+ }
433
+ function withPastedContent(tr, content) {
434
+ return tr.setDocAttribute(
435
+ NEW_LISTS_ATTR,
436
+ newListsValue(listsWorn(content.newLists, numIdsIn(tr.doc)))
412
437
  );
413
- return blocks.length === 0 ? null : new Slice(Fragment.fromArray([...blocks]), open, open);
414
438
  }
415
439
  function insertRichHtml(view, source) {
416
- const slice = richHtmlSlice(view.state, view.dom.ownerDocument, source);
417
- if (slice === null) return false;
418
- view.dispatch(view.state.tr.replaceSelection(slice).scrollIntoView());
440
+ const content = richHtmlSlice(view.state, view.dom.ownerDocument, source);
441
+ if (content === null) return false;
442
+ view.dispatch(
443
+ withPastedContent(
444
+ view.state.tr.replaceSelection(content.slice),
445
+ content
446
+ ).scrollIntoView()
447
+ );
419
448
  return true;
420
449
  }
421
450
  function insertClipboardData(view, data) {
@@ -452,5 +481,6 @@ export {
452
481
  externalClipboard,
453
482
  insertClipboardData,
454
483
  insertRichHtml,
455
- richHtmlSlice
484
+ richHtmlSlice,
485
+ withPastedContent
456
486
  };
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import type { Node as PMNode } from "prosemirror-model";
10
10
  import type { EditorState, Transaction } from "prosemirror-state";
11
+ import { type FormattingContext } from "../docx/formatting";
11
12
  import type { ParagraphProps } from "../docx/paraProps";
12
13
  export interface ParagraphSpot {
13
14
  pos: number;
@@ -33,5 +34,19 @@ export declare function editableParagraphs(state: EditorState): ParagraphSpot[];
33
34
  export declare function paragraphPPr(node: PMNode): string | null;
34
35
  /** How to edit a single paragraph. Null skips that paragraph */
35
36
  export type ParagraphSurgery = (node: PMNode) => ParagraphProps | null;
37
+ /** What an edit does beyond swapping the fragments of the paragraphs it names */
38
+ export interface ParagraphEditExtras {
39
+ /**
40
+ * The context the new display values are resolved against, which is the state's own unless the
41
+ * edit is itself changing it. An edit that starts a list hands in the context that already
42
+ * holds it, so the paragraph is drawn against the list it is joining in that same transaction.
43
+ */
44
+ formatting?: FormattingContext;
45
+ /**
46
+ * What else the edit records in the same transaction, such as the definition of the list the
47
+ * paragraphs are joining, so that undo takes the two back together.
48
+ */
49
+ alongside?: (tr: Transaction) => void;
50
+ }
36
51
  /** Edits the selected paragraphs in a single transaction. Does nothing when there is nothing to edit */
37
- export declare function editParagraphs(state: EditorState, dispatch: ((tr: Transaction) => void) | undefined, surgery: ParagraphSurgery): boolean;
52
+ export declare function editParagraphs(state: EditorState, dispatch: ((tr: Transaction) => void) | undefined, surgery: ParagraphSurgery, extras?: ParagraphEditExtras): boolean;
@@ -3,6 +3,7 @@ import { paragraphAttrsFor } from "../docx/formatting.js";
3
3
  import { docxSchema } from "../schema/index.js";
4
4
  import { editShut } from "../schema/guards.js";
5
5
  import { documentFormatting } from "./documentStyles.js";
6
+ import { paragraphPlacementAt } from "./paragraphPlacement.js";
6
7
  function selectedParagraphs(state) {
7
8
  const spots = /* @__PURE__ */ new Map();
8
9
  for (const range of state.selection.ranges) {
@@ -23,25 +24,32 @@ function paragraphPPr(node) {
23
24
  const pPr = node.attrs.pPr;
24
25
  return typeof pPr === "string" ? pPr : null;
25
26
  }
26
- function writeChanges(state, changed) {
27
+ function writeChanges(state, changed, extras) {
27
28
  const tr = state.tr;
28
- const formatting = documentFormatting(state);
29
+ const formatting = extras.formatting ?? documentFormatting(state);
29
30
  for (const { spot, props } of changed) {
30
31
  tr.setNodeMarkup(tr.mapping.map(spot.pos), void 0, {
31
32
  ...spot.node.attrs,
32
33
  pPr: props.pPr,
33
- ...paragraphAttrsFor(props.pPr, formatting)
34
+ ...paragraphAttrsFor(
35
+ props.pPr,
36
+ formatting,
37
+ paragraphPlacementAt(state.doc, spot.pos)
38
+ )
34
39
  });
35
40
  }
41
+ extras.alongside?.(tr);
36
42
  return tr;
37
43
  }
38
- function editParagraphs(state, dispatch, surgery) {
44
+ function editParagraphs(state, dispatch, surgery, extras = {}) {
39
45
  const changed = editableParagraphs(state).flatMap((spot) => {
40
46
  const props = surgery(spot.node);
41
47
  return props ? [{ spot, props }] : [];
42
48
  });
43
49
  if (changed.length === 0) return false;
44
- if (dispatch) dispatch(writeChanges(state, changed).scrollIntoView());
50
+ if (dispatch) {
51
+ dispatch(writeChanges(state, changed, extras).scrollIntoView());
52
+ }
45
53
  return true;
46
54
  }
47
55
  export {
@@ -0,0 +1,11 @@
1
+ /** Finds the table-style conditions at an editor position for both reads and writes. */
2
+ import type { Node as PMNode } from "prosemirror-model";
3
+ import type { ParagraphPlacement } from "../docx/formatting";
4
+ /**
5
+ * The part of a table the paragraph stands in, which its table style dresses it by. null for a
6
+ * paragraph of the body, and for one in a cell of a table this editor did not build.
7
+ *
8
+ * The cell is found by walking out of the paragraph rather than passed down, because the walk that
9
+ * runs the derivers goes over the blocks and hands each of them nothing but its own position.
10
+ */
11
+ export declare function paragraphPlacementAt(doc: PMNode, pos: number): ParagraphPlacement | null;
@@ -0,0 +1,18 @@
1
+ // src/editor/paragraphPlacement.ts
2
+ import { TableMap } from "prosemirror-tables";
3
+ import { docxSchema } from "../schema/index.js";
4
+ import { cellDefaultsAt, tableCellSources } from "../table/gridBorders.js";
5
+ function paragraphPlacementAt(doc, pos) {
6
+ const $pos = doc.resolve(pos);
7
+ for (let depth = $pos.depth; depth >= 2; depth -= 1) {
8
+ if ($pos.node(depth).type !== docxSchema.nodes.tableCell) continue;
9
+ const table = $pos.node(depth - 2);
10
+ if (table.type !== docxSchema.nodes.table) return null;
11
+ const cell = $pos.before(depth) - $pos.start(depth - 2);
12
+ return cellDefaultsAt(TableMap.get(table), cell, tableCellSources(table)).placement;
13
+ }
14
+ return null;
15
+ }
16
+ export {
17
+ paragraphPlacementAt
18
+ };
@@ -8,7 +8,7 @@ import {
8
8
  resolveClipboardImages
9
9
  } from "../clipboard/images.js";
10
10
  import { documentBodyHeightPx, documentBodyWidthPx } from "../documentStyles.js";
11
- import { richHtmlSlice } from "../externalClipboard.js";
11
+ import { richHtmlSlice, withPastedContent } from "../externalClipboard.js";
12
12
  import { insertPlainTextAt } from "../plainText.js";
13
13
  var LOAD_TIMEOUT_MS = 15e3;
14
14
  var imagePasteKey = new PluginKey("docxImagePaste");
@@ -87,15 +87,18 @@ function imagePaste() {
87
87
  if (!range) return;
88
88
  removeMarker(task);
89
89
  if (task.view.isDestroyed) return;
90
- const slice = resolved === null || resolved.images.size === 0 ? null : richHtmlSlice(
90
+ const content = resolved === null || resolved.images.size === 0 ? null : richHtmlSlice(
91
91
  task.view.state,
92
92
  task.view.dom.ownerDocument,
93
93
  resolved.source,
94
94
  resolved.images
95
95
  );
96
- if (slice !== null) {
96
+ if (content !== null) {
97
97
  task.view.dispatch(
98
- task.view.state.tr.replaceRange(range.from, range.to, slice).scrollIntoView()
98
+ withPastedContent(
99
+ task.view.state.tr.replaceRange(range.from, range.to, content.slice),
100
+ content
101
+ ).scrollIntoView()
99
102
  );
100
103
  } else {
101
104
  insertPlainTextAt(task.view, task.text, range.from, range.to);
@@ -6,7 +6,8 @@
6
6
  import type { Node as PMNode } from "prosemirror-model";
7
7
  import { type EditorState, Plugin } from "prosemirror-state";
8
8
  import { DecorationSet } from "prosemirror-view";
9
- import type { Numbering } from "../../numbering/parseNumbering";
9
+ import { type RunFormat } from "../../model/format";
10
+ import type { LevelAlign, LevelSuffix, Numbering } from "../../numbering/parseNumbering";
10
11
  /** A number to draw on screen and the paragraph position it attaches to */
11
12
  export interface PlacedMarker {
12
13
  from: number;
@@ -19,6 +20,12 @@ export interface PlacedMarker {
19
20
  indentEndPt: number | null;
20
21
  /** The first-line indentation, under the same rule */
21
22
  textIndentPt: number | null;
23
+ /** What the level puts between the number and the text (`w:suff`) */
24
+ suffix: LevelSuffix;
25
+ /** Where the number sits inside the width kept for it (`w:lvlJc`) */
26
+ align: LevelAlign;
27
+ /** The formatting the level puts on the number alone (`lvl/rPr`) */
28
+ run: RunFormat | null;
22
29
  }
23
30
  /** Walks a document to find the paragraphs that get numbers and what those numbers are */
24
31
  export declare function paragraphMarkers(doc: PMNode, numbering: Numbering): PlacedMarker[];
@@ -27,9 +34,8 @@ export declare function markerDecorations(doc: PMNode, numbering: Numbering): De
27
34
  export declare function documentNumbering(state: EditorState): Numbering;
28
35
  /**
29
36
  * Whether a new list can be started in this document.
30
- * A document without numbering.xml has nowhere to write a new definition, so it would be
31
- * blocked at export time. In such a document the commands that start a list do not apply
32
- * in the first place.
37
+ * A document with nowhere to write a new definition would be blocked at export time, so in such
38
+ * a document the commands that start a list do not apply in the first place.
33
39
  */
34
40
  export declare function canStartNewList(state: EditorState): boolean;
35
41
  /** Draws the numbers this document's lists put in front of their paragraphs */
@@ -1,7 +1,9 @@
1
1
  // src/editor/plugins/numberingDecorations.ts
2
2
  import { Plugin, PluginKey } from "prosemirror-state";
3
3
  import { Decoration, DecorationSet } from "prosemirror-view";
4
- import { toParagraphFormat } from "../../model/format.js";
4
+ import {
5
+ toParagraphFormat
6
+ } from "../../model/format.js";
5
7
  import { computeMarkers } from "../../numbering/markers.js";
6
8
  import { editorAttributes, editorCssVariables } from "../../styles/classNames.js";
7
9
  import { documentOf } from "../editorDocument.js";
@@ -45,13 +47,52 @@ function paragraphMarkers(doc, numbering) {
45
47
  from: spot.pos,
46
48
  to: spot.pos + spot.nodeSize,
47
49
  text: marker.text,
50
+ suffix: marker.suffix,
51
+ align: marker.align,
52
+ run: marker.run,
48
53
  ...markerPlacement(spot.format, marker.indent)
49
54
  }
50
55
  ];
51
56
  });
52
57
  }
58
+ function markerSpacing(marker) {
59
+ if (marker.suffix === "tab") {
60
+ return [`${editorCssVariables.markerWidth}:${marker.widthPt}pt`];
61
+ }
62
+ return [
63
+ `${editorCssVariables.markerWidth}:0`,
64
+ `${editorCssVariables.markerGap}:0`
65
+ ];
66
+ }
67
+ function markerFormat(run) {
68
+ if (!run) return [];
69
+ const css = [];
70
+ if (run.bold !== void 0) {
71
+ css.push(
72
+ `${editorCssVariables.markerFontWeight}:${run.bold ? "bold" : "normal"}`
73
+ );
74
+ }
75
+ if (run.italic !== void 0) {
76
+ css.push(
77
+ `${editorCssVariables.markerFontStyle}:${run.italic ? "italic" : "normal"}`
78
+ );
79
+ }
80
+ if (run.color !== void 0) {
81
+ css.push(`${editorCssVariables.markerColor}:${run.color}`);
82
+ }
83
+ if (run.fontSizePt !== void 0) {
84
+ css.push(`${editorCssVariables.markerFontSize}:${run.fontSizePt}pt`);
85
+ }
86
+ if (run.fontFamily !== void 0) {
87
+ css.push(`${editorCssVariables.markerFontFamily}:${run.fontFamily}`);
88
+ }
89
+ return css;
90
+ }
53
91
  function markerStyle(marker) {
54
- const css = [`${editorCssVariables.markerWidth}:${marker.widthPt}pt`];
92
+ const css = [...markerSpacing(marker), ...markerFormat(marker.run)];
93
+ if (marker.align !== "left") {
94
+ css.push(`${editorCssVariables.markerAlign}:${marker.align}`);
95
+ }
55
96
  if (marker.indentStartPt !== null) {
56
97
  css.push(`margin-inline-start:${marker.indentStartPt}pt`);
57
98
  }
@@ -6,6 +6,7 @@ import {
6
6
  } from "../../docx/formatting.js";
7
7
  import { docxSchema } from "../../schema/index.js";
8
8
  import { paragraphPPr } from "../paragraphEdits.js";
9
+ import { paragraphPlacementAt } from "../paragraphPlacement.js";
9
10
  function carriesNothing(node) {
10
11
  return node.attrs.format === null && node.attrs.styleRun === null;
11
12
  }
@@ -18,12 +19,17 @@ function holdsComposition(node, pos, composing) {
18
19
  var paragraphDisplay = {
19
20
  name: "paragraph",
20
21
  nodeTypes: ["paragraph"],
21
- derive(node, pos, _doc, context, previous) {
22
- if (stands(node, previous) || holdsComposition(node, pos, context.composingAt)) {
22
+ derive(node, pos, doc, context, previous) {
23
+ const placement = paragraphPlacementAt(doc, pos);
24
+ if (placement === null && stands(node, previous) || holdsComposition(node, pos, context.composingAt)) {
23
25
  return [];
24
26
  }
25
27
  const formatting = context.document.formatting;
26
- const paragraph = resolveParagraph(paragraphPPr(node), formatting);
28
+ const paragraph = resolveParagraph(
29
+ paragraphPPr(node),
30
+ formatting,
31
+ placement
32
+ );
27
33
  const derived = [
28
34
  { pos, attrs: paragraphAttrsOf(paragraph) }
29
35
  ];
@@ -1,14 +1,28 @@
1
1
  // src/editor/plugins/tableDisplay.ts
2
+ import { tableStyleAttrs } from "../../docx/formatting.js";
3
+ import { parsePropsXml } from "../../ooxml/props.js";
2
4
  import { cellFixes, sameFormattingInputs } from "../../table/gridBorders.js";
3
5
  var tableDisplay = {
4
6
  name: "table",
5
7
  nodeTypes: ["table"],
6
- derive(table, pos, _doc, _context, previous) {
8
+ derive(table, pos, _doc, context, previous) {
7
9
  if (previous !== null && sameFormattingInputs(previous, table)) return [];
8
- return cellFixes(table).map((fix) => ({
9
- pos: pos + 1 + fix.pos,
10
- attrs: fix.attrs
11
- }));
10
+ const tblPr = table.attrs.tblPr;
11
+ const attrs = {
12
+ ...table.attrs,
13
+ ...tableStyleAttrs(
14
+ typeof tblPr === "string" ? parsePropsXml(tblPr) : null,
15
+ context.document.formatting
16
+ )
17
+ };
18
+ const current = table.hasMarkup(table.type, attrs, table.marks) ? table : table.type.create(attrs, table.content, table.marks);
19
+ return [
20
+ { pos, attrs },
21
+ ...cellFixes(current).map((fix) => ({
22
+ pos: pos + 1 + fix.pos,
23
+ attrs: fix.attrs
24
+ }))
25
+ ];
12
26
  }
13
27
  };
14
28
  export {
@@ -188,6 +188,45 @@ export interface CellMargins {
188
188
  bottomPt: number | null;
189
189
  leftPt: number | null;
190
190
  }
191
+ /**
192
+ * The parts of a table a table style formats conditionally (`w:tblStylePr/@w:type`,
193
+ * ST_TblStyleOverrideType), in the order ECMA-376 Part 1 §17.7.6 applies them: a format standing
194
+ * later in this list is laid over one standing earlier.
195
+ */
196
+ export declare const TABLE_STYLE_CONDITIONS: readonly ["wholeTable", "band1Vert", "band2Vert", "band1Horz", "band2Horz", "firstRow", "lastRow", "firstCol", "lastCol", "nwCell", "neCell", "swCell", "seCell"];
197
+ export type TableStyleOverrideType = (typeof TABLE_STYLE_CONDITIONS)[number];
198
+ /**
199
+ * How many rows one row band and how many columns one column band are made of
200
+ * (`w:tblStyleRowBandSize`, `w:tblStyleColBandSize`). A style that says nothing leaves the side
201
+ * null, and its bands are then one row or one column wide.
202
+ */
203
+ export interface BandSizes {
204
+ row: number | null;
205
+ col: number | null;
206
+ }
207
+ /** The lines one conditional format draws around the region it covers. A side it says nothing about is null */
208
+ export interface CellStyleBorders {
209
+ top: string | null;
210
+ bottom: string | null;
211
+ left: string | null;
212
+ right: string | null;
213
+ }
214
+ /**
215
+ * What one conditional format of a table style lays down for the cells it covers.
216
+ *
217
+ * Its lines are those of the region: `borders` is drawn where a cell lies on the edge of the
218
+ * region and `inside` between the cells within it, the same way a table's own lines are drawn
219
+ * around and between its cells.
220
+ */
221
+ export interface CellStyleFormat {
222
+ background: string | null;
223
+ borders: CellStyleBorders;
224
+ inside: InsideBorders;
225
+ margins: CellMargins;
226
+ verticalAlign: CellVerticalAlign | null;
227
+ }
228
+ /** The conditional formats a table style lays down for its cells, by the part of the table each covers */
229
+ export type TableStyleConditions = Readonly<Partial<Record<TableStyleOverrideType, CellStyleFormat>>>;
191
230
  /**
192
231
  * How high a row stands, in points.
193
232
  * `atLeast` is a floor the row grows past when its text needs the room, and `exact` pins
@@ -251,6 +290,17 @@ export declare function toInsideBorders(value: unknown): InsideBorders | null;
251
290
  * there at all, so it is null.
252
291
  */
253
292
  export declare function toCellMargins(value: unknown): CellMargins | null;
293
+ /**
294
+ * The band sizes a table style laid down, as they come back in from `data-style-bands`.
295
+ * A style that said nothing about either is the same as the attribute not being there at all.
296
+ */
297
+ export declare function toBandSizes(value: unknown): BandSizes | null;
298
+ /**
299
+ * The conditional formats a table style laid down for its cells, as they come back in from
300
+ * `data-style-conditions`. A part no table has is not one this editor draws, so it is dropped;
301
+ * a table whose style dresses no part at all is the same as the attribute not being there.
302
+ */
303
+ export declare function toTableStyleConditions(value: unknown): TableStyleConditions | null;
254
304
  export declare function toRowFormat(value: unknown): RowFormat | null;
255
305
  export declare function toCellFormat(value: unknown): CellFormat | null;
256
306
  /**
@@ -74,6 +74,21 @@ var RUN_FORMAT_KEYS = [
74
74
  "verticalAlign",
75
75
  "lang"
76
76
  ];
77
+ var TABLE_STYLE_CONDITIONS = [
78
+ "wholeTable",
79
+ "band1Vert",
80
+ "band2Vert",
81
+ "band1Horz",
82
+ "band2Horz",
83
+ "firstRow",
84
+ "lastRow",
85
+ "firstCol",
86
+ "lastCol",
87
+ "nwCell",
88
+ "neCell",
89
+ "swCell",
90
+ "seCell"
91
+ ];
77
92
  function isRecord(value) {
78
93
  return typeof value === "object" && value !== null && !Array.isArray(value);
79
94
  }
@@ -261,6 +276,53 @@ function toCellMargins(value) {
261
276
  ];
262
277
  return sides.every((side) => side === null) ? null : margins;
263
278
  }
279
+ function toBandSizes(value) {
280
+ if (!isRecord(value)) return null;
281
+ const size = (side) => typeof side === "number" && Number.isInteger(side) && side > 0 ? side : null;
282
+ const bands = { row: size(value.row), col: size(value.col) };
283
+ return bands.row === null && bands.col === null ? null : bands;
284
+ }
285
+ function toCellStyleBorders(value) {
286
+ if (!isRecord(value)) {
287
+ return { top: null, bottom: null, left: null, right: null };
288
+ }
289
+ return {
290
+ top: matching(BORDER_CSS, value.top) ?? null,
291
+ bottom: matching(BORDER_CSS, value.bottom) ?? null,
292
+ left: matching(BORDER_CSS, value.left) ?? null,
293
+ right: matching(BORDER_CSS, value.right) ?? null
294
+ };
295
+ }
296
+ function toCellStyleFormat(value) {
297
+ if (!isRecord(value)) return null;
298
+ return {
299
+ background: matching(FILL, value.background) ?? null,
300
+ borders: toCellStyleBorders(value.borders),
301
+ inside: toInsideBorders(value.inside) ?? {
302
+ horizontal: null,
303
+ vertical: null
304
+ },
305
+ margins: toCellMargins(value.margins) ?? {
306
+ topPt: null,
307
+ rightPt: null,
308
+ bottomPt: null,
309
+ leftPt: null
310
+ },
311
+ verticalAlign: isCellVerticalAlign(value.verticalAlign) ? value.verticalAlign : null
312
+ };
313
+ }
314
+ function toTableStyleConditions(value) {
315
+ if (!isRecord(value)) return null;
316
+ const conditions = {};
317
+ let found = false;
318
+ for (const type of TABLE_STYLE_CONDITIONS) {
319
+ const format = toCellStyleFormat(value[type]);
320
+ if (format === null) continue;
321
+ conditions[type] = format;
322
+ found = true;
323
+ }
324
+ return found ? conditions : null;
325
+ }
264
326
  function toRowHeight(value) {
265
327
  if (!isRecord(value) || typeof value.pt !== "number") return null;
266
328
  if (value.rule === "exact") return { rule: "exact", pt: value.pt };
@@ -300,9 +362,11 @@ function spanCount(value) {
300
362
  export {
301
363
  NO_FILL,
302
364
  RUN_FORMAT_KEYS,
365
+ TABLE_STYLE_CONDITIONS,
303
366
  isHighlightName,
304
367
  isUnderlineKind,
305
368
  spanCount,
369
+ toBandSizes,
306
370
  toCellFormat,
307
371
  toCellMargins,
308
372
  toColWidth,
@@ -312,6 +376,7 @@ export {
312
376
  toRowFormat,
313
377
  toRunFormat,
314
378
  toTableFormat,
379
+ toTableStyleConditions,
315
380
  toTableWidth,
316
381
  widthNumber,
317
382
  withWidthNumber
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The register of list definitions a document node carries.
3
+ *
4
+ * A list started while editing is registered with the definition it was started with, and the
5
+ * register rides on the document node itself: undo takes a registration back with the edit that
6
+ * made it, and the export writes out what was registered rather than working a definition out for
7
+ * itself. Only what `abstractNumXml` writes in full may be registered, and the value is read back
8
+ * through this file so that nothing else can end up in the register.
9
+ */
10
+ import { type NewList } from "./parseNumbering";
11
+ /** The lists registered on one document, by the number each was given */
12
+ export type NewLists = ReadonlyMap<number, NewList>;
13
+ export declare const NO_NEW_LISTS: NewLists;
14
+ /** The document attr the register travels on */
15
+ export declare const NEW_LISTS_ATTR = "newLists";
16
+ /**
17
+ * What the document node carries for a register.
18
+ *
19
+ * The entries run in the order of the numbers they define so that the same register is always the
20
+ * same value, whichever order the lists were started in, and an empty register is the null a
21
+ * document that never started a list carries: a list left is then a list never started.
22
+ */
23
+ export declare function newListsValue(lists: NewLists): unknown;
24
+ /**
25
+ * The register a document node carries. Empty for a node carrying none, and for one carrying an
26
+ * entry that is not a list definition: a definition the writer cannot read back in full is a
27
+ * definition it must not write half of.
28
+ */
29
+ export declare function newListsOf(value: unknown): NewLists;
30
+ /**
31
+ * The register with the definitions of the lists nothing wears any more taken out.
32
+ *
33
+ * A number goes back into circulation with its definition, so leaving a list is exactly what
34
+ * joining it was not: pressing the list button twice gives back the document that was there
35
+ * before the first press.
36
+ */
37
+ export declare function listsWorn(lists: NewLists, worn: ReadonlySet<number>): NewLists;