snice 2.2.2 → 2.2.3

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 (41) hide show
  1. package/dist/index.cjs +163 -129
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.esm.js +163 -129
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.iife.js +163 -129
  6. package/dist/index.iife.js.map +1 -1
  7. package/dist/symbols.cjs.map +1 -1
  8. package/dist/symbols.esm.js +1 -1
  9. package/dist/symbols.esm.js.map +1 -1
  10. package/dist/transitions.cjs.map +1 -1
  11. package/dist/transitions.esm.js +1 -1
  12. package/dist/transitions.esm.js.map +1 -1
  13. package/dist/types/controller.d.ts +1 -7
  14. package/dist/types/element.d.ts +3 -44
  15. package/dist/types/events.d.ts +2 -26
  16. package/dist/types/global.d.ts +1 -5
  17. package/dist/types/index.d.ts +2 -8
  18. package/dist/types/observe.d.ts +1 -16
  19. package/dist/types/request-response.d.ts +2 -28
  20. package/dist/types/router.d.ts +2 -81
  21. package/dist/types/transitions.d.ts +2 -30
  22. package/dist/types/types/DispatchOptions.d.ts +10 -0
  23. package/dist/types/types/IController.d.ts +8 -0
  24. package/dist/types/types/ObserveOptions.d.ts +16 -0
  25. package/dist/types/types/OnOptions.d.ts +16 -0
  26. package/dist/types/types/PageOptions.d.ts +30 -0
  27. package/dist/types/types/PartOptions.d.ts +4 -0
  28. package/dist/types/types/PropertyConverter.d.ts +4 -0
  29. package/dist/types/types/PropertyOptions.d.ts +9 -0
  30. package/dist/types/types/QueryOptions.d.ts +4 -0
  31. package/dist/types/types/RequestOptions.d.ts +18 -0
  32. package/dist/types/types/RespondOptions.d.ts +10 -0
  33. package/dist/types/types/RouterInstance.d.ts +10 -0
  34. package/dist/types/types/RouterOptions.d.ts +32 -0
  35. package/dist/types/types/SimpleArray.d.ts +17 -0
  36. package/dist/types/types/SniceElement.d.ts +8 -0
  37. package/dist/types/types/SniceGlobal.d.ts +5 -0
  38. package/dist/types/types/Transition.d.ts +33 -0
  39. package/dist/types/types/index.d.ts +17 -0
  40. package/dist/types/utils.d.ts +16 -0
  41. package/package.json +2 -3
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v2.2.1
2
+ * snice v2.2.2
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.
@@ -704,6 +704,7 @@ var Snice = (function (exports) {
704
704
  }
705
705
  }
706
706
 
707
+ // @request decorator transforms methods to return Promise<T>
707
708
  /**
708
709
  * Decorator for making requests from elements or controllers.
709
710
  * Uses async generator pattern for bidirectional communication.
@@ -1240,6 +1241,133 @@ var Snice = (function (exports) {
1240
1241
  }
1241
1242
  }
1242
1243
 
1244
+ /**
1245
+ * SimpleArray type for arrays that can be safely reflected to attributes
1246
+ * Supports arrays of: string, number, boolean
1247
+ * Uses full-width comma (,) as separator to avoid conflicts
1248
+ * Strings cannot contain the full-width comma character
1249
+ */
1250
+ class SimpleArray {
1251
+ static { this.SEPARATOR = ','; } // U+FF0C Full-width comma
1252
+ /**
1253
+ * Serialize array to string for attribute storage
1254
+ */
1255
+ static serialize(arr) {
1256
+ if (!Array.isArray(arr))
1257
+ return '';
1258
+ return arr.map(item => {
1259
+ if (typeof item === 'string') {
1260
+ // Validate string doesn't contain our separator
1261
+ if (item.includes(SimpleArray.SEPARATOR)) {
1262
+ throw new Error(`SimpleArray strings cannot contain the character "${SimpleArray.SEPARATOR}" (U+FF0C)`);
1263
+ }
1264
+ return item;
1265
+ }
1266
+ else if (typeof item === 'number' || typeof item === 'boolean') {
1267
+ return String(item);
1268
+ }
1269
+ else {
1270
+ throw new Error(`SimpleArray only supports string, number, and boolean types. Got: ${typeof item}`);
1271
+ }
1272
+ }).join(SimpleArray.SEPARATOR);
1273
+ }
1274
+ /**
1275
+ * Parse string from attribute back to array
1276
+ */
1277
+ static parse(str) {
1278
+ if (str === null || str === undefined)
1279
+ return [];
1280
+ // Empty string should not be parsed as containing an empty string
1281
+ // since empty arrays don't get reflected (handled by the reflection logic)
1282
+ if (str === '')
1283
+ return [];
1284
+ return str.split(SimpleArray.SEPARATOR).map(item => {
1285
+ // Try to parse as number
1286
+ if (/^-?\d+\.?\d*$/.test(item)) {
1287
+ const num = Number(item);
1288
+ if (!isNaN(num))
1289
+ return num;
1290
+ }
1291
+ // Parse as boolean
1292
+ if (item === 'true')
1293
+ return true;
1294
+ if (item === 'false')
1295
+ return false;
1296
+ // Default to string
1297
+ return item;
1298
+ });
1299
+ }
1300
+ }
1301
+
1302
+ /**
1303
+ * Detects the type constructor from an initial value
1304
+ * @param initialValue - The default value assigned to a property field (e.g., `name = "default"` -> "default")
1305
+ * @returns The constructor function (String, Number, Boolean, etc.) or undefined if type can't be determined
1306
+ */
1307
+ function detectType(initialValue) {
1308
+ if (initialValue === null || initialValue === undefined) {
1309
+ return undefined;
1310
+ }
1311
+ if (typeof initialValue === 'string') {
1312
+ return String;
1313
+ }
1314
+ if (typeof initialValue === 'number') {
1315
+ return Number;
1316
+ }
1317
+ if (typeof initialValue === 'boolean') {
1318
+ return Boolean;
1319
+ }
1320
+ if (initialValue instanceof Date) {
1321
+ return Date;
1322
+ }
1323
+ if (typeof initialValue === 'bigint') {
1324
+ return BigInt;
1325
+ }
1326
+ if (Array.isArray(initialValue)) {
1327
+ return Array;
1328
+ }
1329
+ return undefined;
1330
+ }
1331
+ /**
1332
+ * Parses an attribute value based on the property type
1333
+ * @param attributeValue - The raw string value from the HTML attribute (e.g., "123", "true", "hello")
1334
+ * @param propertyOptions - The options from @property decorator (contains explicit type, attribute name, etc.)
1335
+ * @param currentValue - The current stored value of the property (used for type inference when no explicit type)
1336
+ * @param initialValue - The default value assigned to the property field (used for type detection as fallback)
1337
+ * @returns The parsed value in the correct JavaScript type
1338
+ */
1339
+ function parseAttributeValue(attributeValue, propertyOptions, currentValue, initialValue) {
1340
+ // Use explicit type or detect from initial value
1341
+ const typeToUse = propertyOptions.type || detectType(initialValue);
1342
+ switch (typeToUse) {
1343
+ case Boolean:
1344
+ return attributeValue !== null && attributeValue !== 'false';
1345
+ case Number:
1346
+ return attributeValue !== null ? Number(attributeValue) : null;
1347
+ case String:
1348
+ return attributeValue;
1349
+ case Date:
1350
+ return attributeValue ? new Date(attributeValue) : null;
1351
+ case BigInt:
1352
+ if (attributeValue && attributeValue.endsWith('n')) {
1353
+ return BigInt(attributeValue.slice(0, -1));
1354
+ }
1355
+ else {
1356
+ return attributeValue ? BigInt(attributeValue) : null;
1357
+ }
1358
+ case SimpleArray:
1359
+ return SimpleArray.parse(attributeValue);
1360
+ default:
1361
+ // If no type specified and can't detect, try to infer from current value type
1362
+ if (typeof currentValue === 'number' && attributeValue !== null) {
1363
+ return Number(attributeValue);
1364
+ }
1365
+ else {
1366
+ return attributeValue;
1367
+ }
1368
+ }
1369
+ }
1370
+
1243
1371
  /**
1244
1372
  * Applies core element functionality to a constructor
1245
1373
  * This is shared between @element and @page decorators
@@ -1321,42 +1449,17 @@ var Snice = (function (exports) {
1321
1449
  const properties = constructor[PROPERTIES];
1322
1450
  if (properties) {
1323
1451
  for (const [propName, propOptions] of properties) {
1324
- // If attribute exists, it always wins
1325
- if (this.hasAttribute(propName)) {
1452
+ // Check for attribute using proper attribute name
1453
+ const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
1454
+ if (this.hasAttribute(attributeName)) {
1326
1455
  // Attribute exists, parse and set the property value
1327
- const attrValue = this.getAttribute(propName);
1456
+ const attrValue = this.getAttribute(attributeName);
1328
1457
  // Mark as explicitly set since it came from an attribute
1329
1458
  if (!this[EXPLICITLY_SET_PROPERTIES]) {
1330
1459
  this[EXPLICITLY_SET_PROPERTIES] = new Set();
1331
1460
  }
1332
1461
  this[EXPLICITLY_SET_PROPERTIES].add(propName);
1333
- switch (propOptions.type) {
1334
- case Boolean:
1335
- this[propName] = attrValue !== null && attrValue !== 'false';
1336
- break;
1337
- case Number:
1338
- this[propName] = Number(attrValue);
1339
- break;
1340
- case String:
1341
- this[propName] = attrValue;
1342
- break;
1343
- case Date:
1344
- this[propName] = attrValue ? new Date(attrValue) : null;
1345
- break;
1346
- case BigInt:
1347
- if (attrValue && attrValue.endsWith('n')) {
1348
- this[propName] = BigInt(attrValue.slice(0, -1));
1349
- }
1350
- else {
1351
- this[propName] = attrValue ? BigInt(attrValue) : null;
1352
- }
1353
- break;
1354
- case SimpleArray:
1355
- this[propName] = SimpleArray.parse(attrValue);
1356
- break;
1357
- default:
1358
- this[propName] = attrValue;
1359
- }
1462
+ this[propName] = parseAttributeValue(attrValue, propOptions);
1360
1463
  }
1361
1464
  }
1362
1465
  }
@@ -1531,40 +1634,11 @@ var Snice = (function (exports) {
1531
1634
  if (properties) {
1532
1635
  for (const [propName, propOptions] of properties) {
1533
1636
  const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
1534
- if (attributeName === name) {
1637
+ if (attributeName.toLowerCase() === name.toLowerCase()) {
1535
1638
  // Check if the current property value already matches to avoid feedback loops
1536
1639
  const currentValue = this[PROPERTY_VALUES]?.[propName];
1537
1640
  // Parse the new value based on type
1538
- let parsedValue;
1539
- if (propOptions.type === Boolean) {
1540
- parsedValue = newValue !== null && newValue !== 'false';
1541
- }
1542
- else if (propOptions.type === Number) {
1543
- parsedValue = Number(newValue);
1544
- }
1545
- else if (propOptions.type === Date) {
1546
- parsedValue = newValue ? new Date(newValue) : null;
1547
- }
1548
- else if (propOptions.type === BigInt) {
1549
- if (newValue && newValue.endsWith('n')) {
1550
- parsedValue = BigInt(newValue.slice(0, -1));
1551
- }
1552
- else {
1553
- parsedValue = newValue ? BigInt(newValue) : null;
1554
- }
1555
- }
1556
- else if (propOptions.type === SimpleArray) {
1557
- parsedValue = SimpleArray.parse(newValue);
1558
- }
1559
- else {
1560
- // If no type specified, try to infer from current value type
1561
- if (typeof currentValue === 'number' && newValue !== null) {
1562
- parsedValue = Number(newValue);
1563
- }
1564
- else {
1565
- parsedValue = newValue;
1566
- }
1567
- }
1641
+ const parsedValue = parseAttributeValue(newValue, propOptions, currentValue, undefined);
1568
1642
  // Only update if the value actually changed and avoid infinite loops
1569
1643
  if (currentValue !== parsedValue) {
1570
1644
  // Mark as explicitly set since it came from an attribute change
@@ -1663,11 +1737,18 @@ var Snice = (function (exports) {
1663
1737
  if (options?.reflect && options?.type === Object) {
1664
1738
  console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Object type.`);
1665
1739
  }
1666
- context.addInitializer(function () {
1667
- // No longer need warnings here since they're at decoration time
1668
- });
1669
1740
  // Return a field initializer function for new decorators
1670
1741
  return function (initialValue) {
1742
+ // Detect type from initial value if not explicitly provided
1743
+ const finalOptions = { ...options };
1744
+ if (!finalOptions.type && initialValue !== undefined) {
1745
+ finalOptions.type = detectType(initialValue);
1746
+ // Update the metadata with the detected type
1747
+ const constructor = this.constructor;
1748
+ if (constructor[PROPERTIES]) {
1749
+ constructor[PROPERTIES].set(propertyKey, finalOptions);
1750
+ }
1751
+ }
1671
1752
  // Set up the property descriptor on first access
1672
1753
  if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1673
1754
  const descriptor = {
@@ -1675,7 +1756,18 @@ var Snice = (function (exports) {
1675
1756
  if (!this[PROPERTY_VALUES]) {
1676
1757
  this[PROPERTY_VALUES] = {};
1677
1758
  }
1678
- return this[PROPERTY_VALUES][propertyKey];
1759
+ // If we have a stored value, return it
1760
+ if (this[PROPERTY_VALUES][propertyKey] !== undefined) {
1761
+ return this[PROPERTY_VALUES][propertyKey];
1762
+ }
1763
+ // Otherwise check attribute and parse it, or return initial value
1764
+ const attributeName = typeof finalOptions?.attribute === 'string' ? finalOptions?.attribute : propertyKey.toLowerCase();
1765
+ const attrValue = this.getAttribute?.(attributeName);
1766
+ // If attribute exists or we have a type that needs special handling for null (like Boolean)
1767
+ if (attrValue !== null || finalOptions?.type === Boolean) {
1768
+ return parseAttributeValue(attrValue, finalOptions || {}, undefined, initialValue);
1769
+ }
1770
+ return initialValue;
1679
1771
  },
1680
1772
  set(newValue) {
1681
1773
  if (!this[PROPERTY_VALUES]) {
@@ -1692,10 +1784,10 @@ var Snice = (function (exports) {
1692
1784
  this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1693
1785
  }
1694
1786
  this[PROPERTY_VALUES][propertyKey] = newValue;
1695
- if (options?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1696
- const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyKey.toLowerCase();
1787
+ if (finalOptions?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1788
+ const attributeName = typeof finalOptions.attribute === 'string' ? finalOptions.attribute : propertyKey.toLowerCase();
1697
1789
  if (newValue === null || newValue === undefined || newValue === false ||
1698
- (options?.type === SimpleArray && Array.isArray(newValue) && newValue.length === 0)) {
1790
+ (finalOptions?.type === SimpleArray && Array.isArray(newValue) && newValue.length === 0)) {
1699
1791
  this.removeAttribute(attributeName);
1700
1792
  }
1701
1793
  else {
@@ -1706,7 +1798,7 @@ var Snice = (function (exports) {
1706
1798
  else if (typeof newValue === 'bigint') {
1707
1799
  attributeValue = newValue.toString() + 'n';
1708
1800
  }
1709
- else if (options?.type === SimpleArray && Array.isArray(newValue)) {
1801
+ else if (finalOptions?.type === SimpleArray && Array.isArray(newValue)) {
1710
1802
  attributeValue = SimpleArray.serialize(newValue);
1711
1803
  }
1712
1804
  else {
@@ -1834,63 +1926,6 @@ var Snice = (function (exports) {
1834
1926
  };
1835
1927
  };
1836
1928
  }
1837
- /**
1838
- * SimpleArray type for arrays that can be safely reflected to attributes
1839
- * Supports arrays of: string, number, boolean
1840
- * Uses full-width comma (,) as separator to avoid conflicts
1841
- * Strings cannot contain the full-width comma character
1842
- */
1843
- class SimpleArray {
1844
- static { this.SEPARATOR = ','; } // U+FF0C Full-width comma
1845
- /**
1846
- * Serialize array to string for attribute storage
1847
- */
1848
- static serialize(arr) {
1849
- if (!Array.isArray(arr))
1850
- return '';
1851
- return arr.map(item => {
1852
- if (typeof item === 'string') {
1853
- // Validate string doesn't contain our separator
1854
- if (item.includes(SimpleArray.SEPARATOR)) {
1855
- throw new Error(`SimpleArray strings cannot contain the character "${SimpleArray.SEPARATOR}" (U+FF0C)`);
1856
- }
1857
- return item;
1858
- }
1859
- else if (typeof item === 'number' || typeof item === 'boolean') {
1860
- return String(item);
1861
- }
1862
- else {
1863
- throw new Error(`SimpleArray only supports string, number, and boolean types. Got: ${typeof item}`);
1864
- }
1865
- }).join(SimpleArray.SEPARATOR);
1866
- }
1867
- /**
1868
- * Parse string from attribute back to array
1869
- */
1870
- static parse(str) {
1871
- if (str === null || str === undefined)
1872
- return [];
1873
- // Empty string should not be parsed as containing an empty string
1874
- // since empty arrays don't get reflected (handled by the reflection logic)
1875
- if (str === '')
1876
- return [];
1877
- return str.split(SimpleArray.SEPARATOR).map(item => {
1878
- // Try to parse as number
1879
- if (/^-?\d+\.?\d*$/.test(item)) {
1880
- const num = Number(item);
1881
- if (!isNaN(num))
1882
- return num;
1883
- }
1884
- // Parse as boolean
1885
- if (item === 'true')
1886
- return true;
1887
- if (item === 'false')
1888
- return false;
1889
- // Default to string
1890
- return item;
1891
- });
1892
- }
1893
- }
1894
1929
  function watch(...propertyNames) {
1895
1930
  return function (target, context) {
1896
1931
  const methodName = context.name;
@@ -3193,8 +3228,7 @@ var Snice = (function (exports) {
3193
3228
  page,
3194
3229
  initialize,
3195
3230
  navigate,
3196
- register,
3197
- context,
3231
+ register
3198
3232
  };
3199
3233
  }
3200
3234