snice 2.2.3 → 2.3.0

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.
Files changed (111) hide show
  1. package/README.md +17 -10
  2. package/dist/components/accordion/snice-accordion-item.js +3 -3
  3. package/dist/components/accordion/snice-accordion-item.js.map +1 -1
  4. package/dist/components/accordion/snice-accordion.js +1 -1
  5. package/dist/components/accordion/snice-accordion.js.map +1 -1
  6. package/dist/components/alert/snice-alert.js +5 -5
  7. package/dist/components/alert/snice-alert.js.map +1 -1
  8. package/dist/components/avatar/snice-avatar.js +7 -7
  9. package/dist/components/avatar/snice-avatar.js.map +1 -1
  10. package/dist/components/badge/snice-badge.js +10 -10
  11. package/dist/components/badge/snice-badge.js.map +1 -1
  12. package/dist/components/breadcrumbs/snice-breadcrumbs.js +3 -3
  13. package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -1
  14. package/dist/components/breadcrumbs/snice-crumb.js +5 -5
  15. package/dist/components/breadcrumbs/snice-crumb.js.map +1 -1
  16. package/dist/components/button/snice-button.js +12 -12
  17. package/dist/components/button/snice-button.js.map +1 -1
  18. package/dist/components/card/snice-card.js +5 -5
  19. package/dist/components/card/snice-card.js.map +1 -1
  20. package/dist/components/checkbox/snice-checkbox.js +9 -9
  21. package/dist/components/checkbox/snice-checkbox.js.map +1 -1
  22. package/dist/components/chip/snice-chip.js +8 -8
  23. package/dist/components/chip/snice-chip.js.map +1 -1
  24. package/dist/components/date-picker/snice-date-picker.js +18 -18
  25. package/dist/components/date-picker/snice-date-picker.js.map +1 -1
  26. package/dist/components/divider/snice-divider.js +8 -8
  27. package/dist/components/divider/snice-divider.js.map +1 -1
  28. package/dist/components/drawer/snice-drawer.js +9 -9
  29. package/dist/components/drawer/snice-drawer.js.map +1 -1
  30. package/dist/components/input/snice-input.js +24 -24
  31. package/dist/components/input/snice-input.js.map +1 -1
  32. package/dist/components/layout/snice-layout-card.js +2 -2
  33. package/dist/components/layout/snice-layout-card.js.map +1 -1
  34. package/dist/components/layout/snice-layout-centered.js +1 -1
  35. package/dist/components/layout/snice-layout-centered.js.map +1 -1
  36. package/dist/components/layout/snice-layout-fullscreen.js +1 -1
  37. package/dist/components/layout/snice-layout-fullscreen.js.map +1 -1
  38. package/dist/components/layout/snice-layout-sidebar.js +1 -1
  39. package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
  40. package/dist/components/layout/snice-layout-split.js +2 -2
  41. package/dist/components/layout/snice-layout-split.js.map +1 -1
  42. package/dist/components/login/snice-login.js +8 -8
  43. package/dist/components/login/snice-login.js.map +1 -1
  44. package/dist/components/modal/snice-modal.js +7 -7
  45. package/dist/components/modal/snice-modal.js.map +1 -1
  46. package/dist/components/pagination/snice-pagination.js +9 -9
  47. package/dist/components/pagination/snice-pagination.js.map +1 -1
  48. package/dist/components/progress/snice-progress.js +11 -11
  49. package/dist/components/progress/snice-progress.js.map +1 -1
  50. package/dist/components/radio/snice-radio.js +8 -8
  51. package/dist/components/radio/snice-radio.js.map +1 -1
  52. package/dist/components/select/snice-option.js +5 -5
  53. package/dist/components/select/snice-option.js.map +1 -1
  54. package/dist/components/select/snice-select.js +14 -14
  55. package/dist/components/select/snice-select.js.map +1 -1
  56. package/dist/components/skeleton/snice-skeleton.js +6 -6
  57. package/dist/components/skeleton/snice-skeleton.js.map +1 -1
  58. package/dist/components/switch/snice-switch.js +10 -10
  59. package/dist/components/switch/snice-switch.js.map +1 -1
  60. package/dist/components/table/snice-cell-boolean.js +8 -8
  61. package/dist/components/table/snice-cell-boolean.js.map +1 -1
  62. package/dist/components/table/snice-cell-date.js +8 -8
  63. package/dist/components/table/snice-cell-date.js.map +1 -1
  64. package/dist/components/table/snice-cell-duration.js +3 -3
  65. package/dist/components/table/snice-cell-duration.js.map +1 -1
  66. package/dist/components/table/snice-cell-filesize.js +3 -3
  67. package/dist/components/table/snice-cell-filesize.js.map +1 -1
  68. package/dist/components/table/snice-cell-number.js +9 -9
  69. package/dist/components/table/snice-cell-number.js.map +1 -1
  70. package/dist/components/table/snice-cell-progress.js +3 -3
  71. package/dist/components/table/snice-cell-progress.js.map +1 -1
  72. package/dist/components/table/snice-cell-rating.js +3 -3
  73. package/dist/components/table/snice-cell-rating.js.map +1 -1
  74. package/dist/components/table/snice-cell-sparkline.js +13 -13
  75. package/dist/components/table/snice-cell-sparkline.js.map +1 -1
  76. package/dist/components/table/snice-cell-text.js +4 -4
  77. package/dist/components/table/snice-cell-text.js.map +1 -1
  78. package/dist/components/table/snice-cell.js +2 -2
  79. package/dist/components/table/snice-cell.js.map +1 -1
  80. package/dist/components/table/snice-column.js +10 -10
  81. package/dist/components/table/snice-column.js.map +1 -1
  82. package/dist/components/table/snice-header.js +5 -5
  83. package/dist/components/table/snice-header.js.map +1 -1
  84. package/dist/components/table/snice-row.js +5 -5
  85. package/dist/components/table/snice-row.js.map +1 -1
  86. package/dist/components/table/snice-table.js +7 -7
  87. package/dist/components/table/snice-table.js.map +1 -1
  88. package/dist/components/tabs/snice-tab-panel.js +5 -5
  89. package/dist/components/tabs/snice-tab-panel.js.map +1 -1
  90. package/dist/components/tabs/snice-tab.js +2 -2
  91. package/dist/components/tabs/snice-tab.js.map +1 -1
  92. package/dist/components/tabs/snice-tabs.js +4 -4
  93. package/dist/components/tabs/snice-tabs.js.map +1 -1
  94. package/dist/components/toast/snice-toast-container.js +1 -1
  95. package/dist/components/toast/snice-toast-container.js.map +1 -1
  96. package/dist/components/toast/snice-toast.js +4 -4
  97. package/dist/components/toast/snice-toast.js.map +1 -1
  98. package/dist/components/tooltip/snice-tooltip.js +11 -11
  99. package/dist/components/tooltip/snice-tooltip.js.map +1 -1
  100. package/dist/index.cjs +119 -100
  101. package/dist/index.cjs.map +1 -1
  102. package/dist/index.esm.js +119 -100
  103. package/dist/index.esm.js.map +1 -1
  104. package/dist/index.iife.js +119 -100
  105. package/dist/index.iife.js.map +1 -1
  106. package/dist/symbols.esm.js +1 -1
  107. package/dist/transitions.esm.js +1 -1
  108. package/dist/types/testing.d.ts +4 -0
  109. package/dist/types/types/PropertyOptions.d.ts +0 -1
  110. package/dist/types/utils.d.ts +8 -0
  111. package/package.json +5 -1
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v2.2.2
2
+ * snice v2.2.3
3
3
  * Imperative TypeScript framework for building vanilla web components with decorators, routing, and controllers. No virtual DOM, no build complexity.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1340,7 +1340,18 @@ function parseAttributeValue(attributeValue, propertyOptions, currentValue, init
1340
1340
  const typeToUse = propertyOptions.type || detectType(initialValue);
1341
1341
  switch (typeToUse) {
1342
1342
  case Boolean:
1343
- return attributeValue !== null && attributeValue !== 'false';
1343
+ // Boolean semantics per user specification:
1344
+ // - No attribute = false
1345
+ // - String "false" = false
1346
+ // - String "true" = true
1347
+ // - Empty string = true (attribute exists like <element bool-attr>)
1348
+ // - Any other value = true
1349
+ if (attributeValue === null)
1350
+ return false; // No attribute
1351
+ if (attributeValue === 'false')
1352
+ return false; // String "false"
1353
+ // All other cases (including empty string) = true (attribute exists)
1354
+ return true;
1344
1355
  case Number:
1345
1356
  return attributeValue !== null ? Number(attributeValue) : null;
1346
1357
  case String:
@@ -1356,6 +1367,20 @@ function parseAttributeValue(attributeValue, propertyOptions, currentValue, init
1356
1367
  }
1357
1368
  case SimpleArray:
1358
1369
  return SimpleArray.parse(attributeValue);
1370
+ case Array:
1371
+ try {
1372
+ return attributeValue ? JSON.parse(attributeValue) : [];
1373
+ }
1374
+ catch {
1375
+ return [];
1376
+ }
1377
+ case Object:
1378
+ try {
1379
+ return attributeValue ? JSON.parse(attributeValue) : {};
1380
+ }
1381
+ catch {
1382
+ return {};
1383
+ }
1359
1384
  default:
1360
1385
  // If no type specified and can't detect, try to infer from current value type
1361
1386
  if (typeof currentValue === 'number' && attributeValue !== null) {
@@ -1366,6 +1391,40 @@ function parseAttributeValue(attributeValue, propertyOptions, currentValue, init
1366
1391
  }
1367
1392
  }
1368
1393
  }
1394
+ /**
1395
+ * Converts a property value to its attribute string representation
1396
+ * @param value - The property value to convert
1397
+ * @param propertyOptions - The options from @property decorator
1398
+ * @param initialValue - The default value assigned to the property field (used for type detection)
1399
+ * @returns The string representation for the HTML attribute, or null if value should remove attribute
1400
+ */
1401
+ function valueToAttribute(value, propertyOptions, initialValue) {
1402
+ // Handle null/undefined/false values and empty arrays
1403
+ if (value === null || value === undefined || value === false ||
1404
+ (propertyOptions?.type === SimpleArray && Array.isArray(value) && value.length === 0)) {
1405
+ return null;
1406
+ }
1407
+ // Use custom converter if provided
1408
+ if (propertyOptions.converter?.toAttribute) {
1409
+ return propertyOptions.converter.toAttribute(value, propertyOptions.type);
1410
+ }
1411
+ // Use explicit type or detect from initial value
1412
+ const typeToUse = propertyOptions.type || detectType(initialValue);
1413
+ switch (typeToUse) {
1414
+ case Date:
1415
+ return value instanceof Date ? value.toISOString() : String(value);
1416
+ case BigInt:
1417
+ return typeof value === 'bigint' ? value.toString() + 'n' : String(value);
1418
+ case SimpleArray:
1419
+ return Array.isArray(value) ? SimpleArray.serialize(value) : String(value);
1420
+ case Array:
1421
+ return Array.isArray(value) ? JSON.stringify(value) : String(value);
1422
+ case Object:
1423
+ return typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value);
1424
+ default:
1425
+ return String(value);
1426
+ }
1427
+ }
1369
1428
 
1370
1429
  /**
1371
1430
  * Applies core element functionality to a constructor
@@ -1464,36 +1523,8 @@ function applyElementFunctionality(constructor) {
1464
1523
  }
1465
1524
  // Mark that properties have been initialized
1466
1525
  this[PROPERTIES_INITIALIZED] = true;
1467
- // Reflect properties that were explicitly set before connection
1468
- // AND also reflect initial values that have reflect: true
1469
- if (properties) {
1470
- for (const [propName, propOptions] of properties) {
1471
- const wasExplicitlySet = this[EXPLICITLY_SET_PROPERTIES] && this[EXPLICITLY_SET_PROPERTIES].has(propName);
1472
- const hasInitialValue = propName in this[PROPERTY_VALUES];
1473
- if (propOptions.reflect && hasInitialValue && (wasExplicitlySet || this[PROPERTY_VALUES][propName] !== undefined)) {
1474
- const value = this[PROPERTY_VALUES][propName];
1475
- const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
1476
- if (value !== null && value !== undefined && value !== false &&
1477
- !(propOptions.type === SimpleArray && Array.isArray(value) && value.length === 0)) {
1478
- // Handle special types for reflection
1479
- let attributeValue;
1480
- if (value instanceof Date) {
1481
- attributeValue = value.toISOString();
1482
- }
1483
- else if (typeof value === 'bigint') {
1484
- attributeValue = value.toString() + 'n';
1485
- }
1486
- else if (propOptions.type === SimpleArray && Array.isArray(value)) {
1487
- attributeValue = SimpleArray.serialize(value);
1488
- }
1489
- else {
1490
- attributeValue = String(value);
1491
- }
1492
- this.setAttribute(attributeName, attributeValue);
1493
- }
1494
- }
1495
- }
1496
- }
1526
+ // Properties are now stateless and read from DOM attributes only
1527
+ // Initial values are not automatically reflected
1497
1528
  // Clean up any existing event handlers first (for reconnection)
1498
1529
  cleanupEventHandlers(this);
1499
1530
  // Create shadow root if it doesn't exist
@@ -1650,30 +1681,34 @@ function applyElementFunctionality(constructor) {
1650
1681
  this[PROPERTY_VALUES] = {};
1651
1682
  }
1652
1683
  this[PROPERTY_VALUES][propName] = parsedValue;
1653
- // Call watchers manually since we bypassed the setter
1654
- const watchers = constructor[PROPERTY_WATCHERS];
1655
- if (watchers) {
1656
- // Call specific property watchers
1657
- if (watchers.has(propName)) {
1658
- const propertyWatchers = watchers.get(propName);
1659
- for (const watcher of propertyWatchers) {
1660
- try {
1661
- watcher.method.call(this, currentValue, parsedValue, propName);
1662
- }
1663
- catch (error) {
1664
- console.error(`Error in @watch('${propName}') method ${watcher.methodName}:`, error);
1684
+ // Only call watchers if this attribute change didn't originate from a property setter
1685
+ const isFromPropertySetter = this._settingFromProperty?.has(name.toLowerCase());
1686
+ if (!isFromPropertySetter) {
1687
+ // Call watchers manually since we bypassed the setter
1688
+ const watchers = constructor[PROPERTY_WATCHERS];
1689
+ if (watchers) {
1690
+ // Call specific property watchers
1691
+ if (watchers.has(propName)) {
1692
+ const propertyWatchers = watchers.get(propName);
1693
+ for (const watcher of propertyWatchers) {
1694
+ try {
1695
+ watcher.method.call(this, currentValue, parsedValue, propName);
1696
+ }
1697
+ catch (error) {
1698
+ console.error(`Error in @watch('${propName}') method ${watcher.methodName}:`, error);
1699
+ }
1665
1700
  }
1666
1701
  }
1667
- }
1668
- // Call wildcard watchers (watching "*")
1669
- if (watchers.has('*')) {
1670
- const wildcardWatchers = watchers.get('*');
1671
- for (const watcher of wildcardWatchers) {
1672
- try {
1673
- watcher.method.call(this, currentValue, parsedValue, propName);
1674
- }
1675
- catch (error) {
1676
- console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
1702
+ // Call wildcard watchers (watching "*")
1703
+ if (watchers.has('*')) {
1704
+ const wildcardWatchers = watchers.get('*');
1705
+ for (const watcher of wildcardWatchers) {
1706
+ try {
1707
+ watcher.method.call(this, currentValue, parsedValue, propName);
1708
+ }
1709
+ catch (error) {
1710
+ console.error(`Error in @watch('*') method ${watcher.methodName}:`, error);
1711
+ }
1677
1712
  }
1678
1713
  }
1679
1714
  }
@@ -1729,13 +1764,6 @@ function property(options) {
1729
1764
  context.metadata[PROPERTIES] = new Map();
1730
1765
  }
1731
1766
  context.metadata[PROPERTIES].set(propertyKey, options || {});
1732
- // Warn about problematic reflection usage at decoration time
1733
- if (options?.reflect && options?.type === Array) {
1734
- console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Array type.`);
1735
- }
1736
- if (options?.reflect && options?.type === Object) {
1737
- console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Object type.`);
1738
- }
1739
1767
  // Return a field initializer function for new decorators
1740
1768
  return function (initialValue) {
1741
1769
  // Detect type from initial value if not explicitly provided
@@ -1752,60 +1780,51 @@ function property(options) {
1752
1780
  if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1753
1781
  const descriptor = {
1754
1782
  get() {
1755
- if (!this[PROPERTY_VALUES]) {
1756
- this[PROPERTY_VALUES] = {};
1757
- }
1758
- // If we have a stored value, return it
1759
- if (this[PROPERTY_VALUES][propertyKey] !== undefined) {
1760
- return this[PROPERTY_VALUES][propertyKey];
1761
- }
1762
- // Otherwise check attribute and parse it, or return initial value
1783
+ // Always read from DOM attribute - no internal state
1763
1784
  const attributeName = typeof finalOptions?.attribute === 'string' ? finalOptions?.attribute : propertyKey.toLowerCase();
1764
1785
  const attrValue = this.getAttribute?.(attributeName);
1765
- // If attribute exists or we have a type that needs special handling for null (like Boolean)
1766
- if (attrValue !== null || finalOptions?.type === Boolean) {
1786
+ // If attribute exists, parse it
1787
+ if (attrValue !== null) {
1767
1788
  return parseAttributeValue(attrValue, finalOptions || {}, undefined, initialValue);
1768
1789
  }
1790
+ // For Boolean properties that have been explicitly set via attribute,
1791
+ // follow HTML boolean attribute semantics (absence = false)
1792
+ const inferredType = finalOptions?.type || detectType(initialValue);
1793
+ if (inferredType === Boolean && this[EXPLICITLY_SET_PROPERTIES]?.has(propertyKey)) {
1794
+ return false;
1795
+ }
1796
+ // Otherwise return initial value
1769
1797
  return initialValue;
1770
1798
  },
1771
1799
  set(newValue) {
1800
+ // Get old value from stored state for change detection
1772
1801
  if (!this[PROPERTY_VALUES]) {
1773
1802
  this[PROPERTY_VALUES] = {};
1774
1803
  }
1775
- if (!this[EXPLICITLY_SET_PROPERTIES]) {
1776
- this[EXPLICITLY_SET_PROPERTIES] = new Set();
1777
- }
1778
1804
  const oldValue = this[PROPERTY_VALUES][propertyKey];
1805
+ // Check if value actually changed
1779
1806
  if (oldValue === newValue)
1780
1807
  return;
1781
- const isInitialDefaultValue = oldValue === undefined && !this[PROPERTIES_INITIALIZED];
1782
- if (oldValue !== undefined || (isInitialDefaultValue && newValue !== null && newValue !== undefined)) {
1783
- this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1784
- }
1808
+ // Update stored value
1785
1809
  this[PROPERTY_VALUES][propertyKey] = newValue;
1786
- if (finalOptions?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1787
- const attributeName = typeof finalOptions.attribute === 'string' ? finalOptions.attribute : propertyKey.toLowerCase();
1788
- if (newValue === null || newValue === undefined || newValue === false ||
1789
- (finalOptions?.type === SimpleArray && Array.isArray(newValue) && newValue.length === 0)) {
1790
- this.removeAttribute(attributeName);
1791
- }
1792
- else {
1793
- let attributeValue;
1794
- if (newValue instanceof Date) {
1795
- attributeValue = newValue.toISOString();
1796
- }
1797
- else if (typeof newValue === 'bigint') {
1798
- attributeValue = newValue.toString() + 'n';
1799
- }
1800
- else if (finalOptions?.type === SimpleArray && Array.isArray(newValue)) {
1801
- attributeValue = SimpleArray.serialize(newValue);
1802
- }
1803
- else {
1804
- attributeValue = String(newValue);
1805
- }
1806
- this.setAttribute(attributeName, attributeValue);
1807
- }
1810
+ // Always reflect to DOM - properties are always backed by attributes
1811
+ const attributeName = typeof finalOptions.attribute === 'string' ? finalOptions.attribute : propertyKey.toLowerCase();
1812
+ const attributeValue = valueToAttribute(newValue, finalOptions, initialValue);
1813
+ // Flag to prevent attributeChangedCallback from triggering watchers for this change
1814
+ if (!this._settingFromProperty)
1815
+ this._settingFromProperty = new Set();
1816
+ this._settingFromProperty.add(attributeName.toLowerCase());
1817
+ if (attributeValue === null) {
1818
+ this.removeAttribute?.(attributeName);
1819
+ }
1820
+ else {
1821
+ this.setAttribute?.(attributeName, attributeValue);
1808
1822
  }
1823
+ // Remove the flag after a short delay to allow attributeChangedCallback to run
1824
+ setTimeout(() => {
1825
+ this._settingFromProperty?.delete(attributeName.toLowerCase());
1826
+ }, 0);
1827
+ // Trigger watchers directly with proper parsed values
1809
1828
  const constructor = this.constructor;
1810
1829
  const watchers = constructor[PROPERTY_WATCHERS];
1811
1830
  if (watchers) {