@json-to-office/core-docx 0.18.0 → 0.20.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.
@@ -2195,12 +2195,17 @@ function resolveComponentTree(components, theme) {
2195
2195
  async function processDocument(document, theme, themeName) {
2196
2196
  const metadata = createDocumentMetadata(document.props);
2197
2197
  const docDefaults = document.props.componentDefaults;
2198
- const effectiveTheme = docDefaults ? {
2198
+ const mergedComponentDefaults = docDefaults ? mergeWithDefaults2(docDefaults, theme.componentDefaults || {}) : void 0;
2199
+ const docNoProofWords = document.props.noProofWords;
2200
+ const mergedNoProofWords = docNoProofWords || theme.noProofWords ? Array.from(
2201
+ /* @__PURE__ */ new Set([...theme.noProofWords || [], ...docNoProofWords || []])
2202
+ ) : void 0;
2203
+ const effectiveTheme = mergedComponentDefaults || mergedNoProofWords ? {
2199
2204
  ...theme,
2200
- componentDefaults: mergeWithDefaults2(
2201
- docDefaults,
2202
- theme.componentDefaults || {}
2203
- )
2205
+ ...mergedComponentDefaults && {
2206
+ componentDefaults: mergedComponentDefaults
2207
+ },
2208
+ ...mergedNoProofWords && { noProofWords: mergedNoProofWords }
2204
2209
  } : theme;
2205
2210
  const context = createRenderContext(
2206
2211
  {
@@ -2222,7 +2227,8 @@ async function processDocument(document, theme, themeName) {
2222
2227
  sections,
2223
2228
  theme: effectiveTheme,
2224
2229
  themeName,
2225
- trackRevisions: document.props.trackRevisions
2230
+ trackRevisions: document.props.trackRevisions,
2231
+ language: document.props.language
2226
2232
  };
2227
2233
  }
2228
2234
  function createDocumentMetadata(props) {
@@ -3086,7 +3092,7 @@ function convertBorders(borders, theme) {
3086
3092
  ...right && { right }
3087
3093
  } : void 0;
3088
3094
  }
3089
- function createWordStyles(themeNameOrObject = "minimal") {
3095
+ function createWordStyles(themeNameOrObject = "minimal", language) {
3090
3096
  const theme = typeof themeNameOrObject === "string" ? getTheme(themeNameOrObject) || getTheme("minimal") : themeNameOrObject;
3091
3097
  const paragraphStyles = [
3092
3098
  // Normal body text style
@@ -3544,8 +3550,20 @@ function createWordStyles(themeNameOrObject = "minimal") {
3544
3550
  });
3545
3551
  }
3546
3552
  return {
3547
- paragraphStyles
3553
+ paragraphStyles,
3548
3554
  // Cast needed due to docx typing
3555
+ // Document-default proofing language (w:docDefaults/w:rPrDefault). Runs that
3556
+ // don't set their own w:lang inherit this, so Word spell-checks the whole
3557
+ // document in the requested language unless a component overrides it.
3558
+ ...language && {
3559
+ default: {
3560
+ document: {
3561
+ run: {
3562
+ language: { value: language }
3563
+ }
3564
+ }
3565
+ }
3566
+ }
3549
3567
  };
3550
3568
  }
3551
3569
  function getStyleIdForLevel(level) {
@@ -3667,6 +3685,37 @@ import {
3667
3685
 
3668
3686
  // src/utils/textParser.ts
3669
3687
  import { TextRun, ExternalHyperlink, InternalHyperlink } from "docx";
3688
+ function escapeRegExp(s) {
3689
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3690
+ }
3691
+ function buildNoProofWordsRegex(noProofWords) {
3692
+ const words = (noProofWords || []).filter((w) => w && w.trim().length > 0);
3693
+ if (words.length === 0) return null;
3694
+ const escaped = words.slice().sort((a, b) => b.length - a.length).map(escapeRegExp);
3695
+ return new RegExp(
3696
+ `(?<![\\p{L}\\p{N}])(?:${escaped.join("|")})(?![\\p{L}\\p{N}])`,
3697
+ "giu"
3698
+ );
3699
+ }
3700
+ function splitByNoProofWords(text, makeRun, noProofWords) {
3701
+ const regex = buildNoProofWordsRegex(noProofWords);
3702
+ if (!regex || !text) return [makeRun(text, false)];
3703
+ const runs = [];
3704
+ let lastIndex = 0;
3705
+ let match;
3706
+ while ((match = regex.exec(text)) !== null) {
3707
+ if (match.index > lastIndex) {
3708
+ runs.push(makeRun(text.slice(lastIndex, match.index), false));
3709
+ }
3710
+ runs.push(makeRun(match[0], true));
3711
+ lastIndex = match.index + match[0].length;
3712
+ if (regex.lastIndex === match.index) regex.lastIndex++;
3713
+ }
3714
+ if (lastIndex < text.length) {
3715
+ runs.push(makeRun(text.slice(lastIndex), false));
3716
+ }
3717
+ return runs.length > 0 ? runs : [makeRun(text, false)];
3718
+ }
3670
3719
  function parseTextWithDecorators(text, baseStyle = {}, options = {}) {
3671
3720
  if (!text) {
3672
3721
  return [new TextRun({ text: "", ...baseStyle })];
@@ -3677,7 +3726,8 @@ function parseTextWithDecorators(text, baseStyle = {}, options = {}) {
3677
3726
  return processTextWithPlaceholders(
3678
3727
  normalizedText,
3679
3728
  baseStyle,
3680
- options.placeholderContext || {}
3729
+ options.placeholderContext || {},
3730
+ options.noProofWords
3681
3731
  );
3682
3732
  }
3683
3733
  if (options.enableHyperlinks) {
@@ -3748,20 +3798,38 @@ function createTextRunsWithNewlines(text, baseStyle, options, overrideStyle) {
3748
3798
  bold: overrideStyle?.bold ?? baseStyle.bold,
3749
3799
  italics: overrideStyle?.italics ?? baseStyle.italics
3750
3800
  };
3751
- runs.push(
3752
- new TextRun({
3753
- text: line,
3754
- ...baseStyle.font && { font: baseStyle.font },
3755
- ...baseStyle.size && { size: baseStyle.size },
3756
- ...baseStyle.color && {
3757
- color: runStyle.bold && options.boldColor ? options.boldColor : baseStyle.color
3758
- },
3759
- ...runStyle.bold !== void 0 && { bold: runStyle.bold },
3760
- ...runStyle.italics !== void 0 && { italics: runStyle.italics },
3761
- ...baseStyle.underline && { underline: baseStyle.underline },
3762
- ...needsLineBreak && { break: 1 }
3763
- })
3801
+ const commonProps = {
3802
+ ...baseStyle.font && { font: baseStyle.font },
3803
+ ...baseStyle.size && { size: baseStyle.size },
3804
+ ...baseStyle.color && {
3805
+ color: runStyle.bold && options.boldColor ? options.boldColor : baseStyle.color
3806
+ },
3807
+ ...runStyle.bold !== void 0 && { bold: runStyle.bold },
3808
+ ...runStyle.italics !== void 0 && { italics: runStyle.italics },
3809
+ ...baseStyle.underline && { underline: baseStyle.underline },
3810
+ ...baseStyle.language && { language: baseStyle.language }
3811
+ };
3812
+ const wholeRunNoProof = baseStyle.noProof === true;
3813
+ const wordsForLine = wholeRunNoProof ? void 0 : options.noProofWords;
3814
+ let firstSegment = true;
3815
+ const lineRuns = splitByNoProofWords(
3816
+ line,
3817
+ (segment, matched) => {
3818
+ const segNoProof = matched || wholeRunNoProof;
3819
+ const run = new TextRun({
3820
+ text: segment,
3821
+ ...commonProps,
3822
+ ...(matched || baseStyle.noProof !== void 0) && {
3823
+ noProof: segNoProof
3824
+ },
3825
+ ...needsLineBreak && firstSegment && { break: 1 }
3826
+ });
3827
+ firstSegment = false;
3828
+ return run;
3829
+ },
3830
+ wordsForLine
3764
3831
  );
3832
+ runs.push(...lineRuns);
3765
3833
  }
3766
3834
  }
3767
3835
  return runs;
@@ -3866,7 +3934,7 @@ var PlaceholderRegistry = class {
3866
3934
  this.handlers.clear();
3867
3935
  }
3868
3936
  };
3869
- function processTextWithPlaceholders(text, baseStyle = {}, context = {}) {
3937
+ function processTextWithPlaceholders(text, baseStyle = {}, context = {}, noProofWords) {
3870
3938
  const normalizedText = normalizeUnicodeText(text);
3871
3939
  const combinedRegex = /(\*\*\*|___)([\s\S]*?)\1|(\*\*|__)([\s\S]*?)\3|(\*|_)([\s\S]*?)\5|\{([^}]+)\}/g;
3872
3940
  const result = [];
@@ -3876,7 +3944,9 @@ function processTextWithPlaceholders(text, baseStyle = {}, context = {}) {
3876
3944
  if (match.index > lastIndex) {
3877
3945
  const beforeText = normalizedText.substring(lastIndex, match.index);
3878
3946
  if (beforeText) {
3879
- result.push(...createTextRunsWithNewlines2(beforeText, baseStyle));
3947
+ result.push(
3948
+ ...createTextRunsWithNewlines2(beforeText, baseStyle, noProofWords)
3949
+ );
3880
3950
  }
3881
3951
  }
3882
3952
  if (match[7]) {
@@ -3890,11 +3960,17 @@ function processTextWithPlaceholders(text, baseStyle = {}, context = {}) {
3890
3960
  result.push(placeholderResult);
3891
3961
  } else if (typeof placeholderResult === "string") {
3892
3962
  result.push(
3893
- ...createTextRunsWithNewlines2(placeholderResult, baseStyle)
3963
+ ...createTextRunsWithNewlines2(
3964
+ placeholderResult,
3965
+ baseStyle,
3966
+ noProofWords
3967
+ )
3894
3968
  );
3895
3969
  }
3896
3970
  } else {
3897
- result.push(...createTextRunsWithNewlines2(match[0], baseStyle));
3971
+ result.push(
3972
+ ...createTextRunsWithNewlines2(match[0], baseStyle, noProofWords)
3973
+ );
3898
3974
  }
3899
3975
  } else {
3900
3976
  let decoratedText;
@@ -3929,33 +4005,54 @@ function processTextWithPlaceholders(text, baseStyle = {}, context = {}) {
3929
4005
  if (lastIndex < normalizedText.length) {
3930
4006
  const remainingText = normalizedText.substring(lastIndex);
3931
4007
  if (remainingText) {
3932
- result.push(...createTextRunsWithNewlines2(remainingText, baseStyle));
4008
+ result.push(
4009
+ ...createTextRunsWithNewlines2(remainingText, baseStyle, noProofWords)
4010
+ );
3933
4011
  }
3934
4012
  }
3935
4013
  if (result.length === 0 && normalizedText) {
3936
- result.push(...createTextRunsWithNewlines2(normalizedText, baseStyle));
4014
+ result.push(
4015
+ ...createTextRunsWithNewlines2(normalizedText, baseStyle, noProofWords)
4016
+ );
3937
4017
  }
3938
4018
  return result;
3939
4019
  }
3940
- function createTextRunsWithNewlines2(text, baseStyle) {
4020
+ function createTextRunsWithNewlines2(text, baseStyle, noProofWords) {
3941
4021
  const runs = [];
3942
4022
  const lines = text.split("\n");
3943
4023
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
3944
4024
  const line = lines[lineIndex];
3945
4025
  const needsLineBreak = lineIndex > 0;
3946
4026
  if (line || needsLineBreak) {
3947
- runs.push(
3948
- new TextRun2({
3949
- text: line,
3950
- font: baseStyle.font,
3951
- size: baseStyle.size,
3952
- color: baseStyle.color,
3953
- bold: baseStyle.bold,
3954
- italics: baseStyle.italics,
3955
- underline: baseStyle.underline,
3956
- break: needsLineBreak ? 1 : void 0
3957
- })
4027
+ const commonProps = {
4028
+ font: baseStyle.font,
4029
+ size: baseStyle.size,
4030
+ color: baseStyle.color,
4031
+ bold: baseStyle.bold,
4032
+ italics: baseStyle.italics,
4033
+ underline: baseStyle.underline,
4034
+ ...baseStyle.language && { language: baseStyle.language }
4035
+ };
4036
+ const wholeRunNoProof = baseStyle.noProof === true;
4037
+ const wordsForLine = wholeRunNoProof ? void 0 : noProofWords;
4038
+ let firstSegment = true;
4039
+ const lineRuns = splitByNoProofWords(
4040
+ line,
4041
+ (segment, matched) => {
4042
+ const run = new TextRun2({
4043
+ text: segment,
4044
+ ...commonProps,
4045
+ ...(matched || baseStyle.noProof !== void 0) && {
4046
+ noProof: matched || wholeRunNoProof
4047
+ },
4048
+ break: needsLineBreak && firstSegment ? 1 : void 0
4049
+ });
4050
+ firstSegment = false;
4051
+ return run;
4052
+ },
4053
+ wordsForLine
3958
4054
  );
4055
+ runs.push(...lineRuns);
3959
4056
  }
3960
4057
  }
3961
4058
  return runs;
@@ -4300,6 +4397,11 @@ function applyFontWeightAlias(opts) {
4300
4397
  );
4301
4398
  return { font: synth.family, bold: synth.bold, italics: synth.italic };
4302
4399
  }
4400
+ function resolveNoProofWords(theme, optionWords) {
4401
+ const themeWords = theme.noProofWords;
4402
+ const merged = [...themeWords || [], ...optionWords || []];
4403
+ return merged.length > 0 ? Array.from(new Set(merged)) : void 0;
4404
+ }
4303
4405
  function createText(content, theme, themeName, options = {}) {
4304
4406
  const normalizedContent = normalizeUnicodeText(content);
4305
4407
  const style = options.style || "Normal";
@@ -4342,7 +4444,11 @@ function createText(content, theme, themeName, options = {}) {
4342
4444
  ...weighted.italics !== void 0 && { italics: weighted.italics },
4343
4445
  ...options.underline !== void 0 && {
4344
4446
  underline: options.underline ? { type: "single" } : void 0
4345
- }
4447
+ },
4448
+ // Proofing: per-run language and/or no-proof. Omitting language lets the
4449
+ // run inherit the document default set on docDefaults.
4450
+ ...options.language && { language: { value: options.language } },
4451
+ ...options.noProof !== void 0 && { noProof: options.noProof }
4346
4452
  };
4347
4453
  if (options.revision) {
4348
4454
  const revisionRuns = createRevisionRuns(options.revision, baseTextStyle);
@@ -4364,7 +4470,8 @@ function createText(content, theme, themeName, options = {}) {
4364
4470
  } else {
4365
4471
  const textRuns = parseTextWithDecorators(normalizedContent, baseTextStyle, {
4366
4472
  boldColor: options.boldColor,
4367
- enableHyperlinks: true
4473
+ enableHyperlinks: true,
4474
+ noProofWords: resolveNoProofWords(theme, options.noProofWords)
4368
4475
  });
4369
4476
  if (options.bookmarkId) {
4370
4477
  globalBookmarkRegistry.register(
@@ -4499,8 +4606,21 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4499
4606
  },
4500
4607
  ...options.underline !== void 0 && {
4501
4608
  underline: options.underline ? { type: "single" } : void 0
4502
- }
4609
+ },
4610
+ // Proofing: per-run language and/or no-proof (see createText).
4611
+ ...options.language && { language: { value: options.language } },
4612
+ ...options.noProof !== void 0 && { noProof: options.noProof }
4503
4613
  };
4614
+ const headingNoProofWords = resolveNoProofWords(theme, options.noProofWords);
4615
+ const makeHeadingRuns = (value) => splitByNoProofWords(
4616
+ value,
4617
+ (segment, matched) => new TextRun4({
4618
+ text: segment,
4619
+ ...baseTextStyle,
4620
+ ...matched && { noProof: true }
4621
+ }),
4622
+ headingNoProofWords
4623
+ );
4504
4624
  if (options.revision) {
4505
4625
  const revisionRuns = createRevisionRuns(options.revision, baseTextStyle);
4506
4626
  if (options.bookmarkId) {
@@ -4528,13 +4648,12 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4528
4648
  if (hasDecorators) {
4529
4649
  const textRuns = parseTextWithDecorators(normalizedText, baseTextStyle, {
4530
4650
  boldColor: options.boldColor,
4531
- enableHyperlinks: true
4651
+ enableHyperlinks: true,
4652
+ noProofWords: headingNoProofWords
4532
4653
  });
4533
4654
  headingTextChildren.push(...textRuns);
4534
4655
  } else {
4535
- headingTextChildren.push(
4536
- new TextRun4({ text: normalizedText, ...baseTextStyle })
4537
- );
4656
+ headingTextChildren.push(...makeHeadingRuns(normalizedText));
4538
4657
  }
4539
4658
  children.push(
4540
4659
  new Bookmark({
@@ -4546,11 +4665,12 @@ function createHeading(text, level, theme, _themeName, options = {}) {
4546
4665
  if (hasDecorators) {
4547
4666
  const textRuns = parseTextWithDecorators(normalizedText, baseTextStyle, {
4548
4667
  boldColor: options.boldColor,
4549
- enableHyperlinks: true
4668
+ enableHyperlinks: true,
4669
+ noProofWords: headingNoProofWords
4550
4670
  });
4551
4671
  children.push(...textRuns);
4552
4672
  } else {
4553
- children.push(new TextRun4({ text: normalizedText, ...baseTextStyle }));
4673
+ children.push(...makeHeadingRuns(normalizedText));
4554
4674
  }
4555
4675
  }
4556
4676
  return new Paragraph({
@@ -5513,6 +5633,10 @@ function renderHeadingComponent(component, theme, themeName) {
5513
5633
  fontWeight: config.font?.fontWeight,
5514
5634
  italic: config.font?.italic,
5515
5635
  underline: config.font?.underline,
5636
+ // Proofing: local language override + no-proof toggle + known-words list
5637
+ language: config.language,
5638
+ noProof: config.noProof,
5639
+ noProofWords: config.noProofWords,
5516
5640
  // Pagination control
5517
5641
  keepNext: config.keepNext,
5518
5642
  keepLines: config.keepLines,
@@ -5631,6 +5755,10 @@ function renderParagraphComponent(component, theme, themeName) {
5631
5755
  fontWeight: resolvedConfig.font?.fontWeight,
5632
5756
  italic: resolvedConfig.font?.italic,
5633
5757
  underline: resolvedConfig.font?.underline,
5758
+ // Proofing: local language override + no-proof toggle + known-words list
5759
+ language: resolvedConfig.language,
5760
+ noProof: resolvedConfig.noProof,
5761
+ noProofWords: resolvedConfig.noProofWords,
5634
5762
  // Pass outline level for TOC support
5635
5763
  outlineLevel,
5636
5764
  // Pass floating positioning properties
@@ -6491,9 +6619,23 @@ Cause: ${cause}`
6491
6619
  height
6492
6620
  };
6493
6621
  }
6622
+ function withThemeColors(config, theme) {
6623
+ const options = config.options;
6624
+ if (!options || options.colors || !theme?.colors) return config;
6625
+ const palette = [
6626
+ theme.colors.primary,
6627
+ theme.colors.secondary,
6628
+ theme.colors.accent
6629
+ ].filter((c) => typeof c === "string" && c.length > 0).map((c) => c.startsWith("#") ? c : `#${c}`);
6630
+ if (palette.length === 0) return config;
6631
+ return {
6632
+ ...config,
6633
+ options: { ...config.options, colors: palette }
6634
+ };
6635
+ }
6494
6636
  async function renderHighchartsComponent(component, theme, themeName, context) {
6495
6637
  if (!isHighchartsComponent(component)) return [];
6496
- const config = component.props;
6638
+ const config = withThemeColors(component.props, theme);
6497
6639
  const chartResult = await generateChart(
6498
6640
  config,
6499
6641
  context?.services?.highcharts
@@ -6757,7 +6899,7 @@ async function renderDocument(structure, layout, options) {
6757
6899
  }
6758
6900
  const numberingConfigs = globalNumberingRegistry2.getAll();
6759
6901
  return new Document({
6760
- styles: createWordStyles(structure.theme),
6902
+ styles: createWordStyles(structure.theme, structure.language),
6761
6903
  sections,
6762
6904
  features: {
6763
6905
  updateFields: true,