ngxsmk-datepicker 2.0.2 → 2.0.4

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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **npm i ngxsmk-datepicker**
9
9
 
10
- > **Stable Version**: `2.0.2` is the current stable release. For production use, install the latest version from npm.
10
+ > **Stable Version**: `2.0.3` is the current stable release. For production use, install the latest version from npm.
11
11
  >
12
12
  > ⚠️ **Warning**: Version `1.9.26` contains broken styles. If you are using `1.9.26`, please upgrade to `1.9.28` or downgrade to `1.9.25` immediately.
13
13
 
@@ -127,7 +127,7 @@ For details, see [CONTRIBUTING.md](https://github.com/NGXSMK/ngxsmk-datepicker/b
127
127
 
128
128
  Install the package using npm:
129
129
 
130
- npm install ngxsmk-datepicker
130
+ npm install ngxsmk-datepicker@2.0.3
131
131
 
132
132
  ## **Usage**
133
133
 
package/docs/API.md CHANGED
@@ -2180,7 +2180,7 @@ type DateInput =
2180
2180
  ```
2181
2181
 
2182
2182
 
2183
- ### SignalFormField (v2.0.2+)
2183
+ ### SignalFormField (v2.0.3+)
2184
2184
 
2185
2185
  **Status**: Stable
2186
2186
 
@@ -2190,7 +2190,7 @@ Type representing a signal-based form field.
2190
2190
  type SignalFormField = any; // Compatible with Angular 21 FieldTree
2191
2191
  ```
2192
2192
 
2193
- ### SignalFormFieldConfig (v2.0.2+)
2193
+ ### SignalFormFieldConfig (v2.0.3+)
2194
2194
 
2195
2195
  **Status**: Stable
2196
2196
 
@@ -113,7 +113,7 @@ export class TwoWayComponent {
113
113
  }
114
114
  ```
115
115
 
116
- ### Signal Field Resolution (v2.0.2+)
116
+ ### Signal Field Resolution (v2.0.3+)
117
117
 
118
118
  The datepicker includes a robust resolution mechanism for signal-based fields. It can handle:
119
119
  - **Direct Signals**: A signal that contains the field configuration.
@@ -136,6 +136,15 @@ const config: SignalFormFieldConfig = {
136
136
  };
137
137
  ```
138
138
 
139
+ **TypeScript Compatibility (v2.0.3+):**
140
+
141
+ The datepicker is fully compatible with Angular 21+ `FieldTree<string | Date | null, string>` structure. The types accept:
142
+ - `WritableSignal<Date | null>` for date values
143
+ - `WritableSignal<string | null>` for string date values (automatically converted to Date)
144
+ - Any Angular Signal Forms field configuration
145
+
146
+ String values from Signal Forms are automatically normalized to Date objects internally, ensuring seamless integration with Angular 21+ forms.
147
+
139
148
  ## Dirty State Tracking
140
149
 
141
150
  When using the `[field]` binding, the datepicker automatically tracks the form's dirty state. The form will be marked as dirty when a user selects a date:
@@ -1659,48 +1659,26 @@ function provideDatepickerConfig(config) {
1659
1659
  };
1660
1660
  }
1661
1661
 
1662
- // Safe isSignal detection - works with all Angular 17+ versions including 17.0.0
1663
- // In Angular 17.0.0, isSignal might not be available, so we use function-based detection
1664
- // We avoid importing isSignal directly to prevent build errors in Angular 17.0.0
1665
- // This function provides compatibility across all Angular 17 sub-versions
1666
1662
  function safeIsSignal(value) {
1667
1663
  if (value === null || value === undefined) {
1668
1664
  return false;
1669
1665
  }
1670
- // For Angular 17.1.0+, try to use isSignal if available via dynamic access
1671
- // We check at runtime to avoid build-time import resolution issues
1672
1666
  try {
1673
- // Access Angular core module dynamically to check for isSignal
1674
- // This avoids the @angular/core/primitives/signals resolution issue in Angular 17.0.0
1675
1667
  const ngCore = globalThis.ng?.core;
1676
1668
  if (ngCore?.isSignal && typeof ngCore.isSignal === 'function') {
1677
1669
  return ngCore.isSignal(value);
1678
1670
  }
1679
1671
  }
1680
1672
  catch {
1681
- // isSignal not available - use fallback detection
1682
1673
  }
1683
- // Fallback detection for Angular 17.0.0 and all versions
1684
- // This works by detecting signal-like function characteristics
1685
- // Signals in Angular are functions that can be called without arguments
1686
1674
  if (typeof value === 'function') {
1687
1675
  const fn = value;
1688
- // Signals are typically:
1689
- // 1. Functions without a prototype (or minimal prototype)
1690
- // 2. Callable without arguments
1691
- // 3. Return values when called
1692
- // In Angular 17.0.0, we treat callable functions as potential signals
1693
- // This is safe because we're in a reactive context (effect) where signals can be read
1694
1676
  try {
1695
- // Check if it's callable - if it is, treat it as a potential signal
1696
- // This works for Angular 17.0.0 where isSignal might not be available
1697
1677
  if (fn.length === 0 || fn.length === undefined) {
1698
- // No required arguments - likely a signal getter
1699
1678
  return true;
1700
1679
  }
1701
1680
  }
1702
1681
  catch {
1703
- // Not callable - not a signal
1704
1682
  return false;
1705
1683
  }
1706
1684
  }
@@ -1713,36 +1691,25 @@ class FieldSyncService {
1713
1691
  this._isUpdatingFromInternal = false;
1714
1692
  this.injector = inject(Injector);
1715
1693
  }
1716
- /**
1717
- * Helper function to safely read a field value, handling both functions and signals
1718
- * This handles Angular 21 Signal Forms where field.value can be a signal directly
1719
- * Note: Angular signals (including computed) are detected as 'function' type
1720
- */
1721
1694
  readFieldValue(field) {
1722
1695
  if (!field || typeof field !== 'object' || field.value === undefined) {
1723
1696
  return null;
1724
1697
  }
1725
1698
  try {
1726
1699
  const fieldValue = field.value;
1727
- // For Angular 21 Signal Forms, field.value might be a function that returns the signal
1728
- // Try calling it first if it's a function (this handles FieldTree<Date, string> structure)
1729
1700
  if (typeof fieldValue === 'function') {
1730
1701
  try {
1731
1702
  const result = fieldValue();
1732
- // If result is a signal, call it again to get the actual value
1733
1703
  if (safeIsSignal(result)) {
1734
1704
  const signalResult = result();
1735
1705
  return signalResult !== undefined && signalResult !== null ? signalResult : null;
1736
1706
  }
1737
- // If result is a value (Date, object, etc.), return it
1738
1707
  if (result !== undefined && result !== null) {
1739
1708
  return result;
1740
1709
  }
1741
1710
  return null;
1742
1711
  }
1743
1712
  catch {
1744
- // If calling fails, it might be a signal itself that needs to be called
1745
- // Try checking if it's a signal
1746
1713
  if (safeIsSignal(fieldValue)) {
1747
1714
  try {
1748
1715
  const signalResult = fieldValue();
@@ -1755,17 +1722,14 @@ class FieldSyncService {
1755
1722
  return null;
1756
1723
  }
1757
1724
  }
1758
- // Explicitly check for Angular Signal (compatible with all Angular 17+ versions)
1759
1725
  if (safeIsSignal(fieldValue)) {
1760
1726
  const result = fieldValue();
1761
1727
  return result !== undefined && result !== null ? result : null;
1762
1728
  }
1763
- // If it's an object, it might be a Date, range object, or a signal that isSignal didn't catch
1764
1729
  if (fieldValue !== null && typeof fieldValue === 'object') {
1765
1730
  if (fieldValue instanceof Date) {
1766
1731
  return fieldValue;
1767
1732
  }
1768
- // Try calling it as a last resort (for computed signals that isSignal missed)
1769
1733
  try {
1770
1734
  const result = fieldValue();
1771
1735
  if (result !== undefined && result !== null) {
@@ -1773,7 +1737,6 @@ class FieldSyncService {
1773
1737
  }
1774
1738
  }
1775
1739
  catch {
1776
- // Not callable, treat as value (could be range object {start, end} or array)
1777
1740
  return fieldValue;
1778
1741
  }
1779
1742
  }
@@ -1787,26 +1750,19 @@ class FieldSyncService {
1787
1750
  return null;
1788
1751
  }
1789
1752
  }
1790
- /**
1791
- * Helper function to safely read disabled state
1792
- * Handles both direct values, functions, and signals (Angular 21 Signal Forms)
1793
- */
1794
1753
  readDisabledState(field) {
1795
1754
  if (!field || (typeof field !== 'object' && typeof field !== 'function')) {
1796
1755
  return false;
1797
1756
  }
1798
1757
  try {
1799
- // Check disabled property first
1800
1758
  if ('disabled' in field && field.disabled !== undefined) {
1801
1759
  const disabledVal = field.disabled;
1802
- // Explicitly check for Angular Signal (compatible with all Angular 17+ versions)
1803
1760
  if (safeIsSignal(disabledVal)) {
1804
1761
  return !!disabledVal();
1805
1762
  }
1806
1763
  if (typeof disabledVal === 'function') {
1807
1764
  return !!disabledVal();
1808
1765
  }
1809
- // Handle signals detected as objects
1810
1766
  if (typeof disabledVal === 'object' && disabledVal !== null) {
1811
1767
  try {
1812
1768
  return !!disabledVal();
@@ -1824,10 +1780,6 @@ class FieldSyncService {
1824
1780
  return false;
1825
1781
  }
1826
1782
  }
1827
- /**
1828
- * Helper function to safely read validation errors from Angular Signal Forms
1829
- * Returns the errors array from the field's errors signal
1830
- */
1831
1783
  readFieldErrors(field) {
1832
1784
  if (!field || (typeof field !== 'object' && typeof field !== 'function')) {
1833
1785
  return [];
@@ -1835,7 +1787,6 @@ class FieldSyncService {
1835
1787
  try {
1836
1788
  if ('errors' in field && field.errors !== undefined) {
1837
1789
  const errorsVal = field.errors;
1838
- // Check for Angular Signal
1839
1790
  if (safeIsSignal(errorsVal)) {
1840
1791
  const result = errorsVal();
1841
1792
  return Array.isArray(result) ? result : [];
@@ -1844,7 +1795,6 @@ class FieldSyncService {
1844
1795
  const result = errorsVal();
1845
1796
  return Array.isArray(result) ? result : [];
1846
1797
  }
1847
- // Handle signals detected as objects
1848
1798
  if (typeof errorsVal === 'object' && errorsVal !== null) {
1849
1799
  try {
1850
1800
  const result = errorsVal();
@@ -1862,34 +1812,24 @@ class FieldSyncService {
1862
1812
  return [];
1863
1813
  }
1864
1814
  }
1865
- /**
1866
- * Helper function to safely read required state
1867
- * Handles both direct values, functions, and signals (Angular 21 Signal Forms)
1868
- * Also checks for 'required' validation errors in Angular Signal Forms
1869
- */
1870
1815
  readRequiredState(field) {
1871
1816
  if (!field || (typeof field !== 'object' && typeof field !== 'function')) {
1872
1817
  return false;
1873
1818
  }
1874
1819
  try {
1875
- // First, check for 'required' validation error in Angular Signal Forms
1876
- // This handles schema-based validation like required(p.dateDue)
1877
1820
  const errors = this.readFieldErrors(field);
1878
1821
  const hasRequiredError = errors.some(error => error.kind === 'required');
1879
1822
  if (hasRequiredError) {
1880
1823
  return true;
1881
1824
  }
1882
- // Check required property (for direct required attribute)
1883
1825
  if ('required' in field && field.required !== undefined) {
1884
1826
  const requiredVal = field.required;
1885
- // Explicitly check for Angular Signal (compatible with all Angular 17+ versions)
1886
1827
  if (safeIsSignal(requiredVal)) {
1887
1828
  return !!requiredVal();
1888
1829
  }
1889
1830
  if (typeof requiredVal === 'function') {
1890
1831
  return !!requiredVal();
1891
1832
  }
1892
- // Handle signals detected as objects
1893
1833
  if (typeof requiredVal === 'object' && requiredVal !== null) {
1894
1834
  try {
1895
1835
  return !!requiredVal();
@@ -1906,16 +1846,11 @@ class FieldSyncService {
1906
1846
  return false;
1907
1847
  }
1908
1848
  }
1909
- /**
1910
- * Helper function to determine if the field has validation errors
1911
- * Used to set the error state on the datepicker component
1912
- */
1913
1849
  hasValidationErrors(field) {
1914
1850
  if (!field || (typeof field !== 'object' && typeof field !== 'function')) {
1915
1851
  return false;
1916
1852
  }
1917
1853
  try {
1918
- // Check invalid signal first (Angular Signal Forms)
1919
1854
  if ('invalid' in field && field.invalid !== undefined) {
1920
1855
  const invalidVal = field.invalid;
1921
1856
  if (safeIsSignal(invalidVal)) {
@@ -1942,38 +1877,27 @@ class FieldSyncService {
1942
1877
  return false;
1943
1878
  }
1944
1879
  }
1945
- /**
1946
- * Helper to resolve the actual field object from a potential signal or function
1947
- */
1948
1880
  resolveField(field) {
1949
1881
  if (!field)
1950
1882
  return null;
1951
- // Handle objects directly
1952
1883
  if (typeof field === 'object') {
1953
1884
  return field;
1954
1885
  }
1955
- // Handle functions (Signals or getters)
1956
1886
  if (typeof field === 'function') {
1957
1887
  const fieldFn = field;
1958
- // If the function itself has field properties (like value, disabled, setValue),
1959
- // it's likely a Signal that IS the field (Angular Signal Forms pattern).
1960
- // We check for some common properties to identify it as a field config.
1961
1888
  const hasFieldProps = 'value' in fieldFn || 'disabled' in fieldFn || 'required' in fieldFn ||
1962
1889
  'setValue' in fieldFn || 'markAsDirty' in fieldFn || 'errors' in fieldFn;
1963
1890
  if (hasFieldProps) {
1964
1891
  return fieldFn;
1965
1892
  }
1966
- // If it doesn't have properties, it might be a getter or a Signal returning the field config
1967
1893
  if (safeIsSignal(field)) {
1968
1894
  try {
1969
1895
  const result = field();
1970
- // If the result is an object, that's our field config
1971
1896
  if (result && typeof result === 'object') {
1972
1897
  return result;
1973
1898
  }
1974
1899
  }
1975
1900
  catch {
1976
- // Fall through
1977
1901
  }
1978
1902
  }
1979
1903
  // Generic fallback for getters
@@ -1996,17 +1920,13 @@ class FieldSyncService {
1996
1920
  }
1997
1921
  try {
1998
1922
  const effectRef = runInInjectionContext(this.injector, () => effect(() => {
1999
- // Skip if we're updating from internal to prevent circular updates
2000
1923
  if (this._isUpdatingFromInternal) {
2001
1924
  return;
2002
1925
  }
2003
- // Resolve the field object. If fieldInput is a signal, this line tracks it as a dependency.
2004
1926
  const field = this.resolveField(fieldInput);
2005
1927
  if (!field) {
2006
1928
  return;
2007
1929
  }
2008
- // Read the field value directly in the effect context
2009
- // This ensures all signal dependencies (including computed signals) are properly tracked
2010
1930
  let fieldValue = null;
2011
1931
  try {
2012
1932
  const fieldValueRef = field.value;
@@ -2014,28 +1934,21 @@ class FieldSyncService {
2014
1934
  fieldValue = null;
2015
1935
  }
2016
1936
  else if (typeof fieldValueRef === 'function') {
2017
- // For Angular 21 Signal Forms, field.value is often a function that returns a signal
2018
- // Call it in effect context to track dependencies properly
2019
1937
  try {
2020
1938
  const funcResult = fieldValueRef();
2021
- // Check if the result is a signal (common in Angular 21 Signal Forms)
2022
1939
  if (safeIsSignal(funcResult)) {
2023
- // Read the signal value in effect context to track dependencies
2024
1940
  const signalResult = funcResult();
2025
1941
  fieldValue = (signalResult !== undefined && signalResult !== null) ? signalResult : null;
2026
1942
  }
2027
1943
  else if (safeIsSignal(fieldValueRef)) {
2028
- // If fieldValueRef itself is a signal, read it directly
2029
1944
  const signalResult = fieldValueRef();
2030
1945
  fieldValue = (signalResult !== undefined && signalResult !== null) ? signalResult : null;
2031
1946
  }
2032
1947
  else {
2033
- // Result is a direct value
2034
1948
  fieldValue = (funcResult !== undefined && funcResult !== null) ? funcResult : null;
2035
1949
  }
2036
1950
  }
2037
1951
  catch {
2038
- // If calling fails, try checking if it's a signal itself
2039
1952
  if (safeIsSignal(fieldValueRef)) {
2040
1953
  try {
2041
1954
  const signalResult = fieldValueRef();
@@ -2051,46 +1964,29 @@ class FieldSyncService {
2051
1964
  }
2052
1965
  }
2053
1966
  else if (safeIsSignal(fieldValueRef)) {
2054
- // For signals (including computed), read directly in effect context
2055
- // This ensures computed signals track their underlying dependencies
2056
- // Reading the signal here automatically tracks all its dependencies
2057
1967
  const signalResult = fieldValueRef();
2058
1968
  fieldValue = (signalResult !== undefined && signalResult !== null) ? signalResult : null;
2059
1969
  }
2060
1970
  else if (fieldValueRef instanceof Date) {
2061
- // Direct Date value
2062
1971
  fieldValue = fieldValueRef;
2063
1972
  }
2064
1973
  else if (typeof fieldValueRef === 'object') {
2065
- // Could be a range object or array
2066
1974
  fieldValue = fieldValueRef;
2067
1975
  }
2068
1976
  else {
2069
- // Fallback to helper
2070
1977
  fieldValue = this.readFieldValue(field);
2071
1978
  }
2072
1979
  }
2073
1980
  catch {
2074
- // Fallback to helper on any error
2075
1981
  fieldValue = this.readFieldValue(field);
2076
1982
  }
2077
- // Normalize the value using the same normalization function as the component
2078
- // This ensures consistent value comparison
2079
1983
  const normalizedValue = callbacks.normalizeValue(fieldValue);
2080
- // Determine if we should update
2081
- // Always update on initial load (even if null) to ensure component is initialized
2082
1984
  const isInitialLoad = this._lastKnownFieldValue === undefined;
2083
- // Use isValueEqual to compare values - this handles date normalization and equality correctly
2084
- // This prevents overwriting values that were just set internally, even if effect runs after flag reset
2085
1985
  const valuesAreEqual = this._lastKnownFieldValue !== undefined &&
2086
1986
  callbacks.isValueEqual(normalizedValue, this._lastKnownFieldValue);
2087
1987
  const valueChanged = !isInitialLoad && !valuesAreEqual;
2088
1988
  const isValueTransition = (this._lastKnownFieldValue === null || this._lastKnownFieldValue === undefined) &&
2089
1989
  fieldValue !== null && fieldValue !== undefined;
2090
- // Always update on initial load (even if null), value change, or value transition
2091
- // BUT skip if values are equal (prevents overwriting values set internally)
2092
- // This ensures the component value is always set, including null values
2093
- // but prevents circular updates when we just set the value internally
2094
1990
  if ((isInitialLoad || valueChanged || isValueTransition) && !valuesAreEqual) {
2095
1991
  this._lastKnownFieldValue = normalizedValue;
2096
1992
  callbacks.onValueChanged(normalizedValue);
@@ -2098,25 +1994,16 @@ class FieldSyncService {
2098
1994
  callbacks.onStateChanged?.();
2099
1995
  }
2100
1996
  else if (!valuesAreEqual && this._lastKnownFieldValue !== normalizedValue) {
2101
- // Update last known value even if we don't trigger callbacks
2102
- // This handles edge cases where values are equal but references differ
2103
- // But only if values are not equal according to isValueEqual
2104
1997
  this._lastKnownFieldValue = normalizedValue;
2105
1998
  }
2106
- // Always update disabled state
2107
1999
  const disabled = this.readDisabledState(field);
2108
2000
  callbacks.onDisabledChanged(disabled);
2109
- // Always update required state
2110
2001
  const required = this.readRequiredState(field);
2111
2002
  callbacks.onRequiredChanged?.(required);
2112
- // Always update error state (for Angular Signal Forms validation)
2113
2003
  const hasError = this.hasValidationErrors(field);
2114
2004
  callbacks.onErrorStateChanged?.(hasError);
2115
2005
  }));
2116
2006
  this._fieldEffectRef = effectRef;
2117
- // Effects run immediately when created, so the effect has already run
2118
- // and read the signal value in a reactive context. This ensures computed
2119
- // signals are properly tracked and all dependencies are registered.
2120
2007
  return effectRef;
2121
2008
  }
2122
2009
  catch (error) {
@@ -2132,13 +2019,10 @@ class FieldSyncService {
2132
2019
  return false;
2133
2020
  const fieldValue = this.readFieldValue(field);
2134
2021
  const normalizedValue = callbacks.normalizeValue(fieldValue);
2135
- // Check if value has changed or if this is initial load
2136
2022
  const hasValueChanged = !callbacks.isValueEqual(normalizedValue, this._lastKnownFieldValue);
2137
2023
  const isInitialLoad = this._lastKnownFieldValue === undefined;
2138
2024
  const isValueTransition = (this._lastKnownFieldValue === null || this._lastKnownFieldValue === undefined) &&
2139
2025
  fieldValue !== null && fieldValue !== undefined;
2140
- // Always sync on initial load (even if null), value change, or value transition
2141
- // This ensures the component value is set correctly, including null values
2142
2026
  if (isInitialLoad || hasValueChanged || isValueTransition) {
2143
2027
  this._lastKnownFieldValue = normalizedValue;
2144
2028
  callbacks.onValueChanged(normalizedValue);
@@ -2152,7 +2036,6 @@ class FieldSyncService {
2152
2036
  callbacks.onErrorStateChanged?.(hasError);
2153
2037
  return true;
2154
2038
  }
2155
- // Update last known value even if we don't trigger callbacks
2156
2039
  if (this._lastKnownFieldValue !== normalizedValue) {
2157
2040
  this._lastKnownFieldValue = normalizedValue;
2158
2041
  }
@@ -2169,61 +2052,43 @@ class FieldSyncService {
2169
2052
  if (!field || typeof field !== 'object') {
2170
2053
  return;
2171
2054
  }
2172
- // Set flag to prevent effect from processing this update
2173
- // This prevents circular updates when we update the field from component
2174
2055
  this._isUpdatingFromInternal = true;
2175
2056
  try {
2176
- // The value passed in should already be normalized by the component
2177
- // Store it as the last known value BEFORE updating the field
2178
- // This ensures the effect sees a match if it runs, preventing overwrites
2179
2057
  const normalizedValue = value;
2180
2058
  this._lastKnownFieldValue = normalizedValue;
2181
- // Try setValue first (preferred method for Angular 21 Signal Forms)
2182
- // This works with computed signal patterns where setValue updates the underlying signal
2183
2059
  if (typeof field.setValue === 'function') {
2184
2060
  try {
2185
2061
  field.setValue(normalizedValue);
2186
2062
  if (typeof field.markAsDirty === 'function') {
2187
2063
  field.markAsDirty();
2188
2064
  }
2189
- // Use microtask delay to ensure effect has time to see the flag
2190
- // This prevents race condition where effect runs after flag is reset
2191
2065
  Promise.resolve().then(() => {
2192
2066
  this._isUpdatingFromInternal = false;
2193
2067
  });
2194
2068
  return;
2195
2069
  }
2196
2070
  catch {
2197
- // If setValue fails, try updateValue as fallback
2198
- // Don't return here, continue to try updateValue
2199
2071
  }
2200
2072
  }
2201
- // Try updateValue as alternative method
2202
2073
  if (typeof field.updateValue === 'function') {
2203
2074
  try {
2204
2075
  field.updateValue(() => normalizedValue);
2205
2076
  if (typeof field.markAsDirty === 'function') {
2206
2077
  field.markAsDirty();
2207
2078
  }
2208
- // Use microtask delay to ensure effect has time to see the flag
2209
2079
  Promise.resolve().then(() => {
2210
2080
  this._isUpdatingFromInternal = false;
2211
2081
  });
2212
2082
  return;
2213
2083
  }
2214
2084
  catch {
2215
- // If updateValue also fails, continue to fallback
2216
2085
  }
2217
2086
  }
2218
- // Fallback: try to update the underlying signal directly if field.value is a signal
2219
- // This handles cases where field.value is a writable signal or a function returning a signal
2220
2087
  try {
2221
2088
  const val = field.value;
2222
- // For Angular 21 Signal Forms, field.value might be a function that returns the signal
2223
2089
  if (typeof val === 'function') {
2224
2090
  try {
2225
2091
  const signalOrValue = val();
2226
- // If it's a signal, try to update it
2227
2092
  if (safeIsSignal(signalOrValue)) {
2228
2093
  const writableSignal = signalOrValue;
2229
2094
  if (typeof writableSignal.set === 'function') {
@@ -2231,7 +2096,6 @@ class FieldSyncService {
2231
2096
  if (typeof field.markAsDirty === 'function') {
2232
2097
  field.markAsDirty();
2233
2098
  }
2234
- // Use microtask delay to ensure effect has time to see the flag
2235
2099
  Promise.resolve().then(() => {
2236
2100
  this._isUpdatingFromInternal = false;
2237
2101
  });
@@ -2240,11 +2104,8 @@ class FieldSyncService {
2240
2104
  }
2241
2105
  }
2242
2106
  catch {
2243
- // If calling fails, continue to check if val itself is a signal
2244
2107
  }
2245
2108
  }
2246
- // Check if it's a writable signal (has .set method)
2247
- // Compatible with all Angular 17+ versions
2248
2109
  if (safeIsSignal(val)) {
2249
2110
  const writableSignal = val;
2250
2111
  if (typeof writableSignal.set === 'function') {
@@ -2252,7 +2113,6 @@ class FieldSyncService {
2252
2113
  if (typeof field.markAsDirty === 'function') {
2253
2114
  field.markAsDirty();
2254
2115
  }
2255
- // Use microtask delay to ensure effect has time to see the flag
2256
2116
  Promise.resolve().then(() => {
2257
2117
  this._isUpdatingFromInternal = false;
2258
2118
  });
@@ -2261,15 +2121,10 @@ class FieldSyncService {
2261
2121
  }
2262
2122
  }
2263
2123
  catch {
2264
- // Silently handle - field might not support direct signal updates
2265
- // This is expected for computed signals which are read-only
2266
2124
  }
2267
- // If no update method worked, reset flag immediately
2268
2125
  this._isUpdatingFromInternal = false;
2269
2126
  }
2270
2127
  catch (error) {
2271
- // Silently handle errors to prevent breaking the application
2272
- // The error might be due to readonly signals or other constraints
2273
2128
  if (isDevMode()) {
2274
2129
  console.warn('[ngxsmk-datepicker] Field sync error:', error);
2275
2130
  }
@@ -2279,6 +2134,20 @@ class FieldSyncService {
2279
2134
  getLastKnownValue() {
2280
2135
  return this._lastKnownFieldValue;
2281
2136
  }
2137
+ markAsTouched(fieldInput) {
2138
+ const field = this.resolveField(fieldInput);
2139
+ if (!field || typeof field !== 'object') {
2140
+ return;
2141
+ }
2142
+ try {
2143
+ if (typeof field.markAsTouched === 'function') {
2144
+ field.markAsTouched();
2145
+ }
2146
+ }
2147
+ catch {
2148
+ // Ignore errors when marking as touched
2149
+ }
2150
+ }
2282
2151
  cleanup() {
2283
2152
  if (this._fieldEffectRef) {
2284
2153
  this._fieldEffectRef.destroy();
@@ -5618,6 +5487,9 @@ class NgxsmkDatepickerComponent {
5618
5487
  this.valueChange.emit(normalizedVal);
5619
5488
  this.onChange(normalizedVal);
5620
5489
  this.onTouched();
5490
+ if (this._field) {
5491
+ this.fieldSyncService.markAsTouched(this._field);
5492
+ }
5621
5493
  if (!this.isInlineMode && val !== null && !this.timeOnly) {
5622
5494
  if (this.mode === 'single' || (this.mode === 'range' && this.startDate && this.endDate)) {
5623
5495
  this.isCalendarOpen = false;
@@ -6886,6 +6758,9 @@ class NgxsmkDatepickerComponent {
6886
6758
  this._focused = false;
6887
6759
  this.stateChanges.next();
6888
6760
  this.onTouched();
6761
+ if (this._field) {
6762
+ this.fieldSyncService.markAsTouched(this._field);
6763
+ }
6889
6764
  }
6890
6765
  if (!this.allowTyping)
6891
6766
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngxsmk-datepicker",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "author": {
5
5
  "name": "Sachin Dilshan",
6
6
  "url": "https://www.linkedin.com/in/sachindilshan/"
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { AfterViewInit, OnDestroy, EventEmitter, ElementRef, Signal, EffectRef, OnInit, OnChanges, TemplateRef, SimpleChanges, InjectionToken, Provider } from '@angular/core';
2
+ import { AfterViewInit, OnDestroy, EventEmitter, ElementRef, EffectRef, Signal, OnInit, OnChanges, TemplateRef, SimpleChanges, InjectionToken, Provider } from '@angular/core';
3
3
  import { ControlValueAccessor } from '@angular/forms';
4
4
  import { Subject } from 'rxjs';
5
5
 
@@ -160,9 +160,9 @@ interface ValidationError {
160
160
  message?: string;
161
161
  }
162
162
  type SignalFormFieldConfig = {
163
- value?: DatepickerValue | (() => DatepickerValue) | {
164
- (): DatepickerValue;
165
- } | Signal<DatepickerValue>;
163
+ value?: DatepickerValue | string | (() => DatepickerValue | string) | {
164
+ (): DatepickerValue | string;
165
+ } | Signal<DatepickerValue | string>;
166
166
  disabled?: boolean | (() => boolean) | {
167
167
  (): boolean;
168
168
  } | Signal<boolean>;
@@ -181,13 +181,12 @@ type SignalFormFieldConfig = {
181
181
  touched?: boolean | (() => boolean) | {
182
182
  (): boolean;
183
183
  } | Signal<boolean>;
184
- setValue?: (value: DatepickerValue) => void;
185
- updateValue?: (updater: () => DatepickerValue) => void;
184
+ setValue?: (value: DatepickerValue | string) => void;
185
+ updateValue?: (updater: () => DatepickerValue | string) => void;
186
186
  markAsDirty?: () => void;
187
- } & {
188
- [key: string]: unknown;
187
+ markAsTouched?: () => void;
189
188
  };
190
- type SignalFormField = SignalFormFieldConfig | Signal<SignalFormFieldConfig> | (() => SignalFormFieldConfig) | null | undefined;
189
+ type SignalFormField = any;
191
190
  interface FieldSyncCallbacks {
192
191
  onValueChanged: (value: DatepickerValue) => void;
193
192
  onDisabledChanged: (disabled: boolean) => void;
@@ -204,41 +203,17 @@ declare class FieldSyncService {
204
203
  private _lastKnownFieldValue;
205
204
  private _isUpdatingFromInternal;
206
205
  private readonly injector;
207
- /**
208
- * Helper function to safely read a field value, handling both functions and signals
209
- * This handles Angular 21 Signal Forms where field.value can be a signal directly
210
- * Note: Angular signals (including computed) are detected as 'function' type
211
- */
212
206
  private readFieldValue;
213
- /**
214
- * Helper function to safely read disabled state
215
- * Handles both direct values, functions, and signals (Angular 21 Signal Forms)
216
- */
217
207
  private readDisabledState;
218
- /**
219
- * Helper function to safely read validation errors from Angular Signal Forms
220
- * Returns the errors array from the field's errors signal
221
- */
222
208
  private readFieldErrors;
223
- /**
224
- * Helper function to safely read required state
225
- * Handles both direct values, functions, and signals (Angular 21 Signal Forms)
226
- * Also checks for 'required' validation errors in Angular Signal Forms
227
- */
228
209
  private readRequiredState;
229
- /**
230
- * Helper function to determine if the field has validation errors
231
- * Used to set the error state on the datepicker component
232
- */
233
210
  private hasValidationErrors;
234
- /**
235
- * Helper to resolve the actual field object from a potential signal or function
236
- */
237
211
  private resolveField;
238
212
  setupFieldSync(fieldInput: SignalFormField, callbacks: FieldSyncCallbacks): EffectRef | null;
239
213
  syncFieldValue(fieldInput: SignalFormField | Signal<SignalFormField> | (() => unknown) | unknown, callbacks: FieldSyncCallbacks): boolean;
240
214
  updateFieldFromInternal(value: DatepickerValue, fieldInput: SignalFormField | Signal<SignalFormField> | (() => unknown) | unknown): void;
241
215
  getLastKnownValue(): DatepickerValue | undefined;
216
+ markAsTouched(fieldInput: SignalFormField | Signal<SignalFormField> | (() => unknown) | unknown): void;
242
217
  cleanup(): void;
243
218
  static ɵfac: i0.ɵɵFactoryDeclaration<FieldSyncService, never>;
244
219
  static ɵprov: i0.ɵɵInjectableDeclaration<FieldSyncService>;