@ng-forge/dynamic-form-mcp 0.9.0-next.9 → 0.10.0-next.1
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/bin/ng-forge-mcp.js +650 -8
- package/package.json +6 -6
- package/src/index.d.ts +2 -2
- package/src/index.d.ts.map +1 -1
- package/src/index.js +682 -8
- package/src/registry/addons.d.ts +52 -0
- package/src/registry/addons.d.ts.map +1 -0
- package/src/registry/index.d.ts +12 -0
- package/src/registry/index.d.ts.map +1 -1
- package/src/registry/instructions.d.ts +1 -1
- package/src/registry/instructions.d.ts.map +1 -1
- package/src/registry/wrappers.d.ts +1 -1
- package/src/registry/wrappers.d.ts.map +1 -1
- package/src/tools/examples.tool.d.ts.map +1 -1
- package/src/tools/lookup.tool.d.ts.map +1 -1
- package/src/tools/search.tool.d.ts.map +1 -1
package/src/index.js
CHANGED
|
@@ -778,7 +778,7 @@ var VALIDATORS = [
|
|
|
778
778
|
{
|
|
779
779
|
type: "custom",
|
|
780
780
|
category: "custom",
|
|
781
|
-
description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config.",
|
|
781
|
+
description: "Custom synchronous validator using registered function or expression. IMPORTANT: The validator returns { kind: 'errorKind' } on failure. The actual error MESSAGE is defined in 'validationMessages' at the FIELD level, NOT in the validator config. NOTE: TypeScript-authored configs may also use an inline `fn: CustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
|
|
782
782
|
parameters: {
|
|
783
783
|
functionName: {
|
|
784
784
|
name: "functionName",
|
|
@@ -830,7 +830,7 @@ var VALIDATORS = [
|
|
|
830
830
|
{
|
|
831
831
|
type: "async",
|
|
832
832
|
category: "async",
|
|
833
|
-
description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators.",
|
|
833
|
+
description: "Async validator using registered async function (resource-based). Register the function via customFnConfig.asyncValidators. NOTE: TypeScript-authored configs may also use an inline `fn: AsyncCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName`.",
|
|
834
834
|
parameters: {
|
|
835
835
|
functionName: {
|
|
836
836
|
name: "functionName",
|
|
@@ -853,7 +853,7 @@ var VALIDATORS = [
|
|
|
853
853
|
{
|
|
854
854
|
type: "http",
|
|
855
855
|
category: "http",
|
|
856
|
-
description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages).",
|
|
856
|
+
description: "HTTP validator \u2014 supports two modes: (1) Declarative (fully JSON-serializable, no function registration) using http + responseMapping properties, or (2) Function-based using functionName to reference a registered HTTP validator function. Query param values and (optionally) body values are expressions evaluated against the form context. Response mapping uses validWhen (truthy = valid) and errorKind (maps to field-level validationMessages). NOTE: TypeScript-authored configs may also use an inline `fn: HttpCustomValidator` instead of `functionName` (XOR with `functionName`, code-only \u2014 not JSON-serializable); MCP-generated configs should use `functionName` or declarative mode.",
|
|
857
857
|
parameters: {
|
|
858
858
|
functionName: {
|
|
859
859
|
name: "functionName",
|
|
@@ -1400,7 +1400,7 @@ var DOCUMENTATION = [
|
|
|
1400
1400
|
// packages/dynamic-form-mcp/src/registry/wrappers.ts
|
|
1401
1401
|
var WRAPPER_AUTHORING_CONTRACT = `All wrapper components MUST:
|
|
1402
1402
|
|
|
1403
|
-
1. Implement \`
|
|
1403
|
+
1. Implement \`FieldWrapper\` from \`@ng-forge/dynamic-forms\`.
|
|
1404
1404
|
2. Expose \`readonly fieldComponent = viewChild.required('fieldComponent', { read: ViewContainerRef });\`
|
|
1405
1405
|
\u2014 this is the slot where the inner wrapper (or the field) is rendered
|
|
1406
1406
|
imperatively by the outlet.
|
|
@@ -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) {
|
|
@@ -2144,7 +2591,9 @@ const config: FormConfig = {
|
|
|
2144
2591
|
|
|
2145
2592
|
Every field MUST have:
|
|
2146
2593
|
|
|
2147
|
-
- \`key\`:
|
|
2594
|
+
- \`key\`: Identifier, unique within its group scope (the same leaf key may repeat inside different groups, e.g. \`createA.name\` and \`createB.name\`). Page/row/array containers do not introduce a scope. Buttons (which don't bind to form values) don't need unique keys.
|
|
2595
|
+
- **DOM id / data-testid composition:** keys are joined with \`_\` through group ancestors to form the rendered DOM id. A leaf \`street\` inside group \`address\` renders as \`id="address_street"\`. Inside an array item, the item index is appended after: \`id="address_street_0"\`. Use this exact form for \`for=\`, \`aria-describedby\`, \`aria-labelledby\`, or CSS selectors that target group-nested fields.
|
|
2596
|
+
- **Avoid \`_\` in keys when they could collide with a group-prefixed id.** A top-level key \`foo_bar\` and a leaf \`bar\` inside group \`foo\` would both render as \`id="foo_bar"\`. The validator catches this collision at boot, but it's easier to just not put \`_\` in keys that share a name with an existing group + leaf pair.
|
|
2148
2597
|
- \`type\`: One of the registered field types
|
|
2149
2598
|
- \`label\`: Human-readable label (for accessibility)
|
|
2150
2599
|
|
|
@@ -2301,12 +2750,13 @@ Available shorthands: \`required\`, \`email\`, \`min\`, \`max\`, \`minLength\`,
|
|
|
2301
2750
|
|
|
2302
2751
|
### Nullable Values
|
|
2303
2752
|
|
|
2304
|
-
Value fields accept a \`nullable?: boolean\` flag. When \`true\`:
|
|
2753
|
+
Value fields and checked fields (\`checkbox\`, \`toggle\`) accept a \`nullable?: boolean\` flag. When \`true\`:
|
|
2305
2754
|
|
|
2306
2755
|
- \`value\` accepts \`null\` in addition to the field's normal value type.
|
|
2307
|
-
- An omitted \`value\` resolves to \`null\` (instead of the type-specific empty default like \`''\`, \`NaN\`, or \`[]\`).
|
|
2756
|
+
- An omitted \`value\` resolves to \`null\` (instead of the type-specific empty default like \`''\`, \`NaN\`, \`false\`, or \`[]\`).
|
|
2308
2757
|
- Orthogonal to \`required\`. \`nullable\` declares that the **model** accepts \`null\`; \`required\` is a **validation** constraint. Angular's \`Validators.required\` treats \`null\` as invalid, so a field that is both \`nullable\` and \`required\` will fail required-validation when the value is \`null\` \u2014 the two flags describe different layers (data shape vs. validation), not conflicting semantics.
|
|
2309
2758
|
- Map OpenAPI \`nullable: true\` (3.0) or \`type: [T, null]\` (3.1) to this flag.
|
|
2759
|
+
- Container fields (\`group\`, \`array\`, \`row\`, \`page\`) do NOT support \`nullable\` \u2014 their null semantics are ambiguous.
|
|
2310
2760
|
|
|
2311
2761
|
\`\`\`typescript
|
|
2312
2762
|
{
|
|
@@ -2316,6 +2766,14 @@ Value fields accept a \`nullable?: boolean\` flag. When \`true\`:
|
|
|
2316
2766
|
nullable: true,
|
|
2317
2767
|
value: null, // valid; default-resolves to null
|
|
2318
2768
|
}
|
|
2769
|
+
|
|
2770
|
+
{
|
|
2771
|
+
key: 'active',
|
|
2772
|
+
type: 'checkbox',
|
|
2773
|
+
label: 'Active',
|
|
2774
|
+
nullable: true,
|
|
2775
|
+
value: null, // undecided checkbox state \u2014 model permits null, UI renders unchecked
|
|
2776
|
+
}
|
|
2319
2777
|
\`\`\`
|
|
2320
2778
|
|
|
2321
2779
|
Read-side caveat: a user clearing a text input reads back as \`""\`, not \`null\` \u2014 this is a DOM/Web IDL contract, identical to classic Reactive Forms. \`nullable\` is a contract for accepted values, not a guarantee of emitted ones.
|
|
@@ -2411,6 +2869,8 @@ Invokes a registered custom function by name:
|
|
|
2411
2869
|
|
|
2412
2870
|
Register functions via \`customFnConfig.customFunctions\`.
|
|
2413
2871
|
|
|
2872
|
+
> **TypeScript-authored configs:** the same surface also accepts an inline \`fn: CustomFunction\` instead of \`functionName\` (XOR with \`functionName\`, code-only \u2014 not JSON-serializable). The MCP server emits \`functionName\` exclusively; consumers writing configs in TS may prefer \`fn\` when they don't need JSON round-tripping. Same caveat applies to async conditions (\`asyncFn\`), function derivations (\`fn\`), async function derivations (\`asyncFn\`), and validators (\`fn\`).
|
|
2873
|
+
|
|
2414
2874
|
#### Async Custom Condition
|
|
2415
2875
|
|
|
2416
2876
|
Invokes a registered async function that returns \`Promise<boolean>\` or \`Observable<boolean>\`:
|
|
@@ -2484,6 +2944,53 @@ Available adapters:
|
|
|
2484
2944
|
- \`@ng-forge/dynamic-forms-primeng\` - PrimeNG
|
|
2485
2945
|
- \`@ng-forge/dynamic-forms-ionic\` - Ionic
|
|
2486
2946
|
|
|
2947
|
+
### Style Defaults (opt-in)
|
|
2948
|
+
|
|
2949
|
+
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.
|
|
2950
|
+
|
|
2951
|
+
\`\`\`scss
|
|
2952
|
+
@use '@ng-forge/dynamic-forms-material/styles/defaults' as df;
|
|
2953
|
+
|
|
2954
|
+
:root {
|
|
2955
|
+
@include df.apply-material-form-defaults;
|
|
2956
|
+
}
|
|
2957
|
+
\`\`\`
|
|
2958
|
+
|
|
2959
|
+
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).
|
|
2960
|
+
|
|
2961
|
+
## Provider Features
|
|
2962
|
+
|
|
2963
|
+
\`provideDynamicForm()\` accepts optional \`with*\` feature configurators alongside the adapter field bundle:
|
|
2964
|
+
|
|
2965
|
+
### \`withLegacyStatusClasses()\` \u2014 opt into legacy ng-* CSS classes
|
|
2966
|
+
|
|
2967
|
+
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:
|
|
2968
|
+
|
|
2969
|
+
\`\`\`typescript
|
|
2970
|
+
import { provideDynamicForm, withLegacyStatusClasses } from '@ng-forge/dynamic-forms';
|
|
2971
|
+
import { withMaterialFields } from '@ng-forge/dynamic-forms-material';
|
|
2972
|
+
|
|
2973
|
+
export const appConfig = {
|
|
2974
|
+
providers: [
|
|
2975
|
+
provideDynamicForm(
|
|
2976
|
+
...withMaterialFields(),
|
|
2977
|
+
withLegacyStatusClasses(),
|
|
2978
|
+
),
|
|
2979
|
+
],
|
|
2980
|
+
};
|
|
2981
|
+
\`\`\`
|
|
2982
|
+
|
|
2983
|
+
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.
|
|
2984
|
+
|
|
2985
|
+
### Other built-in features
|
|
2986
|
+
|
|
2987
|
+
- \`withLoggerConfig(options?)\` \u2014 configure or disable the dynamic-form logger
|
|
2988
|
+
- \`withEventFormValue()\` \u2014 attach the current form value to every dispatched event
|
|
2989
|
+
- \`withValueExclusionDefaults(options)\` \u2014 set form-wide value exclusion defaults
|
|
2990
|
+
- \`withValidationExecutionDefaults(options)\` \u2014 set form-wide validation execution defaults (e.g. defer until touched)
|
|
2991
|
+
|
|
2992
|
+
All features are exported from \`@ng-forge/dynamic-forms\`.
|
|
2993
|
+
|
|
2487
2994
|
## Common Patterns
|
|
2488
2995
|
|
|
2489
2996
|
### Always End with Submit Button
|
|
@@ -2518,7 +3025,7 @@ Available adapters:
|
|
|
2518
3025
|
|
|
2519
3026
|
1. **Missing labels** - Always include labels for accessibility
|
|
2520
3027
|
2. **Missing validation messages** - Users need clear feedback
|
|
2521
|
-
3. **Duplicate keys** -
|
|
3028
|
+
3. **Duplicate keys within a group scope** - Keys must be unique within the same group. Same key in different groups is fine (different scoped paths). Buttons are exempt.
|
|
2522
3029
|
4. **Select without options** - Select/radio fields must have options
|
|
2523
3030
|
5. **Array without fields** - Array fields need a fields template
|
|
2524
3031
|
6. **Group without fields** - Group fields need child fields
|
|
@@ -9427,6 +9934,7 @@ function getTopicList() {
|
|
|
9427
9934
|
const fieldTypes = ["input", "select", "slider", "radio", "checkbox", "textarea", "datepicker", "toggle", "text", "hidden"];
|
|
9428
9935
|
const containers = ["group", "row", "array", "simplified-array", "page"];
|
|
9429
9936
|
const wrappers = ["wrappers", ...getWrappers().map((w) => w.type)];
|
|
9937
|
+
const addons = ["addons", ...getAddonKinds().map((k) => k.kind)];
|
|
9430
9938
|
const concepts = [
|
|
9431
9939
|
"validation",
|
|
9432
9940
|
"validation-messages",
|
|
@@ -9465,6 +9973,9 @@ ${containers.map(formatTopicItem).join("\n")}
|
|
|
9465
9973
|
## Wrappers
|
|
9466
9974
|
${wrappers.map(formatTopicItem).join("\n")}
|
|
9467
9975
|
|
|
9976
|
+
## Addons
|
|
9977
|
+
${addons.map(formatTopicItem).join("\n")}
|
|
9978
|
+
|
|
9468
9979
|
## Concepts
|
|
9469
9980
|
${concepts.map(formatTopicItem).join("\n")}
|
|
9470
9981
|
|
|
@@ -9633,7 +10144,120 @@ const config = {
|
|
|
9633
10144
|
lines.push(wrappers[0]?.contract ?? "");
|
|
9634
10145
|
return lines.join("\n");
|
|
9635
10146
|
}
|
|
10147
|
+
function formatAddonInfo(addon, depth) {
|
|
10148
|
+
if (depth === "brief") {
|
|
10149
|
+
return [
|
|
10150
|
+
`# ${addon.kind} addon`,
|
|
10151
|
+
"",
|
|
10152
|
+
`${addon.description.split(".")[0]}.`,
|
|
10153
|
+
"",
|
|
10154
|
+
"```typescript",
|
|
10155
|
+
addon.minimalExample,
|
|
10156
|
+
"```",
|
|
10157
|
+
addon.jsonSafe ? "" : "**Code-only** \u2014 dropped by the lenient validator when the config came from JSON."
|
|
10158
|
+
].filter(Boolean).join("\n");
|
|
10159
|
+
}
|
|
10160
|
+
const lines = [];
|
|
10161
|
+
lines.push(`# ${addon.kind} addon`);
|
|
10162
|
+
lines.push("");
|
|
10163
|
+
lines.push(`**Category:** ${addon.category}`);
|
|
10164
|
+
lines.push(`**Package:** \`${addon.package}\``);
|
|
10165
|
+
if (addon.adapter) lines.push(`**Adapter:** ${addon.adapter}`);
|
|
10166
|
+
lines.push(`**JSON-safe:** ${addon.jsonSafe ? "Yes" : "No (code-only)"}`);
|
|
10167
|
+
lines.push(`**Description:** ${addon.description}`);
|
|
10168
|
+
if (Object.keys(addon.props).length > 0) {
|
|
10169
|
+
lines.push("");
|
|
10170
|
+
lines.push("## Config Props");
|
|
10171
|
+
for (const prop of Object.values(addon.props)) {
|
|
10172
|
+
const req = prop.required ? " **(required)**" : "";
|
|
10173
|
+
lines.push(`- \`${prop.name}\`: ${prop.type}${req} \u2014 ${prop.description}`);
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
lines.push("");
|
|
10177
|
+
lines.push("## Minimal Example");
|
|
10178
|
+
lines.push("```typescript");
|
|
10179
|
+
lines.push(addon.minimalExample);
|
|
10180
|
+
lines.push("```");
|
|
10181
|
+
lines.push("");
|
|
10182
|
+
lines.push("## Full Example");
|
|
10183
|
+
lines.push("```typescript");
|
|
10184
|
+
lines.push(addon.example);
|
|
10185
|
+
lines.push("```");
|
|
10186
|
+
return lines.join("\n");
|
|
10187
|
+
}
|
|
10188
|
+
function formatAddonsOverview() {
|
|
10189
|
+
const kinds = getAddonKinds();
|
|
10190
|
+
const lines = [];
|
|
10191
|
+
lines.push("# Addons");
|
|
10192
|
+
lines.push("");
|
|
10193
|
+
lines.push(
|
|
10194
|
+
"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`)."
|
|
10195
|
+
);
|
|
10196
|
+
lines.push("");
|
|
10197
|
+
lines.push("## Applying an addon");
|
|
10198
|
+
lines.push("");
|
|
10199
|
+
lines.push("```typescript");
|
|
10200
|
+
lines.push(`{
|
|
10201
|
+
key: 'q',
|
|
10202
|
+
type: 'input',
|
|
10203
|
+
label: 'Search',
|
|
10204
|
+
addons: [
|
|
10205
|
+
{ slot: 'prefix', kind: 'mat-icon', icon: 'search', ariaLabel: 'Search' },
|
|
10206
|
+
{ slot: 'suffix', kind: 'mat-button', icon: 'close', ariaLabel: 'Clear', preset: 'clear' },
|
|
10207
|
+
],
|
|
10208
|
+
}`);
|
|
10209
|
+
lines.push("```");
|
|
10210
|
+
lines.push("");
|
|
10211
|
+
lines.push("## Registered Kinds");
|
|
10212
|
+
lines.push("");
|
|
10213
|
+
const core = kinds.filter((k) => k.category === "core");
|
|
10214
|
+
const adapter = kinds.filter((k) => k.category === "adapter");
|
|
10215
|
+
if (core.length > 0) {
|
|
10216
|
+
lines.push("### Core (universal \u2014 ship from `@ng-forge/dynamic-forms`)");
|
|
10217
|
+
for (const k of core) {
|
|
10218
|
+
const json = k.jsonSafe ? "" : " _(code-only)_";
|
|
10219
|
+
lines.push(`- **${k.kind}**${json} \u2014 ${k.description.split(".")[0]}.`);
|
|
10220
|
+
}
|
|
10221
|
+
lines.push("");
|
|
10222
|
+
}
|
|
10223
|
+
if (adapter.length > 0) {
|
|
10224
|
+
lines.push("### Adapter (provided by UI integration packages)");
|
|
10225
|
+
for (const k of adapter) {
|
|
10226
|
+
lines.push(`- **${k.kind}** (${k.adapter}, \`${k.package}\`) \u2014 ${k.description.split(".")[0]}.`);
|
|
10227
|
+
}
|
|
10228
|
+
lines.push("");
|
|
10229
|
+
}
|
|
10230
|
+
lines.push('Use `ngforge_lookup topic="<kind>"` (e.g., `mat-button`, `pi-icon`) for full details on any individual addon kind.');
|
|
10231
|
+
lines.push("");
|
|
10232
|
+
lines.push("## Click variants (mutually exclusive on button-like addons)");
|
|
10233
|
+
lines.push("");
|
|
10234
|
+
lines.push("- `preset`: built-in semantics \u2014 `clear` / `reset` / `paste` / `copy` / `toggle-password-visibility`.");
|
|
10235
|
+
lines.push("- `actionRef`: named handler resolved via `provideAddonActions({ ... })`.");
|
|
10236
|
+
lines.push("- `action`: inline function (code-only, dropped by JSON validator).");
|
|
10237
|
+
lines.push("");
|
|
10238
|
+
lines.push("Setting more than one logs a warning and applies the first (precedence: `preset` > `actionRef` > `action`).");
|
|
10239
|
+
return lines.join("\n");
|
|
10240
|
+
}
|
|
9636
10241
|
async function resolveTopicContent(resolvedTopic, depth, uiIntegration) {
|
|
10242
|
+
if (resolvedTopic === "addons" || resolvedTopic === "addon" || resolvedTopic === "addon-kinds") {
|
|
10243
|
+
if (depth === "brief") {
|
|
10244
|
+
const kinds = getAddonKinds();
|
|
10245
|
+
return [
|
|
10246
|
+
"**Addons** render inside a field slot (prefix / suffix / adapter-specific): icons, buttons, inline text, templates, components.",
|
|
10247
|
+
"",
|
|
10248
|
+
"Registered kinds:",
|
|
10249
|
+
...kinds.map((k) => `- \`${k.kind}\` (${k.category}${k.adapter ? `, ${k.adapter}` : ""})${k.jsonSafe ? "" : " \u2014 code-only"}`),
|
|
10250
|
+
"",
|
|
10251
|
+
'Use `addons: [{ slot: "suffix", kind: "<kind>", ... }]` on an addon-supporting field. See `ngforge_lookup topic="addons"` for full docs.'
|
|
10252
|
+
].join("\n");
|
|
10253
|
+
}
|
|
10254
|
+
return formatAddonsOverview();
|
|
10255
|
+
}
|
|
10256
|
+
const addonInfo = getAddonKind(resolvedTopic);
|
|
10257
|
+
const collidesWithFieldOrWrapper = resolvedTopic === "text" || !!getWrapper(resolvedTopic);
|
|
10258
|
+
if (addonInfo && !collidesWithFieldOrWrapper) {
|
|
10259
|
+
return formatAddonInfo(addonInfo, depth);
|
|
10260
|
+
}
|
|
9637
10261
|
if (resolvedTopic === "wrappers" || resolvedTopic === "wrapper") {
|
|
9638
10262
|
if (depth === "brief") {
|
|
9639
10263
|
const wrappers = getWrappers();
|
|
@@ -9784,6 +10408,8 @@ Containers: group, row, array, simplified-array, page
|
|
|
9784
10408
|
|
|
9785
10409
|
Wrappers: wrappers (overview), css (shipping), section, arraySection (demo-only)
|
|
9786
10410
|
|
|
10411
|
+
Addons: addons (overview), text, template, component (core), mat-icon, mat-button, bs-icon, bs-button, pi-icon, pi-button, ion-icon, ion-button
|
|
10412
|
+
|
|
9787
10413
|
Concepts: validation, conditional, derivation, property-derivation, options-format, expression-variables, async-validators
|
|
9788
10414
|
|
|
9789
10415
|
Patterns: field-placement, logic-matrix, context-api, containers, multi-page-gotchas
|
|
@@ -11264,6 +11890,38 @@ logic: [{
|
|
|
11264
11890
|
}]
|
|
11265
11891
|
\`\`\`
|
|
11266
11892
|
|
|
11893
|
+
## Async Sources (HTTP / async functions)
|
|
11894
|
+
|
|
11895
|
+
Property derivations also support \`source: 'http'\` and \`source: 'asyncFunction'\` for server-driven property values \u2014 typically to populate a select's \`options\` from a backend endpoint.
|
|
11896
|
+
|
|
11897
|
+
\`\`\`typescript
|
|
11898
|
+
// HTTP-driven options
|
|
11899
|
+
logic: [{
|
|
11900
|
+
type: 'derivation',
|
|
11901
|
+
source: 'http',
|
|
11902
|
+
targetProperty: 'options',
|
|
11903
|
+
http: {
|
|
11904
|
+
url: '/api/streets/search',
|
|
11905
|
+
queryParams: { q: 'formValue.street' },
|
|
11906
|
+
},
|
|
11907
|
+
responseExpression: 'response.map(d => ({ value: d.id, label: d.name }))',
|
|
11908
|
+
dependsOn: ['street'], // required for HTTP
|
|
11909
|
+
trigger: 'debounced',
|
|
11910
|
+
debounceMs: 300,
|
|
11911
|
+
}]
|
|
11912
|
+
|
|
11913
|
+
// Async-function-driven options
|
|
11914
|
+
logic: [{
|
|
11915
|
+
type: 'derivation',
|
|
11916
|
+
source: 'asyncFunction',
|
|
11917
|
+
targetProperty: 'options',
|
|
11918
|
+
asyncFunctionName: 'fetchCities',
|
|
11919
|
+
dependsOn: ['country'], // required for asyncFunction
|
|
11920
|
+
}]
|
|
11921
|
+
\`\`\`
|
|
11922
|
+
|
|
11923
|
+
\`dependsOn\` is required (non-empty, no \`'*'\` wildcards) for both HTTP and asyncFunction sources \u2014 the collector throws otherwise to prevent runaway requests.
|
|
11924
|
+
|
|
11267
11925
|
## Array Field Support
|
|
11268
11926
|
|
|
11269
11927
|
Inside arrays, \`formValue\` is scoped to the current array item:
|
|
@@ -13241,6 +13899,18 @@ function buildIndex() {
|
|
|
13241
13899
|
toolCall: `ngforge_lookup topic="${wrapper.type}"`
|
|
13242
13900
|
});
|
|
13243
13901
|
}
|
|
13902
|
+
for (const addon of getAddonKinds()) {
|
|
13903
|
+
const subcategory = addon.adapter ? `Addon (${addon.adapter})` : `Addon (${addon.category})`;
|
|
13904
|
+
entries.push({
|
|
13905
|
+
id: addon.kind,
|
|
13906
|
+
category: "addon",
|
|
13907
|
+
subcategory,
|
|
13908
|
+
description: addon.description,
|
|
13909
|
+
aliases: [],
|
|
13910
|
+
content: `${addon.kind} addon ${addon.description} ${addon.package}`.toLowerCase(),
|
|
13911
|
+
toolCall: `ngforge_lookup topic="${addon.kind}"`
|
|
13912
|
+
});
|
|
13913
|
+
}
|
|
13244
13914
|
searchIndex = entries;
|
|
13245
13915
|
return entries;
|
|
13246
13916
|
}
|
|
@@ -13365,6 +14035,10 @@ export {
|
|
|
13365
14035
|
SERVER_NAME,
|
|
13366
14036
|
SERVER_VERSION,
|
|
13367
14037
|
createServer,
|
|
14038
|
+
getAddonKind,
|
|
14039
|
+
getAddonKinds,
|
|
14040
|
+
getAddonKindsByCategory,
|
|
14041
|
+
getFieldAddonSupport,
|
|
13368
14042
|
getFieldType,
|
|
13369
14043
|
getFieldTypes,
|
|
13370
14044
|
getFieldTypesByCategory,
|