@dodlhuat/basix 1.2.5 → 1.2.7

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.
@@ -25,6 +25,9 @@ class VirtualDropdown {
25
25
  private readonly renderLimit: number;
26
26
  private readonly itemHeight: number;
27
27
  private readonly onSelect: ((selectedValues: Array<string | number>) => void) | null;
28
+ // Unique CSS anchor name for this instance — prevents conflicts when
29
+ // multiple dropdowns exist on the same page.
30
+ private readonly anchorName: string;
28
31
 
29
32
  private trigger!: HTMLElement;
30
33
  private triggerText!: HTMLElement;
@@ -59,6 +62,7 @@ class VirtualDropdown {
59
62
  this.renderLimit = config.renderLimit || 20;
60
63
  this.itemHeight = config.itemHeight || 40;
61
64
  this.onSelect = config.onSelect ?? null;
65
+ this.anchorName = `--vd-${Math.random().toString(36).slice(2, 9)}`;
62
66
 
63
67
  this.selectedValues = new Set();
64
68
  this.filteredOptions = [...this.options];
@@ -82,7 +86,7 @@ class VirtualDropdown {
82
86
  <span class="trigger-text">${escapeHtml(this.placeholder)}</span>
83
87
  <span class="trigger-arrow" aria-hidden="true">▼</span>
84
88
  </div>
85
- <div class="dropdown-menu" role="listbox">
89
+ <div class="dropdown-menu" popover="manual" role="listbox">
86
90
  ${this.searchable ? '<div class="dropdown-search"><input type="text" placeholder="Search..." aria-label="Search options"></div>' : ''}
87
91
  <div class="dropdown-list-wrapper">
88
92
  <div class="dropdown-list-scroller">
@@ -101,6 +105,11 @@ class VirtualDropdown {
101
105
  this.spacer = this.querySelector('.virtual-spacer');
102
106
  this.content = this.querySelector('.virtual-content');
103
107
 
108
+ // Wire up anchor positioning: each instance gets a unique anchor name
109
+ // so multiple dropdowns on the same page don't interfere.
110
+ this.trigger.style.setProperty('anchor-name', this.anchorName);
111
+ this.menu.style.setProperty('position-anchor', this.anchorName);
112
+
104
113
  if (this.searchable) {
105
114
  this.searchInput = this.querySelector('.dropdown-search input');
106
115
  }
@@ -130,8 +139,10 @@ class VirtualDropdown {
130
139
  this.trigger.addEventListener('keydown', handleTriggerKeydown as EventListener);
131
140
  this.boundHandlers.set('triggerKeydown', handleTriggerKeydown as EventListener);
132
141
 
142
+ // Close when clicking outside. Still needed because popover="manual"
143
+ // does not auto-dismiss on outside clicks.
133
144
  const handleDocumentClick = (e: MouseEvent): void => {
134
- if (!this.container.contains(e.target as Node)) {
145
+ if (!this.container.contains(e.target as Node) && !this.menu.contains(e.target as Node)) {
135
146
  this.close();
136
147
  }
137
148
  };
@@ -154,6 +165,20 @@ class VirtualDropdown {
154
165
  };
155
166
  this.listWrapper.addEventListener('scroll', handleScroll);
156
167
  this.boundHandlers.set('scroll', handleScroll);
168
+
169
+ // Sync isOpen if the browser closes the popover externally (e.g. another
170
+ // popover="auto" element stealing focus — not typical for "manual" but
171
+ // good defensive practice).
172
+ const handlePopoverToggle = (e: Event): void => {
173
+ const te = e as ToggleEvent;
174
+ if (te.newState === 'closed' && this.isOpen) {
175
+ this.isOpen = false;
176
+ this.container.classList.remove('open');
177
+ this.trigger.setAttribute('aria-expanded', 'false');
178
+ }
179
+ };
180
+ this.menu.addEventListener('toggle', handlePopoverToggle);
181
+ this.boundHandlers.set('popoverToggle', handlePopoverToggle);
157
182
  }
158
183
 
159
184
  private toggle(): void {
@@ -163,7 +188,7 @@ class VirtualDropdown {
163
188
  private open(): void {
164
189
  this.isOpen = true;
165
190
  this.container.classList.add('open');
166
- this.menu.classList.add('open');
191
+ this.menu.showPopover();
167
192
  this.trigger.setAttribute('aria-expanded', 'true');
168
193
  this.renderList();
169
194
 
@@ -175,7 +200,7 @@ class VirtualDropdown {
175
200
  private close(): void {
176
201
  this.isOpen = false;
177
202
  this.container.classList.remove('open');
178
- this.menu.classList.remove('open');
203
+ this.menu.hidePopover();
179
204
  this.trigger.setAttribute('aria-expanded', 'false');
180
205
  }
181
206
 
@@ -327,6 +352,10 @@ class VirtualDropdown {
327
352
  }
328
353
 
329
354
  public destroy(): void {
355
+ if (this.isOpen) {
356
+ this.menu.hidePopover();
357
+ }
358
+
330
359
  const triggerClick = this.boundHandlers.get('triggerClick');
331
360
  if (triggerClick) {
332
361
  this.trigger.removeEventListener('click', triggerClick);
@@ -352,6 +381,11 @@ class VirtualDropdown {
352
381
  this.listWrapper.removeEventListener('scroll', scroll);
353
382
  }
354
383
 
384
+ const popoverToggle = this.boundHandlers.get('popoverToggle');
385
+ if (popoverToggle) {
386
+ this.menu.removeEventListener('toggle', popoverToggle);
387
+ }
388
+
355
389
  this.boundHandlers.clear();
356
390
 
357
391
  this.container.innerHTML = '';
@@ -359,4 +393,4 @@ class VirtualDropdown {
359
393
  }
360
394
  }
361
395
 
362
- export { VirtualDropdown, DropdownOption, VirtualDropdownConfig };
396
+ export { VirtualDropdown, DropdownOption, VirtualDropdownConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dodlhuat/basix",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "description": "Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.",
5
5
  "exports": {
6
6
  "./css/*": "./css/*",