@dodlhuat/basix 1.2.7 → 1.2.9

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.
Files changed (69) hide show
  1. package/README.md +1 -1
  2. package/js/bottom-sheet.d.ts +37 -0
  3. package/js/calendar.d.ts +115 -0
  4. package/js/carousel.d.ts +34 -0
  5. package/js/chart.d.ts +73 -0
  6. package/js/code-viewer.d.ts +16 -0
  7. package/js/context-menu.d.ts +31 -0
  8. package/js/datepicker.d.ts +55 -0
  9. package/js/dropdown.d.ts +30 -0
  10. package/js/editor.d.ts +41 -0
  11. package/js/file-uploader.d.ts +48 -0
  12. package/js/flyout-menu.d.ts +37 -0
  13. package/js/gallery.d.ts +35 -0
  14. package/js/group-picker.d.ts +59 -0
  15. package/js/lightbox.d.ts +46 -0
  16. package/js/modal.d.ts +28 -0
  17. package/js/popover.d.ts +46 -0
  18. package/js/position.d.ts +31 -0
  19. package/js/push-menu.d.ts +31 -0
  20. package/js/range-slider.d.ts +9 -0
  21. package/js/scroll.d.ts +15 -0
  22. package/js/scrollbar.d.ts +48 -0
  23. package/js/select.d.ts +16 -0
  24. package/js/sidebar-nav.d.ts +22 -0
  25. package/js/stepper.d.ts +26 -0
  26. package/js/table.d.ts +98 -0
  27. package/js/tabs.d.ts +57 -0
  28. package/js/theme.d.ts +65 -0
  29. package/js/timepicker.d.ts +37 -0
  30. package/js/toast.d.ts +26 -0
  31. package/js/tooltip.d.ts +34 -0
  32. package/js/tree.d.ts +40 -0
  33. package/js/utils.d.ts +24 -0
  34. package/js/virtual-dropdown.d.ts +55 -0
  35. package/package.json +1 -1
  36. package/js/bottom-sheet.ts +0 -224
  37. package/js/calendar.ts +0 -774
  38. package/js/carousel.ts +0 -222
  39. package/js/chart.ts +0 -694
  40. package/js/code-viewer.ts +0 -188
  41. package/js/context-menu.ts +0 -252
  42. package/js/datepicker.ts +0 -640
  43. package/js/dropdown.ts +0 -180
  44. package/js/editor.ts +0 -492
  45. package/js/file-uploader.ts +0 -361
  46. package/js/flyout-menu.ts +0 -255
  47. package/js/gallery.ts +0 -237
  48. package/js/group-picker.ts +0 -451
  49. package/js/lightbox.ts +0 -333
  50. package/js/modal.ts +0 -171
  51. package/js/popover.ts +0 -221
  52. package/js/position.ts +0 -111
  53. package/js/push-menu.ts +0 -286
  54. package/js/range-slider.ts +0 -33
  55. package/js/scroll.ts +0 -47
  56. package/js/scrollbar.ts +0 -335
  57. package/js/select.ts +0 -235
  58. package/js/sidebar-nav.ts +0 -66
  59. package/js/stepper.ts +0 -109
  60. package/js/table.ts +0 -459
  61. package/js/tabs.ts +0 -280
  62. package/js/theme.ts +0 -235
  63. package/js/timepicker.ts +0 -202
  64. package/js/toast.ts +0 -134
  65. package/js/tooltip.ts +0 -196
  66. package/js/tree.ts +0 -244
  67. package/js/tsconfig.json +0 -18
  68. package/js/utils.ts +0 -119
  69. package/js/virtual-dropdown.ts +0 -396
@@ -1,396 +0,0 @@
1
- import { escapeHtml } from './utils.js';
2
-
3
- interface DropdownOption {
4
- label: string;
5
- value: string | number;
6
- }
7
-
8
- interface VirtualDropdownConfig {
9
- container: string | HTMLElement;
10
- options: DropdownOption[];
11
- multiSelect?: boolean;
12
- searchable?: boolean;
13
- placeholder?: string;
14
- renderLimit?: number;
15
- itemHeight?: number;
16
- onSelect?: (selectedValues: Array<string | number>) => void;
17
- }
18
-
19
- class VirtualDropdown {
20
- private readonly container: HTMLElement;
21
- private readonly options: DropdownOption[];
22
- private readonly multiSelect: boolean;
23
- private readonly searchable: boolean;
24
- private readonly placeholder: string;
25
- private readonly renderLimit: number;
26
- private readonly itemHeight: number;
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;
31
-
32
- private trigger!: HTMLElement;
33
- private triggerText!: HTMLElement;
34
- private menu!: HTMLElement;
35
- private listWrapper!: HTMLElement;
36
- private scroller!: HTMLElement;
37
- private spacer!: HTMLElement;
38
- private content!: HTMLElement;
39
- private searchInput?: HTMLInputElement;
40
-
41
- private selectedValues: Set<string | number>;
42
- private filteredOptions: DropdownOption[];
43
- private isOpen: boolean;
44
- private scrollTop: number;
45
-
46
- private boundHandlers: Map<string, EventListener>;
47
-
48
- constructor(config: VirtualDropdownConfig) {
49
- const containerElement = typeof config.container === 'string'
50
- ? document.querySelector<HTMLElement>(config.container)
51
- : config.container;
52
-
53
- if (!containerElement) {
54
- throw new Error('Container element not found');
55
- }
56
-
57
- this.container = containerElement;
58
- this.options = config.options || [];
59
- this.multiSelect = config.multiSelect ?? false;
60
- this.searchable = config.searchable ?? false;
61
- this.placeholder = config.placeholder || 'Select...';
62
- this.renderLimit = config.renderLimit || 20;
63
- this.itemHeight = config.itemHeight || 40;
64
- this.onSelect = config.onSelect ?? null;
65
- this.anchorName = `--vd-${Math.random().toString(36).slice(2, 9)}`;
66
-
67
- this.selectedValues = new Set();
68
- this.filteredOptions = [...this.options];
69
- this.isOpen = false;
70
- this.scrollTop = 0;
71
- this.boundHandlers = new Map();
72
-
73
- this.init();
74
- }
75
-
76
- private init(): void {
77
- this.container.classList.add('custom-dropdown');
78
- this.renderBase();
79
- this.bindEvents();
80
- this.updateTrigger();
81
- }
82
-
83
- private renderBase(): void {
84
- this.container.innerHTML = `
85
- <div class="dropdown-trigger" tabindex="0" role="button" aria-haspopup="listbox" aria-expanded="false">
86
- <span class="trigger-text">${escapeHtml(this.placeholder)}</span>
87
- <span class="trigger-arrow" aria-hidden="true">▼</span>
88
- </div>
89
- <div class="dropdown-menu" popover="manual" role="listbox">
90
- ${this.searchable ? '<div class="dropdown-search"><input type="text" placeholder="Search..." aria-label="Search options"></div>' : ''}
91
- <div class="dropdown-list-wrapper">
92
- <div class="dropdown-list-scroller">
93
- <div class="virtual-spacer"></div>
94
- <div class="virtual-content"></div>
95
- </div>
96
- </div>
97
- </div>
98
- `;
99
-
100
- this.trigger = this.querySelector('.dropdown-trigger');
101
- this.triggerText = this.querySelector('.trigger-text');
102
- this.menu = this.querySelector('.dropdown-menu');
103
- this.listWrapper = this.querySelector('.dropdown-list-wrapper');
104
- this.scroller = this.querySelector('.dropdown-list-scroller');
105
- this.spacer = this.querySelector('.virtual-spacer');
106
- this.content = this.querySelector('.virtual-content');
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
-
113
- if (this.searchable) {
114
- this.searchInput = this.querySelector('.dropdown-search input');
115
- }
116
- }
117
-
118
- private querySelector<T extends HTMLElement>(selector: string): T {
119
- const element = this.container.querySelector<T>(selector);
120
- if (!element) {
121
- throw new Error(`Required element not found: ${selector}`);
122
- }
123
- return element;
124
- }
125
-
126
- private bindEvents(): void {
127
- const handleTriggerClick = (): void => this.toggle();
128
- this.trigger.addEventListener('click', handleTriggerClick);
129
- this.boundHandlers.set('triggerClick', handleTriggerClick);
130
-
131
- const handleTriggerKeydown = (e: KeyboardEvent): void => {
132
- if (e.key === 'Enter' || e.key === ' ') {
133
- e.preventDefault();
134
- this.toggle();
135
- } else if (e.key === 'Escape' && this.isOpen) {
136
- this.close();
137
- }
138
- };
139
- this.trigger.addEventListener('keydown', handleTriggerKeydown as EventListener);
140
- this.boundHandlers.set('triggerKeydown', handleTriggerKeydown as EventListener);
141
-
142
- // Close when clicking outside. Still needed because popover="manual"
143
- // does not auto-dismiss on outside clicks.
144
- const handleDocumentClick = (e: MouseEvent): void => {
145
- if (!this.container.contains(e.target as Node) && !this.menu.contains(e.target as Node)) {
146
- this.close();
147
- }
148
- };
149
- document.addEventListener('click', handleDocumentClick as EventListener);
150
- this.boundHandlers.set('documentClick', handleDocumentClick as EventListener);
151
-
152
- if (this.searchable && this.searchInput) {
153
- const handleSearchInput = (e: Event): void => {
154
- const target = e.target as HTMLInputElement;
155
- this.handleSearch(target.value);
156
- };
157
- this.searchInput.addEventListener('input', handleSearchInput);
158
- this.boundHandlers.set('searchInput', handleSearchInput);
159
- }
160
-
161
- const handleScroll = (e: Event): void => {
162
- const target = e.target as HTMLElement;
163
- this.scrollTop = target.scrollTop;
164
- this.renderList();
165
- };
166
- this.listWrapper.addEventListener('scroll', handleScroll);
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);
182
- }
183
-
184
- private toggle(): void {
185
- this.isOpen ? this.close() : this.open();
186
- }
187
-
188
- private open(): void {
189
- this.isOpen = true;
190
- this.container.classList.add('open');
191
- this.menu.showPopover();
192
- this.trigger.setAttribute('aria-expanded', 'true');
193
- this.renderList();
194
-
195
- if (this.searchable && this.searchInput) {
196
- this.searchInput.focus();
197
- }
198
- }
199
-
200
- private close(): void {
201
- this.isOpen = false;
202
- this.container.classList.remove('open');
203
- this.menu.hidePopover();
204
- this.trigger.setAttribute('aria-expanded', 'false');
205
- }
206
-
207
- private handleSearch(query: string): void {
208
- if (!query.trim()) {
209
- this.filteredOptions = [...this.options];
210
- } else {
211
- const lowerQuery = query.toLowerCase();
212
- this.filteredOptions = this.options.filter(opt =>
213
- opt.label.toLowerCase().includes(lowerQuery)
214
- );
215
- }
216
-
217
- this.listWrapper.scrollTop = 0;
218
- this.scrollTop = 0;
219
- this.renderList();
220
- }
221
-
222
- private renderList(): void {
223
- const totalHeight = this.filteredOptions.length * this.itemHeight;
224
- this.spacer.style.height = `${totalHeight}px`;
225
-
226
- const startIdx = Math.floor(this.scrollTop / this.itemHeight);
227
- const buffer = 5;
228
- const renderStart = Math.max(0, startIdx - buffer);
229
- const renderEnd = Math.min(
230
- this.filteredOptions.length,
231
- startIdx + this.renderLimit + buffer
232
- );
233
-
234
- const offsetY = renderStart * this.itemHeight;
235
- this.content.style.transform = `translateY(${offsetY}px)`;
236
-
237
- const visibleOptions = this.filteredOptions.slice(renderStart, renderEnd);
238
-
239
- this.content.innerHTML = visibleOptions
240
- .map((opt, idx) => {
241
- const realIdx = renderStart + idx;
242
- const isSelected = this.selectedValues.has(opt.value);
243
- return `
244
- <div class="dropdown-item ${isSelected ? 'selected' : ''}"
245
- data-value="${escapeHtml(String(opt.value))}"
246
- data-idx="${realIdx}"
247
- role="option"
248
- aria-selected="${isSelected}"
249
- tabindex="0"
250
- style="height: ${this.itemHeight}px; line-height: ${this.itemHeight}px;">
251
- ${this.multiSelect ? `<input type="checkbox" ${isSelected ? 'checked' : ''} tabindex="-1" aria-hidden="true">` : ''}
252
- <span class="item-label">${escapeHtml(opt.label)}</span>
253
- </div>
254
- `;
255
- })
256
- .join('');
257
-
258
- this.content.querySelectorAll('.dropdown-item').forEach(item => {
259
- const handleItemClick = (e: Event): void => {
260
- e.stopPropagation();
261
- const value = (item as HTMLElement).dataset.value;
262
- if (value) {
263
- this.handleSelect(value);
264
- }
265
- };
266
-
267
- const handleItemKeydown = (e: KeyboardEvent): void => {
268
- if (e.key === 'Enter' || e.key === ' ') {
269
- e.preventDefault();
270
- const value = (item as HTMLElement).dataset.value;
271
- if (value) {
272
- this.handleSelect(value);
273
- }
274
- }
275
- };
276
-
277
- item.addEventListener('click', handleItemClick);
278
- item.addEventListener('keydown', handleItemKeydown as EventListener);
279
- });
280
- }
281
-
282
- private handleSelect(valueString: string): void {
283
- const selectedOpt = this.filteredOptions.find(
284
- o => String(o.value) === valueString
285
- );
286
-
287
- if (!selectedOpt) return;
288
-
289
- const val = selectedOpt.value;
290
-
291
- if (this.multiSelect) {
292
- if (this.selectedValues.has(val)) {
293
- this.selectedValues.delete(val);
294
- } else {
295
- this.selectedValues.add(val);
296
- }
297
- this.renderList();
298
- } else {
299
- this.selectedValues.clear();
300
- this.selectedValues.add(val);
301
- this.close();
302
- }
303
-
304
- this.updateTrigger();
305
-
306
- if (this.onSelect) {
307
- this.onSelect(Array.from(this.selectedValues));
308
- }
309
- }
310
-
311
- private updateTrigger(): void {
312
- if (this.selectedValues.size === 0) {
313
- this.triggerText.textContent = this.placeholder;
314
- this.triggerText.classList.remove('has-value');
315
- } else {
316
- this.triggerText.classList.add('has-value');
317
-
318
- if (this.multiSelect) {
319
- const count = this.selectedValues.size;
320
- this.triggerText.textContent = `${count} item${count !== 1 ? 's' : ''} selected`;
321
- } else {
322
- const val = Array.from(this.selectedValues)[0];
323
- const opt = this.options.find(o => o.value === val);
324
- this.triggerText.textContent = opt ? opt.label : String(val);
325
- }
326
- }
327
- }
328
-
329
- public getValue(): Array<string | number> {
330
- return Array.from(this.selectedValues);
331
- }
332
-
333
- public setValue(values: Array<string | number>): void {
334
- this.selectedValues.clear();
335
- values.forEach(val => {
336
- if (this.options.some(opt => opt.value === val)) {
337
- this.selectedValues.add(val);
338
- }
339
- });
340
- this.updateTrigger();
341
- if (this.isOpen) {
342
- this.renderList();
343
- }
344
- }
345
-
346
- public clearSelection(): void {
347
- this.selectedValues.clear();
348
- this.updateTrigger();
349
- if (this.isOpen) {
350
- this.renderList();
351
- }
352
- }
353
-
354
- public destroy(): void {
355
- if (this.isOpen) {
356
- this.menu.hidePopover();
357
- }
358
-
359
- const triggerClick = this.boundHandlers.get('triggerClick');
360
- if (triggerClick) {
361
- this.trigger.removeEventListener('click', triggerClick);
362
- }
363
-
364
- const triggerKeydown = this.boundHandlers.get('triggerKeydown');
365
- if (triggerKeydown) {
366
- this.trigger.removeEventListener('keydown', triggerKeydown);
367
- }
368
-
369
- const documentClick = this.boundHandlers.get('documentClick');
370
- if (documentClick) {
371
- document.removeEventListener('click', documentClick);
372
- }
373
-
374
- const searchInput = this.boundHandlers.get('searchInput');
375
- if (searchInput && this.searchInput) {
376
- this.searchInput.removeEventListener('input', searchInput);
377
- }
378
-
379
- const scroll = this.boundHandlers.get('scroll');
380
- if (scroll) {
381
- this.listWrapper.removeEventListener('scroll', scroll);
382
- }
383
-
384
- const popoverToggle = this.boundHandlers.get('popoverToggle');
385
- if (popoverToggle) {
386
- this.menu.removeEventListener('toggle', popoverToggle);
387
- }
388
-
389
- this.boundHandlers.clear();
390
-
391
- this.container.innerHTML = '';
392
- this.container.classList.remove('custom-dropdown', 'open');
393
- }
394
- }
395
-
396
- export { VirtualDropdown, DropdownOption, VirtualDropdownConfig };