@ng-forge/dynamic-form-mcp 0.9.0-next.11 → 0.9.0-next.13

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.
@@ -1553,6 +1553,419 @@ const tagItemTemplate = { key: 'tag', type: 'input', label: 'Tag' } as const sat
1553
1553
  }
1554
1554
  ];
1555
1555
 
1556
+ // packages/dynamic-form-mcp/src/registry/addons.ts
1557
+ var UNIVERSAL_BASE_PROPS = {
1558
+ slot: {
1559
+ name: "slot",
1560
+ type: "'prefix' | 'suffix' | string",
1561
+ description: "Slot to render this addon in. `prefix` and `suffix` are universal; adapters may add more.",
1562
+ required: true
1563
+ },
1564
+ className: {
1565
+ name: "className",
1566
+ type: "string",
1567
+ description: "CSS class applied to the addon host element.",
1568
+ required: false
1569
+ },
1570
+ hidden: {
1571
+ name: "hidden",
1572
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1573
+ description: "Reactive visibility \u2014 `true` removes the addon from layout. JSON-safe when boolean.",
1574
+ required: false
1575
+ },
1576
+ disabled: {
1577
+ name: "disabled",
1578
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1579
+ description: "Reactive disabled state \u2014 primarily relevant for interactive kinds.",
1580
+ required: false
1581
+ }
1582
+ };
1583
+ var ADDON_KINDS = [
1584
+ // ─── Core-shipped universal kinds ────────────────────────────────────────
1585
+ {
1586
+ kind: "text",
1587
+ category: "core",
1588
+ package: "@ng-forge/dynamic-forms",
1589
+ adapter: null,
1590
+ description: "Renders inline text content (currency symbol, unit label, character counter, etc.).",
1591
+ jsonSafe: true,
1592
+ props: {
1593
+ ...UNIVERSAL_BASE_PROPS,
1594
+ text: {
1595
+ name: "text",
1596
+ type: "DynamicText",
1597
+ description: "Text content. Supports plain strings, observables, signals, and i18n keys.",
1598
+ required: true
1599
+ }
1600
+ },
1601
+ example: `{ slot: 'prefix', kind: 'text', text: '$' }`,
1602
+ minimalExample: `{ slot: 'prefix', kind: 'text', text: '$' }`
1603
+ },
1604
+ {
1605
+ kind: "template",
1606
+ category: "core",
1607
+ package: "@ng-forge/dynamic-forms",
1608
+ adapter: null,
1609
+ description: "Renders a named `<ng-template>` projected into <df-dynamic-form>. JSON-safe \u2014 backend ships the key, FE supplies the template.",
1610
+ jsonSafe: true,
1611
+ props: {
1612
+ ...UNIVERSAL_BASE_PROPS,
1613
+ templateKey: {
1614
+ name: "templateKey",
1615
+ type: "string",
1616
+ description: 'Name of an `<ng-template dfTemplate="...">` projected into the host form.',
1617
+ required: true
1618
+ }
1619
+ },
1620
+ example: `{ slot: 'suffix', kind: 'template', templateKey: 'searchIcon' }`,
1621
+ minimalExample: `{ slot: 'suffix', kind: 'template', templateKey: 'mySlot' }`
1622
+ },
1623
+ {
1624
+ kind: "component",
1625
+ category: "core",
1626
+ package: "@ng-forge/dynamic-forms",
1627
+ adapter: null,
1628
+ description: "Renders an arbitrary Angular component. The `component` loader is a function; this kind is dropped from JSON-derived configs by the validator.",
1629
+ jsonSafe: false,
1630
+ props: {
1631
+ ...UNIVERSAL_BASE_PROPS,
1632
+ component: {
1633
+ name: "component",
1634
+ type: "() => Promise<Type<unknown> | { default: Type<unknown> }>",
1635
+ description: "Lazy loader for the Angular component to render.",
1636
+ required: true
1637
+ },
1638
+ inputs: {
1639
+ name: "inputs",
1640
+ type: "Record<string, unknown>",
1641
+ description: "Inputs forwarded to the rendered component.",
1642
+ required: false
1643
+ }
1644
+ },
1645
+ example: `{
1646
+ slot: 'prefix',
1647
+ kind: 'component',
1648
+ component: () => import('./my-component').then(m => m.MyComponent),
1649
+ inputs: { foo: 'bar' },
1650
+ }`,
1651
+ minimalExample: `{ slot: 'prefix', kind: 'component', component: () => import('./x') }`
1652
+ },
1653
+ // ─── PrimeNG-shipped kinds ───────────────────────────────────────────────
1654
+ {
1655
+ kind: "prime-icon",
1656
+ category: "adapter",
1657
+ package: "@ng-forge/dynamic-forms-primeng",
1658
+ adapter: "primeng",
1659
+ description: 'Renders `<i class="pi pi-{icon}">` \u2014 bare PrimeIcons name.',
1660
+ jsonSafe: true,
1661
+ props: {
1662
+ ...UNIVERSAL_BASE_PROPS,
1663
+ icon: {
1664
+ name: "icon",
1665
+ type: "string",
1666
+ description: 'PrimeIcons name without the `pi-` prefix (e.g., "search", "times").',
1667
+ required: true
1668
+ },
1669
+ ariaLabel: {
1670
+ name: "ariaLabel",
1671
+ type: "DynamicText",
1672
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1673
+ required: false
1674
+ }
1675
+ },
1676
+ example: `{ slot: 'prefix', kind: 'prime-icon', icon: 'search', ariaLabel: 'Search' }`,
1677
+ minimalExample: `{ slot: 'prefix', kind: 'prime-icon', icon: 'search' }`
1678
+ },
1679
+ {
1680
+ kind: "prime-button",
1681
+ category: "adapter",
1682
+ package: "@ng-forge/dynamic-forms-primeng",
1683
+ adapter: "primeng",
1684
+ description: "Renders `<p-button>` with optional icon, label, severity, reactive loading, and exactly one click handler (preset, actionRef, or action).",
1685
+ jsonSafe: true,
1686
+ props: {
1687
+ ...UNIVERSAL_BASE_PROPS,
1688
+ icon: { name: "icon", type: "string", description: "PrimeIcons name without the `pi-` prefix.", required: false },
1689
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1690
+ ariaLabel: {
1691
+ name: "ariaLabel",
1692
+ type: "DynamicText",
1693
+ description: "Required when icon-only (no `label`). Used as `aria-label`.",
1694
+ required: false
1695
+ },
1696
+ severity: {
1697
+ name: "severity",
1698
+ type: "'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger' | 'help' | 'contrast'",
1699
+ description: "PrimeNG button colour variant.",
1700
+ required: false
1701
+ },
1702
+ loading: {
1703
+ name: "loading",
1704
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1705
+ description: "Reactive loading state \u2014 renders the native PrimeNG spinner.",
1706
+ required: false
1707
+ },
1708
+ preset: {
1709
+ name: "preset",
1710
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1711
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`. Adapters may extend the preset union.",
1712
+ required: false
1713
+ },
1714
+ actionRef: {
1715
+ name: "actionRef",
1716
+ type: "string",
1717
+ description: "Typed string handle to a handler registered via `provideAddonActions(...)`. Mutually exclusive with `preset` and `action`. JSON-safe.",
1718
+ required: false
1719
+ },
1720
+ action: {
1721
+ name: "action",
1722
+ type: "(ctx: AddonActionContext) => void",
1723
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs). Mutually exclusive with `preset` and `actionRef`.",
1724
+ required: false
1725
+ }
1726
+ },
1727
+ example: `{ slot: 'suffix', kind: 'prime-button', icon: 'times', ariaLabel: 'Clear', preset: 'clear' }`,
1728
+ minimalExample: `{ slot: 'suffix', kind: 'prime-button', icon: 'times', ariaLabel: 'Clear', preset: 'clear' }`
1729
+ },
1730
+ // ─── Material-shipped kinds ──────────────────────────────────────────────
1731
+ {
1732
+ kind: "mat-icon",
1733
+ category: "adapter",
1734
+ package: "@ng-forge/dynamic-forms-material",
1735
+ adapter: "material",
1736
+ description: "Renders `<mat-icon>{icon}</mat-icon>` \u2014 Material Icons ligature name.",
1737
+ jsonSafe: true,
1738
+ props: {
1739
+ ...UNIVERSAL_BASE_PROPS,
1740
+ icon: {
1741
+ name: "icon",
1742
+ type: "string",
1743
+ description: 'Material Icons ligature (e.g., "search", "close", "visibility").',
1744
+ required: true
1745
+ },
1746
+ ariaLabel: {
1747
+ name: "ariaLabel",
1748
+ type: "DynamicText",
1749
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1750
+ required: false
1751
+ }
1752
+ },
1753
+ example: `{ slot: 'prefix', kind: 'mat-icon', icon: 'search', ariaLabel: 'Search' }`,
1754
+ minimalExample: `{ slot: 'prefix', kind: 'mat-icon', icon: 'search' }`
1755
+ },
1756
+ {
1757
+ kind: "mat-button",
1758
+ category: "adapter",
1759
+ package: "@ng-forge/dynamic-forms-material",
1760
+ adapter: "material",
1761
+ description: "Renders `<button mat-icon-button>` (icon-only) or `<button mat-button>` (labeled) with optional color, reactive loading, and exactly one click handler (preset, actionRef, or action).",
1762
+ jsonSafe: true,
1763
+ props: {
1764
+ ...UNIVERSAL_BASE_PROPS,
1765
+ icon: { name: "icon", type: "string", description: "Material Icons ligature.", required: false },
1766
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1767
+ ariaLabel: {
1768
+ name: "ariaLabel",
1769
+ type: "DynamicText",
1770
+ description: "REQUIRED when icon-only (no `label`). Used as `aria-label`.",
1771
+ required: false
1772
+ },
1773
+ color: {
1774
+ name: "color",
1775
+ type: "'primary' | 'accent' | 'warn'",
1776
+ description: "Material button color variant.",
1777
+ required: false
1778
+ },
1779
+ loading: {
1780
+ name: "loading",
1781
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1782
+ description: "Reactive loading state \u2014 disables the button when truthy.",
1783
+ required: false
1784
+ },
1785
+ preset: {
1786
+ name: "preset",
1787
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1788
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`.",
1789
+ required: false
1790
+ },
1791
+ actionRef: {
1792
+ name: "actionRef",
1793
+ type: "string",
1794
+ description: "Typed handle to a handler registered via `provideAddonActions(...)`. JSON-safe.",
1795
+ required: false
1796
+ },
1797
+ action: {
1798
+ name: "action",
1799
+ type: "(ctx: AddonActionContext) => void",
1800
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs).",
1801
+ required: false
1802
+ }
1803
+ },
1804
+ example: `{ slot: 'suffix', kind: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' }`,
1805
+ minimalExample: `{ slot: 'suffix', kind: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' }`
1806
+ },
1807
+ // ─── Bootstrap-shipped kinds ─────────────────────────────────────────────
1808
+ {
1809
+ kind: "bs-icon",
1810
+ category: "adapter",
1811
+ package: "@ng-forge/dynamic-forms-bootstrap",
1812
+ adapter: "bootstrap",
1813
+ description: 'Renders `<i class="bi bi-{icon}">` \u2014 Bootstrap Icons name without the `bi-` prefix.',
1814
+ jsonSafe: true,
1815
+ props: {
1816
+ ...UNIVERSAL_BASE_PROPS,
1817
+ icon: {
1818
+ name: "icon",
1819
+ type: "string",
1820
+ description: 'Bootstrap Icons name without the `bi-` prefix (e.g., "search", "x", "eye").',
1821
+ required: true
1822
+ },
1823
+ ariaLabel: {
1824
+ name: "ariaLabel",
1825
+ type: "DynamicText",
1826
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1827
+ required: false
1828
+ }
1829
+ },
1830
+ example: `{ slot: 'prefix', kind: 'bs-icon', icon: 'search', ariaLabel: 'Search' }`,
1831
+ minimalExample: `{ slot: 'prefix', kind: 'bs-icon', icon: 'search' }`
1832
+ },
1833
+ {
1834
+ kind: "bs-button",
1835
+ category: "adapter",
1836
+ package: "@ng-forge/dynamic-forms-bootstrap",
1837
+ adapter: "bootstrap",
1838
+ description: 'Renders `<button class="btn btn-outline-{severity}">` with optional icon, label, severity, and reactive loading (swaps to a spinner).',
1839
+ jsonSafe: true,
1840
+ props: {
1841
+ ...UNIVERSAL_BASE_PROPS,
1842
+ icon: { name: "icon", type: "string", description: "Bootstrap Icons name without the `bi-` prefix.", required: false },
1843
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1844
+ ariaLabel: {
1845
+ name: "ariaLabel",
1846
+ type: "DynamicText",
1847
+ description: "REQUIRED when icon-only (no `label`). Used as `aria-label`.",
1848
+ required: false
1849
+ },
1850
+ severity: {
1851
+ name: "severity",
1852
+ type: "'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark'",
1853
+ description: "Bootstrap button variant (mapped to `btn-outline-{severity}`).",
1854
+ required: false
1855
+ },
1856
+ loading: {
1857
+ name: "loading",
1858
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1859
+ description: "Reactive loading state \u2014 swaps the button content for a spinner.",
1860
+ required: false
1861
+ },
1862
+ preset: {
1863
+ name: "preset",
1864
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1865
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`.",
1866
+ required: false
1867
+ },
1868
+ actionRef: {
1869
+ name: "actionRef",
1870
+ type: "string",
1871
+ description: "Typed handle to a handler registered via `provideAddonActions(...)`. JSON-safe.",
1872
+ required: false
1873
+ },
1874
+ action: {
1875
+ name: "action",
1876
+ type: "(ctx: AddonActionContext) => void",
1877
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs).",
1878
+ required: false
1879
+ }
1880
+ },
1881
+ example: `{ slot: 'suffix', kind: 'bs-button', icon: 'x', ariaLabel: 'Clear', preset: 'clear' }`,
1882
+ minimalExample: `{ slot: 'suffix', kind: 'bs-button', icon: 'x', ariaLabel: 'Clear', preset: 'clear' }`
1883
+ },
1884
+ // ─── Ionic-shipped kinds ─────────────────────────────────────────────────
1885
+ {
1886
+ kind: "ion-icon",
1887
+ category: "adapter",
1888
+ package: "@ng-forge/dynamic-forms-ionic",
1889
+ adapter: "ionic",
1890
+ description: 'Renders `<ion-icon name="{icon}">` \u2014 Ionicons name.',
1891
+ jsonSafe: true,
1892
+ props: {
1893
+ ...UNIVERSAL_BASE_PROPS,
1894
+ icon: {
1895
+ name: "icon",
1896
+ type: "string",
1897
+ description: 'Ionicons name (e.g., "search-outline", "close-outline", "eye-outline").',
1898
+ required: true
1899
+ },
1900
+ ariaLabel: {
1901
+ name: "ariaLabel",
1902
+ type: "DynamicText",
1903
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1904
+ required: false
1905
+ }
1906
+ },
1907
+ example: `{ slot: 'prefix', kind: 'ion-icon', icon: 'search-outline', ariaLabel: 'Search' }`,
1908
+ minimalExample: `{ slot: 'prefix', kind: 'ion-icon', icon: 'search-outline' }`
1909
+ },
1910
+ {
1911
+ kind: "ion-button",
1912
+ category: "adapter",
1913
+ package: "@ng-forge/dynamic-forms-ionic",
1914
+ adapter: "ionic",
1915
+ description: 'Renders `<ion-button>` with optional icon, label, color, fill, and reactive loading (renders an `<ion-spinner>`). `ion-button` addons render INSIDE `<ion-input>` as native `<ion-button slot="start|end">` via an attribute-selector component so Ionic\'s `::slotted(ion-button[slot=start|end])` shadow CSS sizes them natively.',
1916
+ jsonSafe: true,
1917
+ props: {
1918
+ ...UNIVERSAL_BASE_PROPS,
1919
+ icon: { name: "icon", type: "string", description: "Ionicons name.", required: false },
1920
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1921
+ ariaLabel: {
1922
+ name: "ariaLabel",
1923
+ type: "DynamicText",
1924
+ description: "REQUIRED when icon-only (no `label`). Used as `aria-label`.",
1925
+ required: false
1926
+ },
1927
+ color: {
1928
+ name: "color",
1929
+ type: "'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'danger' | 'light' | 'medium' | 'dark'",
1930
+ description: "Ionic button color.",
1931
+ required: false
1932
+ },
1933
+ fill: {
1934
+ name: "fill",
1935
+ type: "'clear' | 'outline' | 'solid' | 'default'",
1936
+ description: "Ionic button fill style.",
1937
+ required: false
1938
+ },
1939
+ loading: {
1940
+ name: "loading",
1941
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1942
+ description: "Reactive loading state \u2014 renders an `<ion-spinner>`.",
1943
+ required: false
1944
+ },
1945
+ preset: {
1946
+ name: "preset",
1947
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1948
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`.",
1949
+ required: false
1950
+ },
1951
+ actionRef: {
1952
+ name: "actionRef",
1953
+ type: "string",
1954
+ description: "Typed handle to a handler registered via `provideAddonActions(...)`. JSON-safe.",
1955
+ required: false
1956
+ },
1957
+ action: {
1958
+ name: "action",
1959
+ type: "(ctx: AddonActionContext) => void",
1960
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs).",
1961
+ required: false
1962
+ }
1963
+ },
1964
+ example: `{ slot: 'suffix', kind: 'ion-button', icon: 'close-outline', ariaLabel: 'Clear', preset: 'clear' }`,
1965
+ minimalExample: `{ slot: 'suffix', kind: 'ion-button', icon: 'close-outline', ariaLabel: 'Clear', preset: 'clear' }`
1966
+ }
1967
+ ];
1968
+
1556
1969
  // packages/dynamic-form-mcp/src/registry/index.ts
1557
1970
  function getFieldTypes() {
1558
1971
  return FIELD_TYPES;
@@ -1596,6 +2009,12 @@ function getDocPage(id) {
1596
2009
  function getDocPagesByCategory(category) {
1597
2010
  return DOCUMENTATION.filter((d) => d.category === category);
1598
2011
  }
2012
+ function getAddonKinds() {
2013
+ return ADDON_KINDS;
2014
+ }
2015
+ function getAddonKind(kind) {
2016
+ return ADDON_KINDS.find((k) => k.kind === kind);
2017
+ }
1599
2018
 
1600
2019
  // packages/dynamic-form-mcp/src/resources/field-types.resource.ts
1601
2020
  function registerFieldTypesResource(server) {
@@ -2486,6 +2905,53 @@ Available adapters:
2486
2905
  - \`@ng-forge/dynamic-forms-primeng\` - PrimeNG
2487
2906
  - \`@ng-forge/dynamic-forms-ionic\` - Ionic
2488
2907
 
2908
+ ### Style Defaults (opt-in)
2909
+
2910
+ Each adapter ships an opinionated Sass partial with host-framework colors/typography. Library SCSS uses \`var(--df-name, structural-fallback)\` so forms render usefully without any opt-in \u2014 only opt in if you want the adapter-themed look.
2911
+
2912
+ \`\`\`scss
2913
+ @use '@ng-forge/dynamic-forms-material/styles/defaults' as df;
2914
+
2915
+ :root {
2916
+ @include df.apply-material-form-defaults;
2917
+ }
2918
+ \`\`\`
2919
+
2920
+ Substitute \`material\` for \`bootstrap\`, \`primeng\`, or \`ionic\`. Material also ships \`form-material-defaults-dark\` for dark-mode hint/error colors. Ionic ships \`apply-ionic-legacy-error-styles\` that pairs with \`withLegacyStatusClasses()\` (see below).
2921
+
2922
+ ## Provider Features
2923
+
2924
+ \`provideDynamicForm()\` accepts optional \`with*\` feature configurators alongside the adapter field bundle:
2925
+
2926
+ ### \`withLegacyStatusClasses()\` \u2014 opt into legacy ng-* CSS classes
2927
+
2928
+ By default, Angular Signal Forms uses its modern class strategy. If your CSS targets \`.ng-touched\`, \`.ng-invalid\`, \`.ng-dirty\`, etc. on form-bound elements, add this feature to wire up the compat-class strategy:
2929
+
2930
+ \`\`\`typescript
2931
+ import { provideDynamicForm, withLegacyStatusClasses } from '@ng-forge/dynamic-forms';
2932
+ import { withMaterialFields } from '@ng-forge/dynamic-forms-material';
2933
+
2934
+ export const appConfig = {
2935
+ providers: [
2936
+ provideDynamicForm(
2937
+ ...withMaterialFields(),
2938
+ withLegacyStatusClasses(),
2939
+ ),
2940
+ ],
2941
+ };
2942
+ \`\`\`
2943
+
2944
+ Recommended when consuming Ionic's \`apply-ionic-legacy-error-styles\` mixin, when using Bootstrap themes that style \`is-invalid.ng-touched\`, or any custom CSS that depends on the \`ng-*\` classes.
2945
+
2946
+ ### Other built-in features
2947
+
2948
+ - \`withLoggerConfig(options?)\` \u2014 configure or disable the dynamic-form logger
2949
+ - \`withEventFormValue()\` \u2014 attach the current form value to every dispatched event
2950
+ - \`withValueExclusionDefaults(options)\` \u2014 set form-wide value exclusion defaults
2951
+ - \`withValidationExecutionDefaults(options)\` \u2014 set form-wide validation execution defaults (e.g. defer until touched)
2952
+
2953
+ All features are exported from \`@ng-forge/dynamic-forms\`.
2954
+
2489
2955
  ## Common Patterns
2490
2956
 
2491
2957
  ### Always End with Submit Button
@@ -9382,6 +9848,7 @@ function getTopicList() {
9382
9848
  const fieldTypes = ["input", "select", "slider", "radio", "checkbox", "textarea", "datepicker", "toggle", "text", "hidden"];
9383
9849
  const containers = ["group", "row", "array", "simplified-array", "page"];
9384
9850
  const wrappers = ["wrappers", ...getWrappers().map((w) => w.type)];
9851
+ const addons = ["addons", ...getAddonKinds().map((k) => k.kind)];
9385
9852
  const concepts = [
9386
9853
  "validation",
9387
9854
  "validation-messages",
@@ -9420,6 +9887,9 @@ ${containers.map(formatTopicItem).join("\n")}
9420
9887
  ## Wrappers
9421
9888
  ${wrappers.map(formatTopicItem).join("\n")}
9422
9889
 
9890
+ ## Addons
9891
+ ${addons.map(formatTopicItem).join("\n")}
9892
+
9423
9893
  ## Concepts
9424
9894
  ${concepts.map(formatTopicItem).join("\n")}
9425
9895
 
@@ -9588,7 +10058,120 @@ const config = {
9588
10058
  lines.push(wrappers[0]?.contract ?? "");
9589
10059
  return lines.join("\n");
9590
10060
  }
10061
+ function formatAddonInfo(addon, depth) {
10062
+ if (depth === "brief") {
10063
+ return [
10064
+ `# ${addon.kind} addon`,
10065
+ "",
10066
+ `${addon.description.split(".")[0]}.`,
10067
+ "",
10068
+ "```typescript",
10069
+ addon.minimalExample,
10070
+ "```",
10071
+ addon.jsonSafe ? "" : "**Code-only** \u2014 dropped by the lenient validator when the config came from JSON."
10072
+ ].filter(Boolean).join("\n");
10073
+ }
10074
+ const lines = [];
10075
+ lines.push(`# ${addon.kind} addon`);
10076
+ lines.push("");
10077
+ lines.push(`**Category:** ${addon.category}`);
10078
+ lines.push(`**Package:** \`${addon.package}\``);
10079
+ if (addon.adapter) lines.push(`**Adapter:** ${addon.adapter}`);
10080
+ lines.push(`**JSON-safe:** ${addon.jsonSafe ? "Yes" : "No (code-only)"}`);
10081
+ lines.push(`**Description:** ${addon.description}`);
10082
+ if (Object.keys(addon.props).length > 0) {
10083
+ lines.push("");
10084
+ lines.push("## Config Props");
10085
+ for (const prop of Object.values(addon.props)) {
10086
+ const req = prop.required ? " **(required)**" : "";
10087
+ lines.push(`- \`${prop.name}\`: ${prop.type}${req} \u2014 ${prop.description}`);
10088
+ }
10089
+ }
10090
+ lines.push("");
10091
+ lines.push("## Minimal Example");
10092
+ lines.push("```typescript");
10093
+ lines.push(addon.minimalExample);
10094
+ lines.push("```");
10095
+ lines.push("");
10096
+ lines.push("## Full Example");
10097
+ lines.push("```typescript");
10098
+ lines.push(addon.example);
10099
+ lines.push("```");
10100
+ return lines.join("\n");
10101
+ }
10102
+ function formatAddonsOverview() {
10103
+ const kinds = getAddonKinds();
10104
+ const lines = [];
10105
+ lines.push("# Addons");
10106
+ lines.push("");
10107
+ lines.push(
10108
+ "Addons render inside a field slot \u2014 icons, buttons, inline text, custom templates, or custom components. Multiple addons in the same slot render left-to-right (mirrored in RTL). `hidden` and `disabled` are reactive (signals, observables, or `FormStateCondition`)."
10109
+ );
10110
+ lines.push("");
10111
+ lines.push("## Applying an addon");
10112
+ lines.push("");
10113
+ lines.push("```typescript");
10114
+ lines.push(`{
10115
+ key: 'q',
10116
+ type: 'input',
10117
+ label: 'Search',
10118
+ addons: [
10119
+ { slot: 'prefix', kind: 'mat-icon', icon: 'search', ariaLabel: 'Search' },
10120
+ { slot: 'suffix', kind: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' },
10121
+ ],
10122
+ }`);
10123
+ lines.push("```");
10124
+ lines.push("");
10125
+ lines.push("## Registered Kinds");
10126
+ lines.push("");
10127
+ const core = kinds.filter((k) => k.category === "core");
10128
+ const adapter = kinds.filter((k) => k.category === "adapter");
10129
+ if (core.length > 0) {
10130
+ lines.push("### Core (universal \u2014 ship from `@ng-forge/dynamic-forms`)");
10131
+ for (const k of core) {
10132
+ const json = k.jsonSafe ? "" : " _(code-only)_";
10133
+ lines.push(`- **${k.kind}**${json} \u2014 ${k.description.split(".")[0]}.`);
10134
+ }
10135
+ lines.push("");
10136
+ }
10137
+ if (adapter.length > 0) {
10138
+ lines.push("### Adapter (provided by UI integration packages)");
10139
+ for (const k of adapter) {
10140
+ lines.push(`- **${k.kind}** (${k.adapter}, \`${k.package}\`) \u2014 ${k.description.split(".")[0]}.`);
10141
+ }
10142
+ lines.push("");
10143
+ }
10144
+ lines.push('Use `ngforge_lookup topic="<kind>"` (e.g., `mat-button`, `pi-icon`) for full details on any individual addon kind.');
10145
+ lines.push("");
10146
+ lines.push("## Click variants (mutually exclusive on button-like addons)");
10147
+ lines.push("");
10148
+ lines.push("- `preset`: built-in semantics \u2014 `clear` / `reset` / `paste` / `copy` / `toggle-password-visibility`.");
10149
+ lines.push("- `actionRef`: named handler resolved via `provideAddonActions({ ... })`.");
10150
+ lines.push("- `action`: inline function (code-only, dropped by JSON validator).");
10151
+ lines.push("");
10152
+ lines.push("Setting more than one logs a warning and applies the first (precedence: `preset` > `actionRef` > `action`).");
10153
+ return lines.join("\n");
10154
+ }
9591
10155
  async function resolveTopicContent(resolvedTopic, depth, uiIntegration) {
10156
+ if (resolvedTopic === "addons" || resolvedTopic === "addon" || resolvedTopic === "addon-kinds") {
10157
+ if (depth === "brief") {
10158
+ const kinds = getAddonKinds();
10159
+ return [
10160
+ "**Addons** render inside a field slot (prefix / suffix / adapter-specific): icons, buttons, inline text, templates, components.",
10161
+ "",
10162
+ "Registered kinds:",
10163
+ ...kinds.map((k) => `- \`${k.kind}\` (${k.category}${k.adapter ? `, ${k.adapter}` : ""})${k.jsonSafe ? "" : " \u2014 code-only"}`),
10164
+ "",
10165
+ 'Use `addons: [{ slot: "suffix", kind: "<kind>", ... }]` on an addon-supporting field. See `ngforge_lookup topic="addons"` for full docs.'
10166
+ ].join("\n");
10167
+ }
10168
+ return formatAddonsOverview();
10169
+ }
10170
+ const addonInfo = getAddonKind(resolvedTopic);
10171
+ const collidesWithFieldOrWrapper = resolvedTopic === "text" || !!getWrapper(resolvedTopic);
10172
+ if (addonInfo && !collidesWithFieldOrWrapper) {
10173
+ return formatAddonInfo(addonInfo, depth);
10174
+ }
9592
10175
  if (resolvedTopic === "wrappers" || resolvedTopic === "wrapper") {
9593
10176
  if (depth === "brief") {
9594
10177
  const wrappers = getWrappers();
@@ -9739,6 +10322,8 @@ Containers: group, row, array, simplified-array, page
9739
10322
 
9740
10323
  Wrappers: wrappers (overview), css (shipping), section, arraySection (demo-only)
9741
10324
 
10325
+ Addons: addons (overview), text, template, component (core), mat-icon, mat-button, bs-icon, bs-button, pi-icon, pi-button, ion-icon, ion-button
10326
+
9742
10327
  Concepts: validation, conditional, derivation, property-derivation, options-format, expression-variables, async-validators
9743
10328
 
9744
10329
  Patterns: field-placement, logic-matrix, context-api, containers, multi-page-gotchas
@@ -13196,6 +13781,18 @@ function buildIndex() {
13196
13781
  toolCall: `ngforge_lookup topic="${wrapper.type}"`
13197
13782
  });
13198
13783
  }
13784
+ for (const addon of getAddonKinds()) {
13785
+ const subcategory = addon.adapter ? `Addon (${addon.adapter})` : `Addon (${addon.category})`;
13786
+ entries.push({
13787
+ id: addon.kind,
13788
+ category: "addon",
13789
+ subcategory,
13790
+ description: addon.description,
13791
+ aliases: [],
13792
+ content: `${addon.kind} addon ${addon.description} ${addon.package}`.toLowerCase(),
13793
+ toolCall: `ngforge_lookup topic="${addon.kind}"`
13794
+ });
13795
+ }
13199
13796
  searchIndex = entries;
13200
13797
  return entries;
13201
13798
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-forge/dynamic-form-mcp",
3
- "version": "0.9.0-next.11",
3
+ "version": "0.9.0-next.13",
4
4
  "description": "MCP server for ng-forge dynamic forms - AI-assisted form schema generation",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -51,11 +51,11 @@
51
51
  "zod": "^3.25.76"
52
52
  },
53
53
  "devDependencies": {
54
- "@ng-forge/dynamic-forms": "^0.9.0-next.11",
55
- "@ng-forge/dynamic-forms-bootstrap": "^0.9.0-next.11",
56
- "@ng-forge/dynamic-forms-ionic": "^0.9.0-next.11",
57
- "@ng-forge/dynamic-forms-material": "^0.9.0-next.11",
58
- "@ng-forge/dynamic-forms-primeng": "^0.9.0-next.11",
54
+ "@ng-forge/dynamic-forms": "^0.9.0-next.13",
55
+ "@ng-forge/dynamic-forms-bootstrap": "^0.9.0-next.13",
56
+ "@ng-forge/dynamic-forms-ionic": "^0.9.0-next.13",
57
+ "@ng-forge/dynamic-forms-material": "^0.9.0-next.13",
58
+ "@ng-forge/dynamic-forms-primeng": "^0.9.0-next.13",
59
59
  "tsx": "^4.19.0"
60
60
  }
61
61
  }