snice 2.1.1 → 2.1.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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v2.1.0
2
+ * snice v2.1.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.
@@ -70,6 +70,8 @@ var Snice = (function (exports) {
70
70
  // Part symbols
71
71
  const PARTS = getSymbol('parts');
72
72
  const PART_TIMERS = getSymbol('part-timers');
73
+ // Dispatch timing symbols
74
+ const DISPATCH_TIMERS = getSymbol('dispatch-timers');
73
75
 
74
76
  function on(eventName, selectorOrOptions, options) {
75
77
  // Handle overloaded parameters
@@ -298,12 +300,21 @@ var Snice = (function (exports) {
298
300
  */
299
301
  function dispatch(eventName, options) {
300
302
  return function (originalMethod, _context) {
301
- // Create timing wrappers for dispatch
302
- let debounceTimeout;
303
- let throttleLastCall = 0;
304
- let throttleTimeout;
305
303
  return function (...args) {
306
- // Call the original method
304
+ // Create timing wrappers for dispatch (per-instance)
305
+ if (!this[DISPATCH_TIMERS]) {
306
+ this[DISPATCH_TIMERS] = new Map();
307
+ }
308
+ const timerKey = `${eventName}_${_context.name}`;
309
+ if (!this[DISPATCH_TIMERS].has(timerKey)) {
310
+ this[DISPATCH_TIMERS].set(timerKey, {
311
+ debounceTimeout: null,
312
+ throttleLastCall: 0,
313
+ throttleTimeout: null
314
+ });
315
+ }
316
+ const timers = this[DISPATCH_TIMERS].get(timerKey);
317
+ // Call the original method with preserved this context
307
318
  const result = originalMethod.apply(this, args);
308
319
  // Helper to dispatch the event
309
320
  const doDispatch = (detail) => {
@@ -323,21 +334,21 @@ var Snice = (function (exports) {
323
334
  // Helper to handle timed dispatch
324
335
  const timedDispatch = (detail) => {
325
336
  if (options?.debounce) {
326
- clearTimeout(debounceTimeout);
327
- debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
337
+ clearTimeout(timers.debounceTimeout);
338
+ timers.debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
328
339
  }
329
340
  else if (options?.throttle) {
330
341
  const now = Date.now();
331
- const remaining = options.throttle - (now - throttleLastCall);
342
+ const remaining = options.throttle - (now - timers.throttleLastCall);
332
343
  if (remaining <= 0) {
333
- clearTimeout(throttleTimeout);
334
- throttleLastCall = now;
344
+ clearTimeout(timers.throttleTimeout);
345
+ timers.throttleLastCall = now;
335
346
  doDispatch(detail);
336
347
  }
337
- else if (!throttleTimeout) {
338
- throttleTimeout = setTimeout(() => {
339
- throttleLastCall = Date.now();
340
- throttleTimeout = null;
348
+ else if (!timers.throttleTimeout) {
349
+ timers.throttleTimeout = setTimeout(() => {
350
+ timers.throttleLastCall = Date.now();
351
+ timers.throttleTimeout = null;
341
352
  doDispatch(detail);
342
353
  }, remaining);
343
354
  }
@@ -1429,6 +1440,7 @@ var Snice = (function (exports) {
1429
1440
  try {
1430
1441
  const partElement = this.shadowRoot.querySelector(`[part="${partName}"]`);
1431
1442
  if (partElement) {
1443
+ // For initial render, call original method directly to avoid timing restrictions
1432
1444
  const partResult = partHandler.method.call(this);
1433
1445
  const partContent = partResult instanceof Promise ? await partResult : partResult;
1434
1446
  if (partContent !== undefined) {
@@ -2032,94 +2044,51 @@ var Snice = (function (exports) {
2032
2044
  });
2033
2045
  }
2034
2046
  const timers = this[PART_TIMERS].get(partName);
2035
- // Call the original method first to get its result
2036
- const result = originalMethod.apply(this, args);
2037
- // Helper function to update DOM
2038
- const updateDOM = (content) => {
2039
- if (this.shadowRoot && content !== undefined) {
2040
- const partElement = this.shadowRoot.querySelector(`[part="${partName}"]`);
2041
- if (partElement) {
2042
- partElement.innerHTML = content;
2047
+ // Helper function to execute method and update DOM
2048
+ const executeAndUpdate = (...methodArgs) => {
2049
+ const result = originalMethod.apply(this, methodArgs);
2050
+ const updateDOM = (content) => {
2051
+ const hasContent = content !== undefined;
2052
+ const hasElement = this.shadowRoot?.querySelector(`[part="${partName}"]`);
2053
+ if (hasContent && hasElement) {
2054
+ hasElement.innerHTML = content;
2043
2055
  }
2044
- }
2056
+ };
2057
+ const isPromise = result instanceof Promise;
2058
+ return isPromise
2059
+ ? result.then(content => { updateDOM(content); return content; })
2060
+ : (updateDOM(result), result);
2045
2061
  };
2046
- // Check if result is a Promise (async method)
2047
- if (result instanceof Promise) {
2048
- // Handle async method
2049
- if (options.debounce !== undefined && options.debounce > 0) {
2050
- // Debounce: defer DOM update, return original Promise
2051
- if (timers.debounceTimer) {
2052
- clearTimeout(timers.debounceTimer);
2053
- }
2054
- timers.debounceTimer = setTimeout(async () => {
2055
- const content = await result;
2056
- updateDOM(content);
2057
- }, options.debounce);
2058
- return result;
2062
+ const hasDebounce = options.debounce !== undefined && options.debounce > 0;
2063
+ const hasThrottle = options.throttle !== undefined && options.throttle > 0;
2064
+ // Handle timing based on priority: debounce > throttle > immediate
2065
+ switch (true) {
2066
+ case hasDebounce: {
2067
+ clearTimeout(timers.debounceTimer);
2068
+ timers.debounceTimer = setTimeout(() => executeAndUpdate(...args), options.debounce);
2069
+ return undefined;
2059
2070
  }
2060
- if (options.throttle !== undefined && options.throttle > 0) {
2061
- // Throttle: handle timing but return original Promise
2071
+ case hasThrottle: {
2072
+ const throttleMs = options.throttle;
2062
2073
  const now = Date.now();
2063
- if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
2074
+ const canExecuteImmediately = timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= throttleMs;
2075
+ if (canExecuteImmediately) {
2064
2076
  timers.lastThrottleCall = now;
2065
- return result.then(content => {
2066
- updateDOM(content);
2067
- return content;
2068
- });
2077
+ return executeAndUpdate(...args);
2069
2078
  }
2070
- else {
2071
- if (!timers.throttleTimer) {
2072
- const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2073
- timers.throttleTimer = setTimeout(async () => {
2074
- timers.throttleTimer = null;
2075
- timers.lastThrottleCall = Date.now();
2076
- const content = await result;
2077
- updateDOM(content);
2078
- }, remainingTime);
2079
- }
2080
- return result;
2079
+ const hasScheduledTimer = !!timers.throttleTimer;
2080
+ if (!hasScheduledTimer) {
2081
+ const remainingTime = throttleMs - (now - timers.lastThrottleCall);
2082
+ timers.throttleTimer = setTimeout(() => {
2083
+ timers.throttleTimer = null;
2084
+ timers.lastThrottleCall = Date.now();
2085
+ executeAndUpdate(...args);
2086
+ }, remainingTime);
2081
2087
  }
2088
+ return undefined;
2082
2089
  }
2083
- // No timing: update DOM after Promise resolves
2084
- return result.then(content => {
2085
- updateDOM(content);
2086
- return content;
2087
- });
2088
- }
2089
- else {
2090
- // Handle sync method
2091
- if (options.debounce !== undefined && options.debounce > 0) {
2092
- // Debounce: defer DOM update, return result immediately
2093
- if (timers.debounceTimer) {
2094
- clearTimeout(timers.debounceTimer);
2095
- }
2096
- timers.debounceTimer = setTimeout(() => {
2097
- updateDOM(result);
2098
- }, options.debounce);
2099
- return result;
2100
- }
2101
- if (options.throttle !== undefined && options.throttle > 0) {
2102
- // Throttle: handle timing for DOM updates
2103
- const now = Date.now();
2104
- if (timers.lastThrottleCall === 0 || now - timers.lastThrottleCall >= options.throttle) {
2105
- timers.lastThrottleCall = now;
2106
- updateDOM(result);
2107
- }
2108
- else {
2109
- if (!timers.throttleTimer) {
2110
- const remainingTime = options.throttle - (now - timers.lastThrottleCall);
2111
- timers.throttleTimer = setTimeout(() => {
2112
- timers.throttleTimer = null;
2113
- timers.lastThrottleCall = Date.now();
2114
- updateDOM(result);
2115
- }, remainingTime);
2116
- }
2117
- }
2118
- return result;
2119
- }
2120
- // No timing: update DOM immediately
2121
- updateDOM(result);
2122
- return result;
2090
+ default:
2091
+ return executeAndUpdate(...args);
2123
2092
  }
2124
2093
  };
2125
2094
  };