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