@ng-forge/dynamic-form-mcp 0.9.0-next.12 → 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.
package/src/index.js CHANGED
@@ -1551,6 +1551,441 @@ const tagItemTemplate = { key: 'tag', type: 'input', label: 'Tag' } as const sat
1551
1551
  }
1552
1552
  ];
1553
1553
 
1554
+ // packages/dynamic-form-mcp/src/registry/addons.ts
1555
+ var UNIVERSAL_BASE_PROPS = {
1556
+ slot: {
1557
+ name: "slot",
1558
+ type: "'prefix' | 'suffix' | string",
1559
+ description: "Slot to render this addon in. `prefix` and `suffix` are universal; adapters may add more.",
1560
+ required: true
1561
+ },
1562
+ className: {
1563
+ name: "className",
1564
+ type: "string",
1565
+ description: "CSS class applied to the addon host element.",
1566
+ required: false
1567
+ },
1568
+ hidden: {
1569
+ name: "hidden",
1570
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1571
+ description: "Reactive visibility \u2014 `true` removes the addon from layout. JSON-safe when boolean.",
1572
+ required: false
1573
+ },
1574
+ disabled: {
1575
+ name: "disabled",
1576
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1577
+ description: "Reactive disabled state \u2014 primarily relevant for interactive kinds.",
1578
+ required: false
1579
+ }
1580
+ };
1581
+ var ADDON_KINDS = [
1582
+ // ─── Core-shipped universal kinds ────────────────────────────────────────
1583
+ {
1584
+ kind: "text",
1585
+ category: "core",
1586
+ package: "@ng-forge/dynamic-forms",
1587
+ adapter: null,
1588
+ description: "Renders inline text content (currency symbol, unit label, character counter, etc.).",
1589
+ jsonSafe: true,
1590
+ props: {
1591
+ ...UNIVERSAL_BASE_PROPS,
1592
+ text: {
1593
+ name: "text",
1594
+ type: "DynamicText",
1595
+ description: "Text content. Supports plain strings, observables, signals, and i18n keys.",
1596
+ required: true
1597
+ }
1598
+ },
1599
+ example: `{ slot: 'prefix', kind: 'text', text: '$' }`,
1600
+ minimalExample: `{ slot: 'prefix', kind: 'text', text: '$' }`
1601
+ },
1602
+ {
1603
+ kind: "template",
1604
+ category: "core",
1605
+ package: "@ng-forge/dynamic-forms",
1606
+ adapter: null,
1607
+ description: "Renders a named `<ng-template>` projected into <df-dynamic-form>. JSON-safe \u2014 backend ships the key, FE supplies the template.",
1608
+ jsonSafe: true,
1609
+ props: {
1610
+ ...UNIVERSAL_BASE_PROPS,
1611
+ templateKey: {
1612
+ name: "templateKey",
1613
+ type: "string",
1614
+ description: 'Name of an `<ng-template dfTemplate="...">` projected into the host form.',
1615
+ required: true
1616
+ }
1617
+ },
1618
+ example: `{ slot: 'suffix', kind: 'template', templateKey: 'searchIcon' }`,
1619
+ minimalExample: `{ slot: 'suffix', kind: 'template', templateKey: 'mySlot' }`
1620
+ },
1621
+ {
1622
+ kind: "component",
1623
+ category: "core",
1624
+ package: "@ng-forge/dynamic-forms",
1625
+ adapter: null,
1626
+ description: "Renders an arbitrary Angular component. The `component` loader is a function; this kind is dropped from JSON-derived configs by the validator.",
1627
+ jsonSafe: false,
1628
+ props: {
1629
+ ...UNIVERSAL_BASE_PROPS,
1630
+ component: {
1631
+ name: "component",
1632
+ type: "() => Promise<Type<unknown> | { default: Type<unknown> }>",
1633
+ description: "Lazy loader for the Angular component to render.",
1634
+ required: true
1635
+ },
1636
+ inputs: {
1637
+ name: "inputs",
1638
+ type: "Record<string, unknown>",
1639
+ description: "Inputs forwarded to the rendered component.",
1640
+ required: false
1641
+ }
1642
+ },
1643
+ example: `{
1644
+ slot: 'prefix',
1645
+ kind: 'component',
1646
+ component: () => import('./my-component').then(m => m.MyComponent),
1647
+ inputs: { foo: 'bar' },
1648
+ }`,
1649
+ minimalExample: `{ slot: 'prefix', kind: 'component', component: () => import('./x') }`
1650
+ },
1651
+ // ─── PrimeNG-shipped kinds ───────────────────────────────────────────────
1652
+ {
1653
+ kind: "prime-icon",
1654
+ category: "adapter",
1655
+ package: "@ng-forge/dynamic-forms-primeng",
1656
+ adapter: "primeng",
1657
+ description: 'Renders `<i class="pi pi-{icon}">` \u2014 bare PrimeIcons name.',
1658
+ jsonSafe: true,
1659
+ props: {
1660
+ ...UNIVERSAL_BASE_PROPS,
1661
+ icon: {
1662
+ name: "icon",
1663
+ type: "string",
1664
+ description: 'PrimeIcons name without the `pi-` prefix (e.g., "search", "times").',
1665
+ required: true
1666
+ },
1667
+ ariaLabel: {
1668
+ name: "ariaLabel",
1669
+ type: "DynamicText",
1670
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1671
+ required: false
1672
+ }
1673
+ },
1674
+ example: `{ slot: 'prefix', kind: 'prime-icon', icon: 'search', ariaLabel: 'Search' }`,
1675
+ minimalExample: `{ slot: 'prefix', kind: 'prime-icon', icon: 'search' }`
1676
+ },
1677
+ {
1678
+ kind: "prime-button",
1679
+ category: "adapter",
1680
+ package: "@ng-forge/dynamic-forms-primeng",
1681
+ adapter: "primeng",
1682
+ description: "Renders `<p-button>` with optional icon, label, severity, reactive loading, and exactly one click handler (preset, actionRef, or action).",
1683
+ jsonSafe: true,
1684
+ props: {
1685
+ ...UNIVERSAL_BASE_PROPS,
1686
+ icon: { name: "icon", type: "string", description: "PrimeIcons name without the `pi-` prefix.", required: false },
1687
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1688
+ ariaLabel: {
1689
+ name: "ariaLabel",
1690
+ type: "DynamicText",
1691
+ description: "Required when icon-only (no `label`). Used as `aria-label`.",
1692
+ required: false
1693
+ },
1694
+ severity: {
1695
+ name: "severity",
1696
+ type: "'primary' | 'secondary' | 'success' | 'info' | 'warn' | 'danger' | 'help' | 'contrast'",
1697
+ description: "PrimeNG button colour variant.",
1698
+ required: false
1699
+ },
1700
+ loading: {
1701
+ name: "loading",
1702
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1703
+ description: "Reactive loading state \u2014 renders the native PrimeNG spinner.",
1704
+ required: false
1705
+ },
1706
+ preset: {
1707
+ name: "preset",
1708
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1709
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`. Adapters may extend the preset union.",
1710
+ required: false
1711
+ },
1712
+ actionRef: {
1713
+ name: "actionRef",
1714
+ type: "string",
1715
+ description: "Typed string handle to a handler registered via `provideAddonActions(...)`. Mutually exclusive with `preset` and `action`. JSON-safe.",
1716
+ required: false
1717
+ },
1718
+ action: {
1719
+ name: "action",
1720
+ type: "(ctx: AddonActionContext) => void",
1721
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs). Mutually exclusive with `preset` and `actionRef`.",
1722
+ required: false
1723
+ }
1724
+ },
1725
+ example: `{ slot: 'suffix', kind: 'prime-button', icon: 'times', ariaLabel: 'Clear', preset: 'clear' }`,
1726
+ minimalExample: `{ slot: 'suffix', kind: 'prime-button', icon: 'times', ariaLabel: 'Clear', preset: 'clear' }`
1727
+ },
1728
+ // ─── Material-shipped kinds ──────────────────────────────────────────────
1729
+ {
1730
+ kind: "mat-icon",
1731
+ category: "adapter",
1732
+ package: "@ng-forge/dynamic-forms-material",
1733
+ adapter: "material",
1734
+ description: "Renders `<mat-icon>{icon}</mat-icon>` \u2014 Material Icons ligature name.",
1735
+ jsonSafe: true,
1736
+ props: {
1737
+ ...UNIVERSAL_BASE_PROPS,
1738
+ icon: {
1739
+ name: "icon",
1740
+ type: "string",
1741
+ description: 'Material Icons ligature (e.g., "search", "close", "visibility").',
1742
+ required: true
1743
+ },
1744
+ ariaLabel: {
1745
+ name: "ariaLabel",
1746
+ type: "DynamicText",
1747
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1748
+ required: false
1749
+ }
1750
+ },
1751
+ example: `{ slot: 'prefix', kind: 'mat-icon', icon: 'search', ariaLabel: 'Search' }`,
1752
+ minimalExample: `{ slot: 'prefix', kind: 'mat-icon', icon: 'search' }`
1753
+ },
1754
+ {
1755
+ kind: "mat-button",
1756
+ category: "adapter",
1757
+ package: "@ng-forge/dynamic-forms-material",
1758
+ adapter: "material",
1759
+ 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).",
1760
+ jsonSafe: true,
1761
+ props: {
1762
+ ...UNIVERSAL_BASE_PROPS,
1763
+ icon: { name: "icon", type: "string", description: "Material Icons ligature.", required: false },
1764
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1765
+ ariaLabel: {
1766
+ name: "ariaLabel",
1767
+ type: "DynamicText",
1768
+ description: "REQUIRED when icon-only (no `label`). Used as `aria-label`.",
1769
+ required: false
1770
+ },
1771
+ color: {
1772
+ name: "color",
1773
+ type: "'primary' | 'accent' | 'warn'",
1774
+ description: "Material button color variant.",
1775
+ required: false
1776
+ },
1777
+ loading: {
1778
+ name: "loading",
1779
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1780
+ description: "Reactive loading state \u2014 disables the button when truthy.",
1781
+ required: false
1782
+ },
1783
+ preset: {
1784
+ name: "preset",
1785
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1786
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`.",
1787
+ required: false
1788
+ },
1789
+ actionRef: {
1790
+ name: "actionRef",
1791
+ type: "string",
1792
+ description: "Typed handle to a handler registered via `provideAddonActions(...)`. JSON-safe.",
1793
+ required: false
1794
+ },
1795
+ action: {
1796
+ name: "action",
1797
+ type: "(ctx: AddonActionContext) => void",
1798
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs).",
1799
+ required: false
1800
+ }
1801
+ },
1802
+ example: `{ slot: 'suffix', kind: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' }`,
1803
+ minimalExample: `{ slot: 'suffix', kind: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' }`
1804
+ },
1805
+ // ─── Bootstrap-shipped kinds ─────────────────────────────────────────────
1806
+ {
1807
+ kind: "bs-icon",
1808
+ category: "adapter",
1809
+ package: "@ng-forge/dynamic-forms-bootstrap",
1810
+ adapter: "bootstrap",
1811
+ description: 'Renders `<i class="bi bi-{icon}">` \u2014 Bootstrap Icons name without the `bi-` prefix.',
1812
+ jsonSafe: true,
1813
+ props: {
1814
+ ...UNIVERSAL_BASE_PROPS,
1815
+ icon: {
1816
+ name: "icon",
1817
+ type: "string",
1818
+ description: 'Bootstrap Icons name without the `bi-` prefix (e.g., "search", "x", "eye").',
1819
+ required: true
1820
+ },
1821
+ ariaLabel: {
1822
+ name: "ariaLabel",
1823
+ type: "DynamicText",
1824
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1825
+ required: false
1826
+ }
1827
+ },
1828
+ example: `{ slot: 'prefix', kind: 'bs-icon', icon: 'search', ariaLabel: 'Search' }`,
1829
+ minimalExample: `{ slot: 'prefix', kind: 'bs-icon', icon: 'search' }`
1830
+ },
1831
+ {
1832
+ kind: "bs-button",
1833
+ category: "adapter",
1834
+ package: "@ng-forge/dynamic-forms-bootstrap",
1835
+ adapter: "bootstrap",
1836
+ description: 'Renders `<button class="btn btn-outline-{severity}">` with optional icon, label, severity, and reactive loading (swaps to a spinner).',
1837
+ jsonSafe: true,
1838
+ props: {
1839
+ ...UNIVERSAL_BASE_PROPS,
1840
+ icon: { name: "icon", type: "string", description: "Bootstrap Icons name without the `bi-` prefix.", required: false },
1841
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1842
+ ariaLabel: {
1843
+ name: "ariaLabel",
1844
+ type: "DynamicText",
1845
+ description: "REQUIRED when icon-only (no `label`). Used as `aria-label`.",
1846
+ required: false
1847
+ },
1848
+ severity: {
1849
+ name: "severity",
1850
+ type: "'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'light' | 'dark'",
1851
+ description: "Bootstrap button variant (mapped to `btn-outline-{severity}`).",
1852
+ required: false
1853
+ },
1854
+ loading: {
1855
+ name: "loading",
1856
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1857
+ description: "Reactive loading state \u2014 swaps the button content for a spinner.",
1858
+ required: false
1859
+ },
1860
+ preset: {
1861
+ name: "preset",
1862
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1863
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`.",
1864
+ required: false
1865
+ },
1866
+ actionRef: {
1867
+ name: "actionRef",
1868
+ type: "string",
1869
+ description: "Typed handle to a handler registered via `provideAddonActions(...)`. JSON-safe.",
1870
+ required: false
1871
+ },
1872
+ action: {
1873
+ name: "action",
1874
+ type: "(ctx: AddonActionContext) => void",
1875
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs).",
1876
+ required: false
1877
+ }
1878
+ },
1879
+ example: `{ slot: 'suffix', kind: 'bs-button', icon: 'x', ariaLabel: 'Clear', preset: 'clear' }`,
1880
+ minimalExample: `{ slot: 'suffix', kind: 'bs-button', icon: 'x', ariaLabel: 'Clear', preset: 'clear' }`
1881
+ },
1882
+ // ─── Ionic-shipped kinds ─────────────────────────────────────────────────
1883
+ {
1884
+ kind: "ion-icon",
1885
+ category: "adapter",
1886
+ package: "@ng-forge/dynamic-forms-ionic",
1887
+ adapter: "ionic",
1888
+ description: 'Renders `<ion-icon name="{icon}">` \u2014 Ionicons name.',
1889
+ jsonSafe: true,
1890
+ props: {
1891
+ ...UNIVERSAL_BASE_PROPS,
1892
+ icon: {
1893
+ name: "icon",
1894
+ type: "string",
1895
+ description: 'Ionicons name (e.g., "search-outline", "close-outline", "eye-outline").',
1896
+ required: true
1897
+ },
1898
+ ariaLabel: {
1899
+ name: "ariaLabel",
1900
+ type: "DynamicText",
1901
+ description: "Accessible label for icons that convey meaning. Decorative icons omit this.",
1902
+ required: false
1903
+ }
1904
+ },
1905
+ example: `{ slot: 'prefix', kind: 'ion-icon', icon: 'search-outline', ariaLabel: 'Search' }`,
1906
+ minimalExample: `{ slot: 'prefix', kind: 'ion-icon', icon: 'search-outline' }`
1907
+ },
1908
+ {
1909
+ kind: "ion-button",
1910
+ category: "adapter",
1911
+ package: "@ng-forge/dynamic-forms-ionic",
1912
+ adapter: "ionic",
1913
+ 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.',
1914
+ jsonSafe: true,
1915
+ props: {
1916
+ ...UNIVERSAL_BASE_PROPS,
1917
+ icon: { name: "icon", type: "string", description: "Ionicons name.", required: false },
1918
+ label: { name: "label", type: "DynamicText", description: "Visible button label.", required: false },
1919
+ ariaLabel: {
1920
+ name: "ariaLabel",
1921
+ type: "DynamicText",
1922
+ description: "REQUIRED when icon-only (no `label`). Used as `aria-label`.",
1923
+ required: false
1924
+ },
1925
+ color: {
1926
+ name: "color",
1927
+ type: "'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'danger' | 'light' | 'medium' | 'dark'",
1928
+ description: "Ionic button color.",
1929
+ required: false
1930
+ },
1931
+ fill: {
1932
+ name: "fill",
1933
+ type: "'clear' | 'outline' | 'solid' | 'default'",
1934
+ description: "Ionic button fill style.",
1935
+ required: false
1936
+ },
1937
+ loading: {
1938
+ name: "loading",
1939
+ type: "boolean | Signal<boolean> | Observable<boolean>",
1940
+ description: "Reactive loading state \u2014 renders an `<ion-spinner>`.",
1941
+ required: false
1942
+ },
1943
+ preset: {
1944
+ name: "preset",
1945
+ type: "'clear' | 'reset' | 'paste' | 'copy' | 'toggle-password-visibility' | string",
1946
+ description: "Built-in preset action. Mutually exclusive with `actionRef` and `action`.",
1947
+ required: false
1948
+ },
1949
+ actionRef: {
1950
+ name: "actionRef",
1951
+ type: "string",
1952
+ description: "Typed handle to a handler registered via `provideAddonActions(...)`. JSON-safe.",
1953
+ required: false
1954
+ },
1955
+ action: {
1956
+ name: "action",
1957
+ type: "(ctx: AddonActionContext) => void",
1958
+ description: "Inline click handler (code-only \u2014 dropped from JSON-derived configs).",
1959
+ required: false
1960
+ }
1961
+ },
1962
+ example: `{ slot: 'suffix', kind: 'ion-button', icon: 'close-outline', ariaLabel: 'Clear', preset: 'clear' }`,
1963
+ minimalExample: `{ slot: 'suffix', kind: 'ion-button', icon: 'close-outline', ariaLabel: 'Clear', preset: 'clear' }`
1964
+ }
1965
+ ];
1966
+ var FIELD_ADDON_SUPPORT = [
1967
+ {
1968
+ fieldType: "input",
1969
+ adapter: "primeng",
1970
+ slots: ["prefix", "suffix"]
1971
+ },
1972
+ {
1973
+ fieldType: "input",
1974
+ adapter: "material",
1975
+ slots: ["prefix", "suffix"]
1976
+ },
1977
+ {
1978
+ fieldType: "input",
1979
+ adapter: "bootstrap",
1980
+ slots: ["prefix", "suffix"]
1981
+ },
1982
+ {
1983
+ fieldType: "input",
1984
+ adapter: "ionic",
1985
+ slots: ["prefix", "suffix"]
1986
+ }
1987
+ ];
1988
+
1554
1989
  // packages/dynamic-form-mcp/src/registry/index.ts
1555
1990
  function getFieldTypes() {
1556
1991
  return FIELD_TYPES;
@@ -1598,6 +2033,18 @@ function getDocPage(id) {
1598
2033
  function getDocPagesByCategory(category) {
1599
2034
  return DOCUMENTATION.filter((d) => d.category === category);
1600
2035
  }
2036
+ function getAddonKinds() {
2037
+ return ADDON_KINDS;
2038
+ }
2039
+ function getAddonKind(kind) {
2040
+ return ADDON_KINDS.find((k) => k.kind === kind);
2041
+ }
2042
+ function getAddonKindsByCategory(category) {
2043
+ return ADDON_KINDS.filter((k) => k.category === category);
2044
+ }
2045
+ function getFieldAddonSupport() {
2046
+ return FIELD_ADDON_SUPPORT;
2047
+ }
1601
2048
 
1602
2049
  // packages/dynamic-form-mcp/src/resources/field-types.resource.ts
1603
2050
  function registerFieldTypesResource(server) {
@@ -2488,6 +2935,53 @@ Available adapters:
2488
2935
  - \`@ng-forge/dynamic-forms-primeng\` - PrimeNG
2489
2936
  - \`@ng-forge/dynamic-forms-ionic\` - Ionic
2490
2937
 
2938
+ ### Style Defaults (opt-in)
2939
+
2940
+ 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.
2941
+
2942
+ \`\`\`scss
2943
+ @use '@ng-forge/dynamic-forms-material/styles/defaults' as df;
2944
+
2945
+ :root {
2946
+ @include df.apply-material-form-defaults;
2947
+ }
2948
+ \`\`\`
2949
+
2950
+ 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).
2951
+
2952
+ ## Provider Features
2953
+
2954
+ \`provideDynamicForm()\` accepts optional \`with*\` feature configurators alongside the adapter field bundle:
2955
+
2956
+ ### \`withLegacyStatusClasses()\` \u2014 opt into legacy ng-* CSS classes
2957
+
2958
+ 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:
2959
+
2960
+ \`\`\`typescript
2961
+ import { provideDynamicForm, withLegacyStatusClasses } from '@ng-forge/dynamic-forms';
2962
+ import { withMaterialFields } from '@ng-forge/dynamic-forms-material';
2963
+
2964
+ export const appConfig = {
2965
+ providers: [
2966
+ provideDynamicForm(
2967
+ ...withMaterialFields(),
2968
+ withLegacyStatusClasses(),
2969
+ ),
2970
+ ],
2971
+ };
2972
+ \`\`\`
2973
+
2974
+ 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.
2975
+
2976
+ ### Other built-in features
2977
+
2978
+ - \`withLoggerConfig(options?)\` \u2014 configure or disable the dynamic-form logger
2979
+ - \`withEventFormValue()\` \u2014 attach the current form value to every dispatched event
2980
+ - \`withValueExclusionDefaults(options)\` \u2014 set form-wide value exclusion defaults
2981
+ - \`withValidationExecutionDefaults(options)\` \u2014 set form-wide validation execution defaults (e.g. defer until touched)
2982
+
2983
+ All features are exported from \`@ng-forge/dynamic-forms\`.
2984
+
2491
2985
  ## Common Patterns
2492
2986
 
2493
2987
  ### Always End with Submit Button
@@ -9431,6 +9925,7 @@ function getTopicList() {
9431
9925
  const fieldTypes = ["input", "select", "slider", "radio", "checkbox", "textarea", "datepicker", "toggle", "text", "hidden"];
9432
9926
  const containers = ["group", "row", "array", "simplified-array", "page"];
9433
9927
  const wrappers = ["wrappers", ...getWrappers().map((w) => w.type)];
9928
+ const addons = ["addons", ...getAddonKinds().map((k) => k.kind)];
9434
9929
  const concepts = [
9435
9930
  "validation",
9436
9931
  "validation-messages",
@@ -9469,6 +9964,9 @@ ${containers.map(formatTopicItem).join("\n")}
9469
9964
  ## Wrappers
9470
9965
  ${wrappers.map(formatTopicItem).join("\n")}
9471
9966
 
9967
+ ## Addons
9968
+ ${addons.map(formatTopicItem).join("\n")}
9969
+
9472
9970
  ## Concepts
9473
9971
  ${concepts.map(formatTopicItem).join("\n")}
9474
9972
 
@@ -9637,7 +10135,120 @@ const config = {
9637
10135
  lines.push(wrappers[0]?.contract ?? "");
9638
10136
  return lines.join("\n");
9639
10137
  }
10138
+ function formatAddonInfo(addon, depth) {
10139
+ if (depth === "brief") {
10140
+ return [
10141
+ `# ${addon.kind} addon`,
10142
+ "",
10143
+ `${addon.description.split(".")[0]}.`,
10144
+ "",
10145
+ "```typescript",
10146
+ addon.minimalExample,
10147
+ "```",
10148
+ addon.jsonSafe ? "" : "**Code-only** \u2014 dropped by the lenient validator when the config came from JSON."
10149
+ ].filter(Boolean).join("\n");
10150
+ }
10151
+ const lines = [];
10152
+ lines.push(`# ${addon.kind} addon`);
10153
+ lines.push("");
10154
+ lines.push(`**Category:** ${addon.category}`);
10155
+ lines.push(`**Package:** \`${addon.package}\``);
10156
+ if (addon.adapter) lines.push(`**Adapter:** ${addon.adapter}`);
10157
+ lines.push(`**JSON-safe:** ${addon.jsonSafe ? "Yes" : "No (code-only)"}`);
10158
+ lines.push(`**Description:** ${addon.description}`);
10159
+ if (Object.keys(addon.props).length > 0) {
10160
+ lines.push("");
10161
+ lines.push("## Config Props");
10162
+ for (const prop of Object.values(addon.props)) {
10163
+ const req = prop.required ? " **(required)**" : "";
10164
+ lines.push(`- \`${prop.name}\`: ${prop.type}${req} \u2014 ${prop.description}`);
10165
+ }
10166
+ }
10167
+ lines.push("");
10168
+ lines.push("## Minimal Example");
10169
+ lines.push("```typescript");
10170
+ lines.push(addon.minimalExample);
10171
+ lines.push("```");
10172
+ lines.push("");
10173
+ lines.push("## Full Example");
10174
+ lines.push("```typescript");
10175
+ lines.push(addon.example);
10176
+ lines.push("```");
10177
+ return lines.join("\n");
10178
+ }
10179
+ function formatAddonsOverview() {
10180
+ const kinds = getAddonKinds();
10181
+ const lines = [];
10182
+ lines.push("# Addons");
10183
+ lines.push("");
10184
+ lines.push(
10185
+ "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`)."
10186
+ );
10187
+ lines.push("");
10188
+ lines.push("## Applying an addon");
10189
+ lines.push("");
10190
+ lines.push("```typescript");
10191
+ lines.push(`{
10192
+ key: 'q',
10193
+ type: 'input',
10194
+ label: 'Search',
10195
+ addons: [
10196
+ { slot: 'prefix', kind: 'mat-icon', icon: 'search', ariaLabel: 'Search' },
10197
+ { slot: 'suffix', kind: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' },
10198
+ ],
10199
+ }`);
10200
+ lines.push("```");
10201
+ lines.push("");
10202
+ lines.push("## Registered Kinds");
10203
+ lines.push("");
10204
+ const core = kinds.filter((k) => k.category === "core");
10205
+ const adapter = kinds.filter((k) => k.category === "adapter");
10206
+ if (core.length > 0) {
10207
+ lines.push("### Core (universal \u2014 ship from `@ng-forge/dynamic-forms`)");
10208
+ for (const k of core) {
10209
+ const json = k.jsonSafe ? "" : " _(code-only)_";
10210
+ lines.push(`- **${k.kind}**${json} \u2014 ${k.description.split(".")[0]}.`);
10211
+ }
10212
+ lines.push("");
10213
+ }
10214
+ if (adapter.length > 0) {
10215
+ lines.push("### Adapter (provided by UI integration packages)");
10216
+ for (const k of adapter) {
10217
+ lines.push(`- **${k.kind}** (${k.adapter}, \`${k.package}\`) \u2014 ${k.description.split(".")[0]}.`);
10218
+ }
10219
+ lines.push("");
10220
+ }
10221
+ lines.push('Use `ngforge_lookup topic="<kind>"` (e.g., `mat-button`, `pi-icon`) for full details on any individual addon kind.');
10222
+ lines.push("");
10223
+ lines.push("## Click variants (mutually exclusive on button-like addons)");
10224
+ lines.push("");
10225
+ lines.push("- `preset`: built-in semantics \u2014 `clear` / `reset` / `paste` / `copy` / `toggle-password-visibility`.");
10226
+ lines.push("- `actionRef`: named handler resolved via `provideAddonActions({ ... })`.");
10227
+ lines.push("- `action`: inline function (code-only, dropped by JSON validator).");
10228
+ lines.push("");
10229
+ lines.push("Setting more than one logs a warning and applies the first (precedence: `preset` > `actionRef` > `action`).");
10230
+ return lines.join("\n");
10231
+ }
9640
10232
  async function resolveTopicContent(resolvedTopic, depth, uiIntegration) {
10233
+ if (resolvedTopic === "addons" || resolvedTopic === "addon" || resolvedTopic === "addon-kinds") {
10234
+ if (depth === "brief") {
10235
+ const kinds = getAddonKinds();
10236
+ return [
10237
+ "**Addons** render inside a field slot (prefix / suffix / adapter-specific): icons, buttons, inline text, templates, components.",
10238
+ "",
10239
+ "Registered kinds:",
10240
+ ...kinds.map((k) => `- \`${k.kind}\` (${k.category}${k.adapter ? `, ${k.adapter}` : ""})${k.jsonSafe ? "" : " \u2014 code-only"}`),
10241
+ "",
10242
+ 'Use `addons: [{ slot: "suffix", kind: "<kind>", ... }]` on an addon-supporting field. See `ngforge_lookup topic="addons"` for full docs.'
10243
+ ].join("\n");
10244
+ }
10245
+ return formatAddonsOverview();
10246
+ }
10247
+ const addonInfo = getAddonKind(resolvedTopic);
10248
+ const collidesWithFieldOrWrapper = resolvedTopic === "text" || !!getWrapper(resolvedTopic);
10249
+ if (addonInfo && !collidesWithFieldOrWrapper) {
10250
+ return formatAddonInfo(addonInfo, depth);
10251
+ }
9641
10252
  if (resolvedTopic === "wrappers" || resolvedTopic === "wrapper") {
9642
10253
  if (depth === "brief") {
9643
10254
  const wrappers = getWrappers();
@@ -9788,6 +10399,8 @@ Containers: group, row, array, simplified-array, page
9788
10399
 
9789
10400
  Wrappers: wrappers (overview), css (shipping), section, arraySection (demo-only)
9790
10401
 
10402
+ Addons: addons (overview), text, template, component (core), mat-icon, mat-button, bs-icon, bs-button, pi-icon, pi-button, ion-icon, ion-button
10403
+
9791
10404
  Concepts: validation, conditional, derivation, property-derivation, options-format, expression-variables, async-validators
9792
10405
 
9793
10406
  Patterns: field-placement, logic-matrix, context-api, containers, multi-page-gotchas
@@ -13245,6 +13858,18 @@ function buildIndex() {
13245
13858
  toolCall: `ngforge_lookup topic="${wrapper.type}"`
13246
13859
  });
13247
13860
  }
13861
+ for (const addon of getAddonKinds()) {
13862
+ const subcategory = addon.adapter ? `Addon (${addon.adapter})` : `Addon (${addon.category})`;
13863
+ entries.push({
13864
+ id: addon.kind,
13865
+ category: "addon",
13866
+ subcategory,
13867
+ description: addon.description,
13868
+ aliases: [],
13869
+ content: `${addon.kind} addon ${addon.description} ${addon.package}`.toLowerCase(),
13870
+ toolCall: `ngforge_lookup topic="${addon.kind}"`
13871
+ });
13872
+ }
13248
13873
  searchIndex = entries;
13249
13874
  return entries;
13250
13875
  }
@@ -13369,6 +13994,10 @@ export {
13369
13994
  SERVER_NAME,
13370
13995
  SERVER_VERSION,
13371
13996
  createServer,
13997
+ getAddonKind,
13998
+ getAddonKinds,
13999
+ getAddonKindsByCategory,
14000
+ getFieldAddonSupport,
13372
14001
  getFieldType,
13373
14002
  getFieldTypes,
13374
14003
  getFieldTypesByCategory,