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
package/dist/index.esm.js CHANGED
@@ -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.
@@ -701,6 +701,7 @@ function cleanupObservers(instance) {
701
701
  }
702
702
  }
703
703
 
704
+ // @request decorator transforms methods to return Promise<T>
704
705
  /**
705
706
  * Decorator for making requests from elements or controllers.
706
707
  * Uses async generator pattern for bidirectional communication.
@@ -1237,6 +1238,133 @@ function cleanupNativeElementControllers() {
1237
1238
  }
1238
1239
  }
1239
1240
 
1241
+ /**
1242
+ * SimpleArray type for arrays that can be safely reflected to attributes
1243
+ * Supports arrays of: string, number, boolean
1244
+ * Uses full-width comma (,) as separator to avoid conflicts
1245
+ * Strings cannot contain the full-width comma character
1246
+ */
1247
+ class SimpleArray {
1248
+ static { this.SEPARATOR = ','; } // U+FF0C Full-width comma
1249
+ /**
1250
+ * Serialize array to string for attribute storage
1251
+ */
1252
+ static serialize(arr) {
1253
+ if (!Array.isArray(arr))
1254
+ return '';
1255
+ return arr.map(item => {
1256
+ if (typeof item === 'string') {
1257
+ // Validate string doesn't contain our separator
1258
+ if (item.includes(SimpleArray.SEPARATOR)) {
1259
+ throw new Error(`SimpleArray strings cannot contain the character "${SimpleArray.SEPARATOR}" (U+FF0C)`);
1260
+ }
1261
+ return item;
1262
+ }
1263
+ else if (typeof item === 'number' || typeof item === 'boolean') {
1264
+ return String(item);
1265
+ }
1266
+ else {
1267
+ throw new Error(`SimpleArray only supports string, number, and boolean types. Got: ${typeof item}`);
1268
+ }
1269
+ }).join(SimpleArray.SEPARATOR);
1270
+ }
1271
+ /**
1272
+ * Parse string from attribute back to array
1273
+ */
1274
+ static parse(str) {
1275
+ if (str === null || str === undefined)
1276
+ return [];
1277
+ // Empty string should not be parsed as containing an empty string
1278
+ // since empty arrays don't get reflected (handled by the reflection logic)
1279
+ if (str === '')
1280
+ return [];
1281
+ return str.split(SimpleArray.SEPARATOR).map(item => {
1282
+ // Try to parse as number
1283
+ if (/^-?\d+\.?\d*$/.test(item)) {
1284
+ const num = Number(item);
1285
+ if (!isNaN(num))
1286
+ return num;
1287
+ }
1288
+ // Parse as boolean
1289
+ if (item === 'true')
1290
+ return true;
1291
+ if (item === 'false')
1292
+ return false;
1293
+ // Default to string
1294
+ return item;
1295
+ });
1296
+ }
1297
+ }
1298
+
1299
+ /**
1300
+ * Detects the type constructor from an initial value
1301
+ * @param initialValue - The default value assigned to a property field (e.g., `name = "default"` -> "default")
1302
+ * @returns The constructor function (String, Number, Boolean, etc.) or undefined if type can't be determined
1303
+ */
1304
+ function detectType(initialValue) {
1305
+ if (initialValue === null || initialValue === undefined) {
1306
+ return undefined;
1307
+ }
1308
+ if (typeof initialValue === 'string') {
1309
+ return String;
1310
+ }
1311
+ if (typeof initialValue === 'number') {
1312
+ return Number;
1313
+ }
1314
+ if (typeof initialValue === 'boolean') {
1315
+ return Boolean;
1316
+ }
1317
+ if (initialValue instanceof Date) {
1318
+ return Date;
1319
+ }
1320
+ if (typeof initialValue === 'bigint') {
1321
+ return BigInt;
1322
+ }
1323
+ if (Array.isArray(initialValue)) {
1324
+ return Array;
1325
+ }
1326
+ return undefined;
1327
+ }
1328
+ /**
1329
+ * Parses an attribute value based on the property type
1330
+ * @param attributeValue - The raw string value from the HTML attribute (e.g., "123", "true", "hello")
1331
+ * @param propertyOptions - The options from @property decorator (contains explicit type, attribute name, etc.)
1332
+ * @param currentValue - The current stored value of the property (used for type inference when no explicit type)
1333
+ * @param initialValue - The default value assigned to the property field (used for type detection as fallback)
1334
+ * @returns The parsed value in the correct JavaScript type
1335
+ */
1336
+ function parseAttributeValue(attributeValue, propertyOptions, currentValue, initialValue) {
1337
+ // Use explicit type or detect from initial value
1338
+ const typeToUse = propertyOptions.type || detectType(initialValue);
1339
+ switch (typeToUse) {
1340
+ case Boolean:
1341
+ return attributeValue !== null && attributeValue !== 'false';
1342
+ case Number:
1343
+ return attributeValue !== null ? Number(attributeValue) : null;
1344
+ case String:
1345
+ return attributeValue;
1346
+ case Date:
1347
+ return attributeValue ? new Date(attributeValue) : null;
1348
+ case BigInt:
1349
+ if (attributeValue && attributeValue.endsWith('n')) {
1350
+ return BigInt(attributeValue.slice(0, -1));
1351
+ }
1352
+ else {
1353
+ return attributeValue ? BigInt(attributeValue) : null;
1354
+ }
1355
+ case SimpleArray:
1356
+ return SimpleArray.parse(attributeValue);
1357
+ default:
1358
+ // If no type specified and can't detect, try to infer from current value type
1359
+ if (typeof currentValue === 'number' && attributeValue !== null) {
1360
+ return Number(attributeValue);
1361
+ }
1362
+ else {
1363
+ return attributeValue;
1364
+ }
1365
+ }
1366
+ }
1367
+
1240
1368
  /**
1241
1369
  * Applies core element functionality to a constructor
1242
1370
  * This is shared between @element and @page decorators
@@ -1318,42 +1446,17 @@ function applyElementFunctionality(constructor) {
1318
1446
  const properties = constructor[PROPERTIES];
1319
1447
  if (properties) {
1320
1448
  for (const [propName, propOptions] of properties) {
1321
- // If attribute exists, it always wins
1322
- if (this.hasAttribute(propName)) {
1449
+ // Check for attribute using proper attribute name
1450
+ const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
1451
+ if (this.hasAttribute(attributeName)) {
1323
1452
  // Attribute exists, parse and set the property value
1324
- const attrValue = this.getAttribute(propName);
1453
+ const attrValue = this.getAttribute(attributeName);
1325
1454
  // Mark as explicitly set since it came from an attribute
1326
1455
  if (!this[EXPLICITLY_SET_PROPERTIES]) {
1327
1456
  this[EXPLICITLY_SET_PROPERTIES] = new Set();
1328
1457
  }
1329
1458
  this[EXPLICITLY_SET_PROPERTIES].add(propName);
1330
- switch (propOptions.type) {
1331
- case Boolean:
1332
- this[propName] = attrValue !== null && attrValue !== 'false';
1333
- break;
1334
- case Number:
1335
- this[propName] = Number(attrValue);
1336
- break;
1337
- case String:
1338
- this[propName] = attrValue;
1339
- break;
1340
- case Date:
1341
- this[propName] = attrValue ? new Date(attrValue) : null;
1342
- break;
1343
- case BigInt:
1344
- if (attrValue && attrValue.endsWith('n')) {
1345
- this[propName] = BigInt(attrValue.slice(0, -1));
1346
- }
1347
- else {
1348
- this[propName] = attrValue ? BigInt(attrValue) : null;
1349
- }
1350
- break;
1351
- case SimpleArray:
1352
- this[propName] = SimpleArray.parse(attrValue);
1353
- break;
1354
- default:
1355
- this[propName] = attrValue;
1356
- }
1459
+ this[propName] = parseAttributeValue(attrValue, propOptions);
1357
1460
  }
1358
1461
  }
1359
1462
  }
@@ -1528,40 +1631,11 @@ function applyElementFunctionality(constructor) {
1528
1631
  if (properties) {
1529
1632
  for (const [propName, propOptions] of properties) {
1530
1633
  const attributeName = typeof propOptions.attribute === 'string' ? propOptions.attribute : propName.toLowerCase();
1531
- if (attributeName === name) {
1634
+ if (attributeName.toLowerCase() === name.toLowerCase()) {
1532
1635
  // Check if the current property value already matches to avoid feedback loops
1533
1636
  const currentValue = this[PROPERTY_VALUES]?.[propName];
1534
1637
  // Parse the new value based on type
1535
- let parsedValue;
1536
- if (propOptions.type === Boolean) {
1537
- parsedValue = newValue !== null && newValue !== 'false';
1538
- }
1539
- else if (propOptions.type === Number) {
1540
- parsedValue = Number(newValue);
1541
- }
1542
- else if (propOptions.type === Date) {
1543
- parsedValue = newValue ? new Date(newValue) : null;
1544
- }
1545
- else if (propOptions.type === BigInt) {
1546
- if (newValue && newValue.endsWith('n')) {
1547
- parsedValue = BigInt(newValue.slice(0, -1));
1548
- }
1549
- else {
1550
- parsedValue = newValue ? BigInt(newValue) : null;
1551
- }
1552
- }
1553
- else if (propOptions.type === SimpleArray) {
1554
- parsedValue = SimpleArray.parse(newValue);
1555
- }
1556
- else {
1557
- // If no type specified, try to infer from current value type
1558
- if (typeof currentValue === 'number' && newValue !== null) {
1559
- parsedValue = Number(newValue);
1560
- }
1561
- else {
1562
- parsedValue = newValue;
1563
- }
1564
- }
1638
+ const parsedValue = parseAttributeValue(newValue, propOptions, currentValue, undefined);
1565
1639
  // Only update if the value actually changed and avoid infinite loops
1566
1640
  if (currentValue !== parsedValue) {
1567
1641
  // Mark as explicitly set since it came from an attribute change
@@ -1660,11 +1734,18 @@ function property(options) {
1660
1734
  if (options?.reflect && options?.type === Object) {
1661
1735
  console.warn(`⚠️ Property '${propertyKey}' uses reflect:true with Object type.`);
1662
1736
  }
1663
- context.addInitializer(function () {
1664
- // No longer need warnings here since they're at decoration time
1665
- });
1666
1737
  // Return a field initializer function for new decorators
1667
1738
  return function (initialValue) {
1739
+ // Detect type from initial value if not explicitly provided
1740
+ const finalOptions = { ...options };
1741
+ if (!finalOptions.type && initialValue !== undefined) {
1742
+ finalOptions.type = detectType(initialValue);
1743
+ // Update the metadata with the detected type
1744
+ const constructor = this.constructor;
1745
+ if (constructor[PROPERTIES]) {
1746
+ constructor[PROPERTIES].set(propertyKey, finalOptions);
1747
+ }
1748
+ }
1668
1749
  // Set up the property descriptor on first access
1669
1750
  if (!Object.hasOwnProperty.call(this.constructor.prototype, propertyKey)) {
1670
1751
  const descriptor = {
@@ -1672,7 +1753,18 @@ function property(options) {
1672
1753
  if (!this[PROPERTY_VALUES]) {
1673
1754
  this[PROPERTY_VALUES] = {};
1674
1755
  }
1675
- return this[PROPERTY_VALUES][propertyKey];
1756
+ // If we have a stored value, return it
1757
+ if (this[PROPERTY_VALUES][propertyKey] !== undefined) {
1758
+ return this[PROPERTY_VALUES][propertyKey];
1759
+ }
1760
+ // Otherwise check attribute and parse it, or return initial value
1761
+ const attributeName = typeof finalOptions?.attribute === 'string' ? finalOptions?.attribute : propertyKey.toLowerCase();
1762
+ const attrValue = this.getAttribute?.(attributeName);
1763
+ // If attribute exists or we have a type that needs special handling for null (like Boolean)
1764
+ if (attrValue !== null || finalOptions?.type === Boolean) {
1765
+ return parseAttributeValue(attrValue, finalOptions || {}, undefined, initialValue);
1766
+ }
1767
+ return initialValue;
1676
1768
  },
1677
1769
  set(newValue) {
1678
1770
  if (!this[PROPERTY_VALUES]) {
@@ -1689,10 +1781,10 @@ function property(options) {
1689
1781
  this[EXPLICITLY_SET_PROPERTIES].add(propertyKey);
1690
1782
  }
1691
1783
  this[PROPERTY_VALUES][propertyKey] = newValue;
1692
- if (options?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1693
- const attributeName = typeof options.attribute === 'string' ? options.attribute : propertyKey.toLowerCase();
1784
+ if (finalOptions?.reflect && this.setAttribute && this[PROPERTIES_INITIALIZED] && this[EXPLICITLY_SET_PROPERTIES].has(propertyKey)) {
1785
+ const attributeName = typeof finalOptions.attribute === 'string' ? finalOptions.attribute : propertyKey.toLowerCase();
1694
1786
  if (newValue === null || newValue === undefined || newValue === false ||
1695
- (options?.type === SimpleArray && Array.isArray(newValue) && newValue.length === 0)) {
1787
+ (finalOptions?.type === SimpleArray && Array.isArray(newValue) && newValue.length === 0)) {
1696
1788
  this.removeAttribute(attributeName);
1697
1789
  }
1698
1790
  else {
@@ -1703,7 +1795,7 @@ function property(options) {
1703
1795
  else if (typeof newValue === 'bigint') {
1704
1796
  attributeValue = newValue.toString() + 'n';
1705
1797
  }
1706
- else if (options?.type === SimpleArray && Array.isArray(newValue)) {
1798
+ else if (finalOptions?.type === SimpleArray && Array.isArray(newValue)) {
1707
1799
  attributeValue = SimpleArray.serialize(newValue);
1708
1800
  }
1709
1801
  else {
@@ -1831,63 +1923,6 @@ function queryAll(selector, options = {}) {
1831
1923
  };
1832
1924
  };
1833
1925
  }
1834
- /**
1835
- * SimpleArray type for arrays that can be safely reflected to attributes
1836
- * Supports arrays of: string, number, boolean
1837
- * Uses full-width comma (,) as separator to avoid conflicts
1838
- * Strings cannot contain the full-width comma character
1839
- */
1840
- class SimpleArray {
1841
- static { this.SEPARATOR = ','; } // U+FF0C Full-width comma
1842
- /**
1843
- * Serialize array to string for attribute storage
1844
- */
1845
- static serialize(arr) {
1846
- if (!Array.isArray(arr))
1847
- return '';
1848
- return arr.map(item => {
1849
- if (typeof item === 'string') {
1850
- // Validate string doesn't contain our separator
1851
- if (item.includes(SimpleArray.SEPARATOR)) {
1852
- throw new Error(`SimpleArray strings cannot contain the character "${SimpleArray.SEPARATOR}" (U+FF0C)`);
1853
- }
1854
- return item;
1855
- }
1856
- else if (typeof item === 'number' || typeof item === 'boolean') {
1857
- return String(item);
1858
- }
1859
- else {
1860
- throw new Error(`SimpleArray only supports string, number, and boolean types. Got: ${typeof item}`);
1861
- }
1862
- }).join(SimpleArray.SEPARATOR);
1863
- }
1864
- /**
1865
- * Parse string from attribute back to array
1866
- */
1867
- static parse(str) {
1868
- if (str === null || str === undefined)
1869
- return [];
1870
- // Empty string should not be parsed as containing an empty string
1871
- // since empty arrays don't get reflected (handled by the reflection logic)
1872
- if (str === '')
1873
- return [];
1874
- return str.split(SimpleArray.SEPARATOR).map(item => {
1875
- // Try to parse as number
1876
- if (/^-?\d+\.?\d*$/.test(item)) {
1877
- const num = Number(item);
1878
- if (!isNaN(num))
1879
- return num;
1880
- }
1881
- // Parse as boolean
1882
- if (item === 'true')
1883
- return true;
1884
- if (item === 'false')
1885
- return false;
1886
- // Default to string
1887
- return item;
1888
- });
1889
- }
1890
- }
1891
1926
  function watch(...propertyNames) {
1892
1927
  return function (target, context) {
1893
1928
  const methodName = context.name;
@@ -3190,8 +3225,7 @@ function Router(options) {
3190
3225
  page,
3191
3226
  initialize,
3192
3227
  navigate,
3193
- register,
3194
- context,
3228
+ register
3195
3229
  };
3196
3230
  }
3197
3231