@json-to-office/core-docx 2.1.0 → 2.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.
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ var __export = (target, all) => {
17
17
  // src/themes/defaults.ts
18
18
  function ensureThemeDefaults(theme) {
19
19
  return {
20
+ ...theme,
20
21
  $schema: theme.$schema,
21
22
  name: theme.name || "default",
22
23
  displayName: theme.displayName || "Default Theme",
@@ -27,6 +28,7 @@ function ensureThemeDefaults(theme) {
27
28
  ...theme.colors || {}
28
29
  },
29
30
  fonts: {
31
+ ...theme.fonts || {},
30
32
  heading: { ...DEFAULT_FONTS.heading, ...theme.fonts?.heading || {} },
31
33
  body: { ...DEFAULT_FONTS.body, ...theme.fonts?.body || {} },
32
34
  mono: { ...DEFAULT_FONTS.mono, ...theme.fonts?.mono || {} },
@@ -11298,6 +11300,15 @@ function createRenderContext(document, theme, themeName) {
11298
11300
  }
11299
11301
 
11300
11302
  // src/quality/facts.ts
11303
+ import {
11304
+ chartEncodingFor,
11305
+ collectColorLiterals,
11306
+ collectFontFamilies,
11307
+ collectPlaceholders,
11308
+ hasUnitMarker,
11309
+ normalizeHex,
11310
+ normalizeHighchartsChart
11311
+ } from "@json-to-office/quality";
11301
11312
  import { DEFAULT_DOCX_RENDERER_ID as DEFAULT_DOCX_RENDERER_ID2 } from "@json-to-office/shared-docx";
11302
11313
 
11303
11314
  // src/core/generationContext.ts
@@ -11539,6 +11550,164 @@ function tableFact(props, path4, availableWidthTwips) {
11539
11550
  explicitWidths
11540
11551
  };
11541
11552
  }
11553
+ var SERIES_COLOR_TOKENS = [
11554
+ "primary",
11555
+ "accent",
11556
+ "secondary",
11557
+ "accent4",
11558
+ "accent5",
11559
+ "accent6"
11560
+ ];
11561
+ function chartFact(node, props, path4, paletteTokens) {
11562
+ const base = {
11563
+ id: `docx:chart:${path4}`,
11564
+ kind: "docx/chart",
11565
+ path: path4,
11566
+ componentName: typeof node.name === "string" ? node.name : "",
11567
+ paletteTokens
11568
+ };
11569
+ if (node.name === "highcharts") {
11570
+ const shape = normalizeHighchartsChart(props.options);
11571
+ return {
11572
+ ...base,
11573
+ chartType: shape.chartType,
11574
+ encoding: shape.encoding,
11575
+ threeD: shape.threeD,
11576
+ seriesCount: shape.seriesCount,
11577
+ categoryCount: shape.categoryCount,
11578
+ seriesColorsStated: shape.seriesColorsStated,
11579
+ seriesColorsPath: `${path4}/props/options`,
11580
+ ...shape.valueAxisMin !== void 0 && {
11581
+ valueAxisMin: shape.valueAxisMin
11582
+ },
11583
+ unitStated: shape.unitStated,
11584
+ annotation: {
11585
+ stated: shape.annotationStated,
11586
+ path: path4,
11587
+ slot: "props.options.caption.text"
11588
+ }
11589
+ };
11590
+ }
11591
+ const chartType = typeof props.type === "string" ? props.type : "";
11592
+ if (chartType === "") return void 0;
11593
+ const series = Array.isArray(props.data) ? props.data : [];
11594
+ const categoryCount = Math.max(
11595
+ 0,
11596
+ ...series.map((entry) => {
11597
+ const record = asRecord(entry);
11598
+ const labels = Array.isArray(record?.labels) ? record.labels.length : 0;
11599
+ const values = Array.isArray(record?.values) ? record.values.length : 0;
11600
+ return Math.max(labels, values);
11601
+ })
11602
+ );
11603
+ const caption = typeof props.caption === "string" ? props.caption : "";
11604
+ const title = typeof props.title === "string" ? props.title : "";
11605
+ return {
11606
+ ...base,
11607
+ chartType,
11608
+ encoding: chartEncodingFor(chartType),
11609
+ // No 3D type exists in the DOCX chart schema — `office-open` draws none —
11610
+ // so this is always false here, and stays a field rather than an omission
11611
+ // so both formats answer the same questions.
11612
+ threeD: false,
11613
+ seriesCount: series.length,
11614
+ categoryCount,
11615
+ seriesColorsStated: Array.isArray(props.chartColors) && props.chartColors.length > 0,
11616
+ seriesColorsPath: `${path4}/props/chartColors`,
11617
+ // No `valueAxisMin`: a DOCX chart has no axis-floor property, so the
11618
+ // baseline is always the renderer's, which starts a bar at zero.
11619
+ unitStated: hasUnitMarker(
11620
+ typeof props.valAxisTitle === "string" ? props.valAxisTitle : ""
11621
+ ) || hasUnitMarker(title) || hasUnitMarker(caption),
11622
+ annotation: {
11623
+ stated: caption.trim() !== "",
11624
+ path: path4,
11625
+ slot: "props.caption"
11626
+ }
11627
+ };
11628
+ }
11629
+ var DOCX_ALIGNMENTS = /* @__PURE__ */ new Set(["left", "center", "right", "justify"]);
11630
+ function resolvedCellText(content) {
11631
+ return typeof content === "string" ? content : "[component]";
11632
+ }
11633
+ function statesOwnBorders(authored) {
11634
+ if (!authored) return false;
11635
+ if (authored.borderSize !== void 0 || authored.borderColor !== void 0 || authored.hideBorders !== void 0) {
11636
+ return true;
11637
+ }
11638
+ const layers = [
11639
+ asRecord(authored.cellDefaults),
11640
+ asRecord(authored.headerCellDefaults)
11641
+ ];
11642
+ if (layers.some(
11643
+ (layer) => layer?.borderSize !== void 0 || layer?.borderColor !== void 0
11644
+ )) {
11645
+ return true;
11646
+ }
11647
+ const columns = Array.isArray(authored.columns) ? authored.columns : [];
11648
+ return columns.some((column) => {
11649
+ const record = asRecord(column);
11650
+ const defaults = asRecord(record?.cellDefaults);
11651
+ return defaults?.borderSize !== void 0 || defaults?.borderColor !== void 0;
11652
+ });
11653
+ }
11654
+ function tableDesignFact(props, path4, theme, themeName, authored) {
11655
+ const authoredColumns = Array.isArray(props.columns) ? props.columns : [];
11656
+ if (authoredColumns.length === 0) return void 0;
11657
+ const model = resolveTableModel(
11658
+ props,
11659
+ theme,
11660
+ themeName
11661
+ );
11662
+ const bodyRows = model.rows;
11663
+ const drawnRows = bodyRows.length + (model.header ? 1 : 0);
11664
+ if (drawnRows === 0) return void 0;
11665
+ const fullGrid = statesOwnBorders(authored) && [...model.header ? [model.header] : [], ...bodyRows].every(
11666
+ (row) => row.cells.every(
11667
+ (cell) => ["top", "right", "bottom", "left"].every(
11668
+ (side) => !cell.borders[side].hidden && cell.borders[side].size > 0
11669
+ )
11670
+ )
11671
+ );
11672
+ const columns = authoredColumns.map(
11673
+ (column, index) => {
11674
+ const authoredColumn = asRecord(column) ?? {};
11675
+ const cells = Array.isArray(authoredColumn.cells) ? authoredColumn.cells : [];
11676
+ const alignments = /* @__PURE__ */ new Set();
11677
+ const values = [];
11678
+ bodyRows.forEach((row) => {
11679
+ const cell = row.cells[index];
11680
+ if (!cell || cell.missing) return;
11681
+ values.push(resolvedCellText(cell.content));
11682
+ alignments.add(cell.horizontalAlignment);
11683
+ });
11684
+ const header = model.header?.cells[index];
11685
+ return {
11686
+ index,
11687
+ path: `${path4}/props/columns/${index}`,
11688
+ ...header?.content !== void 0 && {
11689
+ header: resolvedCellText(header.content)
11690
+ },
11691
+ values,
11692
+ alignment: alignments.size === 1 ? [...alignments][0] : alignments.size === 0 ? "left" : "mixed",
11693
+ hasCellDefaults: asRecord(authoredColumn.cellDefaults) !== void 0,
11694
+ hasHeader: asRecord(authoredColumn.header) !== void 0,
11695
+ cellsWithOwnAlignment: cells.flatMap((cell, cellIndex) => {
11696
+ const alignment2 = asRecord(cell)?.horizontalAlignment;
11697
+ return typeof alignment2 === "string" && DOCX_ALIGNMENTS.has(alignment2) && alignment2 !== "right" ? [cellIndex] : [];
11698
+ })
11699
+ };
11700
+ }
11701
+ );
11702
+ return {
11703
+ id: `docx:table-design:${path4}`,
11704
+ kind: "docx/table",
11705
+ path: path4,
11706
+ columns,
11707
+ rowCount: drawnRows,
11708
+ fullGrid
11709
+ };
11710
+ }
11542
11711
  var TWIPS_PER_POINT3 = 20;
11543
11712
  var SINGLE_LINE_RATIO = 1.2;
11544
11713
  function finiteNumber(value) {
@@ -11567,20 +11736,23 @@ function characterSpacingPt(spacing) {
11567
11736
  if (rule === void 0 || value === void 0) return 0;
11568
11737
  return rule.type === "condensed" ? -value / TWIPS_PER_POINT3 : value / TWIPS_PER_POINT3;
11569
11738
  }
11570
- function pointerExists(root, pointer) {
11739
+ function nodeAtPointer(root, pointer) {
11571
11740
  let current = root;
11572
11741
  for (const token of pointer.split("/").slice(1)) {
11573
11742
  if (Array.isArray(current)) {
11574
11743
  const index = Number(token);
11575
- if (!Number.isInteger(index)) return false;
11744
+ if (!Number.isInteger(index)) return void 0;
11576
11745
  current = current[index];
11577
11746
  continue;
11578
11747
  }
11579
11748
  const record = asRecord(current);
11580
- if (!record) return false;
11749
+ if (!record) return void 0;
11581
11750
  current = record[token];
11582
11751
  }
11583
- return current !== void 0;
11752
+ return current;
11753
+ }
11754
+ function pointerExists(root, pointer) {
11755
+ return nodeAtPointer(root, pointer) !== void 0;
11584
11756
  }
11585
11757
  function styleKey(node, props) {
11586
11758
  if (node.name === "heading") {
@@ -11761,6 +11933,67 @@ function prepareDocxQualityDocument(document, options = {}) {
11761
11933
  ...fact.relatedPaths && { relatedPaths: fact.relatedPaths }
11762
11934
  };
11763
11935
  };
11936
+ const paletteHexes = {};
11937
+ for (const [token, value] of Object.entries(
11938
+ resolved.theme.colors ?? {}
11939
+ )) {
11940
+ if (typeof value !== "string") continue;
11941
+ const hex = normalizeHex(value);
11942
+ if (hex) paletteHexes[token] = hex;
11943
+ }
11944
+ const paletteTokens = SERIES_COLOR_TOKENS.filter(
11945
+ (token) => paletteHexes[token] !== void 0
11946
+ );
11947
+ const authoredPropsAt = (pointer) => asRecord(asRecord(nodeAtPointer(context.document, pointer))?.props);
11948
+ addFact({
11949
+ id: "docx:theme",
11950
+ kind: "docx/theme",
11951
+ path: "/props",
11952
+ themeName: context.themeName,
11953
+ paletteHexes,
11954
+ // `heading` and `body` only. A theme also names `mono` and `light`, but
11955
+ // those paint nothing until a component asks for them — counting an
11956
+ // unused `Courier New` against a document's family budget would flag a
11957
+ // report that only ever uses one typeface.
11958
+ fontFamilies: [
11959
+ ...new Set(
11960
+ ["heading", "body"].flatMap((role) => {
11961
+ const family = asRecord(
11962
+ resolved.theme.fonts?.[role]
11963
+ )?.family;
11964
+ return typeof family === "string" && family.trim() !== "" ? [family] : [];
11965
+ })
11966
+ )
11967
+ ]
11968
+ });
11969
+ collectColorLiterals(document).forEach((literal, index) => {
11970
+ addFact({
11971
+ id: `docx:color:${index}:${literal.path}`,
11972
+ kind: "docx/color",
11973
+ path: literal.path,
11974
+ raw: literal.raw,
11975
+ hex: literal.hex
11976
+ });
11977
+ });
11978
+ collectFontFamilies(document).forEach((use, index) => {
11979
+ addFact({
11980
+ id: `docx:font:${index}:${use.path}`,
11981
+ kind: "docx/font-family",
11982
+ path: use.path,
11983
+ family: use.family
11984
+ });
11985
+ });
11986
+ collectPlaceholders(document).forEach((occurrence, index) => {
11987
+ addFact({
11988
+ id: `docx:placeholder:${index}:${occurrence.path}`,
11989
+ kind: "docx/placeholder",
11990
+ path: occurrence.path,
11991
+ text: occurrence.text,
11992
+ placeholderKind: occurrence.match.kind,
11993
+ pattern: occurrence.match.pattern,
11994
+ excerpt: occurrence.match.excerpt
11995
+ });
11996
+ });
11764
11997
  let previousVisitPath;
11765
11998
  let lastFrame;
11766
11999
  let flowIndex = 0;
@@ -11770,6 +12003,18 @@ function prepareDocxQualityDocument(document, options = {}) {
11770
12003
  if (node.name === "table") {
11771
12004
  const fact = tableFact(props, path4, page.availableWidthTwips);
11772
12005
  if (fact) addFact(fact);
12006
+ const design = tableDesignFact(
12007
+ props,
12008
+ path4,
12009
+ resolved.theme,
12010
+ context.themeName,
12011
+ authoredPropsAt(path4)
12012
+ );
12013
+ if (design) addFact(design);
12014
+ }
12015
+ if (node.name === "chart" || node.name === "highcharts") {
12016
+ const fact = chartFact(node, props, path4, paletteTokens);
12017
+ if (fact) addFact(fact);
11773
12018
  }
11774
12019
  if (node.name === "paragraph" || node.name === "text-box") {
11775
12020
  const floating = asRecord(props.floating);
@@ -12193,9 +12438,17 @@ import {
12193
12438
 
12194
12439
  // src/quality/rules.ts
12195
12440
  import {
12441
+ chartInfoDesignFindings,
12442
+ DEFAULT_MAXIMUM_CHART_SERIES,
12443
+ DEFAULT_MAXIMUM_PIE_SLICES,
12444
+ fontCountFinding,
12196
12445
  mergeQualityProfiles,
12446
+ nearestPaletteToken,
12447
+ offPaletteFinding,
12448
+ placeholderFinding,
12197
12449
  QUALITY_CODES,
12198
- QualityEngine
12450
+ QualityEngine,
12451
+ tableInfoDesignFindings
12199
12452
  } from "@json-to-office/quality";
12200
12453
 
12201
12454
  // src/quality/text-metrics.ts
@@ -12661,6 +12914,179 @@ var docxLineBoxRule = {
12661
12914
  });
12662
12915
  }
12663
12916
  };
12917
+ var docxPlaceholderRule = {
12918
+ id: "docx/placeholder-text",
12919
+ code: QUALITY_CODES.PLACEHOLDER_TEXT,
12920
+ category: "integrity",
12921
+ defaultSeverity: "warning",
12922
+ defaultCertainty: "deterministic",
12923
+ formats: ["docx"],
12924
+ evaluate: ({ facts }) => facts.filter(
12925
+ (fact) => fact.kind === "docx/placeholder"
12926
+ ).map(
12927
+ (fact) => placeholderFinding({
12928
+ path: fact.path,
12929
+ kind: fact.placeholderKind,
12930
+ pattern: fact.pattern,
12931
+ excerpt: fact.excerpt
12932
+ })
12933
+ )
12934
+ };
12935
+ var DEFAULT_MAX_FONT_FAMILIES = 3;
12936
+ var docxFontCountRule = {
12937
+ id: "docx/font-count",
12938
+ code: QUALITY_CODES.FONT_COUNT,
12939
+ category: "brand",
12940
+ defaultSeverity: "warning",
12941
+ defaultCertainty: "deterministic",
12942
+ formats: ["docx"],
12943
+ defaultParameters: { maximumFamilies: DEFAULT_MAX_FONT_FAMILIES },
12944
+ evaluate: ({ facts, configuration }) => {
12945
+ const maximum = numberParameter(
12946
+ configuration.parameters,
12947
+ "maximumFamilies",
12948
+ DEFAULT_MAX_FONT_FAMILIES
12949
+ );
12950
+ const theme = facts.find(
12951
+ (fact) => fact.kind === "docx/theme"
12952
+ );
12953
+ const families = new Set(theme?.fontFamilies ?? []);
12954
+ const extraPaths = [];
12955
+ for (const fact of facts) {
12956
+ if (fact.kind !== "docx/font-family") continue;
12957
+ const use = fact;
12958
+ if (!families.has(use.family)) extraPaths.push(use.path);
12959
+ families.add(use.family);
12960
+ }
12961
+ if (families.size <= maximum) return [];
12962
+ return [
12963
+ fontCountFinding({
12964
+ path: theme?.path ?? "/props",
12965
+ families: [...families].sort(),
12966
+ maximum,
12967
+ relatedPaths: [...new Set(extraPaths)]
12968
+ })
12969
+ ];
12970
+ }
12971
+ };
12972
+ var docxPaletteRule = {
12973
+ id: "docx/palette-adherence",
12974
+ code: QUALITY_CODES.OFF_PALETTE,
12975
+ category: "brand",
12976
+ defaultSeverity: "info",
12977
+ defaultCertainty: "deterministic",
12978
+ formats: ["docx"],
12979
+ evaluate: ({ facts }) => {
12980
+ const theme = facts.find(
12981
+ (fact) => fact.kind === "docx/theme"
12982
+ );
12983
+ const palette = theme?.paletteHexes ?? {};
12984
+ const known = new Set(Object.values(palette));
12985
+ return facts.filter((fact) => fact.kind === "docx/color").filter((fact) => !known.has(fact.hex)).map((fact) => {
12986
+ const nearest = nearestPaletteToken(fact.hex, palette);
12987
+ return offPaletteFinding({
12988
+ path: fact.path,
12989
+ raw: fact.raw,
12990
+ hex: fact.hex,
12991
+ ...nearest && { nearest }
12992
+ });
12993
+ });
12994
+ }
12995
+ };
12996
+ var DEFAULT_MAX_TABLE_ROWS_PER_PAGE = 25;
12997
+ var docxChartRule = {
12998
+ id: "docx/chart-design",
12999
+ code: QUALITY_CODES.CHART_OVERLOADED,
13000
+ category: "information-design",
13001
+ defaultSeverity: "warning",
13002
+ defaultCertainty: "deterministic",
13003
+ formats: ["docx"],
13004
+ defaultParameters: {
13005
+ maximumSeries: DEFAULT_MAXIMUM_CHART_SERIES,
13006
+ maximumSlices: DEFAULT_MAXIMUM_PIE_SLICES
13007
+ },
13008
+ evaluate: ({ facts, configuration }) => {
13009
+ const maximumSeries = numberParameter(
13010
+ configuration.parameters,
13011
+ "maximumSeries",
13012
+ DEFAULT_MAXIMUM_CHART_SERIES
13013
+ );
13014
+ const maximumSlices = numberParameter(
13015
+ configuration.parameters,
13016
+ "maximumSlices",
13017
+ DEFAULT_MAXIMUM_PIE_SLICES
13018
+ );
13019
+ return facts.filter((fact) => fact.kind === "docx/chart").flatMap((fact) => {
13020
+ const fix = seriesColorFix(fact);
13021
+ return chartInfoDesignFindings(fact, {
13022
+ maximumSeries,
13023
+ maximumSlices,
13024
+ ...fix && { seriesColorFix: fix }
13025
+ });
13026
+ });
13027
+ }
13028
+ };
13029
+ function seriesColorFix(fact) {
13030
+ if (fact.componentName !== "chart") return void 0;
13031
+ if (fact.seriesCount < 1 || fact.paletteTokens.length === 0) return void 0;
13032
+ const tokens = Array.from(
13033
+ { length: fact.seriesCount },
13034
+ (_, index) => fact.paletteTokens[index % fact.paletteTokens.length]
13035
+ );
13036
+ return [{ op: "add", path: fact.seriesColorsPath, value: tokens }];
13037
+ }
13038
+ var docxTableDesignRule = {
13039
+ id: "docx/table-design",
13040
+ code: QUALITY_CODES.TABLE_NUMERIC_ALIGN,
13041
+ category: "information-design",
13042
+ defaultSeverity: "warning",
13043
+ defaultCertainty: "deterministic",
13044
+ formats: ["docx"],
13045
+ defaultParameters: { maximumRows: DEFAULT_MAX_TABLE_ROWS_PER_PAGE },
13046
+ evaluate: ({ facts, configuration }) => {
13047
+ const maximumRows = numberParameter(
13048
+ configuration.parameters,
13049
+ "maximumRows",
13050
+ DEFAULT_MAX_TABLE_ROWS_PER_PAGE
13051
+ );
13052
+ return facts.filter((fact) => fact.kind === "docx/table").flatMap(
13053
+ (fact) => tableInfoDesignFindings(fact, {
13054
+ maximumRows,
13055
+ rowSurface: "page",
13056
+ rowSeverity: "info",
13057
+ alignFix: alignColumnRight
13058
+ })
13059
+ );
13060
+ }
13061
+ };
13062
+ function alignColumnRight(column) {
13063
+ const operations = [
13064
+ column.hasCellDefaults ? {
13065
+ op: "add",
13066
+ path: `${column.path}/cellDefaults/horizontalAlignment`,
13067
+ value: "right"
13068
+ } : {
13069
+ op: "add",
13070
+ path: `${column.path}/cellDefaults`,
13071
+ value: { horizontalAlignment: "right" }
13072
+ }
13073
+ ];
13074
+ if (column.hasHeader) {
13075
+ operations.push({
13076
+ op: "add",
13077
+ path: `${column.path}/header/horizontalAlignment`,
13078
+ value: "right"
13079
+ });
13080
+ }
13081
+ for (const index of column.cellsWithOwnAlignment) {
13082
+ operations.push({
13083
+ op: "add",
13084
+ path: `${column.path}/cells/${index}/horizontalAlignment`,
13085
+ value: "right"
13086
+ });
13087
+ }
13088
+ return operations;
13089
+ }
12664
13090
  var DOCX_QUALITY_RULES = {
12665
13091
  id: "docx/default",
12666
13092
  rules: [
@@ -12669,7 +13095,12 @@ var DOCX_QUALITY_RULES = {
12669
13095
  docxTextFitRule,
12670
13096
  docxFrameCollisionRule,
12671
13097
  docxSvgTextBoundsRule,
12672
- docxLineBoxRule
13098
+ docxLineBoxRule,
13099
+ docxPlaceholderRule,
13100
+ docxChartRule,
13101
+ docxTableDesignRule,
13102
+ docxFontCountRule,
13103
+ docxPaletteRule
12673
13104
  ]
12674
13105
  };
12675
13106
  var DOCX_QUALITY_PROFILES = {
@@ -13042,34 +13473,36 @@ function validateDocument(document, customComponents, options) {
13042
13473
  const errors = [...documentResult.errors || []];
13043
13474
  function validateComponents(components, pathPrefix = "children") {
13044
13475
  components.forEach((componentData, index) => {
13045
- const customComponent = customComponents.find(
13046
- (cc) => cc.name === componentData.name
13047
- );
13048
- if (!customComponent) {
13476
+ if (!componentData || typeof componentData !== "object" || Array.isArray(componentData)) {
13049
13477
  return;
13050
13478
  }
13051
- const versionEntry = resolveComponentVersion(
13052
- customComponent.name,
13053
- customComponent.versions,
13054
- componentData.version
13055
- );
13056
- const validation = validateComponentProps(
13057
- versionEntry,
13058
- componentData.props,
13059
- customComponent.name,
13060
- // Reject unknown custom props unless the caller allows them.
13061
- { clean: options?.allowUnknownFields === true }
13479
+ const customComponent = customComponents.find(
13480
+ (cc) => cc.name === componentData.name
13062
13481
  );
13063
- if (!validation.valid && validation.errors) {
13064
- const indexedErrors = validation.errors.map(
13065
- (error) => ({
13066
- ...error,
13067
- path: `${pathPrefix}[${index}].${error.path}`
13068
- })
13482
+ if (customComponent) {
13483
+ const versionEntry = resolveComponentVersion(
13484
+ customComponent.name,
13485
+ customComponent.versions,
13486
+ componentData.version
13487
+ );
13488
+ const validation = validateComponentProps(
13489
+ versionEntry,
13490
+ componentData.props,
13491
+ customComponent.name,
13492
+ // Reject unknown custom props unless the caller allows them.
13493
+ { clean: options?.allowUnknownFields === true }
13069
13494
  );
13070
- errors.push(...indexedErrors);
13495
+ if (!validation.valid && validation.errors) {
13496
+ const indexedErrors = validation.errors.map(
13497
+ (error) => ({
13498
+ ...error,
13499
+ path: `${pathPrefix}[${index}].${error.path}`
13500
+ })
13501
+ );
13502
+ errors.push(...indexedErrors);
13503
+ }
13071
13504
  }
13072
- if (componentData.children && Array.isArray(componentData.children)) {
13505
+ if (Array.isArray(componentData.children)) {
13073
13506
  validateComponents(
13074
13507
  componentData.children,
13075
13508
  `${pathPrefix}[${index}].children`