@json-to-office/core-docx 0.30.0 → 0.32.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.
@@ -2428,39 +2428,15 @@ async function extractSections(components, context) {
2428
2428
  context
2429
2429
  );
2430
2430
  const shouldPageBreak = component.props?.pageBreak !== false;
2431
- if (component.props?.title) {
2432
- const headingLevel = Math.min(
2433
- Math.max(component.props.level || 1, 1),
2434
- 6
2435
- );
2436
- sectionComponents.unshift(
2437
- resolveComponentDefaults(
2438
- {
2439
- name: "heading",
2440
- props: {
2441
- text: component.props.title,
2442
- level: headingLevel,
2443
- pageBreak: shouldPageBreak,
2444
- // Apply zero-spacing to prevent unwanted initial line
2445
- spacing: {
2446
- before: 0,
2447
- after: 0
2448
- }
2449
- }
2450
- },
2451
- context.fullTheme
2452
- )
2453
- );
2454
- }
2455
2431
  sections.push({
2456
- title: component.props?.title,
2457
- level: component.props?.level,
2458
2432
  components: sectionComponents,
2459
2433
  header: component.props?.header,
2460
2434
  footer: component.props?.footer,
2461
2435
  isExplicitSection: true,
2462
- // Store pageBreak for titleless sections to be handled at layout level
2463
- pageBreak: !component.props?.title ? shouldPageBreak : void 0,
2436
+ // Page break is handled at layout level (sections render no title of
2437
+ // their own — a visible title is an explicit heading child; the
2438
+ // authoring label lives in props.meta.title and is never rendered)
2439
+ pageBreak: shouldPageBreak,
2464
2440
  // Preserve page configuration override if present
2465
2441
  page: component.props?.page
2466
2442
  });
@@ -2485,29 +2461,6 @@ async function flattenComponents(components, context) {
2485
2461
  children: await flattenComponents(component.children, context)
2486
2462
  });
2487
2463
  } else if (isSectionComponent(component) && component.children) {
2488
- if (component.props?.title) {
2489
- const headingLevel = Math.min(
2490
- Math.max(component.props.level || 1, 1),
2491
- 6
2492
- );
2493
- flattened.push(
2494
- resolveComponentDefaults(
2495
- {
2496
- name: "heading",
2497
- props: {
2498
- text: component.props.title,
2499
- level: headingLevel,
2500
- // Apply zero-spacing to prevent unwanted initial line
2501
- spacing: {
2502
- before: 0,
2503
- after: 0
2504
- }
2505
- }
2506
- },
2507
- context.fullTheme
2508
- )
2509
- );
2510
- }
2511
2464
  flattened.push(...await flattenComponents(component.children, context));
2512
2465
  } else {
2513
2466
  flattened.push(component);
@@ -2570,7 +2523,6 @@ function applyLayout(sections, theme, themeName) {
2570
2523
  footer: section.footer,
2571
2524
  isUserSection: isSourceUserSection,
2572
2525
  belongsToUserSection: isSourceUserSection,
2573
- sectionTitleLevel: section.level,
2574
2526
  pageOverride: section.page
2575
2527
  });
2576
2528
  continue;
@@ -2605,8 +2557,6 @@ function applyLayout(sections, theme, themeName) {
2605
2557
  // at the start of column content.
2606
2558
  isUserSection: isSourceUserSection && j === 0,
2607
2559
  belongsToUserSection: isSourceUserSection,
2608
- // Pass section title level for TOC scoping
2609
- sectionTitleLevel: section.level,
2610
2560
  // Pass page override for this section
2611
2561
  pageOverride: section.page
2612
2562
  });
@@ -4442,6 +4392,26 @@ function componentHasRevision(component) {
4442
4392
  import { createHash as createHash3 } from "crypto";
4443
4393
  var componentCache = null;
4444
4394
  var cacheKeyGen = null;
4395
+ var bypassStats = /* @__PURE__ */ new Map();
4396
+ function recordBypass(type, reason) {
4397
+ const entry = bypassStats.get(type);
4398
+ if (entry) {
4399
+ entry.renders++;
4400
+ } else {
4401
+ bypassStats.set(type, { renders: 1, reason });
4402
+ }
4403
+ }
4404
+ var DATE_INDEPENDENT_PLACEHOLDERS = /* @__PURE__ */ new Set(["PAGE", "TOTAL_PAGES"]);
4405
+ function usesDateSensitivePlaceholder(serialized) {
4406
+ const placeholderRegex = /\{([^}{]+)\}/g;
4407
+ let match;
4408
+ while ((match = placeholderRegex.exec(serialized)) !== null) {
4409
+ const name = match[1].toUpperCase();
4410
+ if (DATE_INDEPENDENT_PLACEHOLDERS.has(name)) continue;
4411
+ if (PlaceholderRegistry.has(name)) return true;
4412
+ }
4413
+ return false;
4414
+ }
4445
4415
  function createThemeHash(theme) {
4446
4416
  const themeString = JSON.stringify(theme);
4447
4417
  return createHash3("sha256").update(themeString).digest("hex").substring(0, 8);
@@ -4477,22 +4447,31 @@ function initializeComponentCache(cache) {
4477
4447
  }
4478
4448
  }
4479
4449
  async function renderComponentWithCache(component, theme, themeName, context, bypassCache = false) {
4480
- const forceBypassForType = component.name === "toc" || component.name === "section" || component.name === "visual" || component.name === "heading" || // Both explicit lists and markdown-list paragraphs register numbering in
4481
- // the current document scope. Cached paragraphs can otherwise reference
4482
- // definitions that only existed in a previous render.
4483
- component.name === "list" || component.name === "paragraph" || "id" in component || componentHasRevision(component);
4450
+ const bypassReason = componentHasRevision(
4451
+ component
4452
+ ) ? "revision-ids" : "id" in component ? "bookmark-id" : component.name === "toc" || component.name === "section" || component.name === "visual" || component.name === "heading" || // Both explicit lists and markdown-list paragraphs register
4453
+ // numbering in the current document scope. Cached paragraphs can
4454
+ // otherwise reference definitions that only existed in a previous
4455
+ // render.
4456
+ component.name === "list" || component.name === "paragraph" ? "dynamic-context" : null;
4484
4457
  if (!componentCache) {
4485
4458
  initializeComponentCache();
4486
4459
  }
4487
- if (!componentCache || bypassCache || forceBypassForType || !componentCache.getConfig().enabled) {
4460
+ if (bypassReason !== null) {
4461
+ recordBypass(component.name, bypassReason);
4462
+ return renderComponent(component, theme, themeName, context);
4463
+ }
4464
+ if (!componentCache || bypassCache || !componentCache.getConfig().enabled) {
4488
4465
  return renderComponent(component, theme, themeName, context);
4489
4466
  }
4490
4467
  const componentProps = JSON.stringify(component.props || {});
4491
4468
  const themeHash = createThemeHash(theme);
4492
4469
  const contextKey = context?.section ? `${context.section.currentLayout}:${context.section.columnCount}` : "no-section";
4493
- const generationDateKey = getGenerationDate().toISOString();
4494
4470
  const baseDirKey = getBaseDir() ?? "";
4495
4471
  const childrenKey = "children" in component && component.children ? `:children:${JSON.stringify(component.children)}` : "";
4472
+ const generationDateKey = usesDateSensitivePlaceholder(
4473
+ componentProps + childrenKey
4474
+ ) ? getGenerationDate().toISOString() : "no-date";
4496
4475
  const cacheKey = `component:${component.name}:${themeHash}:${contextKey}:${generationDateKey}:${baseDirKey}:${componentProps}${childrenKey}`;
4497
4476
  const cached = await componentCache.get(cacheKey);
4498
4477
  if (cached) {
@@ -6948,8 +6927,6 @@ function renderTocComponent(component, theme, context) {
6948
6927
  );
6949
6928
  }
6950
6929
  const stylesWithLevels = [];
6951
- const sectionTitleLevel = context?.section?.sectionTitleLevel;
6952
- const startLevel = effectiveScope === "section" && sectionTitleLevel ? sectionTitleLevel : 0;
6953
6930
  if (componentProps.styles && componentProps.styles.length > 0) {
6954
6931
  for (const styleMapping of componentProps.styles) {
6955
6932
  const styleId = styleMapping.styleId;
@@ -6960,7 +6937,7 @@ function renderTocComponent(component, theme, context) {
6960
6937
  );
6961
6938
  }
6962
6939
  }
6963
- const effectiveDepthStart = effectiveScope === "section" && sectionTitleLevel ? Math.max(depthStart, startLevel + 1) : depthStart;
6940
+ const effectiveDepthStart = depthStart;
6964
6941
  const effectiveDepthEnd = depthEnd;
6965
6942
  const tocOptions = {
6966
6943
  hyperlink: true,
@@ -7355,6 +7332,11 @@ function createLimiter(max) {
7355
7332
  var DEFAULT_FALLBACK_CONCURRENCY = 4;
7356
7333
  var BATCH_TIMEOUT_BASE_MS = 3e4;
7357
7334
  var BATCH_TIMEOUT_PER_SLIDE_MS = 1e4;
7335
+ var prepassStats = {
7336
+ documents: 0,
7337
+ collected: 0,
7338
+ unique: 0
7339
+ };
7358
7340
  function collectVisualProps(root) {
7359
7341
  const found = [];
7360
7342
  const seen = /* @__PURE__ */ new WeakSet();
@@ -7431,6 +7413,9 @@ async function prerasterizeVisuals(root, serviceConfig, options = {}) {
7431
7413
  }
7432
7414
  }
7433
7415
  if (targets.size === 0) return map;
7416
+ prepassStats.documents++;
7417
+ prepassStats.collected += visuals.length;
7418
+ prepassStats.unique += targets.size;
7434
7419
  const unique = [...targets.values()];
7435
7420
  const limit = createLimiter(
7436
7421
  Math.max(1, options.concurrency ?? DEFAULT_FALLBACK_CONCURRENCY)
@@ -7831,9 +7816,7 @@ async function renderSection(section, theme, themeName, context, sectionOrdinal,
7831
7816
  currentLayout: section.layoutType,
7832
7817
  columnCount: section.properties.column?.count || 1,
7833
7818
  // Always pass the same bookmark ID for all layout chunks of the same section
7834
- sectionBookmarkId,
7835
- // Pass section title level for TOC scoping (excludes section title from its own TOC)
7836
- sectionTitleLevel: section.sectionTitleLevel
7819
+ sectionBookmarkId
7837
7820
  }
7838
7821
  };
7839
7822
  if (sectionBookmarkId && isFirstLayoutOfUserSection && sharedLinkId !== void 0) {
@@ -8764,10 +8747,13 @@ var nestedSectionsComponent = createComponent({
8764
8747
  components.push({
8765
8748
  name: "section",
8766
8749
  props: {
8767
- title: "First level 1 section",
8768
- level: 1
8750
+ meta: { title: "First level 1 section" }
8769
8751
  },
8770
8752
  children: [
8753
+ {
8754
+ name: "heading",
8755
+ props: { text: "First level 1 section", level: 1 }
8756
+ },
8771
8757
  {
8772
8758
  name: "heading",
8773
8759
  props: {
@@ -8799,10 +8785,13 @@ var nestedSectionsComponent = createComponent({
8799
8785
  components.push({
8800
8786
  name: "section",
8801
8787
  props: {
8802
- title: "Second level 1 section",
8803
- level: 1
8788
+ meta: { title: "Second level 1 section" }
8804
8789
  },
8805
8790
  children: [
8791
+ {
8792
+ name: "heading",
8793
+ props: { text: "Second level 1 section", level: 1 }
8794
+ },
8806
8795
  {
8807
8796
  name: "heading",
8808
8797
  props: {
@@ -8846,9 +8835,22 @@ var plugin_demo_default = {
8846
8835
  {
8847
8836
  name: "section",
8848
8837
  props: {
8849
- title: "Weather Information"
8838
+ meta: {
8839
+ title: "Weather Information"
8840
+ }
8850
8841
  },
8851
8842
  children: [
8843
+ {
8844
+ name: "heading",
8845
+ props: {
8846
+ text: "Weather Information",
8847
+ level: 1,
8848
+ spacing: {
8849
+ before: 0,
8850
+ after: 0
8851
+ }
8852
+ }
8853
+ },
8852
8854
  {
8853
8855
  name: "paragraph",
8854
8856
  props: {
@@ -8876,9 +8878,22 @@ var plugin_demo_default = {
8876
8878
  {
8877
8879
  name: "section",
8878
8880
  props: {
8879
- title: "Sales Data"
8881
+ meta: {
8882
+ title: "Sales Data"
8883
+ }
8880
8884
  },
8881
8885
  children: [
8886
+ {
8887
+ name: "heading",
8888
+ props: {
8889
+ text: "Sales Data",
8890
+ level: 1,
8891
+ spacing: {
8892
+ before: 0,
8893
+ after: 0
8894
+ }
8895
+ }
8896
+ },
8882
8897
  {
8883
8898
  name: "paragraph",
8884
8899
  props: {
@@ -8899,9 +8914,22 @@ var plugin_demo_default = {
8899
8914
  {
8900
8915
  name: "section",
8901
8916
  props: {
8902
- title: "Quick Links"
8917
+ meta: {
8918
+ title: "Quick Links"
8919
+ }
8903
8920
  },
8904
8921
  children: [
8922
+ {
8923
+ name: "heading",
8924
+ props: {
8925
+ text: "Quick Links",
8926
+ level: 1,
8927
+ spacing: {
8928
+ before: 0,
8929
+ after: 0
8930
+ }
8931
+ }
8932
+ },
8905
8933
  {
8906
8934
  name: "paragraph",
8907
8935
  props: {
@@ -8913,9 +8941,22 @@ var plugin_demo_default = {
8913
8941
  {
8914
8942
  name: "section",
8915
8943
  props: {
8916
- title: "More Data Examples"
8944
+ meta: {
8945
+ title: "More Data Examples"
8946
+ }
8917
8947
  },
8918
8948
  children: [
8949
+ {
8950
+ name: "heading",
8951
+ props: {
8952
+ text: "More Data Examples",
8953
+ level: 1,
8954
+ spacing: {
8955
+ before: 0,
8956
+ after: 0
8957
+ }
8958
+ }
8959
+ },
8919
8960
  {
8920
8961
  name: "dataTable",
8921
8962
  props: {