@ng-forge/dynamic-forms-bootstrap 0.5.1 → 0.5.2
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.
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AsyncPipe } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { inject, input, computed, ChangeDetectionStrategy, Component, ElementRef, viewChild, effect, Directive, InjectionToken, afterRenderEffect, linkedSignal, model, isSignal } from '@angular/core';
|
|
4
|
-
import { EventBus, ARRAY_CONTEXT, resolveTokens, DynamicTextPipe, DEFAULT_PROPS, buildBaseInputs
|
|
4
|
+
import { EventBus, ARRAY_CONTEXT, resolveTokens, DynamicTextPipe, DEFAULT_PROPS, buildBaseInputs } from '@ng-forge/dynamic-forms';
|
|
5
5
|
import { FormField } from '@angular/forms/signals';
|
|
6
|
-
import { createResolvedErrorsSignal, shouldShowErrors, setupMetaTracking, isEqual, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, datepickerFieldMapper } from '@ng-forge/dynamic-forms/integration';
|
|
6
|
+
import { createResolvedErrorsSignal, shouldShowErrors, setupMetaTracking, isEqual, valueFieldMapper, optionsFieldMapper, checkboxFieldMapper, submitButtonFieldMapper, nextButtonFieldMapper, previousButtonFieldMapper, addArrayItemButtonMapper, prependArrayItemButtonMapper, insertArrayItemButtonMapper, removeArrayItemButtonMapper, popArrayItemButtonMapper, shiftArrayItemButtonMapper, datepickerFieldMapper } from '@ng-forge/dynamic-forms/integration';
|
|
7
7
|
import { explicitEffect } from 'ngxtension/explicit-effect';
|
|
8
8
|
|
|
9
9
|
class BsButtonFieldComponent {
|
|
@@ -1724,7 +1724,11 @@ const BsField = {
|
|
|
1724
1724
|
Next: 'next',
|
|
1725
1725
|
Previous: 'previous',
|
|
1726
1726
|
AddArrayItem: 'addArrayItem',
|
|
1727
|
+
PrependArrayItem: 'prependArrayItem',
|
|
1728
|
+
InsertArrayItem: 'insertArrayItem',
|
|
1727
1729
|
RemoveArrayItem: 'removeArrayItem',
|
|
1730
|
+
PopArrayItem: 'popArrayItem',
|
|
1731
|
+
ShiftArrayItem: 'shiftArrayItem',
|
|
1728
1732
|
Textarea: 'textarea',
|
|
1729
1733
|
Radio: 'radio',
|
|
1730
1734
|
MultiCheckbox: 'multi-checkbox',
|
|
@@ -1738,11 +1742,15 @@ const BsField = {
|
|
|
1738
1742
|
* For specific button types (submit, next, prev, add/remove array items),
|
|
1739
1743
|
* use the dedicated field types and their specific mappers.
|
|
1740
1744
|
*
|
|
1745
|
+
* Supports template property for array events (AppendArrayItemEvent, PrependArrayItemEvent, InsertArrayItemEvent)
|
|
1746
|
+
* which enables the $template token in eventArgs.
|
|
1747
|
+
*
|
|
1741
1748
|
* @param fieldDef The button field definition
|
|
1742
1749
|
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
1743
1750
|
*/
|
|
1744
1751
|
function buttonFieldMapper(fieldDef) {
|
|
1745
1752
|
const defaultProps = inject(DEFAULT_PROPS);
|
|
1753
|
+
const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
|
|
1746
1754
|
return computed(() => {
|
|
1747
1755
|
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
1748
1756
|
const inputs = {
|
|
@@ -1762,226 +1770,33 @@ function buttonFieldMapper(fieldDef) {
|
|
|
1762
1770
|
if ('eventArgs' in fieldDef && fieldDef.eventArgs !== undefined) {
|
|
1763
1771
|
inputs['eventArgs'] = fieldDef.eventArgs;
|
|
1764
1772
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
*
|
|
1776
|
-
* Disabled state is resolved using the button-logic-resolver which considers:
|
|
1777
|
-
* 1. Explicit `disabled: true` on the field definition
|
|
1778
|
-
* 2. Field-level `logic` array (if present, overrides form-level defaults)
|
|
1779
|
-
* 3. Form-level `options.submitButton` defaults (disableWhenInvalid, disableWhileSubmitting)
|
|
1780
|
-
*
|
|
1781
|
-
* @param fieldDef The submit button field definition
|
|
1782
|
-
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
1783
|
-
*/
|
|
1784
|
-
function submitButtonFieldMapper(fieldDef) {
|
|
1785
|
-
const rootFormRegistry = inject(RootFormRegistryService);
|
|
1786
|
-
const defaultProps = inject(DEFAULT_PROPS);
|
|
1787
|
-
const formOptions = inject(FORM_OPTIONS);
|
|
1788
|
-
const fieldWithLogic = fieldDef;
|
|
1789
|
-
return computed(() => {
|
|
1790
|
-
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
1791
|
-
// Use rootFormRegistry instead of fieldSignalContext.form because when the submit button
|
|
1792
|
-
// is inside a group/array, fieldSignalContext.form points to the nested form tree,
|
|
1793
|
-
// not the root form. We need root form validity for submit button disabled state (#157).
|
|
1794
|
-
const disabledSignal = resolveSubmitButtonDisabled({
|
|
1795
|
-
form: rootFormRegistry.getRootForm(),
|
|
1796
|
-
formOptions: formOptions(),
|
|
1797
|
-
fieldLogic: fieldWithLogic.logic,
|
|
1798
|
-
explicitlyDisabled: fieldDef.disabled,
|
|
1799
|
-
});
|
|
1800
|
-
const inputs = {
|
|
1801
|
-
...baseInputs,
|
|
1802
|
-
// No event - native form submit handles it via form's onNativeSubmit
|
|
1803
|
-
// Set type="submit" to trigger native form submission
|
|
1804
|
-
props: { ...fieldDef.props, type: 'submit' },
|
|
1805
|
-
// Evaluate the signal inside the computed - component receives plain boolean
|
|
1806
|
-
disabled: disabledSignal(),
|
|
1807
|
-
};
|
|
1808
|
-
if (fieldDef.hidden !== undefined) {
|
|
1809
|
-
inputs['hidden'] = fieldDef.hidden;
|
|
1810
|
-
}
|
|
1811
|
-
return inputs;
|
|
1812
|
-
});
|
|
1813
|
-
}
|
|
1814
|
-
/**
|
|
1815
|
-
* Mapper for next page button - preconfigures NextPageEvent
|
|
1816
|
-
*
|
|
1817
|
-
* Disabled state is resolved using the button-logic-resolver which considers:
|
|
1818
|
-
* 1. Explicit `disabled: true` on the field definition
|
|
1819
|
-
* 2. Field-level `logic` array (if present, overrides form-level defaults)
|
|
1820
|
-
* 3. Form-level `options.nextButton` defaults (disableWhenPageInvalid, disableWhileSubmitting)
|
|
1821
|
-
*
|
|
1822
|
-
* @param fieldDef The next button field definition
|
|
1823
|
-
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
1824
|
-
*/
|
|
1825
|
-
function nextButtonFieldMapper(fieldDef) {
|
|
1826
|
-
const fieldSignalContext = inject(FIELD_SIGNAL_CONTEXT);
|
|
1827
|
-
const defaultProps = inject(DEFAULT_PROPS);
|
|
1828
|
-
const formOptions = inject(FORM_OPTIONS);
|
|
1829
|
-
const fieldWithLogic = fieldDef;
|
|
1830
|
-
return computed(() => {
|
|
1831
|
-
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
1832
|
-
const disabledSignal = resolveNextButtonDisabled({
|
|
1833
|
-
form: fieldSignalContext.form,
|
|
1834
|
-
formOptions: formOptions(),
|
|
1835
|
-
fieldLogic: fieldWithLogic.logic,
|
|
1836
|
-
explicitlyDisabled: fieldDef.disabled,
|
|
1837
|
-
currentPageValid: fieldSignalContext.currentPageValid,
|
|
1838
|
-
});
|
|
1839
|
-
const inputs = {
|
|
1840
|
-
...baseInputs,
|
|
1841
|
-
event: NextPageEvent,
|
|
1842
|
-
// Evaluate the signal inside the computed - component receives plain boolean
|
|
1843
|
-
disabled: disabledSignal(),
|
|
1844
|
-
};
|
|
1845
|
-
if (fieldDef.hidden !== undefined) {
|
|
1846
|
-
inputs['hidden'] = fieldDef.hidden;
|
|
1847
|
-
}
|
|
1848
|
-
return inputs;
|
|
1849
|
-
});
|
|
1850
|
-
}
|
|
1851
|
-
/**
|
|
1852
|
-
* Mapper for previous page button - preconfigures PreviousPageEvent
|
|
1853
|
-
* Note: Does not auto-disable based on validation. Users can explicitly disable if needed.
|
|
1854
|
-
*
|
|
1855
|
-
* @param fieldDef The previous button field definition
|
|
1856
|
-
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
1857
|
-
*/
|
|
1858
|
-
function previousButtonFieldMapper(fieldDef) {
|
|
1859
|
-
const defaultProps = inject(DEFAULT_PROPS);
|
|
1860
|
-
return computed(() => {
|
|
1861
|
-
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
1862
|
-
const inputs = {
|
|
1863
|
-
...baseInputs,
|
|
1864
|
-
event: PreviousPageEvent,
|
|
1865
|
-
};
|
|
1866
|
-
// Add disabled binding only if explicitly set by user
|
|
1867
|
-
if (fieldDef.disabled !== undefined) {
|
|
1868
|
-
inputs['disabled'] = fieldDef.disabled;
|
|
1869
|
-
}
|
|
1870
|
-
if (fieldDef.hidden !== undefined) {
|
|
1871
|
-
inputs['hidden'] = fieldDef.hidden;
|
|
1872
|
-
}
|
|
1873
|
-
return inputs;
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
/**
|
|
1877
|
-
* Mapper for add array item button - preconfigures AddArrayItemEvent with array context.
|
|
1878
|
-
*
|
|
1879
|
-
* Supports two modes:
|
|
1880
|
-
* 1. Inside array template: Uses ARRAY_CONTEXT to determine target array
|
|
1881
|
-
* 2. Outside array: Uses `arrayKey` property from field definition
|
|
1882
|
-
*
|
|
1883
|
-
* @param fieldDef The add array item button field definition
|
|
1884
|
-
* @returns Signal containing Record of input names to values for ngComponentOutlet
|
|
1885
|
-
*/
|
|
1886
|
-
function addArrayItemButtonFieldMapper(fieldDef) {
|
|
1887
|
-
const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
|
|
1888
|
-
const logger = inject(DynamicFormLogger);
|
|
1889
|
-
const defaultProps = inject(DEFAULT_PROPS);
|
|
1890
|
-
// Determine the target array key
|
|
1891
|
-
// Priority: explicit arrayKey from fieldDef > arrayKey from context
|
|
1892
|
-
const targetArrayKey = fieldDef.arrayKey ?? arrayContext?.arrayKey;
|
|
1893
|
-
if (!targetArrayKey) {
|
|
1894
|
-
logger.warn(`addArrayItem button "${fieldDef.key}" has no array context. ` +
|
|
1895
|
-
'Either place it inside an array field, or provide an explicit arrayKey property.');
|
|
1896
|
-
}
|
|
1897
|
-
// Set default eventArgs for AddArrayItemEvent (arrayKey)
|
|
1898
|
-
// User can override by providing eventArgs in field definition
|
|
1899
|
-
const defaultEventArgs = ['$arrayKey'];
|
|
1900
|
-
const eventArgs = 'eventArgs' in fieldDef && fieldDef.eventArgs !== undefined ? fieldDef.eventArgs : defaultEventArgs;
|
|
1901
|
-
return computed(() => {
|
|
1902
|
-
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
1903
|
-
// Read signal value if index is a signal (supports differential updates)
|
|
1904
|
-
const getIndex = () => {
|
|
1905
|
-
if (!arrayContext)
|
|
1906
|
-
return -1;
|
|
1907
|
-
return isSignal(arrayContext.index) ? arrayContext.index() : arrayContext.index;
|
|
1908
|
-
};
|
|
1909
|
-
const inputs = {
|
|
1910
|
-
...baseInputs,
|
|
1911
|
-
event: AddArrayItemEvent,
|
|
1912
|
-
eventArgs,
|
|
1913
|
-
eventContext: {
|
|
1773
|
+
// Add eventContext for token resolution (supports $template, $arrayKey, $index, etc.)
|
|
1774
|
+
const template = 'template' in fieldDef ? fieldDef.template : undefined;
|
|
1775
|
+
if (template || arrayContext) {
|
|
1776
|
+
// Read signal value if index is a signal (supports differential updates)
|
|
1777
|
+
const getIndex = () => {
|
|
1778
|
+
if (!arrayContext)
|
|
1779
|
+
return -1;
|
|
1780
|
+
return isSignal(arrayContext.index) ? arrayContext.index() : arrayContext.index;
|
|
1781
|
+
};
|
|
1782
|
+
inputs['eventContext'] = {
|
|
1914
1783
|
key: fieldDef.key,
|
|
1915
1784
|
index: getIndex(),
|
|
1916
|
-
arrayKey:
|
|
1785
|
+
arrayKey: arrayContext?.arrayKey ?? '',
|
|
1917
1786
|
formValue: arrayContext?.formValue ?? {},
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
// Add disabled binding only if explicitly set by user
|
|
1921
|
-
if (fieldDef.disabled !== undefined) {
|
|
1922
|
-
inputs['disabled'] = fieldDef.disabled;
|
|
1923
|
-
}
|
|
1924
|
-
if (fieldDef.hidden !== undefined) {
|
|
1925
|
-
inputs['hidden'] = fieldDef.hidden;
|
|
1787
|
+
template,
|
|
1788
|
+
};
|
|
1926
1789
|
}
|
|
1927
1790
|
return inputs;
|
|
1928
1791
|
});
|
|
1929
1792
|
}
|
|
1793
|
+
|
|
1930
1794
|
/**
|
|
1931
|
-
*
|
|
1932
|
-
*
|
|
1933
|
-
* Supports two modes:
|
|
1934
|
-
* 1. Inside array template: Uses ARRAY_CONTEXT to determine target array and removes item at current index
|
|
1935
|
-
* 2. Outside array: Uses `arrayKey` property from field definition, removes last item by default
|
|
1795
|
+
* Navigation button mappers for Bootstrap - re-exported from integration package.
|
|
1936
1796
|
*
|
|
1937
|
-
*
|
|
1938
|
-
*
|
|
1797
|
+
* These mappers handle submit, next, and previous buttons with proper
|
|
1798
|
+
* disabled state resolution based on form validity and options.
|
|
1939
1799
|
*/
|
|
1940
|
-
function removeArrayItemButtonFieldMapper(fieldDef) {
|
|
1941
|
-
const arrayContext = inject(ARRAY_CONTEXT, { optional: true });
|
|
1942
|
-
const logger = inject(DynamicFormLogger);
|
|
1943
|
-
const defaultProps = inject(DEFAULT_PROPS);
|
|
1944
|
-
// Determine the target array key
|
|
1945
|
-
// Priority: explicit arrayKey from fieldDef > arrayKey from context
|
|
1946
|
-
const targetArrayKey = fieldDef.arrayKey ?? arrayContext?.arrayKey;
|
|
1947
|
-
if (!targetArrayKey) {
|
|
1948
|
-
logger.warn(`removeArrayItem button "${fieldDef.key}" has no array context. ` +
|
|
1949
|
-
'Either place it inside an array field, or provide an explicit arrayKey property.');
|
|
1950
|
-
}
|
|
1951
|
-
// Set default eventArgs for RemoveArrayItemEvent (arrayKey, index if inside array)
|
|
1952
|
-
// When outside array, only pass arrayKey (removes last by default)
|
|
1953
|
-
// User can override by providing eventArgs in field definition
|
|
1954
|
-
const defaultEventArgs = arrayContext ? ['$arrayKey', '$index'] : ['$arrayKey'];
|
|
1955
|
-
const eventArgs = 'eventArgs' in fieldDef && fieldDef.eventArgs !== undefined ? fieldDef.eventArgs : defaultEventArgs;
|
|
1956
|
-
return computed(() => {
|
|
1957
|
-
const baseInputs = buildBaseInputs(fieldDef, defaultProps());
|
|
1958
|
-
// Read signal value if index is a signal (supports differential updates)
|
|
1959
|
-
const getIndex = () => {
|
|
1960
|
-
if (!arrayContext)
|
|
1961
|
-
return -1; // -1 means remove last (no specific index)
|
|
1962
|
-
return isSignal(arrayContext.index) ? arrayContext.index() : arrayContext.index;
|
|
1963
|
-
};
|
|
1964
|
-
const inputs = {
|
|
1965
|
-
...baseInputs,
|
|
1966
|
-
event: RemoveArrayItemEvent,
|
|
1967
|
-
eventArgs,
|
|
1968
|
-
eventContext: {
|
|
1969
|
-
key: fieldDef.key,
|
|
1970
|
-
index: getIndex(),
|
|
1971
|
-
arrayKey: targetArrayKey ?? '',
|
|
1972
|
-
formValue: arrayContext?.formValue ?? {},
|
|
1973
|
-
},
|
|
1974
|
-
};
|
|
1975
|
-
// Add disabled binding only if explicitly set by user
|
|
1976
|
-
if (fieldDef.disabled !== undefined) {
|
|
1977
|
-
inputs['disabled'] = fieldDef.disabled;
|
|
1978
|
-
}
|
|
1979
|
-
if (fieldDef.hidden !== undefined) {
|
|
1980
|
-
inputs['hidden'] = fieldDef.hidden;
|
|
1981
|
-
}
|
|
1982
|
-
return inputs;
|
|
1983
|
-
});
|
|
1984
|
-
}
|
|
1985
1800
|
|
|
1986
1801
|
const BOOTSTRAP_FIELD_TYPES = [
|
|
1987
1802
|
{
|
|
@@ -2027,13 +1842,37 @@ const BOOTSTRAP_FIELD_TYPES = [
|
|
|
2027
1842
|
{
|
|
2028
1843
|
name: BsField.AddArrayItem,
|
|
2029
1844
|
loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
|
|
2030
|
-
mapper:
|
|
1845
|
+
mapper: addArrayItemButtonMapper,
|
|
1846
|
+
valueHandling: 'exclude',
|
|
1847
|
+
},
|
|
1848
|
+
{
|
|
1849
|
+
name: BsField.PrependArrayItem,
|
|
1850
|
+
loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
|
|
1851
|
+
mapper: prependArrayItemButtonMapper,
|
|
1852
|
+
valueHandling: 'exclude',
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
name: BsField.InsertArrayItem,
|
|
1856
|
+
loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
|
|
1857
|
+
mapper: insertArrayItemButtonMapper,
|
|
2031
1858
|
valueHandling: 'exclude',
|
|
2032
1859
|
},
|
|
2033
1860
|
{
|
|
2034
1861
|
name: BsField.RemoveArrayItem,
|
|
2035
1862
|
loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
|
|
2036
|
-
mapper:
|
|
1863
|
+
mapper: removeArrayItemButtonMapper,
|
|
1864
|
+
valueHandling: 'exclude',
|
|
1865
|
+
},
|
|
1866
|
+
{
|
|
1867
|
+
name: BsField.PopArrayItem,
|
|
1868
|
+
loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
|
|
1869
|
+
mapper: popArrayItemButtonMapper,
|
|
1870
|
+
valueHandling: 'exclude',
|
|
1871
|
+
},
|
|
1872
|
+
{
|
|
1873
|
+
name: BsField.ShiftArrayItem,
|
|
1874
|
+
loadComponent: () => Promise.resolve().then(function () { return bsButton_component; }),
|
|
1875
|
+
mapper: shiftArrayItemButtonMapper,
|
|
2037
1876
|
valueHandling: 'exclude',
|
|
2038
1877
|
},
|
|
2039
1878
|
{
|