inviton-powerduck 0.0.340 → 0.0.341

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.
@@ -319,8 +319,8 @@ namespace laddaLiteUtils {
319
319
  }
320
320
  }
321
321
 
322
- if (!retTarget.className.includes('ladda-button-root')) {
323
- retTarget.className += ' ladda-button-root';
322
+ if (!retTarget.classList.contains('ladda-button-root')) {
323
+ retTarget.classList.add('ladda-button-root');
324
324
  }
325
325
 
326
326
  return retTarget;
@@ -368,58 +368,76 @@ namespace laddaLiteUtils {
368
368
  spinner: any;
369
369
  }
370
370
 
371
- export const createLaddaInstance = (button: any) => {
372
- if (!globalState.windowExists) {
373
- return;
371
+ /**
372
+ * Brings `button` to a known-good ladda state and returns its (cached)
373
+ * instance, (re)creating only whatever is missing. Safe — and meant — to be
374
+ * called on every spin.
375
+ *
376
+ * Why idempotent instead of "create once": the host <button> is usually owned
377
+ * by a framework (Vue, here). When that framework re-renders the button it
378
+ * overwrites `className` and can rebuild the button's children, which strips
379
+ * the `ladda-*` classes (the spinner CSS is entirely scoped to `.ladda-button`)
380
+ * and/or the `.ladda-label` / `.ladda-spinner` wrappers. The original
381
+ * create-once code then reused a cached instance and never restored any of it,
382
+ * so the spinner silently broke on the 2nd/3rd activation. Re-asserting the
383
+ * full contract here (without double-wrapping) makes it self-healing.
384
+ */
385
+ export const ensureLadda = (button: any): LaddaInstance | null => {
386
+ if (!globalState.windowExists || button == null) {
387
+ return null;
374
388
  }
375
389
 
376
- const laddaLabel = document.createElement('span');
377
- laddaLabel.setAttribute('class', 'ladda-label');
378
-
379
- // Wrap innerHTML into new element
380
- const docRange = document.createRange();
381
- docRange.selectNodeContents(button);
382
- docRange.surroundContents(laddaLabel);
383
- button.appendChild(laddaLabel);
390
+ // Re-assert classes/attrs every time (a framework re-render may have dropped them).
391
+ button.classList.add('ladda-button-root');
392
+ button.classList.add('ladda-button');
393
+ button.setAttribute('data-style', 'zoom-in');
384
394
 
385
- // Append spinner placeholder
386
- const laddaSpinner = document.createElement('span');
387
- laddaSpinner.setAttribute('class', 'ladda-spinner');
388
- button.appendChild(laddaSpinner);
389
- button.className += ' ladda-button';
395
+ // Wrap the (framework-rendered) content into a single .ladda-label, exactly
396
+ // once. If a re-render removed it, re-wrap; if it survived, reuse it as-is so
397
+ // we never produce nested .ladda-label > .ladda-label.
398
+ let spinnerHost: HTMLElement = button.querySelector(':scope > .ladda-spinner');
399
+ let label: HTMLElement = button.querySelector(':scope > .ladda-label');
400
+ if (label == null) {
401
+ label = document.createElement('span');
402
+ label.setAttribute('class', 'ladda-label');
403
+ const content = Array.prototype.slice.call(button.childNodes).filter((n: any) => n !== spinnerHost);
404
+ content.forEach((n: any) => label.appendChild(n));
405
+ button.insertBefore(label, button.firstChild);
406
+ }
390
407
 
391
- const newId = Math.floor(Math.random() * 999999999 + 1);
392
- button.setAttribute('data-style', 'zoom-in');
393
- button.setAttribute(ID_ATTRIBUTE, newId);
408
+ if (spinnerHost == null) {
409
+ spinnerHost = document.createElement('span');
410
+ spinnerHost.setAttribute('class', 'ladda-spinner');
411
+ button.appendChild(spinnerHost);
412
+ }
394
413
 
395
- const laddaInstance = {
396
- timer: <any>null,
397
- spinner: createSpinner(button),
398
- };
414
+ // Resolve (or (re)create) the cached spinner instance for this button.
415
+ let id = button.getAttribute(ID_ATTRIBUTE);
416
+ let laddaInstance: LaddaInstance = id != null ? instanceCache[id] : null;
417
+ if (laddaInstance == null) {
418
+ id = String(Math.floor(Math.random() * 999999999 + 1));
419
+ button.setAttribute(ID_ATTRIBUTE, id);
420
+ laddaInstance = {
421
+ timer: <any>null,
422
+ spinner: createSpinner(button),
423
+ };
424
+ instanceCache[id] = laddaInstance;
425
+ }
399
426
 
400
- instanceCache[newId] = laddaInstance;
401
427
  return laddaInstance;
402
428
  };
403
429
  }
404
430
 
405
431
  export class LaddaLite {
406
432
  static showSpin(target: HTMLElement): void {
407
- if (target == null) {
408
- return;
409
- }
410
-
411
- if (!globalState.windowExists) {
433
+ if (target == null || !globalState.windowExists) {
412
434
  return;
413
435
  }
414
436
 
415
- let laddaInstance;
416
437
  const button = laddaLiteUtils.getTarget(target);
417
- const instanceId = laddaLiteUtils.getInstanceId(button);
418
-
419
- if (instanceId == null || button.querySelector('.ladda-spinner') == null) {
420
- laddaInstance = laddaLiteUtils.createLaddaInstance(button);
421
- } else {
422
- laddaInstance = laddaLiteUtils.instanceCache[instanceId];
438
+ const laddaInstance = laddaLiteUtils.ensureLadda(button);
439
+ if (laddaInstance == null) {
440
+ return;
423
441
  }
424
442
 
425
443
  clearTimeout(laddaInstance.timer);
@@ -429,20 +447,21 @@ export class LaddaLite {
429
447
  }
430
448
 
431
449
  static hideSpin(target: HTMLElement): void {
432
- if (target == null) {
433
- return;
434
- }
435
-
436
- if (!globalState.windowExists) {
450
+ if (target == null || !globalState.windowExists) {
437
451
  return;
438
452
  }
439
453
 
440
454
  const button = laddaLiteUtils.getTarget(target);
441
455
  const instanceId = laddaLiteUtils.getInstanceId(button);
442
- const laddaInstance = laddaLiteUtils.instanceCache[instanceId];
443
- if (laddaInstance != null && instanceId != null) {
444
- button.disabled = false;
445
- button.removeAttribute('data-loading');
456
+ const laddaInstance = instanceId != null ? laddaLiteUtils.instanceCache[instanceId] : null;
457
+
458
+ // Always re-enable and clear the loading flag, even if the cached instance
459
+ // was lost (e.g. the framework replaced the button element) — never strand a
460
+ // disabled button.
461
+ button.disabled = false;
462
+ button.removeAttribute('data-loading');
463
+
464
+ if (laddaInstance != null) {
446
465
  laddaInstance.timer = setTimeout(() => {
447
466
  laddaInstance.spinner.stop();
448
467
  }, 1000);
@@ -26,35 +26,64 @@ class LaddaButtonComponent extends TsxComponent<LaddaButtonArgs> implements Ladd
26
26
  @Prop() loading!: boolean;
27
27
  @Prop() clicked: (e: any) => Promise<any>;
28
28
 
29
+ // Whether the spinner should currently be showing. Tracked so `updated()` can
30
+ // restore the ladda DOM after a Vue re-render strips it (Ladda mutates this
31
+ // Vue-owned <button> out-of-band, so any patch can wipe its classes/wrappers).
32
+ private _spinning: boolean = false;
33
+
29
34
  @Watch('loading')
30
35
  onLoadingChanged(val: boolean) {
31
36
  if (val) {
32
- LaddaLite.showSpin(this.$el as HTMLElement);
37
+ this.spin(this.$el as HTMLElement);
33
38
  } else {
34
- LaddaLite.hideSpin(this.$el as HTMLElement);
39
+ this.unspin(this.$el as HTMLElement);
35
40
  }
36
41
  }
37
42
 
38
43
  mounted() {
39
44
  if (this.loading) {
40
- LaddaLite.showSpin(this.$el as HTMLElement);
45
+ this.spin(this.$el as HTMLElement);
46
+ }
47
+ }
48
+
49
+ updated() {
50
+ // Vue just patched this button. If we're mid-spin and the patch stripped
51
+ // ladda's class or spinner wrapper, re-assert it (ensureLadda is idempotent,
52
+ // so this is a no-op when nothing was lost).
53
+ if (!this._spinning) {
54
+ return;
55
+ }
56
+
57
+ const el = this.$el as HTMLElement;
58
+ if (!el.classList.contains('ladda-button') || el.querySelector('.ladda-spinner') == null) {
59
+ LaddaLite.showSpin(el);
41
60
  }
42
61
  }
43
62
 
63
+ private spin(target: HTMLElement) {
64
+ this._spinning = true;
65
+ LaddaLite.showSpin(target);
66
+ }
67
+
68
+ private unspin(target: HTMLElement) {
69
+ this._spinning = false;
70
+ LaddaLite.hideSpin(target);
71
+ }
72
+
44
73
  handleButtonClicked(e) {
45
74
  let targetElem = e.target as HTMLElement;
46
75
  if (targetElem.nodeName.toLowerCase() != 'button') {
47
76
  targetElem = targetElem.closest('button') as HTMLElement;
48
77
  }
49
78
 
50
- LaddaLite.showSpin(targetElem);
79
+ this.spin(targetElem);
51
80
 
52
81
  this.clicked(e)
53
82
  .then(() => {
54
- LaddaLite.hideSpin(targetElem);
83
+ this.unspin(targetElem);
55
84
  })
56
85
  .catch(() => {
57
- LaddaLite.hideSpin(targetElem);
86
+ this.unspin(targetElem);
58
87
  });
59
88
  }
60
89
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "inviton-powerduck",
3
3
  "type": "module",
4
- "version": "0.0.340",
4
+ "version": "0.0.341",
5
5
  "files": [
6
6
  "app/",
7
7
  "common/",