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