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.
@@ -162,7 +162,6 @@ export class SniceLogin extends HTMLElement implements SniceLoginElement {
162
162
 
163
163
  @on('click', 'snice-button')
164
164
  async handleButtonClick(event: Event) {
165
- console.log('Button clicked'); // Debug
166
165
  event.preventDefault();
167
166
  // Trigger form submit
168
167
  if (this.form) {
@@ -173,15 +172,12 @@ export class SniceLogin extends HTMLElement implements SniceLoginElement {
173
172
  @on('submit', '.login__form')
174
173
  @dispatch('@snice/login-attempt', { bubbles: true, composed: true })
175
174
  async handleSubmit(event: Event) {
176
- console.log('Submit handler called'); // Debug
177
175
  event.preventDefault();
178
176
 
179
177
  if (this.loading || this.disabled) {
180
- console.log('Submit blocked - loading or disabled'); // Debug
181
178
  return;
182
179
  }
183
180
 
184
- console.log('Clearing error and setting loading...'); // Debug
185
181
  this.clearAlert();
186
182
  this.loading = true;
187
183
  this.updateLoadingState();
@@ -191,10 +187,8 @@ export class SniceLogin extends HTMLElement implements SniceLoginElement {
191
187
 
192
188
  try {
193
189
  const credentials = this.getFormData();
194
- console.log('Final credentials for login:', credentials); // Debug log
195
190
 
196
191
  if (!credentials.username || !credentials.password) {
197
- console.log('Missing credentials detected'); // Debug
198
192
  this.showAlert('Username and password are required', 'error');
199
193
  return;
200
194
  }
@@ -210,7 +204,6 @@ export class SniceLogin extends HTMLElement implements SniceLoginElement {
210
204
  }
211
205
  } catch (error) {
212
206
  const errorMessage = error instanceof Error ? error.message : 'Login failed';
213
- console.log('Login error:', errorMessage); // Debug
214
207
  this.showAlert(errorMessage, 'error');
215
208
  this.dispatchLoginError(errorMessage);
216
209
  } finally {
@@ -243,11 +236,7 @@ export class SniceLogin extends HTMLElement implements SniceLoginElement {
243
236
 
244
237
  @dispatch('@snice/login-success', { bubbles: true, composed: true })
245
238
  private dispatchLoginSuccess(result: LoginResult) {
246
- return {
247
- user: result.user,
248
- token: result.token,
249
- timestamp: new Date().toISOString()
250
- };
239
+ return { timestamp: new Date().toISOString() };
251
240
  }
252
241
 
253
242
  @dispatch('@snice/login-error', { bubbles: true, composed: true })
@@ -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.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.
@@ -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
  };