@dodlhuat/basix 1.3.2 → 1.3.4

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 (119) hide show
  1. package/README.md +13 -7
  2. package/css/accordion.scss +0 -5
  3. package/css/badge.scss +9 -6
  4. package/css/bottom-sheet.scss +3 -8
  5. package/css/breadcrumb.scss +6 -15
  6. package/css/button.scss +63 -16
  7. package/css/calendar.scss +0 -54
  8. package/css/card.scss +0 -5
  9. package/css/carousel.scss +0 -3
  10. package/css/chart.scss +0 -25
  11. package/css/chat-bubbles.scss +0 -15
  12. package/css/checkbox.scss +3 -2
  13. package/css/chips.scss +13 -7
  14. package/css/code-viewer.scss +1 -5
  15. package/css/colors.scss +4 -0
  16. package/css/context-menu.scss +4 -6
  17. package/css/datepicker.scss +4 -7
  18. package/css/docs.scss +0 -8
  19. package/css/dropdown.scss +15 -1
  20. package/css/editor.scss +1 -23
  21. package/css/file-uploader.scss +2 -2
  22. package/css/flyout-menu.scss +66 -44
  23. package/css/form.scss +0 -28
  24. package/css/gallery.scss +2 -3
  25. package/css/group-picker.scss +5 -35
  26. package/css/icons.scss +0 -3
  27. package/css/lightbox.scss +2 -4
  28. package/css/mixins.scss +8 -0
  29. package/css/modal.scss +24 -3
  30. package/css/parameters.scss +6 -1
  31. package/css/popover.scss +3 -15
  32. package/css/progress.scss +0 -6
  33. package/css/push-menu.scss +3 -28
  34. package/css/radiobutton.scss +2 -1
  35. package/css/range-slider.scss +1 -7
  36. package/css/scrollbar.scss +2 -6
  37. package/css/sidebar-nav.scss +24 -11
  38. package/css/stepper.scss +0 -4
  39. package/css/style.css +287 -89
  40. package/css/style.css.map +1 -1
  41. package/css/style.min.css +1 -1
  42. package/css/style.min.css.map +1 -1
  43. package/css/style.scss +1 -1
  44. package/css/table.scss +0 -4
  45. package/css/tabs.scss +49 -2
  46. package/css/timeline.scss +1 -13
  47. package/css/timepicker.scss +6 -7
  48. package/css/toast.scss +1 -1
  49. package/css/tooltip.scss +1 -5
  50. package/css/tree.scss +1 -1
  51. package/css/typography.scss +38 -9
  52. package/css/virtual-dropdown.scss +3 -28
  53. package/js/bottom-sheet.d.ts +3 -1
  54. package/js/bottom-sheet.js +26 -27
  55. package/js/calendar.d.ts +7 -0
  56. package/js/calendar.js +14 -33
  57. package/js/carousel.d.ts +2 -0
  58. package/js/carousel.js +13 -5
  59. package/js/chart.d.ts +4 -0
  60. package/js/chart.js +13 -31
  61. package/js/code-viewer.d.ts +1 -0
  62. package/js/code-viewer.js +4 -0
  63. package/js/context-menu.d.ts +9 -2
  64. package/js/context-menu.js +17 -14
  65. package/js/datepicker.d.ts +4 -0
  66. package/js/datepicker.js +26 -11
  67. package/js/dropdown.d.ts +4 -3
  68. package/js/dropdown.js +17 -9
  69. package/js/editor.d.ts +1 -0
  70. package/js/editor.js +9 -3
  71. package/js/file-uploader.d.ts +4 -0
  72. package/js/file-uploader.js +52 -43
  73. package/js/flyout-menu.d.ts +5 -3
  74. package/js/flyout-menu.js +23 -46
  75. package/js/gallery.d.ts +5 -0
  76. package/js/gallery.js +44 -26
  77. package/js/group-picker.d.ts +5 -0
  78. package/js/group-picker.js +12 -17
  79. package/js/lightbox.d.ts +3 -0
  80. package/js/lightbox.js +12 -6
  81. package/js/modal.d.ts +3 -1
  82. package/js/modal.js +15 -12
  83. package/js/popover.d.ts +2 -0
  84. package/js/popover.js +26 -30
  85. package/js/position.d.ts +2 -0
  86. package/js/position.js +1 -5
  87. package/js/push-menu.d.ts +2 -0
  88. package/js/push-menu.js +22 -35
  89. package/js/range-slider.d.ts +1 -0
  90. package/js/range-slider.js +5 -3
  91. package/js/scroll.d.ts +2 -0
  92. package/js/scroll.js +1 -0
  93. package/js/scrollbar.d.ts +2 -0
  94. package/js/scrollbar.js +24 -36
  95. package/js/select.d.ts +1 -0
  96. package/js/select.js +5 -10
  97. package/js/sidebar-nav.d.ts +12 -0
  98. package/js/sidebar-nav.js +47 -0
  99. package/js/stepper.d.ts +2 -0
  100. package/js/stepper.js +7 -1
  101. package/js/table.d.ts +4 -0
  102. package/js/table.js +15 -22
  103. package/js/tabs.d.ts +2 -0
  104. package/js/tabs.js +6 -14
  105. package/js/theme.d.ts +1 -0
  106. package/js/theme.js +5 -13
  107. package/js/timepicker.d.ts +3 -0
  108. package/js/timepicker.js +81 -67
  109. package/js/toast.d.ts +3 -0
  110. package/js/toast.js +24 -15
  111. package/js/tooltip.d.ts +2 -0
  112. package/js/tooltip.js +21 -19
  113. package/js/tree.d.ts +3 -0
  114. package/js/tree.js +13 -0
  115. package/js/utils.d.ts +1 -3
  116. package/js/utils.js +0 -3
  117. package/js/virtual-dropdown.d.ts +3 -0
  118. package/js/virtual-dropdown.js +25 -0
  119. package/package.json +2 -2
@@ -1,49 +1,17 @@
1
1
  import { escapeHtml } from './utils.js';
2
+ /** Drag-and-drop file uploader with progress tracking and XHR-based uploads. */
2
3
  class FileUploader {
4
+ container;
5
+ dropZone;
6
+ fileInput;
7
+ fileList;
8
+ uploadBtn;
9
+ files = new Map();
10
+ uploadUrl;
11
+ maxFileSize;
12
+ allowedTypes;
13
+ abortControllers = new Map();
3
14
  constructor(elementOrSelector, config = {}) {
4
- this.files = new Map();
5
- this.abortControllers = new Map();
6
- this.preventDefaults = (e) => {
7
- e.preventDefault();
8
- e.stopPropagation();
9
- };
10
- this.handleDragEnter = () => {
11
- this.dropZone.classList.add('drag-over');
12
- };
13
- this.handleDragLeave = () => {
14
- this.dropZone.classList.remove('drag-over');
15
- };
16
- this.handleDrop = (e) => {
17
- const droppedFiles = e.dataTransfer?.files;
18
- if (droppedFiles) {
19
- this.handleFiles(droppedFiles);
20
- }
21
- };
22
- this.handleDropZoneClick = () => {
23
- this.fileInput.click();
24
- };
25
- this.handleFileInputChange = (e) => {
26
- const target = e.target;
27
- if (target.files) {
28
- this.handleFiles(target.files);
29
- target.value = '';
30
- }
31
- };
32
- this.handleUploadClick = async () => {
33
- if (this.files.size === 0)
34
- return;
35
- this.uploadBtn.disabled = true;
36
- this.uploadBtn.textContent = 'Uploading...';
37
- const uploadPromises = Array.from(this.files.values()).map(({ file, element }) => this.uploadFile(file, element));
38
- const results = await Promise.allSettled(uploadPromises);
39
- this.uploadBtn.textContent = 'Upload Complete';
40
- setTimeout(() => {
41
- this.dispatchUploadCompletedEvent(results);
42
- this.fileList.innerHTML = '';
43
- this.files.clear();
44
- this.updateUploadButton();
45
- }, 1500);
46
- };
47
15
  const container = typeof elementOrSelector === 'string'
48
16
  ? document.querySelector(elementOrSelector)
49
17
  : elementOrSelector;
@@ -89,6 +57,47 @@ class FileUploader {
89
57
  this.fileInput.addEventListener('change', this.handleFileInputChange);
90
58
  this.uploadBtn.addEventListener('click', this.handleUploadClick);
91
59
  }
60
+ preventDefaults = (e) => {
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ };
64
+ handleDragEnter = () => {
65
+ this.dropZone.classList.add('drag-over');
66
+ };
67
+ handleDragLeave = () => {
68
+ this.dropZone.classList.remove('drag-over');
69
+ };
70
+ handleDrop = (e) => {
71
+ const droppedFiles = e.dataTransfer?.files;
72
+ if (droppedFiles) {
73
+ this.handleFiles(droppedFiles);
74
+ }
75
+ };
76
+ handleDropZoneClick = () => {
77
+ this.fileInput.click();
78
+ };
79
+ handleFileInputChange = (e) => {
80
+ const target = e.target;
81
+ if (target.files) {
82
+ this.handleFiles(target.files);
83
+ target.value = '';
84
+ }
85
+ };
86
+ handleUploadClick = async () => {
87
+ if (this.files.size === 0)
88
+ return;
89
+ this.uploadBtn.disabled = true;
90
+ this.uploadBtn.textContent = 'Uploading...';
91
+ const uploadPromises = Array.from(this.files.values()).map(({ file, element }) => this.uploadFile(file, element));
92
+ const results = await Promise.allSettled(uploadPromises);
93
+ this.uploadBtn.textContent = 'Upload Complete';
94
+ setTimeout(() => {
95
+ this.dispatchUploadCompletedEvent(results);
96
+ this.fileList.innerHTML = '';
97
+ this.files.clear();
98
+ this.updateUploadButton();
99
+ }, 1500);
100
+ };
92
101
  handleFiles(fileList) {
93
102
  Array.from(fileList).forEach(file => {
94
103
  const key = this.fileKey(file);
@@ -1,3 +1,4 @@
1
+ /** Configuration options for the FlyoutMenu. */
1
2
  interface FlyoutMenuOptions {
2
3
  triggerSelector?: string;
3
4
  menuSelector?: string;
@@ -11,6 +12,7 @@ interface FlyoutMenuOptions {
11
12
  enableHeader?: boolean;
12
13
  enableFooter?: boolean;
13
14
  }
15
+ /** Off-canvas flyout navigation with nested submenu support. */
14
16
  declare class FlyoutMenu {
15
17
  private options;
16
18
  private menuTrigger;
@@ -19,7 +21,7 @@ declare class FlyoutMenu {
19
21
  private closeBtn;
20
22
  private submenuToggles;
21
23
  private menuLinks;
22
- private submenuHandlers;
24
+ private abortController;
23
25
  constructor(options?: FlyoutMenuOptions);
24
26
  private init;
25
27
  private hydrateMenu;
@@ -27,8 +29,8 @@ declare class FlyoutMenu {
27
29
  private renderHeader;
28
30
  private renderFooter;
29
31
  private bindEvents;
30
- private open;
31
- private close;
32
+ open: () => void;
33
+ close: () => void;
32
34
  private handleSubmenu;
33
35
  private handleKeydown;
34
36
  setDirection(direction: 'left' | 'right'): void;
package/js/flyout-menu.js CHANGED
@@ -1,9 +1,14 @@
1
+ /** Off-canvas flyout navigation with nested submenu support. */
1
2
  class FlyoutMenu {
3
+ options;
4
+ menuTrigger;
5
+ flyoutMenu;
6
+ flyoutOverlay;
7
+ closeBtn = null;
8
+ submenuToggles = null;
9
+ menuLinks = null;
10
+ abortController = new AbortController();
2
11
  constructor(options = {}) {
3
- this.closeBtn = null;
4
- this.submenuToggles = null;
5
- this.menuLinks = null;
6
- this.submenuHandlers = new Map();
7
12
  this.options = {
8
13
  triggerSelector: '.menu-trigger',
9
14
  menuSelector: '#flyoutMenu',
@@ -21,10 +26,6 @@ class FlyoutMenu {
21
26
  this.menuTrigger = document.querySelector(this.options.triggerSelector);
22
27
  this.flyoutMenu = document.querySelector(this.options.menuSelector);
23
28
  this.flyoutOverlay = document.querySelector(this.options.overlaySelector);
24
- this.open = this.open.bind(this);
25
- this.close = this.close.bind(this);
26
- this.handleSubmenu = this.handleSubmenu.bind(this);
27
- this.handleKeydown = this.handleKeydown.bind(this);
28
29
  this.init();
29
30
  }
30
31
  init() {
@@ -54,16 +55,13 @@ class FlyoutMenu {
54
55
  processListItems(ul) {
55
56
  const items = Array.from(ul.children);
56
57
  items.forEach((li, index) => {
57
- // Check if it has a nested UL
58
58
  const nestedUl = li.querySelector('ul');
59
59
  if (nestedUl) {
60
60
  li.classList.add('has-submenu');
61
61
  nestedUl.classList.add('submenu');
62
- // Get text content (excluding nested UL text)
63
62
  const textNode = Array.from(li.childNodes).find(node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim() !== '');
64
63
  const text = textNode?.textContent?.trim() || 'Menu Item';
65
64
  textNode?.remove();
66
- // Create Toggle Button
67
65
  const button = document.createElement('button');
68
66
  button.className = 'submenu-toggle';
69
67
  button.style.setProperty('--delay', `${(index + 1) * 0.1}s`);
@@ -74,11 +72,9 @@ class FlyoutMenu {
74
72
  </svg>
75
73
  `;
76
74
  li.insertBefore(button, nestedUl);
77
- // Recursively process nested UL
78
75
  this.processListItems(nestedUl);
79
76
  }
80
77
  else {
81
- // Leaf node - ensure it has a link
82
78
  const link = li.querySelector('a');
83
79
  if (link) {
84
80
  link.style.setProperty('--delay', `${(index + 1) * 0.1}s`);
@@ -113,36 +109,30 @@ class FlyoutMenu {
113
109
  this.flyoutMenu.append(footer);
114
110
  }
115
111
  bindEvents() {
116
- // Open
117
- this.menuTrigger?.addEventListener('click', this.open);
118
- // Close
119
- this.closeBtn?.addEventListener('click', this.close);
120
- this.flyoutOverlay?.addEventListener('click', this.close);
121
- // Submenus
112
+ const sig = { signal: this.abortController.signal };
113
+ this.menuTrigger?.addEventListener('click', this.open, sig);
114
+ this.closeBtn?.addEventListener('click', this.close, sig);
115
+ this.flyoutOverlay?.addEventListener('click', this.close, sig);
122
116
  this.submenuToggles?.forEach(toggle => {
123
- const handler = (e) => this.handleSubmenu(e, toggle);
124
- this.submenuHandlers.set(toggle, handler);
125
- toggle.addEventListener('click', handler);
117
+ toggle.addEventListener('click', (e) => this.handleSubmenu(e, toggle), sig);
126
118
  });
127
- // Close on Link Click
128
119
  this.menuLinks?.forEach(link => {
129
- link.addEventListener('click', this.close);
120
+ link.addEventListener('click', this.close, sig);
130
121
  });
131
- // Keyboard navigation
132
- document.addEventListener('keydown', this.handleKeydown);
122
+ document.addEventListener('keydown', this.handleKeydown, sig);
133
123
  }
134
- open() {
124
+ open = () => {
135
125
  this.flyoutMenu?.classList.add('is-open');
136
126
  this.flyoutOverlay?.classList.add('is-visible');
137
127
  document.body.style.overflow = 'hidden';
138
128
  this.menuTrigger?.setAttribute('aria-expanded', 'true');
139
- }
140
- close() {
129
+ };
130
+ close = () => {
141
131
  this.flyoutMenu?.classList.remove('is-open');
142
132
  this.flyoutOverlay?.classList.remove('is-visible');
143
133
  document.body.style.overflow = '';
144
134
  this.menuTrigger?.setAttribute('aria-expanded', 'false');
145
- }
135
+ };
146
136
  handleSubmenu(e, toggle) {
147
137
  e.preventDefault();
148
138
  e.stopPropagation();
@@ -151,7 +141,6 @@ class FlyoutMenu {
151
141
  const parentUl = parentLi?.parentElement;
152
142
  if (!parentUl || !parentLi)
153
143
  return;
154
- // Close other submenus at the same level
155
144
  const siblings = Array.from(parentUl.children);
156
145
  siblings.forEach(sibling => {
157
146
  if (sibling !== parentLi) {
@@ -166,11 +155,11 @@ class FlyoutMenu {
166
155
  toggle.classList.toggle('active');
167
156
  submenu?.classList.toggle('is-open');
168
157
  }
169
- handleKeydown(e) {
158
+ handleKeydown = (e) => {
170
159
  if (e.key === 'Escape' && this.flyoutMenu?.classList.contains('is-open')) {
171
160
  this.close();
172
161
  }
173
- }
162
+ };
174
163
  setDirection(direction) {
175
164
  if (!this.flyoutMenu)
176
165
  return;
@@ -182,19 +171,7 @@ class FlyoutMenu {
182
171
  this.options.direction = direction;
183
172
  }
184
173
  destroy() {
185
- this.menuTrigger?.removeEventListener('click', this.open);
186
- this.closeBtn?.removeEventListener('click', this.close);
187
- this.flyoutOverlay?.removeEventListener('click', this.close);
188
- this.submenuToggles?.forEach(toggle => {
189
- const handler = this.submenuHandlers.get(toggle);
190
- if (handler)
191
- toggle.removeEventListener('click', handler);
192
- });
193
- this.submenuHandlers.clear();
194
- this.menuLinks?.forEach(link => {
195
- link.removeEventListener('click', this.close);
196
- });
197
- document.removeEventListener('keydown', this.handleKeydown);
174
+ this.abortController.abort();
198
175
  document.body.style.overflow = '';
199
176
  }
200
177
  }
package/js/gallery.d.ts CHANGED
@@ -1,20 +1,25 @@
1
+ /** A single image record for MasonryGallery. */
1
2
  interface ImageData {
2
3
  src: string;
3
4
  title: string;
4
5
  desc: string;
5
6
  }
7
+ /** Configuration options for MasonryGallery. */
6
8
  interface MasonryGalleryOptions {
7
9
  fetchFunction: () => Promise<ImageData[]>;
8
10
  minColumnWidth?: number;
9
11
  scrollThreshold?: number;
10
12
  loaderSelector?: string;
11
13
  reload?: number;
14
+ enableLightbox?: boolean;
12
15
  }
16
+ /** Infinite-scroll masonry gallery that distributes images across dynamically sized columns. */
13
17
  declare class MasonryGallery {
14
18
  private container;
15
19
  private readonly loader;
16
20
  private options;
17
21
  private columns;
22
+ private allImages;
18
23
  private isFetching;
19
24
  private resizeObserver;
20
25
  private abortController;
package/js/gallery.js CHANGED
@@ -1,19 +1,17 @@
1
1
  import { escapeHtml } from './utils.js';
2
+ import { Lightbox } from './lightbox.js';
3
+ /** Infinite-scroll masonry gallery that distributes images across dynamically sized columns. */
2
4
  class MasonryGallery {
5
+ container;
6
+ loader;
7
+ options;
8
+ columns = [];
9
+ allImages = [];
10
+ isFetching = false;
11
+ resizeObserver = null;
12
+ abortController = null;
13
+ reloaded = 0;
3
14
  constructor(containerId, options) {
4
- this.columns = [];
5
- this.isFetching = false;
6
- this.resizeObserver = null;
7
- this.abortController = null;
8
- this.reloaded = 0;
9
- this.handleScroll = () => {
10
- if (this.isFetching)
11
- return;
12
- const rect = this.container.getBoundingClientRect();
13
- if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
14
- this.loadMoreImages();
15
- }
16
- };
17
15
  const container = document.getElementById(containerId);
18
16
  if (!container) {
19
17
  throw new Error(`Container with id "${containerId}" not found`);
@@ -25,6 +23,7 @@ class MasonryGallery {
25
23
  scrollThreshold: options.scrollThreshold ?? 100,
26
24
  reload: 2,
27
25
  fetchFunction: options.fetchFunction,
26
+ enableLightbox: options.enableLightbox ?? true,
28
27
  };
29
28
  this.init();
30
29
  }
@@ -42,6 +41,7 @@ class MasonryGallery {
42
41
  }
43
42
  buildColumns(count) {
44
43
  this.container.innerHTML = '';
44
+ this.container.classList.add('masonry-container');
45
45
  this.columns = [];
46
46
  for (let i = 0; i < count; i++) {
47
47
  const col = document.createElement('div');
@@ -51,18 +51,14 @@ class MasonryGallery {
51
51
  }
52
52
  }
53
53
  addEventListeners() {
54
+ this.abortController = new AbortController();
55
+ const sig = this.abortController.signal;
54
56
  let resizeTimeout;
55
57
  window.addEventListener("resize", () => {
56
58
  clearTimeout(resizeTimeout);
57
- resizeTimeout = setTimeout(() => {
58
- this.reLayout();
59
- }, 200);
60
- });
61
- this.abortController = new AbortController();
62
- window.addEventListener("scroll", this.handleScroll, {
63
- passive: true,
64
- signal: this.abortController.signal,
65
- });
59
+ resizeTimeout = setTimeout(() => this.reLayout(), 200);
60
+ }, { signal: sig });
61
+ window.addEventListener("scroll", this.handleScroll, { passive: true, signal: sig });
66
62
  }
67
63
  reLayout() {
68
64
  const items = this.columns.flatMap(col => Array.from(col.children));
@@ -76,6 +72,14 @@ class MasonryGallery {
76
72
  }
77
73
  items.forEach(item => this.addToShortestColumn(item));
78
74
  }
75
+ handleScroll = () => {
76
+ if (this.isFetching)
77
+ return;
78
+ const rect = this.container.getBoundingClientRect();
79
+ if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
80
+ this.loadMoreImages();
81
+ }
82
+ };
79
83
  async loadMoreImages(isAutoFill = false) {
80
84
  if (!isAutoFill)
81
85
  this.reloaded++;
@@ -97,8 +101,6 @@ class MasonryGallery {
97
101
  finally {
98
102
  this.isFetching = false;
99
103
  this.toggleLoader(false);
100
- // If the rendered content doesn't fill the viewport, auto-load the next
101
- // batch without waiting for a scroll event (multi-column layout is shorter)
102
104
  requestAnimationFrame(() => {
103
105
  const rect = this.container.getBoundingClientRect();
104
106
  if (rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
@@ -113,14 +115,29 @@ class MasonryGallery {
113
115
  }
114
116
  }
115
117
  renderImages(imageDataList) {
118
+ const startIndex = this.allImages.length;
119
+ this.allImages.push(...imageDataList);
116
120
  // Sort columns by current height so we start filling from the shortest.
117
121
  // Then round-robin across them — this avoids the problem where unloaded
118
122
  // images (0 height) cause offsetHeight-based distribution to pile all
119
123
  // new items into a single column.
120
124
  const sorted = [...this.columns].sort((a, b) => a.offsetHeight - b.offsetHeight);
121
- imageDataList.forEach((data, index) => {
125
+ imageDataList.forEach((data, i) => {
122
126
  const item = this.createCard(data);
123
- const col = sorted[index % sorted.length];
127
+ if (this.options.enableLightbox) {
128
+ const index = startIndex + i;
129
+ item.addEventListener('click', () => {
130
+ new Lightbox({
131
+ images: this.allImages.map(img => ({
132
+ src: img.src,
133
+ alt: img.title,
134
+ caption: img.desc,
135
+ })),
136
+ startIndex: index,
137
+ }).show();
138
+ });
139
+ }
140
+ const col = sorted[i % sorted.length];
124
141
  col.appendChild(item);
125
142
  requestAnimationFrame(() => {
126
143
  const img = item.querySelector("img");
@@ -170,6 +187,7 @@ class MasonryGallery {
170
187
  this.abortController.abort();
171
188
  this.abortController = null;
172
189
  }
190
+ this.allImages = [];
173
191
  }
174
192
  }
175
193
  export { MasonryGallery };
@@ -1,12 +1,15 @@
1
+ /** A single subgroup item within a GroupPicker group. */
1
2
  interface SubgroupData {
2
3
  id: string;
3
4
  label: string;
4
5
  }
6
+ /** A group with an optional list of subgroups for GroupPicker. */
5
7
  interface GroupData {
6
8
  id: string;
7
9
  label: string;
8
10
  subgroups?: SubgroupData[];
9
11
  }
12
+ /** The current selection state returned by GroupPicker. */
10
13
  interface GroupPickerSelection {
11
14
  parentGroups: string[];
12
15
  subgroups: {
@@ -14,6 +17,7 @@ interface GroupPickerSelection {
14
17
  subgroupId: string;
15
18
  }[];
16
19
  }
20
+ /** Configuration options for the GroupPicker component. */
17
21
  interface GroupPickerOptions {
18
22
  onSelectionChange?: (selection: GroupPickerSelection) => void;
19
23
  searchPlaceholder?: string;
@@ -22,6 +26,7 @@ interface GroupPickerOptions {
22
26
  emptyLabel?: string;
23
27
  selectionPlaceholder?: string;
24
28
  }
29
+ /** Searchable picker for selecting groups and their subgroups. */
25
30
  declare class GroupPicker {
26
31
  private container;
27
32
  private data;
@@ -1,11 +1,18 @@
1
1
  import { escapeHtml } from './utils.js';
2
+ /** Searchable picker for selecting groups and their subgroups. */
2
3
  class GroupPicker {
4
+ container;
5
+ data;
6
+ options;
7
+ abortController;
8
+ selectedParents = new Set();
9
+ selectedSubs = new Map();
10
+ expandedGroups = new Set();
11
+ searchQuery = '';
12
+ searchInput;
13
+ listEl;
14
+ selectionEl;
3
15
  constructor(selector, data, options = {}) {
4
- // State
5
- this.selectedParents = new Set();
6
- this.selectedSubs = new Map();
7
- this.expandedGroups = new Set();
8
- this.searchQuery = '';
9
16
  const el = typeof selector === 'string'
10
17
  ? document.querySelector(selector)
11
18
  : selector;
@@ -31,11 +38,9 @@ class GroupPicker {
31
38
  }
32
39
  render() {
33
40
  this.container.innerHTML = '';
34
- // Selection summary — Basix .chips container
35
41
  this.selectionEl = document.createElement('div');
36
42
  this.selectionEl.className = 'chips group-picker__selection';
37
43
  this.selectionEl.dataset.placeholder = this.options.selectionPlaceholder;
38
- // Search — Basix form input with font icon overlay
39
44
  const searchWrap = document.createElement('div');
40
45
  searchWrap.className = 'group-picker__search';
41
46
  searchWrap.innerHTML = `
@@ -44,7 +49,6 @@ class GroupPicker {
44
49
  `;
45
50
  this.searchInput = searchWrap.querySelector('input');
46
51
  this.searchInput.placeholder = this.options.searchPlaceholder;
47
- // List
48
52
  this.listEl = document.createElement('div');
49
53
  this.listEl.className = 'group-picker__list';
50
54
  this.container.append(this.selectionEl, searchWrap, this.listEl);
@@ -99,15 +103,12 @@ class GroupPicker {
99
103
  ? this.highlightText(group.label, query)
100
104
  : escapeHtml(group.label);
101
105
  if (hasChildren) {
102
- // Chevron — Basix font icon
103
106
  const chevron = document.createElement('span');
104
107
  chevron.className = 'icon icon-navigate_next group-picker__chevron';
105
108
  chevron.setAttribute('aria-hidden', 'true');
106
- // Count — Basix badge
107
109
  const count = document.createElement('span');
108
110
  count.className = 'badge badge-sm';
109
111
  count.textContent = `${subs.length}`;
110
- // Action button — Basix button, button-primary when selected
111
112
  const actionBtn = document.createElement('button');
112
113
  actionBtn.className = 'group-picker__group-action';
113
114
  if (isParentSelected) {
@@ -125,14 +126,12 @@ class GroupPicker {
125
126
  header.addEventListener('click', () => {
126
127
  this.toggleExpand(group.id);
127
128
  }, { signal: this.abortController.signal });
128
- // Subgroups — Basix .chips container
129
129
  const subsContainer = document.createElement('div');
130
130
  subsContainer.className = 'group-picker__subgroups';
131
131
  const subsList = document.createElement('div');
132
132
  subsList.className = 'chips group-picker__subgroup-list';
133
133
  const displaySubs = query && !groupMatches ? matchingSubs : subs;
134
134
  for (const sub of displaySubs) {
135
- // Subgroup chip — Basix .chip.clickable
136
135
  const subEl = document.createElement('span');
137
136
  subEl.className = 'chip clickable group-picker__subgroup';
138
137
  subEl.dataset.subId = sub.id;
@@ -162,7 +161,6 @@ class GroupPicker {
162
161
  }
163
162
  }
164
163
  else {
165
- // Leaf group — Basix font icon check mark
166
164
  const checkEl = document.createElement('span');
167
165
  checkEl.className = 'icon icon-check group-picker__leaf-check';
168
166
  checkEl.setAttribute('aria-hidden', 'true');
@@ -194,7 +192,6 @@ class GroupPicker {
194
192
  }
195
193
  }
196
194
  }
197
- // Basix .chip.closeable structure
198
195
  createChip(label, isParent, onRemove) {
199
196
  const chip = document.createElement('span');
200
197
  chip.className = isParent
@@ -210,7 +207,6 @@ class GroupPicker {
210
207
  chip.append(document.createTextNode(label), btn);
211
208
  return chip;
212
209
  }
213
- // State management
214
210
  toggleParentGroup(groupId) {
215
211
  if (this.selectedParents.has(groupId)) {
216
212
  this.selectedParents.delete(groupId);
@@ -299,7 +295,6 @@ class GroupPicker {
299
295
  const regex = new RegExp(`(${escapedQuery})`, 'gi');
300
296
  return safeText.replace(regex, '<mark>$1</mark>');
301
297
  }
302
- // Public API
303
298
  getSelection() {
304
299
  const parentGroups = [...this.selectedParents];
305
300
  const subgroups = [];
package/js/lightbox.d.ts CHANGED
@@ -1,8 +1,10 @@
1
+ /** A single image entry for the Lightbox gallery. */
1
2
  interface LightboxImage {
2
3
  src: string;
3
4
  alt?: string;
4
5
  caption?: string;
5
6
  }
7
+ /** Configuration options for a Lightbox instance. */
6
8
  interface LightboxOptions {
7
9
  src?: string;
8
10
  alt?: string;
@@ -13,6 +15,7 @@ interface LightboxOptions {
13
15
  onOpen?: () => void;
14
16
  onClose?: () => void;
15
17
  }
18
+ /** Full-screen image viewer with gallery navigation, zoom, and touch support. */
16
19
  declare class Lightbox {
17
20
  private images;
18
21
  private currentIndex;
package/js/lightbox.js CHANGED
@@ -1,11 +1,17 @@
1
+ /** Full-screen image viewer with gallery navigation, zoom, and touch support. */
1
2
  class Lightbox {
3
+ images;
4
+ currentIndex;
5
+ closeable;
6
+ onOpen;
7
+ onClose;
8
+ wrapper = null;
9
+ imgEl = null;
10
+ captionEl = null;
11
+ counterEl = null;
12
+ isZoomed = false;
13
+ abortController = new AbortController();
2
14
  constructor(options) {
3
- this.wrapper = null;
4
- this.imgEl = null;
5
- this.captionEl = null;
6
- this.counterEl = null;
7
- this.isZoomed = false;
8
- this.abortController = new AbortController();
9
15
  if (options.images && options.images.length > 0) {
10
16
  this.images = options.images;
11
17
  }
package/js/modal.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  type ModalType = 'default' | 'success' | 'error' | 'warning' | 'info';
2
+ /** Configuration options for a Modal dialog. */
2
3
  interface ModalOptions {
3
4
  content: string;
4
5
  header?: string;
@@ -6,6 +7,7 @@ interface ModalOptions {
6
7
  closeable?: boolean;
7
8
  type?: ModalType;
8
9
  }
10
+ /** Overlay dialog with optional header, footer, close button, and type variants. */
9
11
  declare class Modal {
10
12
  private content;
11
13
  private readonly header?;
@@ -17,7 +19,7 @@ declare class Modal {
17
19
  constructor(options: ModalOptions);
18
20
  constructor(content: string, header?: string, footer?: string, closeable?: boolean, type?: ModalType);
19
21
  show(): void;
20
- hide(): void;
22
+ hide: () => void;
21
23
  private handleEscape;
22
24
  private handleBackgroundClick;
23
25
  private buildTemplate;