@json-to-office/shared 2.6.0 → 3.1.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
@@ -38,6 +38,18 @@ import {
38
38
  transformValueError,
39
39
  transformValueErrors
40
40
  } from "./chunk-LLBCT7WL.js";
41
+ import {
42
+ FeatureRequirementCollector,
43
+ RENDERER_DEPENDENCY_MISSING,
44
+ RendererRegistry,
45
+ UnsupportedRendererFeatureError,
46
+ assertNever,
47
+ assertRendererSupports,
48
+ diagnoseUnsupportedFeatures,
49
+ partitionDiagnostics,
50
+ rendererError,
51
+ rendererWarning
52
+ } from "./chunk-QLZNOXT5.js";
41
53
  import {
42
54
  convertToJsonSchema,
43
55
  createComponentSchema,
@@ -46,7 +58,7 @@ import {
46
58
  fixSchemaReferences,
47
59
  restructureNameDiscriminatedUnions,
48
60
  unionBranches
49
- } from "./chunk-6TNT7DGC.js";
61
+ } from "./chunk-LFHU4EOV.js";
50
62
  import {
51
63
  CANVASES,
52
64
  ChromeSchema,
@@ -75,18 +87,6 @@ import {
75
87
  resolveTypeRoles,
76
88
  validateDesignColors
77
89
  } from "./chunk-4MJFAJFW.js";
78
- import {
79
- FeatureRequirementCollector,
80
- RENDERER_DEPENDENCY_MISSING,
81
- RendererRegistry,
82
- UnsupportedRendererFeatureError,
83
- assertNever,
84
- assertRendererSupports,
85
- diagnoseUnsupportedFeatures,
86
- partitionDiagnostics,
87
- rendererError,
88
- rendererWarning
89
- } from "./chunk-QLZNOXT5.js";
90
90
  import {
91
91
  compareSemver,
92
92
  isValidSemver,
@@ -1483,6 +1483,1927 @@ ${authored}` : css
1483
1483
  };
1484
1484
  }
1485
1485
 
1486
+ // src/blocks/schema.ts
1487
+ import { Type } from "@sinclair/typebox";
1488
+ var BLOCK_SLOT_ROLES = [
1489
+ "actionTitle",
1490
+ "takeaway",
1491
+ "source",
1492
+ "tracker",
1493
+ "footer"
1494
+ ];
1495
+ var BlockSlotSchema = Type.Recursive(
1496
+ (Self) => Type.Object(
1497
+ {
1498
+ type: Type.Union(
1499
+ [
1500
+ "string",
1501
+ "number",
1502
+ "integer",
1503
+ "boolean",
1504
+ "object",
1505
+ "array",
1506
+ "component"
1507
+ ].map((v) => Type.Literal(v)),
1508
+ {
1509
+ description: "Content type accepted by this slot. Use component for a document component or registered plugin."
1510
+ }
1511
+ ),
1512
+ description: Type.Optional(
1513
+ Type.String({
1514
+ description: "Explain this slot\u2019s content and purpose to authors."
1515
+ })
1516
+ ),
1517
+ required: Type.Optional(
1518
+ Type.Boolean({
1519
+ description: "Require a value when no default is provided. Defaults to false."
1520
+ })
1521
+ ),
1522
+ default: Type.Optional(
1523
+ Type.Unknown({
1524
+ description: "Value used when the caller omits this slot. Must satisfy the slot\u2019s type and constraints."
1525
+ })
1526
+ ),
1527
+ enum: Type.Optional(
1528
+ Type.Array(
1529
+ Type.Union([Type.String(), Type.Number(), Type.Boolean()]),
1530
+ {
1531
+ minItems: 1,
1532
+ description: "Allowed scalar values for this slot."
1533
+ }
1534
+ )
1535
+ ),
1536
+ minItems: Type.Optional(
1537
+ Type.Integer({
1538
+ minimum: 0,
1539
+ description: "Minimum number of array entries, inclusive."
1540
+ })
1541
+ ),
1542
+ maxItems: Type.Optional(
1543
+ Type.Integer({
1544
+ minimum: 0,
1545
+ description: "Maximum number of array entries, inclusive."
1546
+ })
1547
+ ),
1548
+ minLength: Type.Optional(
1549
+ Type.Integer({
1550
+ minimum: 0,
1551
+ description: "Minimum string length in characters, inclusive."
1552
+ })
1553
+ ),
1554
+ maxLength: Type.Optional(
1555
+ Type.Integer({
1556
+ minimum: 0,
1557
+ description: "Maximum string length in characters, inclusive."
1558
+ })
1559
+ ),
1560
+ minimum: Type.Optional(
1561
+ Type.Number({ description: "Minimum numeric value, inclusive." })
1562
+ ),
1563
+ maximum: Type.Optional(
1564
+ Type.Number({ description: "Maximum numeric value, inclusive." })
1565
+ ),
1566
+ maxWords: Type.Optional(
1567
+ Type.Integer({
1568
+ minimum: 1,
1569
+ description: "Maximum whitespace-separated word count. Exceeding it fails validation."
1570
+ })
1571
+ ),
1572
+ oneLine: Type.Optional(
1573
+ Type.Boolean({
1574
+ description: "Reject newline characters in string values. Does not prevent visual line wrapping."
1575
+ })
1576
+ ),
1577
+ items: Type.Optional({
1578
+ ...Self,
1579
+ description: "Slot type and constraints for each array entry."
1580
+ }),
1581
+ properties: Type.Optional(
1582
+ Type.Record(Type.String(), Self, {
1583
+ description: "Named child slots accepted by an object slot. Undeclared properties are rejected."
1584
+ })
1585
+ ),
1586
+ role: Type.Optional(
1587
+ Type.Union(
1588
+ BLOCK_SLOT_ROLES.map((role) => Type.Literal(role)),
1589
+ {
1590
+ description: "Content role for quality profiles: actionTitle, takeaway, source, tracker or footer. A profile may require or measure it; the theme only styles it."
1591
+ }
1592
+ )
1593
+ )
1594
+ },
1595
+ { additionalProperties: false }
1596
+ ),
1597
+ // Named so the export hoists it under a stable definition rather than a
1598
+ // TypeBox ordinal that shifts with what the process built before it.
1599
+ { $id: "BlockSlot" }
1600
+ );
1601
+ var JsonBlockDefinitionSchema = Type.Unsafe(
1602
+ Type.Object(
1603
+ {
1604
+ description: Type.Optional(
1605
+ Type.String({
1606
+ description: "Describe what this reusable block renders and when to use it."
1607
+ })
1608
+ ),
1609
+ slots: Type.Record(Type.String(), BlockSlotSchema, {
1610
+ description: "Named inputs and their types, defaults and constraints. Use an empty object for a block with no inputs."
1611
+ }),
1612
+ body: Type.Array(Type.Unknown(), {
1613
+ description: "Components and binding directives expanded in order when this block is invoked."
1614
+ }),
1615
+ section: Type.Optional(
1616
+ Type.Object(
1617
+ {
1618
+ tracker: Type.Optional(
1619
+ Type.Unknown({
1620
+ description: "Section tracker value or binding, available to headers and footers through $context at /section/tracker."
1621
+ })
1622
+ ),
1623
+ header: Type.Optional(
1624
+ Type.Array(Type.Unknown(), {
1625
+ description: "Header component templates. Explicit header settings on the section take precedence."
1626
+ })
1627
+ ),
1628
+ footer: Type.Optional(
1629
+ Type.Array(Type.Unknown(), {
1630
+ description: "Footer component templates. Explicit footer settings on the section take precedence."
1631
+ })
1632
+ ),
1633
+ pageBreak: Type.Optional(
1634
+ Type.Boolean({
1635
+ description: "Start the containing section on a new page. An explicit section pageBreak setting takes precedence."
1636
+ })
1637
+ ),
1638
+ scope: Type.Optional(
1639
+ Type.Union([Type.Literal("section"), Type.Literal("following")], {
1640
+ description: "Apply header/footer templates to this section only, or inherit them in following sections. Defaults to section."
1641
+ })
1642
+ )
1643
+ },
1644
+ {
1645
+ additionalProperties: false,
1646
+ description: "DOCX section tracker, header/footer templates and page-break behavior. Place this block at the section boundary."
1647
+ }
1648
+ )
1649
+ ),
1650
+ slide: Type.Optional(
1651
+ Type.Object(
1652
+ {
1653
+ background: Type.Optional(
1654
+ Type.Unknown({
1655
+ description: "Slide background (color, gradient or image) or a binding. A background the slide states itself takes precedence."
1656
+ })
1657
+ ),
1658
+ grid: Type.Optional(
1659
+ Type.Unknown({
1660
+ description: "Grid configuration merged over the presentation grid when resolving grid placements in this block\u2019s body."
1661
+ })
1662
+ ),
1663
+ notes: Type.Optional(
1664
+ Type.Unknown({
1665
+ description: "Speaker notes or a binding. Notes the slide states itself take precedence."
1666
+ })
1667
+ )
1668
+ },
1669
+ {
1670
+ additionalProperties: false,
1671
+ description: "PPTX slide background, grid and notes supplied by this block. Invoke the block as a direct child of a slide."
1672
+ }
1673
+ )
1674
+ )
1675
+ },
1676
+ { additionalProperties: false }
1677
+ )
1678
+ );
1679
+ var BlockDefinitionsSchema = Type.Record(
1680
+ Type.String({ pattern: "^[a-zA-Z][a-zA-Z0-9_-]*$" }),
1681
+ JsonBlockDefinitionSchema,
1682
+ {
1683
+ description: "Document-local JSON block definitions. Names are not built into the engine."
1684
+ }
1685
+ );
1686
+ var BlockInvocationPropsSchema = Type.Object(
1687
+ {
1688
+ ref: Type.String({
1689
+ minLength: 1,
1690
+ description: "Name in this document\u2019s props.blocks."
1691
+ }),
1692
+ slots: Type.Optional(
1693
+ Type.Record(Type.String(), Type.Unknown(), {
1694
+ description: "Input values keyed by the slot names declared in the referenced block definition."
1695
+ })
1696
+ )
1697
+ },
1698
+ { additionalProperties: false }
1699
+ );
1700
+ function blockSlotJsonSchema(slot) {
1701
+ const { oneLine, properties, items, role: _role, ...rest } = slot;
1702
+ delete rest.required;
1703
+ delete rest.maxWords;
1704
+ if (slot.type === "component") {
1705
+ return {
1706
+ type: "object",
1707
+ properties: { name: { type: "string" } },
1708
+ required: ["name"],
1709
+ description: slot.description
1710
+ };
1711
+ }
1712
+ return {
1713
+ ...rest,
1714
+ ...oneLine && { pattern: "^[^\\r\\n]*$" },
1715
+ ...items && { items: blockSlotJsonSchema(items) },
1716
+ ...properties && {
1717
+ properties: Object.fromEntries(
1718
+ Object.entries(properties).map(([key, value]) => [
1719
+ key,
1720
+ blockSlotJsonSchema(value)
1721
+ ])
1722
+ ),
1723
+ required: Object.entries(properties).filter(([, value]) => value.required && value.default === void 0).map(([key]) => key),
1724
+ additionalProperties: false
1725
+ }
1726
+ };
1727
+ }
1728
+
1729
+ // src/blocks/directives.ts
1730
+ var BLOCK_DIRECTIVES = {
1731
+ $slot: { keys: ["$slot", "default", "props"], result: "dynamic" },
1732
+ $item: { keys: ["$item", "default", "props"], result: "dynamic" },
1733
+ $theme: { keys: ["$theme", "default"], result: "dynamic" },
1734
+ $context: { keys: ["$context", "default"], result: "dynamic" },
1735
+ $count: { keys: ["$count"], result: "number" },
1736
+ $if: { keys: ["$if", "then", "else"], result: "dynamic" },
1737
+ $each: { keys: ["$each", "template"], result: "array" },
1738
+ $join: { keys: ["$join", "separator", "keepEmpty"], result: "string" },
1739
+ $measure: { keys: ["$measure", "fraction", "unit"], result: "number" }
1740
+ };
1741
+ var BLOCK_OPERAND_ROOTS = ["$item", "$slot", "$context"];
1742
+
1743
+ // src/blocks/evaluator.ts
1744
+ import { Value } from "@sinclair/typebox/value";
1745
+ var BlockEvaluationError = class extends Error {
1746
+ constructor(issues) {
1747
+ super(issues.map((i) => `${i.path}: ${i.message}`).join("\n"));
1748
+ this.issues = issues;
1749
+ this.name = "BlockEvaluationError";
1750
+ }
1751
+ };
1752
+ var isBlockRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1753
+ var blockPointerKey = (s) => s.replace(/~/g, "~0").replace(/\//g, "~1");
1754
+ var own = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
1755
+ function blockValueAt(root, path) {
1756
+ if (path === "") return root;
1757
+ if (!path.startsWith("/")) return void 0;
1758
+ let value = root;
1759
+ for (const part of path.slice(1).split("/")) {
1760
+ const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
1761
+ if (!isBlockRecord(value) && !Array.isArray(value) || !own(value, key))
1762
+ return void 0;
1763
+ value = value[key];
1764
+ }
1765
+ return value;
1766
+ }
1767
+ function toAuthoredBlockPointer(map, pointer2) {
1768
+ let best;
1769
+ for (const path of Object.keys(map)) {
1770
+ if ((pointer2 === path || pointer2.startsWith(`${path}/`)) && (best === void 0 || path.length > best.length))
1771
+ best = path;
1772
+ }
1773
+ return best === void 0 ? pointer2 : `${map[best]}${pointer2.slice(best.length)}`;
1774
+ }
1775
+ var BLOCK_SLOT_PLACEMENT_PROPS = [
1776
+ "x",
1777
+ "y",
1778
+ "w",
1779
+ "h",
1780
+ "position",
1781
+ "grid",
1782
+ "gridConfig",
1783
+ "direction",
1784
+ "gap",
1785
+ "weights",
1786
+ "alignment",
1787
+ "spacing"
1788
+ ];
1789
+ var blockWordCount = (text) => text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
1790
+ var present = (value) => value !== void 0 && value !== null && value !== "" && value !== false && (!Array.isArray(value) || value.length > 0);
1791
+ var fail = (path, code, message) => {
1792
+ throw new BlockEvaluationError([{ path, code, message }]);
1793
+ };
1794
+ function resolveBlockSlot(slot, input, path, issues) {
1795
+ const value = input === void 0 && slot.default !== void 0 ? structuredClone(slot.default) : input;
1796
+ if (value === void 0) {
1797
+ if (slot.required)
1798
+ issues.push({
1799
+ path,
1800
+ code: "block_required_slot",
1801
+ message: "Required block slot is missing."
1802
+ });
1803
+ return void 0;
1804
+ }
1805
+ const validType = slot.type === "array" ? Array.isArray(value) : slot.type === "component" ? isBlockRecord(value) && typeof value.name === "string" : slot.type === "object" ? isBlockRecord(value) : slot.type === "integer" ? typeof value === "number" && Number.isInteger(value) : typeof value === slot.type && (typeof value !== "number" || Number.isFinite(value));
1806
+ if (!validType) {
1807
+ issues.push({
1808
+ path,
1809
+ code: "block_slot_type",
1810
+ message: `Expected ${slot.type}.`
1811
+ });
1812
+ return value;
1813
+ }
1814
+ const issue = (message) => issues.push({ path, code: "block_slot_budget", message });
1815
+ if (slot.enum && !slot.enum.includes(value))
1816
+ issue("Value is not one of the declared choices.");
1817
+ if (typeof value === "string") {
1818
+ if (slot.oneLine && /[\r\n]/.test(value))
1819
+ issue("Slot must contain one line.");
1820
+ if (slot.minLength !== void 0 && value.length < slot.minLength)
1821
+ issue(`Minimum length is ${slot.minLength}.`);
1822
+ if (slot.maxLength !== void 0 && value.length > slot.maxLength)
1823
+ issue(`Maximum length is ${slot.maxLength}.`);
1824
+ if (slot.maxWords !== void 0 && blockWordCount(value) > slot.maxWords)
1825
+ issue(`Maximum word count is ${slot.maxWords}.`);
1826
+ }
1827
+ if (typeof value === "number") {
1828
+ if (slot.minimum !== void 0 && value < slot.minimum)
1829
+ issue(`Minimum value is ${slot.minimum}.`);
1830
+ if (slot.maximum !== void 0 && value > slot.maximum)
1831
+ issue(`Maximum value is ${slot.maximum}.`);
1832
+ }
1833
+ if (Array.isArray(value)) {
1834
+ if (slot.minItems !== void 0 && value.length < slot.minItems)
1835
+ issue(`Minimum item count is ${slot.minItems}.`);
1836
+ if (slot.maxItems !== void 0 && value.length > slot.maxItems)
1837
+ issue(`Maximum item count is ${slot.maxItems}.`);
1838
+ return slot.items ? value.map(
1839
+ (v, i) => resolveBlockSlot(slot.items, v, `${path}/${i}`, issues)
1840
+ ) : value;
1841
+ }
1842
+ if (slot.type === "component" && isBlockRecord(value)) {
1843
+ const checkPlacement = (node, pointer2, depth = 0) => {
1844
+ if (depth > 64) {
1845
+ issues.push({
1846
+ path: pointer2,
1847
+ code: "block_expansion_limit",
1848
+ message: "Component slot exceeds 64 levels."
1849
+ });
1850
+ return;
1851
+ }
1852
+ if (Array.isArray(node)) {
1853
+ node.forEach(
1854
+ (item, i) => checkPlacement(item, `${pointer2}/${i}`, depth + 1)
1855
+ );
1856
+ return;
1857
+ }
1858
+ if (!isBlockRecord(node)) return;
1859
+ const props = typeof node.name === "string" && isBlockRecord(node.props) ? node.props : {};
1860
+ for (const key of BLOCK_SLOT_PLACEMENT_PROPS) {
1861
+ if (own(props, key))
1862
+ issues.push({
1863
+ path: `${pointer2}/props/${key}`,
1864
+ code: "block_slot_placement",
1865
+ message: "Block placement belongs in the definition, not in a component slot."
1866
+ });
1867
+ }
1868
+ for (const [key, item] of Object.entries(node))
1869
+ checkPlacement(item, `${pointer2}/${blockPointerKey(key)}`, depth + 1);
1870
+ };
1871
+ checkPlacement(value, path);
1872
+ }
1873
+ if (slot.type === "object" && isBlockRecord(value) && slot.properties)
1874
+ return resolveBlockSlots(slot.properties, value, path, issues);
1875
+ return value;
1876
+ }
1877
+ function resolveBlockSlots(slots, values, path, issues) {
1878
+ const out = {};
1879
+ for (const key of Object.keys(values)) {
1880
+ if (!own(slots, key))
1881
+ issues.push({
1882
+ path: `${path}/${blockPointerKey(key)}`,
1883
+ code: "block_unknown_slot",
1884
+ message: `Unknown slot '${key}'. Expected: ${Object.keys(slots).join(", ")}.`
1885
+ });
1886
+ }
1887
+ for (const [key, slot] of Object.entries(slots)) {
1888
+ const value = resolveBlockSlot(
1889
+ slot,
1890
+ own(values, key) ? values[key] : void 0,
1891
+ `${path}/${blockPointerKey(key)}`,
1892
+ issues
1893
+ );
1894
+ if (value !== void 0)
1895
+ Object.defineProperty(out, key, {
1896
+ value,
1897
+ enumerable: true,
1898
+ writable: true,
1899
+ configurable: true
1900
+ });
1901
+ }
1902
+ return out;
1903
+ }
1904
+ var DIRECTIVES = Object.fromEntries(
1905
+ Object.entries(BLOCK_DIRECTIVES).map(([key, directive]) => [
1906
+ key,
1907
+ directive.keys
1908
+ ])
1909
+ );
1910
+ function slotDescriptorAt(slots, pointer2) {
1911
+ let descriptor = { type: "object", properties: slots };
1912
+ for (const escaped of pointer2.slice(1).split("/")) {
1913
+ const key = escaped.replace(/~1/g, "/").replace(/~0/g, "~");
1914
+ if (descriptor?.type === "object") {
1915
+ if (!descriptor.properties) return { type: "object" };
1916
+ descriptor = own(descriptor.properties, key) ? descriptor.properties[key] : void 0;
1917
+ } else if (descriptor?.type === "array" && /^(0|[1-9]\d*)$/.test(key))
1918
+ descriptor = descriptor.items ?? { type: "object" };
1919
+ else if (descriptor?.type === "component") return { type: "object" };
1920
+ else return void 0;
1921
+ }
1922
+ return descriptor;
1923
+ }
1924
+ var OPERAND_DIRECTIVES = ["$if", "$each", "$count"];
1925
+ var isPointer = (value) => typeof value === "string" && (value === "" || value.startsWith("/"));
1926
+ function blockOperand(value, key) {
1927
+ if (isPointer(value))
1928
+ return {
1929
+ root: OPERAND_DIRECTIVES.includes(key) ? "$slot" : key,
1930
+ pointer: value
1931
+ };
1932
+ if (!OPERAND_DIRECTIVES.includes(key) || !isBlockRecord(value))
1933
+ return void 0;
1934
+ const keys = Object.keys(value);
1935
+ const root = BLOCK_OPERAND_ROOTS.find((candidate) => candidate === keys[0]);
1936
+ if (keys.length !== 1 || !root || !isPointer(value[root])) return void 0;
1937
+ return { root, pointer: value[root] };
1938
+ }
1939
+ function checkTemplate(value, path, slots, issues, repeated = false, depth = 0) {
1940
+ if (depth > 64) {
1941
+ issues.push({
1942
+ path,
1943
+ code: "block_depth",
1944
+ message: "Definition exceeds 64 levels."
1945
+ });
1946
+ return;
1947
+ }
1948
+ if (Array.isArray(value)) {
1949
+ value.forEach(
1950
+ (v, i) => checkTemplate(v, `${path}/${i}`, slots, issues, repeated, depth + 1)
1951
+ );
1952
+ return;
1953
+ }
1954
+ if (!isBlockRecord(value)) return;
1955
+ const keys = Object.keys(value).filter((k) => k.startsWith("$"));
1956
+ if (keys.length) {
1957
+ const key = keys[0];
1958
+ const allowed = DIRECTIVES[key];
1959
+ if (!allowed || keys.length !== 1 || Object.keys(value).some((k) => !allowed.includes(k))) {
1960
+ issues.push({
1961
+ path,
1962
+ code: "block_invalid_binding",
1963
+ message: "Unknown or malformed block directive."
1964
+ });
1965
+ return;
1966
+ }
1967
+ if ([
1968
+ "$slot",
1969
+ "$item",
1970
+ "$theme",
1971
+ "$context",
1972
+ "$if",
1973
+ "$each",
1974
+ "$count"
1975
+ ].includes(key)) {
1976
+ const operand = blockOperand(value[key], key);
1977
+ if (!operand)
1978
+ issues.push({
1979
+ path,
1980
+ code: "block_invalid_binding",
1981
+ message: OPERAND_DIRECTIVES.includes(key) ? `${key} takes a slot pointer such as /items, or one reference: ${BLOCK_OPERAND_ROOTS.map((root) => `{ "${root}": ... }`).join(", ")}.` : "Bindings use JSON Pointers, e.g. /title."
1982
+ });
1983
+ else if (operand.root === "$slot") {
1984
+ const descriptor = slotDescriptorAt(slots, operand.pointer);
1985
+ if (!descriptor)
1986
+ issues.push({
1987
+ path,
1988
+ code: "block_unknown_binding",
1989
+ message: `No slot field '${operand.pointer}' is declared.`
1990
+ });
1991
+ else if (["$each", "$count"].includes(key) && descriptor.type !== "array")
1992
+ issues.push({
1993
+ path,
1994
+ code: "block_invalid_binding",
1995
+ message: `${key} requires an array slot.`
1996
+ });
1997
+ }
1998
+ if (operand?.root === "$item" && !repeated)
1999
+ issues.push({
2000
+ path,
2001
+ code: "block_invalid_binding",
2002
+ message: "$item is only available inside $each."
2003
+ });
2004
+ if ((key === "$slot" || key === "$item") && own(value, "props") && !isBlockRecord(value.props))
2005
+ issues.push({
2006
+ path: `${path}/props`,
2007
+ code: "block_invalid_binding",
2008
+ message: "props must be an object of component props merged beneath a component-slot value."
2009
+ });
2010
+ }
2011
+ if (key === "$join" && value.keepEmpty !== void 0 && typeof value.keepEmpty !== "boolean")
2012
+ issues.push({
2013
+ path,
2014
+ code: "block_invalid_binding",
2015
+ message: "keepEmpty must be boolean."
2016
+ });
2017
+ if (key === "$if" && !own(value, "then"))
2018
+ issues.push({
2019
+ path,
2020
+ code: "block_invalid_binding",
2021
+ message: "$if requires then."
2022
+ });
2023
+ if (key === "$each" && (!own(value, "template") || Array.isArray(value.template)))
2024
+ issues.push({
2025
+ path,
2026
+ code: "block_invalid_binding",
2027
+ message: "$each requires one template value; use a group for multiple flow children."
2028
+ });
2029
+ if (key === "$join" && (!Array.isArray(value.$join) || value.separator !== void 0 && typeof value.separator !== "string"))
2030
+ issues.push({
2031
+ path,
2032
+ code: "block_invalid_binding",
2033
+ message: "$join requires an array and an optional string separator."
2034
+ });
2035
+ if (key === "$measure" && (!["width", "height"].includes(String(value.$measure)) || !["pt", "twip", "in"].includes(String(value.unit ?? "pt")) || value.fraction !== void 0 && (typeof value.fraction !== "number" || value.fraction < 0 || value.fraction > 1)))
2036
+ issues.push({
2037
+ path,
2038
+ code: "block_invalid_binding",
2039
+ message: "$measure requires width/height, pt/twip/in and a fraction between 0 and 1."
2040
+ });
2041
+ }
2042
+ for (const [key, item] of Object.entries(value)) {
2043
+ if (key.startsWith("$") && key !== "$join") continue;
2044
+ checkTemplate(
2045
+ item,
2046
+ `${path}/${blockPointerKey(key)}`,
2047
+ slots,
2048
+ issues,
2049
+ repeated || own(value, "$each"),
2050
+ depth + 1
2051
+ );
2052
+ }
2053
+ }
2054
+ function readBlockDefinitions(document) {
2055
+ const value = isBlockRecord(document) && isBlockRecord(document.props) ? document.props.blocks : void 0;
2056
+ return value ?? {};
2057
+ }
2058
+ function validateBlockDefinitions(definitions, format, reservedNames = []) {
2059
+ if (!Value.Check(BlockDefinitionsSchema, definitions))
2060
+ return [...Value.Errors(BlockDefinitionsSchema, definitions)].slice(0, 100).map((e) => ({
2061
+ path: `/props/blocks${e.path}`,
2062
+ code: "block_invalid_definition",
2063
+ message: e.message
2064
+ }));
2065
+ const issues = [];
2066
+ for (const [name, def] of Object.entries(definitions)) {
2067
+ const path = `/props/blocks/${blockPointerKey(name)}`;
2068
+ if (reservedNames.includes(name))
2069
+ issues.push({
2070
+ path,
2071
+ code: "block_name_collision",
2072
+ message: `Block '${name}' conflicts with a registered component.`
2073
+ });
2074
+ if (format !== "docx" && def.section)
2075
+ issues.push({
2076
+ path: `${path}/section`,
2077
+ code: "block_format",
2078
+ message: "Section effects are DOCX-only."
2079
+ });
2080
+ if (format !== "pptx" && def.slide)
2081
+ issues.push({
2082
+ path: `${path}/slide`,
2083
+ code: "block_format",
2084
+ message: "Slide effects are PPTX-only."
2085
+ });
2086
+ const checkSlot = (slot, pointer2) => {
2087
+ if (slot.default !== void 0)
2088
+ resolveBlockSlot(slot, slot.default, `${pointer2}/default`, issues);
2089
+ for (const [minimum, maximum] of [
2090
+ ["minItems", "maxItems"],
2091
+ ["minLength", "maxLength"],
2092
+ ["minimum", "maximum"]
2093
+ ]) {
2094
+ if (slot[minimum] !== void 0 && slot[maximum] !== void 0 && slot[minimum] > slot[maximum])
2095
+ issues.push({
2096
+ path: pointer2,
2097
+ code: "block_invalid_definition",
2098
+ message: `${minimum} exceeds ${maximum}.`
2099
+ });
2100
+ }
2101
+ if (slot.items) checkSlot(slot.items, `${pointer2}/items`);
2102
+ for (const [key, nested] of Object.entries(slot.properties ?? {}))
2103
+ checkSlot(nested, `${pointer2}/properties/${blockPointerKey(key)}`);
2104
+ };
2105
+ for (const [key, slot] of Object.entries(def.slots))
2106
+ checkSlot(slot, `${path}/slots/${blockPointerKey(key)}`);
2107
+ checkTemplate(def.body, `${path}/body`, def.slots, issues);
2108
+ if (def.section)
2109
+ checkTemplate(def.section, `${path}/section`, def.slots, issues);
2110
+ if (def.slide) checkTemplate(def.slide, `${path}/slide`, def.slots, issues);
2111
+ }
2112
+ return issues;
2113
+ }
2114
+ function validateBlockInvocations(document, definitions, format, reservedNames = []) {
2115
+ const issues = validateBlockDefinitions(definitions, format, reservedNames);
2116
+ if (issues.length) return issues;
2117
+ const walk = (v, path) => {
2118
+ if (Array.isArray(v)) {
2119
+ v.forEach((item, i) => walk(item, `${path}/${i}`));
2120
+ return;
2121
+ }
2122
+ if (!isBlockRecord(v) || v.enabled === false) return;
2123
+ if (v.name === "block" && isBlockRecord(v.props) && typeof v.props.ref === "string") {
2124
+ const def = own(definitions, v.props.ref) ? definitions[v.props.ref] : void 0;
2125
+ if (!def)
2126
+ issues.push({
2127
+ path: `${path}/props/ref`,
2128
+ code: "block_unknown_reference",
2129
+ message: `Block '${v.props.ref}' is not defined in this document.`
2130
+ });
2131
+ else {
2132
+ if (v.props.slots === void 0 || isBlockRecord(v.props.slots))
2133
+ resolveBlockSlots(
2134
+ def.slots,
2135
+ v.props.slots ?? {},
2136
+ `${path}/props/slots`,
2137
+ issues
2138
+ );
2139
+ if (def.section && !/^\/children\/\d+\/children\/\d+$/.test(path))
2140
+ issues.push({
2141
+ path,
2142
+ code: "invalid_placement",
2143
+ message: "A block with section effects must be a direct child of a top-level section."
2144
+ });
2145
+ if (def.slide && !/^\/children\/\d+\/children\/\d+$/.test(path))
2146
+ issues.push({
2147
+ path,
2148
+ code: "invalid_placement",
2149
+ message: "A block with slide effects must be a direct child of a slide."
2150
+ });
2151
+ }
2152
+ }
2153
+ for (const [key, item] of Object.entries(v)) {
2154
+ if (path === "/props" && key === "blocks") continue;
2155
+ walk(item, `${path}/${blockPointerKey(key)}`);
2156
+ }
2157
+ };
2158
+ walk(document, "");
2159
+ return issues;
2160
+ }
2161
+ var JsonBlockEvaluator = class {
2162
+ constructor(definitions, options) {
2163
+ this.definitions = definitions;
2164
+ this.options = options;
2165
+ const issues = validateBlockDefinitions(
2166
+ definitions,
2167
+ options.format,
2168
+ options.reservedNames
2169
+ );
2170
+ if (issues.length) throw new BlockEvaluationError(issues);
2171
+ }
2172
+ sourceMap = {};
2173
+ blocks = [];
2174
+ nodes = 0;
2175
+ guard(path, depth) {
2176
+ if (depth > 64 || ++this.nodes > 5e4)
2177
+ fail(
2178
+ path,
2179
+ "block_expansion_limit",
2180
+ "Block expansion exceeds the depth/node limit (64/50000)."
2181
+ );
2182
+ }
2183
+ /**
2184
+ * A directive operand and the authored pointer it came from: the slot the
2185
+ * pointer form names, or the current item, slot or context a reference
2186
+ * object names. Definitions are validated before this runs, so a malformed
2187
+ * operand cannot reach it.
2188
+ */
2189
+ operand(raw, key, env) {
2190
+ const { root, pointer: pointer2 } = blockOperand(raw, key);
2191
+ const { value, source } = this.reference(root, pointer2, env);
2192
+ return {
2193
+ value,
2194
+ source,
2195
+ // Element i of the array is authored at pointer/i, looked up the same
2196
+ // way: a slot the enclosing invocation built from its own repeat maps
2197
+ // element by element, and pointer + "/i" is not the same as that.
2198
+ element: (index) => this.reference(root, `${pointer2}/${index}`, env).source
2199
+ };
2200
+ }
2201
+ /**
2202
+ * What a reference reads and where the author wrote it. One resolution for
2203
+ * the binding form and the operand form, so `{ "$context": ... }` is
2204
+ * attributed the same way whichever directive carries it.
2205
+ */
2206
+ reference(root, pointer2, env) {
2207
+ if (root === "$item")
2208
+ return {
2209
+ value: blockValueAt(env.item, pointer2),
2210
+ source: `${env.itemSource ?? env.source}${pointer2}`
2211
+ };
2212
+ if (root === "$context") {
2213
+ const authored = toAuthoredBlockPointer(
2214
+ env.contextSources ?? {},
2215
+ pointer2
2216
+ );
2217
+ return {
2218
+ value: blockValueAt(env.context, pointer2),
2219
+ source: authored === pointer2 ? env.source : authored
2220
+ };
2221
+ }
2222
+ if (root === "$theme")
2223
+ return {
2224
+ value: blockValueAt(this.options.theme, pointer2),
2225
+ source: env.source
2226
+ };
2227
+ return {
2228
+ value: blockValueAt(env.slots, pointer2),
2229
+ source: env.slotSources ? toAuthoredBlockPointer(env.slotSources, pointer2) : `${env.source}/props/slots${pointer2}`
2230
+ };
2231
+ }
2232
+ evaluate(value, env, out, definitionPath, depth = 0) {
2233
+ this.guard(env.source, depth);
2234
+ this.sourceMap[out] = env.source;
2235
+ if (Array.isArray(value)) {
2236
+ const result2 = [];
2237
+ value.forEach((v, i) => {
2238
+ const evaluated = this.evaluate(
2239
+ v,
2240
+ env,
2241
+ `${out}/${result2.length}`,
2242
+ `${definitionPath}/${i}`,
2243
+ depth + 1
2244
+ );
2245
+ if (evaluated !== void 0) {
2246
+ if (isBlockRecord(v) && ("$if" in v || "$each" in v) && Array.isArray(evaluated)) {
2247
+ const base = `${out}/${result2.length}`;
2248
+ const maps = Object.entries(this.sourceMap).filter(
2249
+ ([key]) => key.startsWith(`${base}/`)
2250
+ );
2251
+ for (const [key] of maps) delete this.sourceMap[key];
2252
+ for (const [key, source] of maps) {
2253
+ const rest = key.slice(base.length + 1);
2254
+ const [index, ...suffix] = rest.split("/");
2255
+ this.sourceMap[`${out}/${result2.length + Number(index)}${suffix.length ? "/" + suffix.join("/") : ""}`] = source;
2256
+ }
2257
+ result2.push(...evaluated);
2258
+ } else result2.push(evaluated);
2259
+ }
2260
+ });
2261
+ return result2;
2262
+ }
2263
+ if (!isBlockRecord(value)) return value;
2264
+ if ("$slot" in value || "$item" in value || "$theme" in value || "$context" in value) {
2265
+ const key = ["$slot", "$item", "$theme", "$context"].find(
2266
+ (k) => k in value
2267
+ );
2268
+ const pointer2 = value[key];
2269
+ const { value: found, source } = this.reference(
2270
+ key,
2271
+ pointer2,
2272
+ env
2273
+ );
2274
+ this.sourceMap[out] = source;
2275
+ let result2 = found !== void 0 ? structuredClone(found) : void 0;
2276
+ if (result2 === void 0 && own(value, "default"))
2277
+ result2 = this.evaluate(
2278
+ value.default,
2279
+ env,
2280
+ out,
2281
+ `${definitionPath}/default`,
2282
+ depth + 1
2283
+ );
2284
+ if (result2 === void 0 && key === "$theme")
2285
+ return fail(
2286
+ definitionPath,
2287
+ "block_unknown_theme_binding",
2288
+ `Theme value '${pointer2}' is missing; declare a fallback or use an existing token.`
2289
+ );
2290
+ if ((key === "$slot" || key === "$item") && own(value, "props") && isBlockRecord(result2) && typeof result2.name === "string") {
2291
+ const origin = this.sourceMap[out];
2292
+ const defaults = this.evaluate(
2293
+ value.props,
2294
+ env,
2295
+ `${out}/props`,
2296
+ `${definitionPath}/props`,
2297
+ depth + 1
2298
+ );
2299
+ const authored = isBlockRecord(result2.props) ? result2.props : {};
2300
+ for (const propKey of Object.keys(authored)) {
2301
+ const pointerKey = `${out}/props/${blockPointerKey(propKey)}`;
2302
+ for (const mapped of Object.keys(this.sourceMap))
2303
+ if (mapped === pointerKey || mapped.startsWith(`${pointerKey}/`))
2304
+ delete this.sourceMap[mapped];
2305
+ this.sourceMap[pointerKey] = `${origin}/props/${blockPointerKey(propKey)}`;
2306
+ }
2307
+ result2 = {
2308
+ ...result2,
2309
+ props: {
2310
+ ...isBlockRecord(defaults) ? defaults : {},
2311
+ ...authored
2312
+ }
2313
+ };
2314
+ }
2315
+ return result2;
2316
+ }
2317
+ if ("$if" in value) {
2318
+ const operand = this.operand(value.$if, "$if", env);
2319
+ const branch = present(operand.value) ? value.then : value.else;
2320
+ const result2 = this.evaluate(branch, env, out, definitionPath, depth + 1);
2321
+ const literal = !isBlockRecord(branch) || !Object.keys(branch).some((k) => k.startsWith("$"));
2322
+ if (literal && this.sourceMap[out] === env.source)
2323
+ this.sourceMap[out] = operand.source;
2324
+ return result2;
2325
+ }
2326
+ if ("$count" in value) {
2327
+ const operand = this.operand(value.$count, "$count", env);
2328
+ if (!Array.isArray(operand.value))
2329
+ return fail(
2330
+ operand.source,
2331
+ "block_slot_type",
2332
+ "$count requires an array."
2333
+ );
2334
+ return operand.value.length;
2335
+ }
2336
+ if ("$each" in value) {
2337
+ const operand = this.operand(value.$each, "$each", env);
2338
+ const list = operand.value;
2339
+ if (!Array.isArray(list))
2340
+ return fail(
2341
+ operand.source,
2342
+ "block_slot_type",
2343
+ "$each requires an array."
2344
+ );
2345
+ const result2 = [];
2346
+ list.forEach((item, i) => {
2347
+ const pointer2 = `${out}/${result2.length}`;
2348
+ const evaluated = this.evaluate(
2349
+ value.template,
2350
+ {
2351
+ ...env,
2352
+ item,
2353
+ itemSource: operand.element(i)
2354
+ },
2355
+ pointer2,
2356
+ `${definitionPath}/template`,
2357
+ depth + 1
2358
+ );
2359
+ if (evaluated !== void 0) {
2360
+ if (this.sourceMap[pointer2] === env.source)
2361
+ this.sourceMap[pointer2] = operand.element(i);
2362
+ result2.push(evaluated);
2363
+ } else
2364
+ for (const key of Object.keys(this.sourceMap)) {
2365
+ if (key === pointer2 || key.startsWith(`${pointer2}/`))
2366
+ delete this.sourceMap[key];
2367
+ }
2368
+ });
2369
+ return result2;
2370
+ }
2371
+ if ("$join" in value) {
2372
+ const values = value.$join.map(
2373
+ (v, i) => this.evaluate(
2374
+ v,
2375
+ env,
2376
+ `${out}/${i}`,
2377
+ `${definitionPath}/$join/${i}`,
2378
+ depth + 1
2379
+ )
2380
+ );
2381
+ const first = values.findIndex(present);
2382
+ if (first >= 0) this.sourceMap[out] = this.sourceMap[`${out}/${first}`];
2383
+ return (value.keepEmpty === true ? values : values.filter(present)).map((v) => String(v ?? "")).join(String(value.separator ?? ""));
2384
+ }
2385
+ if ("$measure" in value) {
2386
+ if (!this.options.measure)
2387
+ return fail(
2388
+ definitionPath,
2389
+ "block_unsupported_operation",
2390
+ "This format does not support $measure."
2391
+ );
2392
+ return this.options.measure(
2393
+ value.$measure,
2394
+ value.unit ?? "pt",
2395
+ env.context
2396
+ ) * Number(value.fraction ?? 1);
2397
+ }
2398
+ const result = {};
2399
+ for (const [key, item] of Object.entries(value)) {
2400
+ const evaluated = this.evaluate(
2401
+ item,
2402
+ env,
2403
+ `${out}/${blockPointerKey(key)}`,
2404
+ `${definitionPath}/${blockPointerKey(key)}`,
2405
+ depth + 1
2406
+ );
2407
+ if (evaluated !== void 0)
2408
+ Object.defineProperty(result, key, {
2409
+ value: evaluated,
2410
+ enumerable: true,
2411
+ configurable: true,
2412
+ writable: true
2413
+ });
2414
+ }
2415
+ return result;
2416
+ }
2417
+ expand(value, path = "", depth = 0) {
2418
+ this.guard(path, depth);
2419
+ if (Array.isArray(value))
2420
+ return value.map((v, i) => this.expand(v, `${path}/${i}`, depth + 1));
2421
+ if (!isBlockRecord(value)) return value;
2422
+ if (value.name === "block" && value.enabled !== false) {
2423
+ if (!isBlockRecord(value.props) || typeof value.props.ref !== "string")
2424
+ return fail(
2425
+ path,
2426
+ "block_invalid_invocation",
2427
+ "A block requires props.ref and optional props.slots."
2428
+ );
2429
+ if (Object.keys(value.props).some(
2430
+ (key) => !["ref", "slots"].includes(key)
2431
+ ) || value.props.slots !== void 0 && !isBlockRecord(value.props.slots))
2432
+ return fail(
2433
+ path,
2434
+ "block_invalid_invocation",
2435
+ "Block props accept only ref and an object of slots."
2436
+ );
2437
+ const def = own(this.definitions, value.props.ref) ? this.definitions[value.props.ref] : void 0;
2438
+ if (!def)
2439
+ return fail(
2440
+ `${path}/props/ref`,
2441
+ "block_unknown_reference",
2442
+ `Block '${value.props.ref}' is not defined in this document.`
2443
+ );
2444
+ const issues = [];
2445
+ const source = toAuthoredBlockPointer(this.sourceMap, path);
2446
+ const slotsPath = `${path}/props/slots`;
2447
+ const slots = resolveBlockSlots(
2448
+ def.slots,
2449
+ value.props.slots ?? {},
2450
+ slotsPath,
2451
+ issues
2452
+ );
2453
+ if (issues.length)
2454
+ throw new BlockEvaluationError(
2455
+ issues.map((issue) => ({
2456
+ ...issue,
2457
+ path: toAuthoredBlockPointer(this.sourceMap, issue.path)
2458
+ }))
2459
+ );
2460
+ const slotSources = Object.fromEntries([
2461
+ ["", toAuthoredBlockPointer(this.sourceMap, slotsPath)],
2462
+ ...Object.entries(this.sourceMap).filter(([key]) => key.startsWith(`${slotsPath}/`)).map(([key, origin]) => [key.slice(slotsPath.length), origin])
2463
+ ]);
2464
+ const env = {
2465
+ slots,
2466
+ slotSources,
2467
+ source,
2468
+ definition: `/props/blocks/${blockPointerKey(value.props.ref)}`,
2469
+ context: this.options.contextAt?.(path) ?? this.options.context ?? {},
2470
+ contextSources: this.options.contextSources
2471
+ };
2472
+ if (def.section)
2473
+ this.options.onSection?.({
2474
+ settings: def.section,
2475
+ environment: env,
2476
+ path
2477
+ });
2478
+ if (def.slide)
2479
+ this.options.onSlide?.({ settings: def.slide, environment: env, path });
2480
+ this.blocks.push(source);
2481
+ const children = this.evaluate(
2482
+ def.body,
2483
+ env,
2484
+ `${path}/children`,
2485
+ `${env.definition}/body`,
2486
+ depth + 1
2487
+ );
2488
+ return {
2489
+ name: "group",
2490
+ ...value.id !== void 0 && { id: value.id },
2491
+ children: this.expand(children, `${path}/children`, depth + 1)
2492
+ };
2493
+ }
2494
+ if (value.enabled === false) return { ...value };
2495
+ const result = { ...value };
2496
+ for (const [key, item] of Object.entries(value)) {
2497
+ if (path === "/props" && key === "blocks") continue;
2498
+ Object.defineProperty(result, key, {
2499
+ value: this.expand(item, `${path}/${blockPointerKey(key)}`, depth + 1),
2500
+ enumerable: true,
2501
+ configurable: true,
2502
+ writable: true
2503
+ });
2504
+ }
2505
+ return result;
2506
+ }
2507
+ };
2508
+
2509
+ // src/blocks/metadata.ts
2510
+ import { Value as Value2 } from "@sinclair/typebox/value";
2511
+ function blockSlotsJsonSchema(definition) {
2512
+ return {
2513
+ type: "object",
2514
+ additionalProperties: false,
2515
+ properties: Object.fromEntries(
2516
+ Object.entries(definition.slots).map(([key, slot]) => [
2517
+ key,
2518
+ blockSlotJsonSchema(slot)
2519
+ ])
2520
+ ),
2521
+ required: Object.entries(definition.slots).filter(([, slot]) => slot.required && slot.default === void 0).map(([key]) => key)
2522
+ };
2523
+ }
2524
+ function documentBlockMetadata(document) {
2525
+ const definitions = readBlockDefinitions(document);
2526
+ if (!Value2.Check(BlockDefinitionsSchema, definitions))
2527
+ return { definitions: [], invocations: [], invalidDefinitions: true };
2528
+ const invocations2 = [];
2529
+ const walk = (value, path) => {
2530
+ if (Array.isArray(value)) {
2531
+ value.forEach((item, i) => walk(item, `${path}/${i}`));
2532
+ return;
2533
+ }
2534
+ if (!isBlockRecord(value)) return;
2535
+ if (value.name === "block" && isBlockRecord(value.props) && typeof value.props.ref === "string")
2536
+ invocations2.push({
2537
+ ref: value.props.ref,
2538
+ path,
2539
+ slotsPath: `${path}/props/slots`,
2540
+ defined: Object.prototype.hasOwnProperty.call(
2541
+ definitions,
2542
+ value.props.ref
2543
+ )
2544
+ });
2545
+ for (const [key, item] of Object.entries(value)) {
2546
+ if (path === "/props" && key === "blocks") continue;
2547
+ walk(item, `${path}/${blockPointerKey(key)}`);
2548
+ }
2549
+ };
2550
+ walk(document, "");
2551
+ return {
2552
+ definitions: Object.entries(definitions).map(([name, definition]) => ({
2553
+ name,
2554
+ definitionPointer: `/props/blocks/${blockPointerKey(name)}`,
2555
+ definition,
2556
+ slotsSchema: blockSlotsJsonSchema(definition)
2557
+ })),
2558
+ invocations: invocations2,
2559
+ invalidDefinitions: false
2560
+ };
2561
+ }
2562
+ function visitInvocationSlots(document, blocks, visit) {
2563
+ const definitions = readBlockDefinitions(document);
2564
+ for (const path of blocks) {
2565
+ const node = blockValueAt(document, path);
2566
+ if (!isBlockRecord(node) || !isBlockRecord(node.props) || typeof node.props.ref !== "string")
2567
+ continue;
2568
+ const ref = node.props.ref;
2569
+ const definition = definitions[ref];
2570
+ if (!definition) continue;
2571
+ const walk = (slot, value, pointer2, name) => {
2572
+ visit(ref, slot, value, pointer2, name);
2573
+ if (isBlockRecord(value) && slot.properties) {
2574
+ for (const [key, property] of Object.entries(slot.properties)) {
2575
+ walk(
2576
+ property,
2577
+ blockValueAt(value, `/${blockPointerKey(key)}`),
2578
+ `${pointer2}/${blockPointerKey(key)}`,
2579
+ `${name}.${key}`
2580
+ );
2581
+ }
2582
+ }
2583
+ if (Array.isArray(value) && slot.items)
2584
+ value.forEach(
2585
+ (item, i) => walk(slot.items, item, `${pointer2}/${i}`, name)
2586
+ );
2587
+ };
2588
+ for (const [name, slot] of Object.entries(definition.slots)) {
2589
+ const authored = blockValueAt(
2590
+ node.props.slots,
2591
+ `/${blockPointerKey(name)}`
2592
+ );
2593
+ walk(
2594
+ slot,
2595
+ authored === void 0 && slot.default !== void 0 ? slot.default : authored,
2596
+ `${path}/props/slots/${blockPointerKey(name)}`,
2597
+ name
2598
+ );
2599
+ }
2600
+ }
2601
+ }
2602
+ function blockSlotBudgets(document, blocks) {
2603
+ const result = [];
2604
+ visitInvocationSlots(document, blocks, (ref, slot, value, pointer2, name) => {
2605
+ if (typeof value === "string" && slot.maxWords !== void 0)
2606
+ result.push({
2607
+ block: ref,
2608
+ slot: name,
2609
+ path: pointer2,
2610
+ words: blockWordCount(value),
2611
+ maxWords: slot.maxWords
2612
+ });
2613
+ });
2614
+ return result;
2615
+ }
2616
+ function blockSlotRoles(document, blocks) {
2617
+ const result = [];
2618
+ visitInvocationSlots(document, blocks, (ref, slot, value, pointer2, name) => {
2619
+ if (!slot.role) return;
2620
+ result.push({
2621
+ block: ref,
2622
+ invocation: pointer2.replace(/\/props\/slots\/.*$/, ""),
2623
+ slot: name,
2624
+ role: slot.role,
2625
+ path: pointer2,
2626
+ value
2627
+ });
2628
+ });
2629
+ return result;
2630
+ }
2631
+
2632
+ // src/blocks/schema-types.ts
2633
+ var allTypes = [
2634
+ "null",
2635
+ "boolean",
2636
+ "number",
2637
+ "string",
2638
+ "array",
2639
+ "object"
2640
+ ];
2641
+ var intersection = (a, b) => new Set([...a].filter((value) => b.has(value)));
2642
+ var union = (sets) => new Set(sets.flatMap((set) => [...set]));
2643
+ var typeOf = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
2644
+ function possibleValueTypes(schema, resolve, seen = /* @__PURE__ */ new Set()) {
2645
+ if (schema === false) return /* @__PURE__ */ new Set();
2646
+ if (schema === true || seen.has(schema)) return new Set(allTypes);
2647
+ const negated = schema.not;
2648
+ if (negated === true || negated && typeof negated === "object" && Object.keys(negated).length === 0)
2649
+ return /* @__PURE__ */ new Set();
2650
+ const next = new Set(seen).add(schema);
2651
+ let types = new Set(allTypes);
2652
+ if (schema.type) {
2653
+ const declared = Array.isArray(schema.type) ? schema.type : [schema.type];
2654
+ types = intersection(
2655
+ types,
2656
+ new Set(
2657
+ allTypes.filter(
2658
+ (type) => declared.includes(type) || type === "number" && declared.includes("integer")
2659
+ )
2660
+ )
2661
+ );
2662
+ }
2663
+ if (Object.hasOwn(schema, "const"))
2664
+ types = intersection(types, /* @__PURE__ */ new Set([typeOf(schema.const)]));
2665
+ if (Array.isArray(schema.enum))
2666
+ types = intersection(types, new Set(schema.enum.map(typeOf)));
2667
+ if (typeof schema.$ref === "string") {
2668
+ const target = resolve(schema.$ref);
2669
+ if (target !== void 0)
2670
+ types = intersection(types, possibleValueTypes(target, resolve, next));
2671
+ }
2672
+ for (const key of ["anyOf", "oneOf"]) {
2673
+ if (Array.isArray(schema[key]))
2674
+ types = intersection(
2675
+ types,
2676
+ union(
2677
+ schema[key].map(
2678
+ (branch) => possibleValueTypes(branch, resolve, next)
2679
+ )
2680
+ )
2681
+ );
2682
+ }
2683
+ if (Array.isArray(schema.allOf))
2684
+ for (const branch of schema.allOf)
2685
+ types = intersection(types, possibleValueTypes(branch, resolve, next));
2686
+ if (negated && typeof negated === "object" && negated.type && Object.keys(negated).every(
2687
+ (key) => ["type", "description", "title", "$comment"].includes(key)
2688
+ )) {
2689
+ const excluded = (Array.isArray(negated.type) ? negated.type : [negated.type]).filter((type) => type !== "integer");
2690
+ types = new Set([...types].filter((type) => !excluded.includes(type)));
2691
+ }
2692
+ return types;
2693
+ }
2694
+ function arrayItemSchema(schema, resolve, seen = /* @__PURE__ */ new Set()) {
2695
+ if (schema === false) return false;
2696
+ if (schema === true || seen.has(schema)) return {};
2697
+ const next = new Set(seen).add(schema);
2698
+ const constraints = [];
2699
+ if (typeof schema.$ref === "string") {
2700
+ const target = resolve(schema.$ref);
2701
+ if (target !== void 0)
2702
+ constraints.push(arrayItemSchema(target, resolve, next));
2703
+ }
2704
+ if (schema.items !== void 0)
2705
+ constraints.push(
2706
+ Array.isArray(schema.items) ? {
2707
+ anyOf: [
2708
+ ...schema.items,
2709
+ ...schema.additionalItems === false ? [] : [schema.additionalItems ?? {}]
2710
+ ]
2711
+ } : schema.items
2712
+ );
2713
+ for (const key of ["anyOf", "oneOf"])
2714
+ if (Array.isArray(schema[key])) {
2715
+ constraints.push({
2716
+ anyOf: schema[key].filter(
2717
+ (branch) => possibleValueTypes(branch, resolve).has("array")
2718
+ ).map(
2719
+ (branch) => arrayItemSchema(branch, resolve, next)
2720
+ )
2721
+ });
2722
+ }
2723
+ if (Array.isArray(schema.allOf))
2724
+ constraints.push(
2725
+ ...schema.allOf.map(
2726
+ (branch) => arrayItemSchema(branch, resolve, next)
2727
+ )
2728
+ );
2729
+ return constraints.length === 0 ? {} : constraints.length === 1 ? constraints[0] : { allOf: constraints };
2730
+ }
2731
+
2732
+ // src/blocks/authoring-schema.ts
2733
+ var object = (properties, required) => ({
2734
+ type: "object",
2735
+ properties,
2736
+ required,
2737
+ additionalProperties: false
2738
+ });
2739
+ var pointer = (description) => ({
2740
+ type: "string",
2741
+ pattern: "^(|/.*)$",
2742
+ description
2743
+ });
2744
+ var referenceDescriptions = (format) => ({
2745
+ $slot: "Read a named input slot by JSON Pointer, e.g. /title or /client/name.",
2746
+ $item: "Read the current $each entry by JSON Pointer. Use an empty string for the whole entry or /title for a property.",
2747
+ $theme: "Read the active theme by JSON Pointer, e.g. /colors/primary. A missing value requires a default.",
2748
+ $context: format === "pptx" ? "Read deck or slide context by JSON Pointer, e.g. /document/title, /slide/width or /slide/index." : "Read document or section context by JSON Pointer, e.g. /document/title or /section/tracker."
2749
+ });
2750
+ var measureDescriptions = (format) => format === "pptx" ? {
2751
+ axis: "Measure the slide canvas width or height, in the unit given.",
2752
+ unit: "Measurement unit: points, twentieths of a point, or inches. Defaults to pt; use in for frame coordinates."
2753
+ } : {
2754
+ axis: "Measure the usable page width or height after margins, using the containing section\u2019s page settings.",
2755
+ unit: "Measurement unit: points, twentieths of a point, or inches. Defaults to pt."
2756
+ };
2757
+ var describe = (schema, description) => ({
2758
+ ...typeof schema === "boolean" ? { allOf: [schema] } : schema,
2759
+ description
2760
+ });
2761
+ var metadata = (schema) => typeof schema === "boolean" ? {} : {
2762
+ ...schema.description && { description: schema.description },
2763
+ ...schema.markdownDescription && {
2764
+ markdownDescription: schema.markdownDescription
2765
+ }
2766
+ };
2767
+ var hasKey = (key) => ({ type: "object", required: [key] });
2768
+ var directiveNames = Object.keys(BLOCK_DIRECTIVES);
2769
+ function createBlockAuthoringSchema(definitions, componentDefinition, excludedComponents = [], format = "docx") {
2770
+ const prefix = `BlockTemplate_${componentDefinition}`;
2771
+ const references = referenceDescriptions(format);
2772
+ const measure = measureDescriptions(format);
2773
+ const operand = (description) => ({
2774
+ description,
2775
+ anyOf: [
2776
+ pointer(description),
2777
+ ...BLOCK_OPERAND_ROOTS.map(
2778
+ (root) => object({ [root]: pointer(references[root]) }, [root])
2779
+ )
2780
+ ]
2781
+ });
2782
+ const bodyName = `${prefix}_Body`;
2783
+ const ref = (name) => ({ $ref: `#/definitions/${name}` });
2784
+ if (definitions[bodyName]) return ref(bodyName);
2785
+ const originals = { ...definitions };
2786
+ const source = originals[componentDefinition];
2787
+ originals[componentDefinition] = {
2788
+ ...source,
2789
+ anyOf: (source.anyOf ?? [source]).filter(
2790
+ (branch) => !excludedComponents.includes(branch.properties?.name?.const)
2791
+ )
2792
+ };
2793
+ const resolve = (pointer2) => {
2794
+ if (!pointer2.startsWith("#/definitions/")) return void 0;
2795
+ let node = originals;
2796
+ for (const key of pointer2.slice("#/definitions/".length).split("/")) {
2797
+ const decoded = key.replace(/~1/g, "/").replace(/~0/g, "~");
2798
+ if (!node || typeof node !== "object" || !Object.hasOwn(node, decoded))
2799
+ return void 0;
2800
+ node = node[decoded];
2801
+ }
2802
+ return typeof node === "boolean" || node && typeof node === "object" ? node : void 0;
2803
+ };
2804
+ const values = /* @__PURE__ */ new Map();
2805
+ const literals = /* @__PURE__ */ new Map();
2806
+ let nextId = 0;
2807
+ const componentRef = ref(componentDefinition);
2808
+ const shared = /* @__PURE__ */ new Map();
2809
+ const share = (schema) => {
2810
+ const key = JSON.stringify(schema);
2811
+ let name = shared.get(key);
2812
+ if (!name) {
2813
+ name = `${prefix}_Shared${nextId++}`;
2814
+ shared.set(key, name);
2815
+ definitions[name] = schema;
2816
+ }
2817
+ return ref(name);
2818
+ };
2819
+ const presence = Object.fromEntries(
2820
+ directiveNames.map((key) => [key, share(hasKey(key))])
2821
+ );
2822
+ const anyDirective = share({ anyOf: Object.values(presence) });
2823
+ const starterPrefixes = [
2824
+ .../* @__PURE__ */ new Set([
2825
+ "",
2826
+ ...directiveNames.flatMap(
2827
+ (key) => Array.from(
2828
+ { length: key.length - 1 },
2829
+ (_, index) => key.slice(0, index + 1)
2830
+ )
2831
+ )
2832
+ ])
2833
+ ];
2834
+ const starterObject = share({
2835
+ type: "object",
2836
+ // An enum here would itself become a list of bogus property suggestions.
2837
+ propertyNames: {
2838
+ pattern: `^(?:${starterPrefixes.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`
2839
+ }
2840
+ });
2841
+ function literal(schema) {
2842
+ if (typeof schema === "boolean") return schema;
2843
+ const key = JSON.stringify(schema);
2844
+ const cached = literals.get(key);
2845
+ if (cached) return { ...ref(cached), ...metadata(schema) };
2846
+ const name = `${prefix}_Literal${nextId++}`;
2847
+ literals.set(key, name);
2848
+ definitions[name] = {};
2849
+ const result = { ...schema };
2850
+ if (typeof schema.$ref === "string") {
2851
+ const target = resolve(schema.$ref);
2852
+ if (target !== void 0) {
2853
+ const transformed = literal(target);
2854
+ if (typeof transformed === "object") result.$ref = transformed.$ref;
2855
+ else {
2856
+ delete result.$ref;
2857
+ if (!transformed) result.not = {};
2858
+ }
2859
+ }
2860
+ }
2861
+ if (schema.properties)
2862
+ result.properties = Object.fromEntries(
2863
+ Object.entries(
2864
+ schema.properties
2865
+ ).map(([key2, value]) => [
2866
+ key2,
2867
+ // Literal discriminators retain canonical component/version dispatch
2868
+ // and their individual choice descriptions.
2869
+ ["name", "version"].includes(key2) && typeof value === "object" && typeof value.const === "string" ? value : author(value)
2870
+ ])
2871
+ );
2872
+ if (schema.patternProperties)
2873
+ result.patternProperties = Object.fromEntries(
2874
+ Object.entries(
2875
+ schema.patternProperties
2876
+ ).map(([key2, value]) => [key2, author(value)])
2877
+ );
2878
+ if (schema.items !== void 0)
2879
+ result.items = Array.isArray(schema.items) ? schema.items.map((item) => author(item, true)) : author(schema.items, true);
2880
+ for (const key2 of ["additionalProperties", "additionalItems"])
2881
+ if (typeof schema[key2] === "object")
2882
+ result[key2] = author(schema[key2], key2 === "additionalItems");
2883
+ for (const key2 of ["anyOf", "oneOf", "allOf"])
2884
+ if (Array.isArray(schema[key2]))
2885
+ result[key2] = schema[key2].map((branch) => {
2886
+ const transformed = literal(branch);
2887
+ return typeof branch === "object" && typeof branch.properties?.name?.const === "string" && typeof transformed === "object" && transformed.$ref ? definitions[transformed.$ref.slice("#/definitions/".length)] : transformed;
2888
+ });
2889
+ for (const key2 of ["then", "else"])
2890
+ if (schema[key2] !== void 0) result[key2] = literal(schema[key2]);
2891
+ definitions[name] = result;
2892
+ return { ...ref(name), ...metadata(schema) };
2893
+ }
2894
+ function author(input, sequence = false) {
2895
+ if (input === false) return false;
2896
+ const annotations = metadata(input);
2897
+ const schema = typeof input === "object" ? { ...input } : input;
2898
+ if (typeof schema === "object") {
2899
+ delete schema.description;
2900
+ delete schema.markdownDescription;
2901
+ }
2902
+ const key = `${sequence ? "sequence" : "value"}:${JSON.stringify(schema)}`;
2903
+ const cached = values.get(key);
2904
+ if (cached) return { ...ref(cached), ...annotations };
2905
+ const name = `${prefix}_Value${nextId++}`;
2906
+ values.set(key, name);
2907
+ definitions[name] = {};
2908
+ const self = ref(name);
2909
+ const types = possibleValueTypes(schema, resolve);
2910
+ if (types.size === 0) {
2911
+ definitions[name] = { allOf: [literal(schema)] };
2912
+ return self;
2913
+ }
2914
+ const value = () => sequence ? author(schema) : self;
2915
+ const branch = () => sequence ? { anyOf: [self, { type: "array", items: self }] } : self;
2916
+ const item = () => sequence ? value() : author(arrayItemSchema(schema, resolve));
2917
+ const specs = {};
2918
+ for (const directive of directiveNames) {
2919
+ const result = BLOCK_DIRECTIVES[directive].result;
2920
+ if (result !== "dynamic" && !types.has(result) && !(sequence && result === "array"))
2921
+ continue;
2922
+ switch (directive) {
2923
+ case "$slot":
2924
+ case "$item":
2925
+ case "$theme":
2926
+ case "$context":
2927
+ specs[directive] = object(
2928
+ {
2929
+ [directive]: pointer(references[directive]),
2930
+ default: describe(
2931
+ value(),
2932
+ "Fallback value or binding used only when the referenced value is missing. Null, false and empty values do not trigger it."
2933
+ ),
2934
+ ...directive === "$slot" || directive === "$item" ? {
2935
+ props: {
2936
+ type: "object",
2937
+ description: "Component props merged beneath a component-slot value. Put placement (x, y, w, h, grid) and styling defaults here; the slot content may override styling but never placement."
2938
+ }
2939
+ } : {}
2940
+ },
2941
+ [directive]
2942
+ );
2943
+ break;
2944
+ case "$if":
2945
+ specs[directive] = object(
2946
+ {
2947
+ $if: operand(
2948
+ 'Test a slot by JSON Pointer, e.g. /subtitle, or a reference such as { "$item": "/numeric" }. Missing, null, false, empty text and empty arrays select else; zero selects then.'
2949
+ ),
2950
+ then: describe(
2951
+ branch(),
2952
+ "Value or components to emit when the slot tested by $if is present."
2953
+ ),
2954
+ else: describe(
2955
+ branch(),
2956
+ "Value or components to emit otherwise. Omit to produce no output."
2957
+ )
2958
+ },
2959
+ ["$if", "then"]
2960
+ );
2961
+ break;
2962
+ case "$each":
2963
+ specs[directive] = object(
2964
+ {
2965
+ $each: operand(
2966
+ 'Repeat template for each entry in an array slot, e.g. /items, or in an array of the current entry, { "$item": "/cells" }. Read the current entry with $item.'
2967
+ ),
2968
+ template: describe(
2969
+ item(),
2970
+ "One template evaluated per array entry. Use $item for the current entry and a group for multiple components."
2971
+ )
2972
+ },
2973
+ ["$each", "template"]
2974
+ );
2975
+ break;
2976
+ case "$count":
2977
+ specs[directive] = object(
2978
+ {
2979
+ $count: operand(
2980
+ 'Return the number of entries in an array slot, e.g. /items, or in an array of the current entry, { "$item": "/cells" }.'
2981
+ )
2982
+ },
2983
+ ["$count"]
2984
+ );
2985
+ break;
2986
+ case "$join":
2987
+ specs[directive] = object(
2988
+ {
2989
+ $join: {
2990
+ type: "array",
2991
+ items: author({}),
2992
+ description: "Evaluate these values or bindings and join them as text. Empty values are skipped unless keepEmpty is true."
2993
+ },
2994
+ separator: {
2995
+ type: "string",
2996
+ description: "Text inserted between joined values. Defaults to an empty string."
2997
+ },
2998
+ keepEmpty: {
2999
+ type: "boolean",
3000
+ description: "Keep missing, null, false, empty text and empty arrays in the join. Defaults to false."
3001
+ }
3002
+ },
3003
+ ["$join"]
3004
+ );
3005
+ break;
3006
+ case "$measure":
3007
+ specs[directive] = object(
3008
+ {
3009
+ $measure: {
3010
+ enum: ["width", "height"],
3011
+ description: measure.axis
3012
+ },
3013
+ fraction: {
3014
+ type: "number",
3015
+ minimum: 0,
3016
+ maximum: 1,
3017
+ description: "Fraction of the measured dimension, from 0 to 1. Defaults to 1."
3018
+ },
3019
+ unit: {
3020
+ enum: ["pt", "twip", "in"],
3021
+ description: measure.unit
3022
+ }
3023
+ },
3024
+ ["$measure"]
3025
+ );
3026
+ break;
3027
+ default: {
3028
+ const exhaustive = directive;
3029
+ throw new Error(
3030
+ `Missing authoring schema for directive ${exhaustive}`
3031
+ );
3032
+ }
3033
+ }
3034
+ }
3035
+ definitions[name] = {
3036
+ allOf: [
3037
+ {
3038
+ if: anyDirective,
3039
+ then: {
3040
+ allOf: directiveNames.map((directive) => ({
3041
+ if: presence[directive],
3042
+ then: specs[directive] ? share({
3043
+ ...specs[directive],
3044
+ properties: Object.fromEntries(
3045
+ Object.entries(specs[directive].properties).map(
3046
+ ([key2, property]) => [key2, share(property)]
3047
+ )
3048
+ )
3049
+ }) : false
3050
+ }))
3051
+ },
3052
+ else: literal(schema)
3053
+ },
3054
+ {
3055
+ // Keep starters while the first key is empty or a partial directive
3056
+ // ("$", "$sl", ...). Any ordinary or completed key ends this phase.
3057
+ // Prefixes come from the evaluator's directive registry.
3058
+ if: starterObject,
3059
+ then: {
3060
+ properties: Object.fromEntries(
3061
+ Object.entries(specs).map(([key2, spec]) => [
3062
+ key2,
3063
+ share(spec.properties[key2])
3064
+ ])
3065
+ )
3066
+ }
3067
+ }
3068
+ ]
3069
+ };
3070
+ return { ...self, ...annotations };
3071
+ }
3072
+ definitions[bodyName] = author(componentRef, true);
3073
+ return ref(bodyName);
3074
+ }
3075
+
3076
+ // src/blocks/compose.ts
3077
+ async function composeBlocksWithPlugins(evaluator, document, options) {
3078
+ const preserve = options.preserve ?? /* @__PURE__ */ new Set();
3079
+ let visited = 0;
3080
+ const walk = async (value, path, depth) => {
3081
+ if (depth > 64 || ++visited > 1e5)
3082
+ throw new BlockEvaluationError([
3083
+ {
3084
+ path: toAuthoredBlockPointer(evaluator.sourceMap, path),
3085
+ code: "block_expansion_limit",
3086
+ message: "Combined plugin/block expansion exceeds depth/node limits (64/100000)."
3087
+ }
3088
+ ]);
3089
+ if (Array.isArray(value)) {
3090
+ const children = [];
3091
+ for (let i = 0; i < value.length; i++)
3092
+ children.push(await walk(value[i], `${path}/${i}`, depth + 1));
3093
+ return {
3094
+ standard: children.map((c) => c.standard),
3095
+ preserved: children.map((c) => c.preserved)
3096
+ };
3097
+ }
3098
+ if (!isBlockRecord(value) || value.enabled === false)
3099
+ return { standard: value, preserved: value };
3100
+ if (value.name === "block")
3101
+ return walk(evaluator.expand(value, path, depth), path, depth + 1);
3102
+ const standard = { ...value };
3103
+ const kept = { ...value };
3104
+ for (const [key, item] of Object.entries(value)) {
3105
+ if (path === "/props" && key === "blocks") continue;
3106
+ const processed = await walk(item, `${path}/${key}`, depth + 1);
3107
+ Object.defineProperty(standard, key, {
3108
+ value: processed.standard,
3109
+ enumerable: true,
3110
+ configurable: true,
3111
+ writable: true
3112
+ });
3113
+ Object.defineProperty(kept, key, {
3114
+ value: processed.preserved,
3115
+ enumerable: true,
3116
+ configurable: true,
3117
+ writable: true
3118
+ });
3119
+ }
3120
+ if (typeof value.name === "string" && options.plugins.has(value.name)) {
3121
+ const source = toAuthoredBlockPointer(evaluator.sourceMap, path);
3122
+ const emitted = await options.render(standard, source);
3123
+ evaluator.sourceMap[`${path}/children`] = source;
3124
+ const processed = await walk(emitted, `${path}/children`, depth + 1);
3125
+ return {
3126
+ standard: { name: "group", children: processed.standard },
3127
+ preserved: preserve.has(value.name) ? value : { name: "group", children: processed.preserved }
3128
+ };
3129
+ }
3130
+ return { standard, preserved: kept };
3131
+ };
3132
+ return walk(document, "", 0);
3133
+ }
3134
+
3135
+ // src/blocks/editor.ts
3136
+ import { Value as Value3 } from "@sinclair/typebox/value";
3137
+ var clone = (value) => JSON.parse(JSON.stringify(value));
3138
+ function range(minimum, maximum, unit) {
3139
+ if (minimum !== void 0 && maximum !== void 0)
3140
+ return minimum === maximum ? `${minimum} ${unit}` : `${minimum}\u2013${maximum} ${unit}`;
3141
+ if (minimum !== void 0) return `at least ${minimum} ${unit}`;
3142
+ if (maximum !== void 0) return `at most ${maximum} ${unit}`;
3143
+ return void 0;
3144
+ }
3145
+ function blockSlotFacts(slot) {
3146
+ const facts = [];
3147
+ if (slot.required && slot.default === void 0) facts.push("Required");
3148
+ if (slot.default !== void 0)
3149
+ facts.push(`Default: \`${JSON.stringify(slot.default)}\``);
3150
+ if (slot.type === "component")
3151
+ facts.push("A component; placement stays in the definition");
3152
+ if (slot.enum)
3153
+ facts.push(
3154
+ `One of ${slot.enum.map((value) => `\`${JSON.stringify(value)}\``).join(", ")}`
3155
+ );
3156
+ const length = range(slot.minLength, slot.maxLength, "characters");
3157
+ if (length) facts.push(length);
3158
+ if (slot.maxWords !== void 0) facts.push(`at most ${slot.maxWords} words`);
3159
+ if (slot.oneLine) facts.push("one line");
3160
+ const bounds = range(slot.minimum, slot.maximum, "");
3161
+ if (bounds) facts.push(bounds.trim());
3162
+ const entries = range(slot.minItems, slot.maxItems, "entries");
3163
+ if (entries) facts.push(entries);
3164
+ if (slot.role) facts.push(`Role: ${slot.role}`);
3165
+ return facts;
3166
+ }
3167
+ function blockSlotMarkdown(slot) {
3168
+ return [slot.description, blockSlotFacts(slot).join(" \xB7 ")].filter(Boolean).join("\n\n");
3169
+ }
3170
+ function blockSlotEditorSchema(slot, componentRef) {
3171
+ let schema;
3172
+ if (slot.type === "component") {
3173
+ schema = componentRef ? {
3174
+ allOf: [
3175
+ componentRef,
3176
+ {
3177
+ properties: {
3178
+ props: {
3179
+ propertyNames: {
3180
+ not: { enum: [...BLOCK_SLOT_PLACEMENT_PROPS] },
3181
+ errorMessage: "Block placement belongs in the definition, not in a component slot."
3182
+ }
3183
+ }
3184
+ }
3185
+ }
3186
+ ]
3187
+ } : {
3188
+ type: "object",
3189
+ properties: { name: { type: "string" } },
3190
+ required: ["name"]
3191
+ };
3192
+ } else {
3193
+ const { oneLine, properties, items, ...rest } = slot;
3194
+ for (const key of ["role", "required", "maxWords", "description"])
3195
+ delete rest[key];
3196
+ schema = { ...rest };
3197
+ if (oneLine) schema.pattern = "^[^\\r\\n]*$";
3198
+ if (items) schema.items = blockSlotEditorSchema(items, componentRef);
3199
+ if (properties) {
3200
+ schema.properties = Object.fromEntries(
3201
+ Object.entries(properties).map(([key, value]) => [
3202
+ key,
3203
+ blockSlotEditorSchema(value, componentRef)
3204
+ ])
3205
+ );
3206
+ schema.required = Object.entries(properties).filter(([, value]) => value.required && value.default === void 0).map(([key]) => key);
3207
+ schema.additionalProperties = false;
3208
+ }
3209
+ }
3210
+ if (slot.description) schema.description = slot.description;
3211
+ const markdown = blockSlotMarkdown(slot);
3212
+ if (markdown) schema.markdownDescription = markdown;
3213
+ return schema;
3214
+ }
3215
+ function blockSlotsEditorSchema(definition, componentRef) {
3216
+ return {
3217
+ type: "object",
3218
+ additionalProperties: false,
3219
+ description: "Input values keyed by the slot names declared in the referenced block definition.",
3220
+ properties: Object.fromEntries(
3221
+ Object.entries(definition.slots).map(([key, slot]) => [
3222
+ key,
3223
+ blockSlotEditorSchema(slot, componentRef)
3224
+ ])
3225
+ ),
3226
+ required: Object.entries(definition.slots).filter(([, slot]) => slot.required && slot.default === void 0).map(([key]) => key)
3227
+ };
3228
+ }
3229
+ function blockInvocationPropsSchema(definitions, componentRef) {
3230
+ const names = Object.keys(definitions);
3231
+ const schema = {
3232
+ type: "object",
3233
+ additionalProperties: false,
3234
+ required: ["ref"],
3235
+ properties: {
3236
+ ref: {
3237
+ type: "string",
3238
+ minLength: 1,
3239
+ description: "Name in this document\u2019s props.blocks.",
3240
+ ...names.length && {
3241
+ anyOf: names.map((name) => ({
3242
+ const: name,
3243
+ type: "string",
3244
+ description: definitions[name].description ?? `Block "${name}", defined in this document.`
3245
+ }))
3246
+ }
3247
+ },
3248
+ slots: {
3249
+ type: "object",
3250
+ description: "Input values keyed by the slot names declared in the referenced block definition."
3251
+ }
3252
+ }
3253
+ };
3254
+ if (names.length)
3255
+ schema.allOf = names.map((name) => ({
3256
+ if: { properties: { ref: { const: name } }, required: ["ref"] },
3257
+ then: {
3258
+ properties: {
3259
+ slots: blockSlotsEditorSchema(definitions[name], componentRef)
3260
+ }
3261
+ }
3262
+ }));
3263
+ return schema;
3264
+ }
3265
+ function applyDocumentBlocksToSchema(schema, definitions, targets) {
3266
+ for (const target of targets) {
3267
+ const definition = schema.definitions?.[target.name];
3268
+ if (!definition) continue;
3269
+ const props = blockInvocationPropsSchema(definitions, target.componentRef);
3270
+ const seen = /* @__PURE__ */ new Set();
3271
+ const walk = (node) => {
3272
+ if (!node || typeof node !== "object" || seen.has(node)) return;
3273
+ seen.add(node);
3274
+ if (Array.isArray(node)) {
3275
+ node.forEach(walk);
3276
+ return;
3277
+ }
3278
+ const value = node;
3279
+ if (value.properties?.name?.const === "block" && value.properties.props) {
3280
+ value.properties.props = clone(props);
3281
+ return;
3282
+ }
3283
+ for (const [key, child] of Object.entries(value))
3284
+ if (key !== "$ref") walk(child);
3285
+ };
3286
+ walk(definition);
3287
+ }
3288
+ }
3289
+ function invocations(node, visit) {
3290
+ if (Array.isArray(node)) {
3291
+ node.forEach((item) => invocations(item, visit));
3292
+ return;
3293
+ }
3294
+ if (!isBlockRecord(node)) return;
3295
+ if (node.name === "block" && isBlockRecord(node.props) && typeof node.props.ref === "string")
3296
+ visit(node.props.ref, node);
3297
+ for (const value of Object.values(node)) invocations(value, visit);
3298
+ }
3299
+ function blockDependencies(definitions, name) {
3300
+ const order = [];
3301
+ const seen = /* @__PURE__ */ new Set([name]);
3302
+ const walk = (current) => {
3303
+ const definition = Object.prototype.hasOwnProperty.call(
3304
+ definitions,
3305
+ current
3306
+ ) ? definitions[current] : void 0;
3307
+ if (!definition) return;
3308
+ invocations(
3309
+ [definition.body, definition.section, definition.slide],
3310
+ (ref) => {
3311
+ if (seen.has(ref)) return;
3312
+ seen.add(ref);
3313
+ if (!Object.prototype.hasOwnProperty.call(definitions, ref)) return;
3314
+ walk(ref);
3315
+ order.push(ref);
3316
+ }
3317
+ );
3318
+ };
3319
+ walk(name);
3320
+ return order;
3321
+ }
3322
+ function exampleValue(slot, name, format) {
3323
+ if (slot.default !== void 0) return clone(slot.default);
3324
+ if (slot.enum?.length) return slot.enum[0];
3325
+ switch (slot.type) {
3326
+ case "string":
3327
+ return name;
3328
+ case "number":
3329
+ case "integer": {
3330
+ const minimum = slot.minimum ?? 0;
3331
+ return slot.maximum !== void 0 && slot.maximum < minimum ? slot.maximum : minimum;
3332
+ }
3333
+ case "boolean":
3334
+ return true;
3335
+ case "array": {
3336
+ const count = Math.min(
3337
+ Math.max(3, slot.minItems ?? 0),
3338
+ slot.maxItems ?? Number.POSITIVE_INFINITY
3339
+ );
3340
+ const item = slot.items ?? { type: "string" };
3341
+ return Array.from(
3342
+ { length: count },
3343
+ (_, index) => exampleValue(item, `${name} ${index + 1}`, format)
3344
+ );
3345
+ }
3346
+ case "object":
3347
+ return exampleSlots(slot.properties ?? {}, format);
3348
+ case "component":
3349
+ return format === "docx" ? { name: "paragraph", props: { text: name } } : { name: "text", props: { text: name } };
3350
+ default:
3351
+ return name;
3352
+ }
3353
+ }
3354
+ function exampleSlots(slots, format) {
3355
+ return Object.fromEntries(
3356
+ Object.entries(slots).filter(
3357
+ ([, slot]) => slot.required && slot.default === void 0 || slot.role
3358
+ ).map(([key, slot]) => [key, exampleValue(slot, key, format)])
3359
+ );
3360
+ }
3361
+ function blockInvocationExample(name, definition, options) {
3362
+ let found;
3363
+ if (isBlockRecord(options.document)) {
3364
+ const authored = Object.fromEntries(
3365
+ Object.entries(options.document).filter(([key]) => key !== "props")
3366
+ );
3367
+ invocations(authored, (ref, invocation) => {
3368
+ if (found || ref !== name) return;
3369
+ const props = invocation.props;
3370
+ found = {
3371
+ name: "block",
3372
+ props: {
3373
+ ref,
3374
+ ...isBlockRecord(props.slots) && { slots: clone(props.slots) }
3375
+ }
3376
+ };
3377
+ });
3378
+ }
3379
+ return found ?? {
3380
+ name: "block",
3381
+ props: {
3382
+ ref: name,
3383
+ slots: exampleSlots(definition.slots, options.format)
3384
+ }
3385
+ };
3386
+ }
3387
+ function blockReferencesFromDocument(document, source) {
3388
+ const definitions = readBlockDefinitions(document);
3389
+ if (!Value3.Check(BlockDefinitionsSchema, definitions) || validateBlockDefinitions(definitions, source.format).length > 0)
3390
+ return [];
3391
+ return Object.entries(definitions).map(([name, definition]) => ({
3392
+ name,
3393
+ format: source.format,
3394
+ template: source.template,
3395
+ definitionPointer: `/props/blocks/${blockPointerKey(name)}`,
3396
+ description: definition.description ?? "",
3397
+ definition,
3398
+ slotsSchema: blockSlotsJsonSchema(definition),
3399
+ example: blockInvocationExample(name, definition, {
3400
+ document,
3401
+ format: source.format
3402
+ }),
3403
+ dependencies: blockDependencies(definitions, name)
3404
+ }));
3405
+ }
3406
+
1486
3407
  // src/utils/deepMerge.ts
1487
3408
  function isObject(item) {
1488
3409
  return item !== null && typeof item === "object" && !Array.isArray(item);
@@ -1508,6 +3429,12 @@ function mergeWithDefaults(userConfig, themeDefaults) {
1508
3429
  return deepMerge(themeDefaults, userConfig);
1509
3430
  }
1510
3431
  export {
3432
+ BLOCK_SLOT_PLACEMENT_PROPS,
3433
+ BLOCK_SLOT_ROLES,
3434
+ BlockDefinitionsSchema,
3435
+ BlockEvaluationError,
3436
+ BlockInvocationPropsSchema,
3437
+ BlockSlotSchema,
1511
3438
  CANVASES,
1512
3439
  ChromeSchema,
1513
3440
  ComponentValidationError,
@@ -1526,6 +3453,8 @@ export {
1526
3453
  FontRegistryEntrySchema,
1527
3454
  FontRegistrySchema,
1528
3455
  FontSourceSchema,
3456
+ JsonBlockDefinitionSchema,
3457
+ JsonBlockEvaluator,
1529
3458
  MAX_RASTERIZE_BATCH_SLIDES,
1530
3459
  MAX_RASTERIZE_FONTS,
1531
3460
  MAX_RASTERIZE_FONT_BYTES,
@@ -1549,10 +3478,26 @@ export {
1549
3478
  UnknownPreservedComponentError,
1550
3479
  UnsupportedRendererFeatureError,
1551
3480
  WEIGHT_LABELS,
3481
+ applyDocumentBlocksToSchema,
1552
3482
  applyExportMode,
1553
3483
  applyFontSubstitution,
1554
3484
  assertNever,
1555
3485
  assertRendererSupports,
3486
+ blockDependencies,
3487
+ blockInvocationExample,
3488
+ blockInvocationPropsSchema,
3489
+ blockPointerKey,
3490
+ blockReferencesFromDocument,
3491
+ blockSlotBudgets,
3492
+ blockSlotEditorSchema,
3493
+ blockSlotFacts,
3494
+ blockSlotJsonSchema,
3495
+ blockSlotMarkdown,
3496
+ blockSlotRoles,
3497
+ blockSlotsEditorSchema,
3498
+ blockSlotsJsonSchema,
3499
+ blockValueAt,
3500
+ blockWordCount,
1556
3501
  buildDefaultSubstitutionMap,
1557
3502
  calculatePosition,
1558
3503
  capsFormatting,
@@ -1564,7 +3509,9 @@ export {
1564
3509
  collectFontNamesFromDocx,
1565
3510
  collectFontNamesFromPptx,
1566
3511
  compareSemver,
3512
+ composeBlocksWithPlugins,
1567
3513
  convertToJsonSchema,
3514
+ createBlockAuthoringSchema,
1568
3515
  createComponent,
1569
3516
  createComponentSchema,
1570
3517
  createComponentSchemaObject,
@@ -1577,6 +3524,7 @@ export {
1577
3524
  designColors,
1578
3525
  detectFontFormat,
1579
3526
  diagnoseUnsupportedFeatures,
3527
+ documentBlockMetadata,
1580
3528
  documentFontRegistry,
1581
3529
  exportSchemaToFile,
1582
3530
  extractStandardComponentNames,
@@ -1591,6 +3539,7 @@ export {
1591
3539
  getValidationSummary,
1592
3540
  groupErrorsByPath,
1593
3541
  isAllowedFontUrl,
3542
+ isBlockRecord,
1594
3543
  isLiteralSchema,
1595
3544
  isObjectSchema,
1596
3545
  isSafeFont,
@@ -1602,8 +3551,10 @@ export {
1602
3551
  mergeWithDefaults,
1603
3552
  parseSemver,
1604
3553
  partitionDiagnostics,
3554
+ readBlockDefinitions,
1605
3555
  rendererError,
1606
3556
  rendererWarning,
3557
+ resolveBlockSlot,
1607
3558
  resolveComponentVersion,
1608
3559
  resolveDesignColor,
1609
3560
  resolveTypeRoles,
@@ -1611,9 +3562,12 @@ export {
1611
3562
  rewriteFontFamilyName,
1612
3563
  synthesizeFamilyName,
1613
3564
  themeFontRegistry,
3565
+ toAuthoredBlockPointer,
1614
3566
  transformValueError,
1615
3567
  transformValueErrors,
1616
3568
  unionBranches,
3569
+ validateBlockDefinitions,
3570
+ validateBlockInvocations,
1617
3571
  validateCustomComponentProps,
1618
3572
  validateDesignColors,
1619
3573
  validateFontReferences,