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.
- package/README.md +17 -10
- package/dist/components/accordion/snice-accordion-item.js +3 -3
- package/dist/components/accordion/snice-accordion-item.js.map +1 -1
- package/dist/components/accordion/snice-accordion.js +1 -1
- package/dist/components/accordion/snice-accordion.js.map +1 -1
- package/dist/components/alert/snice-alert.js +5 -5
- package/dist/components/alert/snice-alert.js.map +1 -1
- package/dist/components/avatar/snice-avatar.js +7 -7
- package/dist/components/avatar/snice-avatar.js.map +1 -1
- package/dist/components/badge/snice-badge.js +10 -10
- package/dist/components/badge/snice-badge.js.map +1 -1
- package/dist/components/breadcrumbs/snice-breadcrumbs.js +3 -3
- package/dist/components/breadcrumbs/snice-breadcrumbs.js.map +1 -1
- package/dist/components/breadcrumbs/snice-crumb.js +5 -5
- package/dist/components/breadcrumbs/snice-crumb.js.map +1 -1
- package/dist/components/button/snice-button.js +12 -12
- package/dist/components/button/snice-button.js.map +1 -1
- package/dist/components/card/snice-card.js +5 -5
- package/dist/components/card/snice-card.js.map +1 -1
- package/dist/components/checkbox/snice-checkbox.js +9 -9
- package/dist/components/checkbox/snice-checkbox.js.map +1 -1
- package/dist/components/chip/snice-chip.js +8 -8
- package/dist/components/chip/snice-chip.js.map +1 -1
- package/dist/components/date-picker/snice-date-picker.js +18 -18
- package/dist/components/date-picker/snice-date-picker.js.map +1 -1
- package/dist/components/divider/snice-divider.js +8 -8
- package/dist/components/divider/snice-divider.js.map +1 -1
- package/dist/components/drawer/snice-drawer.js +9 -9
- package/dist/components/drawer/snice-drawer.js.map +1 -1
- package/dist/components/input/snice-input.js +24 -24
- package/dist/components/input/snice-input.js.map +1 -1
- package/dist/components/layout/snice-layout-card.js +2 -2
- package/dist/components/layout/snice-layout-card.js.map +1 -1
- package/dist/components/layout/snice-layout-centered.js +1 -1
- package/dist/components/layout/snice-layout-centered.js.map +1 -1
- package/dist/components/layout/snice-layout-fullscreen.js +1 -1
- package/dist/components/layout/snice-layout-fullscreen.js.map +1 -1
- package/dist/components/layout/snice-layout-sidebar.js +1 -1
- package/dist/components/layout/snice-layout-sidebar.js.map +1 -1
- package/dist/components/layout/snice-layout-split.js +2 -2
- package/dist/components/layout/snice-layout-split.js.map +1 -1
- package/dist/components/login/snice-login.js +8 -8
- package/dist/components/login/snice-login.js.map +1 -1
- package/dist/components/modal/snice-modal.js +7 -7
- package/dist/components/modal/snice-modal.js.map +1 -1
- package/dist/components/pagination/snice-pagination.js +9 -9
- package/dist/components/pagination/snice-pagination.js.map +1 -1
- package/dist/components/progress/snice-progress.js +11 -11
- package/dist/components/progress/snice-progress.js.map +1 -1
- package/dist/components/radio/snice-radio.js +8 -8
- package/dist/components/radio/snice-radio.js.map +1 -1
- package/dist/components/select/snice-option.js +5 -5
- package/dist/components/select/snice-option.js.map +1 -1
- package/dist/components/select/snice-select.js +14 -14
- package/dist/components/select/snice-select.js.map +1 -1
- package/dist/components/skeleton/snice-skeleton.js +6 -6
- package/dist/components/skeleton/snice-skeleton.js.map +1 -1
- package/dist/components/switch/snice-switch.js +10 -10
- package/dist/components/switch/snice-switch.js.map +1 -1
- package/dist/components/table/snice-cell-boolean.js +8 -8
- package/dist/components/table/snice-cell-boolean.js.map +1 -1
- package/dist/components/table/snice-cell-date.js +8 -8
- package/dist/components/table/snice-cell-date.js.map +1 -1
- package/dist/components/table/snice-cell-duration.js +3 -3
- package/dist/components/table/snice-cell-duration.js.map +1 -1
- package/dist/components/table/snice-cell-filesize.js +3 -3
- package/dist/components/table/snice-cell-filesize.js.map +1 -1
- package/dist/components/table/snice-cell-number.js +9 -9
- package/dist/components/table/snice-cell-number.js.map +1 -1
- package/dist/components/table/snice-cell-progress.js +3 -3
- package/dist/components/table/snice-cell-progress.js.map +1 -1
- package/dist/components/table/snice-cell-rating.js +3 -3
- package/dist/components/table/snice-cell-rating.js.map +1 -1
- package/dist/components/table/snice-cell-sparkline.js +13 -13
- package/dist/components/table/snice-cell-sparkline.js.map +1 -1
- package/dist/components/table/snice-cell-text.js +4 -4
- package/dist/components/table/snice-cell-text.js.map +1 -1
- package/dist/components/table/snice-cell.js +2 -2
- package/dist/components/table/snice-cell.js.map +1 -1
- package/dist/components/table/snice-column.js +10 -10
- package/dist/components/table/snice-column.js.map +1 -1
- package/dist/components/table/snice-header.js +5 -5
- package/dist/components/table/snice-header.js.map +1 -1
- package/dist/components/table/snice-row.js +5 -5
- package/dist/components/table/snice-row.js.map +1 -1
- package/dist/components/table/snice-table.js +7 -7
- package/dist/components/table/snice-table.js.map +1 -1
- package/dist/components/tabs/snice-tab-panel.js +5 -5
- package/dist/components/tabs/snice-tab-panel.js.map +1 -1
- package/dist/components/tabs/snice-tab.js +2 -2
- package/dist/components/tabs/snice-tab.js.map +1 -1
- package/dist/components/tabs/snice-tabs.js +4 -4
- package/dist/components/tabs/snice-tabs.js.map +1 -1
- package/dist/components/toast/snice-toast-container.js +1 -1
- package/dist/components/toast/snice-toast-container.js.map +1 -1
- package/dist/components/toast/snice-toast.js +4 -4
- package/dist/components/toast/snice-toast.js.map +1 -1
- package/dist/components/tooltip/snice-tooltip.js +11 -11
- package/dist/components/tooltip/snice-tooltip.js.map +1 -1
- package/dist/index.cjs +119 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +119 -100
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +119 -100
- package/dist/index.iife.js.map +1 -1
- package/dist/symbols.esm.js +1 -1
- package/dist/transitions.esm.js +1 -1
- package/dist/types/testing.d.ts +4 -0
- package/dist/types/types/PropertyOptions.d.ts +0 -1
- package/dist/types/utils.d.ts +8 -0
- package/package.json +5 -1
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v2.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
|
-
|
|
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
|
-
//
|
|
1468
|
-
//
|
|
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
|
-
//
|
|
1654
|
-
const
|
|
1655
|
-
if (
|
|
1656
|
-
// Call
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
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
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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
|
-
|
|
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
|
|
1766
|
-
if (attrValue !== null
|
|
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
|
-
|
|
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
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
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) {
|