inviton-powerduck 0.0.224 → 0.0.226

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,104 +1,104 @@
1
- /* Javascript plotting library for jQuery, version 0.8.3.
2
-
3
- Copyright (c) 2007-2014 IOLA and Ole Laursen.
4
- Licensed under the MIT license.
5
-
6
- */
7
- (function ($) {
8
- var options = { series: { stack: null } };
9
- function init(plot) {
10
- function findMatchingSeries(s, allseries) {
11
- var res = null;
12
- for (var i = 0; i < allseries.length; ++i) {
13
- if (s == allseries[i]) break;
14
- if (allseries[i].stack == s.stack) res = allseries[i];
15
- }
16
- return res;
17
- }
18
- function stackData(plot, s, datapoints) {
19
- if (s.stack == null || s.stack === false) return;
20
- var other = findMatchingSeries(s, plot.getData());
21
- if (!other) return;
22
- var ps = datapoints.pointsize,
23
- points = datapoints.points,
24
- otherps = other.datapoints.pointsize,
25
- otherpoints = other.datapoints.points,
26
- newpoints = [],
27
- px,
28
- py,
29
- intery,
30
- qx,
31
- qy,
32
- bottom,
33
- withlines = s.lines.show,
34
- horizontal = s.bars.horizontal,
35
- withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
36
- withsteps = withlines && s.lines.steps,
37
- fromgap = true,
38
- keyOffset = horizontal ? 1 : 0,
39
- accumulateOffset = horizontal ? 0 : 1,
40
- i = 0,
41
- j = 0,
42
- l,
43
- m;
44
- while (true) {
45
- if (i >= points.length) break;
46
- l = newpoints.length;
47
- if (points[i] == null) {
48
- for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
49
- i += ps;
50
- } else if (j >= otherpoints.length) {
51
- if (!withlines) {
52
- for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
53
- }
54
- i += ps;
55
- } else if (otherpoints[j] == null) {
56
- for (m = 0; m < ps; ++m) newpoints.push(null);
57
- fromgap = true;
58
- j += otherps;
59
- } else {
60
- px = points[i + keyOffset];
61
- py = points[i + accumulateOffset];
62
- qx = otherpoints[j + keyOffset];
63
- qy = otherpoints[j + accumulateOffset];
64
- bottom = 0;
65
- if (px == qx) {
66
- for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
67
- newpoints[l + accumulateOffset] += qy;
68
- bottom = qy;
69
- i += ps;
70
- j += otherps;
71
- } else if (px > qx) {
72
- if (withlines && i > 0 && points[i - ps] != null) {
73
- intery = py + ((points[i - ps + accumulateOffset] - py) * (qx - px)) / (points[i - ps + keyOffset] - px);
74
- newpoints.push(qx);
75
- newpoints.push(intery + qy);
76
- for (m = 2; m < ps; ++m) newpoints.push(points[i + m]);
77
- bottom = qy;
78
- }
79
- j += otherps;
80
- } else {
81
- if (fromgap && withlines) {
82
- i += ps;
83
- continue;
84
- }
85
- for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
86
- if (withlines && j > 0 && otherpoints[j - otherps] != null)
87
- bottom = qy + ((otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx)) / (otherpoints[j - otherps + keyOffset] - qx);
88
- newpoints[l + accumulateOffset] += bottom;
89
- i += ps;
90
- }
91
- fromgap = false;
92
- if (l != newpoints.length && withbottom) newpoints[l + 2] += bottom;
93
- }
94
- if (withsteps && l != newpoints.length && l > 0 && newpoints[l] != null && newpoints[l] != newpoints[l - ps] && newpoints[l + 1] != newpoints[l - ps + 1]) {
95
- for (m = 0; m < ps; ++m) newpoints[l + ps + m] = newpoints[l + m];
96
- newpoints[l + 1] = newpoints[l - ps + 1];
97
- }
98
- }
99
- datapoints.points = newpoints;
100
- }
101
- plot.hooks.processDatapoints.push(stackData);
102
- }
103
- $.plot.plugins.push({ init: init, options: options, name: "stack", version: "1.2" });
104
- })(jQuery);
1
+ /* Javascript plotting library for jQuery, version 0.8.3.
2
+
3
+ Copyright (c) 2007-2014 IOLA and Ole Laursen.
4
+ Licensed under the MIT license.
5
+
6
+ */
7
+ (function ($) {
8
+ var options = { series: { stack: null } };
9
+ function init(plot) {
10
+ function findMatchingSeries(s, allseries) {
11
+ var res = null;
12
+ for (var i = 0; i < allseries.length; ++i) {
13
+ if (s == allseries[i]) break;
14
+ if (allseries[i].stack == s.stack) res = allseries[i];
15
+ }
16
+ return res;
17
+ }
18
+ function stackData(plot, s, datapoints) {
19
+ if (s.stack == null || s.stack === false) return;
20
+ var other = findMatchingSeries(s, plot.getData());
21
+ if (!other) return;
22
+ var ps = datapoints.pointsize,
23
+ points = datapoints.points,
24
+ otherps = other.datapoints.pointsize,
25
+ otherpoints = other.datapoints.points,
26
+ newpoints = [],
27
+ px,
28
+ py,
29
+ intery,
30
+ qx,
31
+ qy,
32
+ bottom,
33
+ withlines = s.lines.show,
34
+ horizontal = s.bars.horizontal,
35
+ withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
36
+ withsteps = withlines && s.lines.steps,
37
+ fromgap = true,
38
+ keyOffset = horizontal ? 1 : 0,
39
+ accumulateOffset = horizontal ? 0 : 1,
40
+ i = 0,
41
+ j = 0,
42
+ l,
43
+ m;
44
+ while (true) {
45
+ if (i >= points.length) break;
46
+ l = newpoints.length;
47
+ if (points[i] == null) {
48
+ for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
49
+ i += ps;
50
+ } else if (j >= otherpoints.length) {
51
+ if (!withlines) {
52
+ for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
53
+ }
54
+ i += ps;
55
+ } else if (otherpoints[j] == null) {
56
+ for (m = 0; m < ps; ++m) newpoints.push(null);
57
+ fromgap = true;
58
+ j += otherps;
59
+ } else {
60
+ px = points[i + keyOffset];
61
+ py = points[i + accumulateOffset];
62
+ qx = otherpoints[j + keyOffset];
63
+ qy = otherpoints[j + accumulateOffset];
64
+ bottom = 0;
65
+ if (px == qx) {
66
+ for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
67
+ newpoints[l + accumulateOffset] += qy;
68
+ bottom = qy;
69
+ i += ps;
70
+ j += otherps;
71
+ } else if (px > qx) {
72
+ if (withlines && i > 0 && points[i - ps] != null) {
73
+ intery = py + ((points[i - ps + accumulateOffset] - py) * (qx - px)) / (points[i - ps + keyOffset] - px);
74
+ newpoints.push(qx);
75
+ newpoints.push(intery + qy);
76
+ for (m = 2; m < ps; ++m) newpoints.push(points[i + m]);
77
+ bottom = qy;
78
+ }
79
+ j += otherps;
80
+ } else {
81
+ if (fromgap && withlines) {
82
+ i += ps;
83
+ continue;
84
+ }
85
+ for (m = 0; m < ps; ++m) newpoints.push(points[i + m]);
86
+ if (withlines && j > 0 && otherpoints[j - otherps] != null)
87
+ bottom = qy + ((otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx)) / (otherpoints[j - otherps + keyOffset] - qx);
88
+ newpoints[l + accumulateOffset] += bottom;
89
+ i += ps;
90
+ }
91
+ fromgap = false;
92
+ if (l != newpoints.length && withbottom) newpoints[l + 2] += bottom;
93
+ }
94
+ if (withsteps && l != newpoints.length && l > 0 && newpoints[l] != null && newpoints[l] != newpoints[l - ps] && newpoints[l + 1] != newpoints[l - ps + 1]) {
95
+ for (m = 0; m < ps; ++m) newpoints[l + ps + m] = newpoints[l + m];
96
+ newpoints[l + 1] = newpoints[l - ps + 1];
97
+ }
98
+ }
99
+ datapoints.points = newpoints;
100
+ }
101
+ plot.hooks.processDatapoints.push(stackData);
102
+ }
103
+ $.plot.plugins.push({ init: init, options: options, name: "stack", version: "1.2" });
104
+ })(jQuery);
@@ -1,4 +1,4 @@
1
- .gopass-filter-wrapper {
1
+ .smart-dropdown-wrapper {
2
2
  /* ... [Previous variables and base styles] ... */
3
3
  --bs-blue: #0d6efd;
4
4
  --bs-btn-bg: #0d6efd;
@@ -257,7 +257,10 @@
257
257
  }
258
258
 
259
259
  .list-item-custom-container {
260
+ padding: 8px 16px;
260
261
  cursor: pointer;
262
+ display: flex;
263
+ align-items: center;
261
264
  border-bottom: 1px solid #f8f9fa;
262
265
  transition: background-color 0.15s;
263
266
 
@@ -376,7 +379,7 @@
376
379
 
377
380
  /* When in 'input' mode on Desktop: Hide internal search header and footer */
378
381
  @media (min-width: 769px) {
379
- &.mode-input {
382
+ &.mode-input {
380
383
  .internal-search-header {
381
384
  display: none !important;
382
385
  }
@@ -407,7 +410,7 @@
407
410
 
408
411
  /* On Mobile, always show the internal search/footer even if in 'input' mode */
409
412
  /* because the external input is behind the modal */
410
- &.mode-input {
413
+ &.mode-input {
411
414
  .internal-search-header {
412
415
  display: flex !important;
413
416
  }
@@ -49,6 +49,21 @@ export interface SmartDropdownSection {
49
49
  items: SmartDropdownItem[];
50
50
  }
51
51
 
52
+ /** Callback to change selection state of an item */
53
+ export type SelectionChangedCallback = (forceSelected?: boolean) => void;
54
+
55
+ /** Arguments passed to customListItemRender */
56
+ export interface CustomListItemRenderArgs {
57
+ /** The item being rendered */
58
+ item: SmartDropdownItem;
59
+ /** Whether the item is currently selected */
60
+ isSelected: boolean;
61
+ /** Callback to change selection state. Call with true to select, false to deselect, or undefined to toggle */
62
+ selectionChanged: SelectionChangedCallback;
63
+ /** Renders the item using the default SmartDropdown renderer */
64
+ baseRender: () => VNode;
65
+ }
66
+
52
67
  interface SmartDropdownArgs extends FormItemWrapperArgs {
53
68
  categories: SmartDropdownCategoryItem[];
54
69
  customSections: SmartDropdownSection[];
@@ -60,9 +75,12 @@ interface SmartDropdownArgs extends FormItemWrapperArgs {
60
75
  buttonLayout: 'footer' | 'inline';
61
76
  searchMode: 'dropdown' | 'input';
62
77
  customTriggerScope?: 'mobile' | 'desktop' | 'all';
63
- customSectionRender?: (item: SmartDropdownItem, selected: boolean) => VNode;
78
+ /** Custom renderer for list items. Receives args object with item, isSelected, selectionChanged, and baseRender */
79
+ customListItemRender?: (args: CustomListItemRenderArgs) => VNode;
64
80
  customTriggerRender?: () => VNode;
65
81
  changed: (e: SmartDropdownItem[]) => void;
82
+ /** Called when an item is clicked. Return true to prevent default selection behavior. */
83
+ onItemClick?: (item: SmartDropdownItem) => boolean;
66
84
  }
67
85
 
68
86
  @Component
@@ -99,9 +117,10 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
99
117
  @Prop({ type: String, default: 'footer' }) readonly buttonLayout!: 'footer' | 'inline';
100
118
  @Prop({ type: String, default: 'dropdown' }) readonly searchMode!: 'dropdown' | 'input';
101
119
  @Prop({ type: String, default: 'all' }) readonly customTriggerScope!: 'mobile' | 'desktop' | 'all';
102
- @Prop({ type: Function }) readonly customSectionRender?: (item: SmartDropdownItem, selected: boolean) => VNode;
120
+ @Prop({ type: Function }) readonly customListItemRender?: (args: CustomListItemRenderArgs) => VNode;
103
121
  @Prop({ type: Function }) readonly customTriggerRender?: () => VNode;
104
122
  @Prop() readonly changed: (e: SmartDropdownItem[]) => void;
123
+ @Prop({ type: Function }) readonly onItemClick?: (item: SmartDropdownItem) => boolean;
105
124
 
106
125
  // --- State ---
107
126
  isOpen = false;
@@ -414,21 +433,32 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
414
433
  }, 800);
415
434
  }
416
435
 
417
- handleItemClick(item: SmartDropdownItem) {
418
- const isSelected = this.isSelected(item);
436
+ handleItemClick(item: SmartDropdownItem, forceSelected?: boolean) {
437
+ // Allow parent to intercept and handle the click
438
+ if (this.onItemClick?.(item)) {
439
+ this.closeDropdown();
440
+ return;
441
+ }
442
+
443
+ const isCurrentlySelected = this.isSelected(item);
444
+ // Use forceSelected if provided, otherwise toggle based on current state
445
+ const shouldBeSelected = forceSelected !== undefined ? forceSelected : !isCurrentlySelected;
446
+
419
447
  let newSelection: SmartDropdownItem[] = [];
420
448
 
421
449
  if (this.multiselect) {
422
- if (isSelected) {
450
+ if (shouldBeSelected && !isCurrentlySelected) {
451
+ // Add item
452
+ newSelection = [...this.value, item];
453
+ } else if (!shouldBeSelected && isCurrentlySelected) {
454
+ // Remove item
423
455
  newSelection = this.value.filter(i => i.id !== item.id);
424
456
  } else {
425
- newSelection = [
426
- ...this.value,
427
- item,
428
- ];
457
+ // No change needed
458
+ newSelection = [...this.value];
429
459
  }
430
460
  } else {
431
- newSelection = [item];
461
+ newSelection = shouldBeSelected ? [item] : [];
432
462
  this.closeDropdown();
433
463
  }
434
464
 
@@ -600,17 +630,52 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
600
630
  );
601
631
  }
602
632
 
633
+ /** Renders the default list item content (without wrapper) */
634
+ renderDefaultListItemContent(item: SmartDropdownItem, isSelected: boolean): VNode {
635
+ const searchResult = item as SmartDropdownSearchResultItem;
636
+ const hasSubtitle = !!searchResult.subtitle;
637
+ const hasImage = !!searchResult.imageUrl;
638
+ const showCheckbox = this.multiselect && !this.isSearchActive && !hasImage;
639
+
640
+ return (
641
+ <>
642
+ {hasImage
643
+ ? (
644
+ <img src={searchResult.imageUrl} class="result-image" alt="" />
645
+ )
646
+ : (
647
+ showCheckbox && <div class={`checkbox-visual ${isSelected ? 'checked' : ''}`} aria-hidden="true"></div>
648
+ )}
649
+
650
+ <div class="item-content">
651
+ <span class="item-text">{item.text}</span>
652
+ {hasSubtitle && (
653
+ <span class="item-subtitle">{searchResult.subtitle}</span>
654
+ )}
655
+ </div>
656
+ </>
657
+ );
658
+ }
659
+
603
660
  renderListItem(item: SmartDropdownItem, index: number) {
604
661
  const isSelected = this.isSelected(item);
605
662
  const isFocused = this.focusedIndex === index;
606
663
  const uniqueId = `${this.uid}-option-${index}`;
607
664
 
665
+ // Create selectionChanged callback for this item
666
+ const selectionChanged: SelectionChangedCallback = (forceSelected?: boolean) => {
667
+ this.handleItemClick(item, forceSelected);
668
+ };
669
+
670
+ // Create baseRender function that returns the default rendering
671
+ const baseRender = (): VNode => this.renderDefaultListItemContent(item, isSelected);
672
+
608
673
  // Wrapper properties for accessibility
609
674
  const wrapperProps = {
610
675
  'id': uniqueId,
611
676
  'class': {
612
- 'list-item-custom-container': !!this.customSectionRender,
613
- 'list-item': !this.customSectionRender,
677
+ 'list-item-custom-container': !!this.customListItemRender,
678
+ 'list-item': !this.customListItemRender,
614
679
  'selected': isSelected,
615
680
  'focused': isFocused, // Visual focus state
616
681
  'no-checkbox': !this.multiselect,
@@ -622,36 +687,18 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
622
687
  };
623
688
 
624
689
  // 1. Custom Renderer
625
- if (this.customSectionRender) {
690
+ if (this.customListItemRender) {
626
691
  return (
627
692
  <div {...wrapperProps}>
628
- {this.customSectionRender(item, isSelected)}
693
+ {this.customListItemRender({ item, isSelected, selectionChanged, baseRender })}
629
694
  </div>
630
695
  );
631
696
  }
632
697
 
633
698
  // 2. Default Renderer
634
- const searchResult = item as SmartDropdownSearchResultItem;
635
- const hasSubtitle = !!searchResult.subtitle;
636
- const hasImage = !!searchResult.imageUrl;
637
- const showCheckbox = this.multiselect && !this.isSearchActive && !hasImage;
638
-
639
699
  return (
640
700
  <div {...wrapperProps}>
641
- {hasImage
642
- ? (
643
- <img src={searchResult.imageUrl} class="result-image" alt="" />
644
- )
645
- : (
646
- showCheckbox && <div class={`checkbox-visual ${isSelected ? 'checked' : ''}`} aria-hidden="true"></div>
647
- )}
648
-
649
- <div class="item-content">
650
- <span class="item-text">{item.text}</span>
651
- {hasSubtitle && (
652
- <span class="item-subtitle">{searchResult.subtitle}</span>
653
- )}
654
- </div>
701
+ {this.renderDefaultListItemContent(item, isSelected)}
655
702
  </div>
656
703
  );
657
704
  }
@@ -662,7 +709,7 @@ export default class SmartDropdown extends TsxComponent<SmartDropdownArgs> imple
662
709
  return (
663
710
  <FormItemWrapper label={this.label} cssClass={this.cssClass} mandatory={this.mandatory} wrap={this.wrap} appendIcon={this.appendIcon} prependIcon={this.prependIcon} hint={this.hint} marginType={this.marginType} appendClicked={this.appendClicked} prependClicked={this.prependClicked} prependIconClicked={this.prependIconClicked} appendIconClicked={this.appendIconClicked} maxWidth={this.maxWidth} validationState={this.validationState} labelButtons={this.labelButtons} subtitle={this.subtitle} showClearValueButton={this.showClearValueButton}>
664
711
  <div
665
- class={`gopass-filter-wrapper mode-${this.searchMode}`}
712
+ class={`smart-dropdown-wrapper mode-${this.searchMode}`}
666
713
  // Only the outer wrapper acts as combobox if not in direct input mode
667
714
  role={this.searchMode !== 'input' ? 'combobox' : undefined}
668
715
  aria-expanded={this.isOpen}