@dodlhuat/basix 1.3.1 → 1.3.3

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 (118) hide show
  1. package/README.md +14 -8
  2. package/css/accordion.scss +0 -5
  3. package/css/badge.scss +1 -6
  4. package/css/bottom-sheet.scss +3 -8
  5. package/css/breadcrumb.scss +6 -15
  6. package/css/button.scss +2 -1
  7. package/css/calendar.scss +0 -67
  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 +3 -7
  14. package/css/code-viewer.scss +1 -5
  15. package/css/context-menu.scss +5 -21
  16. package/css/datepicker.scss +6 -9
  17. package/css/docs.scss +0 -4
  18. package/css/dropdown.scss +1 -1
  19. package/css/editor.scss +1 -23
  20. package/css/file-uploader.scss +2 -2
  21. package/css/flyout-menu.scss +65 -58
  22. package/css/form.scss +0 -28
  23. package/css/gallery.scss +2 -3
  24. package/css/group-picker.scss +5 -35
  25. package/css/icons.scss +0 -3
  26. package/css/lightbox.scss +2 -4
  27. package/css/mixins.scss +8 -0
  28. package/css/modal.scss +3 -3
  29. package/css/parameters.scss +6 -1
  30. package/css/popover.scss +3 -15
  31. package/css/progress.scss +0 -6
  32. package/css/push-menu.scss +3 -28
  33. package/css/radiobutton.scss +2 -1
  34. package/css/range-slider.scss +1 -7
  35. package/css/scrollbar.scss +9 -16
  36. package/css/sidebar-nav.scss +0 -12
  37. package/css/stepper.scss +0 -4
  38. package/css/style.css +108 -116
  39. package/css/style.css.map +1 -1
  40. package/css/style.min.css +1 -1
  41. package/css/style.min.css.map +1 -1
  42. package/css/style.scss +1 -1
  43. package/css/table.scss +0 -4
  44. package/css/tabs.scss +0 -2
  45. package/css/timeline.scss +1 -13
  46. package/css/timepicker.scss +55 -39
  47. package/css/toast.scss +1 -1
  48. package/css/tooltip.scss +1 -5
  49. package/css/tree.scss +1 -1
  50. package/css/typography.scss +3 -3
  51. package/css/virtual-dropdown.scss +3 -28
  52. package/js/bottom-sheet.d.ts +3 -1
  53. package/js/bottom-sheet.js +26 -27
  54. package/js/calendar.d.ts +7 -0
  55. package/js/calendar.js +14 -33
  56. package/js/carousel.d.ts +2 -0
  57. package/js/carousel.js +13 -5
  58. package/js/chart.d.ts +4 -0
  59. package/js/chart.js +13 -31
  60. package/js/code-viewer.d.ts +1 -0
  61. package/js/code-viewer.js +4 -0
  62. package/js/context-menu.d.ts +9 -2
  63. package/js/context-menu.js +17 -14
  64. package/js/datepicker.d.ts +4 -0
  65. package/js/datepicker.js +26 -11
  66. package/js/dropdown.d.ts +3 -3
  67. package/js/dropdown.js +6 -9
  68. package/js/editor.d.ts +1 -1
  69. package/js/editor.js +14 -20
  70. package/js/file-uploader.d.ts +4 -0
  71. package/js/file-uploader.js +52 -43
  72. package/js/flyout-menu.d.ts +5 -3
  73. package/js/flyout-menu.js +23 -46
  74. package/js/gallery.d.ts +4 -0
  75. package/js/gallery.js +39 -50
  76. package/js/group-picker.d.ts +5 -0
  77. package/js/group-picker.js +12 -17
  78. package/js/lightbox.d.ts +3 -0
  79. package/js/lightbox.js +12 -6
  80. package/js/modal.d.ts +3 -1
  81. package/js/modal.js +14 -11
  82. package/js/popover.d.ts +2 -0
  83. package/js/popover.js +26 -30
  84. package/js/position.d.ts +2 -0
  85. package/js/position.js +1 -5
  86. package/js/push-menu.d.ts +2 -1
  87. package/js/push-menu.js +25 -48
  88. package/js/range-slider.d.ts +1 -0
  89. package/js/range-slider.js +5 -3
  90. package/js/scroll.d.ts +2 -0
  91. package/js/scroll.js +1 -0
  92. package/js/scrollbar.d.ts +2 -0
  93. package/js/scrollbar.js +24 -36
  94. package/js/select.d.ts +1 -0
  95. package/js/select.js +5 -10
  96. package/js/sidebar-nav.d.ts +2 -0
  97. package/js/sidebar-nav.js +8 -0
  98. package/js/stepper.d.ts +2 -0
  99. package/js/stepper.js +7 -1
  100. package/js/table.d.ts +4 -0
  101. package/js/table.js +15 -22
  102. package/js/tabs.d.ts +2 -0
  103. package/js/tabs.js +6 -14
  104. package/js/theme.d.ts +1 -0
  105. package/js/theme.js +5 -13
  106. package/js/timepicker.d.ts +22 -5
  107. package/js/timepicker.js +160 -57
  108. package/js/toast.d.ts +3 -1
  109. package/js/toast.js +25 -22
  110. package/js/tooltip.d.ts +2 -0
  111. package/js/tooltip.js +21 -19
  112. package/js/tree.d.ts +3 -0
  113. package/js/tree.js +13 -0
  114. package/js/utils.d.ts +1 -3
  115. package/js/utils.js +0 -3
  116. package/js/virtual-dropdown.d.ts +3 -0
  117. package/js/virtual-dropdown.js +25 -0
  118. package/package.json +2 -2
package/js/gallery.d.ts CHANGED
@@ -1,8 +1,10 @@
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;
@@ -10,6 +12,7 @@ interface MasonryGalleryOptions {
10
12
  loaderSelector?: string;
11
13
  reload?: number;
12
14
  }
15
+ /** Infinite-scroll masonry gallery that distributes images across dynamically sized columns. */
13
16
  declare class MasonryGallery {
14
17
  private container;
15
18
  private readonly loader;
@@ -22,6 +25,7 @@ declare class MasonryGallery {
22
25
  constructor(containerId: string, options: MasonryGalleryOptions);
23
26
  private init;
24
27
  private setupLayout;
28
+ private buildColumns;
25
29
  private addEventListeners;
26
30
  private reLayout;
27
31
  private handleScroll;
package/js/gallery.js CHANGED
@@ -1,19 +1,15 @@
1
1
  import { escapeHtml } from './utils.js';
2
+ /** Infinite-scroll masonry gallery that distributes images across dynamically sized columns. */
2
3
  class MasonryGallery {
4
+ container;
5
+ loader;
6
+ options;
7
+ columns = [];
8
+ isFetching = false;
9
+ resizeObserver = null;
10
+ abortController = null;
11
+ reloaded = 0;
3
12
  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
13
  const container = document.getElementById(containerId);
18
14
  if (!container) {
19
15
  throw new Error(`Container with id "${containerId}" not found`);
@@ -37,54 +33,49 @@ class MasonryGallery {
37
33
  const containerWidth = this.container.getBoundingClientRect().width;
38
34
  const numColumns = Math.max(1, Math.floor(containerWidth / this.options.minColumnWidth));
39
35
  if (this.columns.length !== numColumns) {
40
- this.container.innerHTML = "";
41
- this.columns = [];
42
- for (let i = 0; i < numColumns; i++) {
43
- const col = document.createElement("div");
44
- col.className = "masonry-column";
45
- this.container.appendChild(col);
46
- this.columns.push(col);
47
- }
36
+ this.buildColumns(numColumns);
37
+ }
38
+ }
39
+ buildColumns(count) {
40
+ this.container.innerHTML = '';
41
+ this.columns = [];
42
+ for (let i = 0; i < count; i++) {
43
+ const col = document.createElement('div');
44
+ col.className = 'masonry-column';
45
+ this.container.appendChild(col);
46
+ this.columns.push(col);
48
47
  }
49
48
  }
50
49
  addEventListeners() {
50
+ this.abortController = new AbortController();
51
+ const sig = this.abortController.signal;
51
52
  let resizeTimeout;
52
53
  window.addEventListener("resize", () => {
53
54
  clearTimeout(resizeTimeout);
54
- resizeTimeout = setTimeout(() => {
55
- this.reLayout();
56
- }, 200);
57
- });
58
- this.abortController = new AbortController();
59
- window.addEventListener("scroll", this.handleScroll, {
60
- passive: true,
61
- signal: this.abortController.signal,
62
- });
55
+ resizeTimeout = setTimeout(() => this.reLayout(), 200);
56
+ }, { signal: sig });
57
+ window.addEventListener("scroll", this.handleScroll, { passive: true, signal: sig });
63
58
  }
64
59
  reLayout() {
65
- const items = [];
66
- this.columns.forEach((col) => {
67
- Array.from(col.children).forEach((child) => {
68
- items.push(child);
69
- });
70
- col.innerHTML = "";
71
- });
60
+ const items = this.columns.flatMap(col => Array.from(col.children));
72
61
  const availableWidth = Math.min(1200, window.innerWidth - 40);
73
62
  const numColumns = Math.max(1, Math.floor(availableWidth / this.options.minColumnWidth));
74
63
  if (this.columns.length !== numColumns) {
75
- this.container.innerHTML = "";
76
- this.columns = [];
77
- for (let i = 0; i < numColumns; i++) {
78
- const col = document.createElement("div");
79
- col.className = "masonry-column";
80
- this.container.appendChild(col);
81
- this.columns.push(col);
82
- }
64
+ this.buildColumns(numColumns);
83
65
  }
84
- items.forEach((item) => {
85
- this.addToShortestColumn(item);
86
- });
66
+ else {
67
+ this.columns.forEach(col => { col.innerHTML = ''; });
68
+ }
69
+ items.forEach(item => this.addToShortestColumn(item));
87
70
  }
71
+ handleScroll = () => {
72
+ if (this.isFetching)
73
+ return;
74
+ const rect = this.container.getBoundingClientRect();
75
+ if (rect.bottom > 0 && rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
76
+ this.loadMoreImages();
77
+ }
78
+ };
88
79
  async loadMoreImages(isAutoFill = false) {
89
80
  if (!isAutoFill)
90
81
  this.reloaded++;
@@ -106,8 +97,6 @@ class MasonryGallery {
106
97
  finally {
107
98
  this.isFetching = false;
108
99
  this.toggleLoader(false);
109
- // If the rendered content doesn't fill the viewport, auto-load the next
110
- // batch without waiting for a scroll event (multi-column layout is shorter)
111
100
  requestAnimationFrame(() => {
112
101
  const rect = this.container.getBoundingClientRect();
113
102
  if (rect.bottom <= window.innerHeight + this.options.scrollThreshold) {
@@ -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;
package/js/modal.js CHANGED
@@ -1,8 +1,15 @@
1
1
  import { sanitizeHtml } from './utils.js';
2
2
  const CLOSE_ICON = '<div class="icon icon-close close"></div>';
3
+ /** Overlay dialog with optional header, footer, close button, and type variants. */
3
4
  class Modal {
5
+ content;
6
+ header;
7
+ footer;
8
+ closeable;
9
+ type;
10
+ template;
11
+ modalWrapper = null;
4
12
  constructor(contentOrOptions, header, footer, closeable = true, type = 'default') {
5
- this.modalWrapper = null;
6
13
  if (typeof contentOrOptions === 'object') {
7
14
  this.content = contentOrOptions.content;
8
15
  this.header = contentOrOptions.header;
@@ -18,9 +25,6 @@ class Modal {
18
25
  this.type = type;
19
26
  }
20
27
  this.template = this.buildTemplate();
21
- this.hide = this.hide.bind(this);
22
- this.handleEscape = this.handleEscape.bind(this);
23
- this.handleBackgroundClick = this.handleBackgroundClick.bind(this);
24
28
  }
25
29
  show() {
26
30
  this.hide();
@@ -45,11 +49,10 @@ class Modal {
45
49
  wrapper.classList.add('is-visible');
46
50
  });
47
51
  }
48
- hide() {
52
+ hide = () => {
49
53
  const wrapper = this.modalWrapper;
50
54
  if (!wrapper)
51
55
  return;
52
- // Remove event listeners
53
56
  const closeBtn = wrapper.querySelector('.close');
54
57
  closeBtn?.removeEventListener('click', this.hide);
55
58
  const background = wrapper.querySelector('.modal-background');
@@ -63,17 +66,17 @@ class Modal {
63
66
  this.modalWrapper = null;
64
67
  }
65
68
  }, 300);
66
- }
67
- handleEscape(e) {
69
+ };
70
+ handleEscape = (e) => {
68
71
  if (e.key === 'Escape') {
69
72
  this.hide();
70
73
  }
71
- }
72
- handleBackgroundClick(e) {
74
+ };
75
+ handleBackgroundClick = (e) => {
73
76
  if (e.target?.classList.contains('modal-background')) {
74
77
  this.hide();
75
78
  }
76
- }
79
+ };
77
80
  buildTemplate() {
78
81
  const parts = ['<div class="modal">'];
79
82
  if (this.closeable) {
package/js/popover.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { Placement } from './position.js';
2
2
  type PopoverPlacement = Placement | 'auto';
3
3
  type PopoverAlign = 'start' | 'center' | 'end';
4
4
  type PopoverTrigger = 'click' | 'hover';
5
+ /** Configuration options for a Popover instance. */
5
6
  interface PopoverOptions {
6
7
  content: string;
7
8
  placement?: PopoverPlacement;
@@ -15,6 +16,7 @@ interface PopoverOptions {
15
16
  onOpen?: () => void;
16
17
  onClose?: () => void;
17
18
  }
19
+ /** Anchored popover triggered by click or hover, with auto-placement and optional arrow. */
18
20
  declare class Popover {
19
21
  private static openPopovers;
20
22
  private static idCounter;
package/js/popover.js CHANGED
@@ -1,31 +1,16 @@
1
1
  import { computePosition } from './position.js';
2
2
  import { sanitizeHtml } from './utils.js';
3
- // Must match $arrow in popover.scss
4
3
  const ARROW_SIZE = 6;
4
+ /** Anchored popover triggered by click or hover, with auto-placement and optional arrow. */
5
5
  class Popover {
6
+ static openPopovers = new Set();
7
+ static idCounter = 0;
8
+ trigger;
9
+ opts;
10
+ popoverEl = null;
11
+ _isOpen = false;
12
+ hoverTimer = null;
6
13
  constructor(triggerEl, options) {
7
- this.popoverEl = null;
8
- this._isOpen = false;
9
- this.hoverTimer = null;
10
- // ── Event handlers ─────────────────────────────────────────────────────────
11
- this.onClick = () => { this.toggle(); };
12
- this.onMouseEnter = () => {
13
- if (this.hoverTimer !== null)
14
- clearTimeout(this.hoverTimer);
15
- this.open();
16
- };
17
- this.onMouseLeave = () => {
18
- this.hoverTimer = window.setTimeout(() => this.close(), 120);
19
- };
20
- this.onOutsideClick = (e) => {
21
- const t = e.target;
22
- if (!this.popoverEl?.contains(t) && !this.trigger.contains(t))
23
- this.close();
24
- };
25
- this.onEscape = (e) => {
26
- if (e.key === 'Escape')
27
- this.close();
28
- };
29
14
  const el = typeof triggerEl === 'string'
30
15
  ? document.querySelector(triggerEl)
31
16
  : triggerEl;
@@ -47,7 +32,6 @@ class Popover {
47
32
  };
48
33
  this.attachTrigger();
49
34
  }
50
- // ── Public API ─────────────────────────────────────────────────────────────
51
35
  get isOpen() { return this._isOpen; }
52
36
  open() {
53
37
  if (this._isOpen)
@@ -109,7 +93,6 @@ class Popover {
109
93
  });
110
94
  });
111
95
  }
112
- // ── Build ──────────────────────────────────────────────────────────────────
113
96
  buildEl() {
114
97
  const id = `popover-${++Popover.idCounter}`;
115
98
  const el = document.createElement('div');
@@ -117,8 +100,6 @@ class Popover {
117
100
  el.id = id;
118
101
  el.setAttribute('role', 'dialog');
119
102
  el.setAttribute('data-arrow', String(this.opts.arrow));
120
- // Wrap plain content in .popover-body so it gets proper padding.
121
- // Skip wrapping when content already uses structured popover elements.
122
103
  const hasStructure = /class="popover-(header|body|footer|menu)/.test(this.opts.content);
123
104
  const safeContent = sanitizeHtml(this.opts.content);
124
105
  el.innerHTML = hasStructure
@@ -128,7 +109,6 @@ class Popover {
128
109
  this.trigger.setAttribute('aria-controls', id);
129
110
  return el;
130
111
  }
131
- // ── Positioning ────────────────────────────────────────────────────────────
132
112
  reposition() {
133
113
  if (!this.popoverEl)
134
114
  return;
@@ -145,6 +125,24 @@ class Popover {
145
125
  this.popoverEl.style.left = `${left}px`;
146
126
  this.popoverEl.style.top = `${top}px`;
147
127
  }
128
+ onClick = () => { this.toggle(); };
129
+ onMouseEnter = () => {
130
+ if (this.hoverTimer !== null)
131
+ clearTimeout(this.hoverTimer);
132
+ this.open();
133
+ };
134
+ onMouseLeave = () => {
135
+ this.hoverTimer = window.setTimeout(() => this.close(), 120);
136
+ };
137
+ onOutsideClick = (e) => {
138
+ const t = e.target;
139
+ if (!this.popoverEl?.contains(t) && !this.trigger.contains(t))
140
+ this.close();
141
+ };
142
+ onEscape = (e) => {
143
+ if (e.key === 'Escape')
144
+ this.close();
145
+ };
148
146
  attachTrigger() {
149
147
  if (this.opts.triggerMode === 'click') {
150
148
  this.trigger.addEventListener('click', this.onClick);
@@ -160,6 +158,4 @@ class Popover {
160
158
  this.trigger.removeEventListener('mouseleave', this.onMouseLeave);
161
159
  }
162
160
  }
163
- Popover.openPopovers = new Set();
164
- Popover.idCounter = 0;
165
161
  export { Popover };
package/js/position.d.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  type Placement = 'top' | 'bottom' | 'left' | 'right';
6
6
  type Align = 'start' | 'center' | 'end';
7
+ /** Options accepted by `computePosition`. */
7
8
  interface PositionOptions {
8
9
  placement: Placement | 'auto';
9
10
  align?: Align;
@@ -11,6 +12,7 @@ interface PositionOptions {
11
12
  margin?: number;
12
13
  arrowSize?: number;
13
14
  }
15
+ /** Result returned by `computePosition`. */
14
16
  interface PositionResult {
15
17
  left: number;
16
18
  top: number;
package/js/position.js CHANGED
@@ -44,7 +44,6 @@ function computePosition(trigger, floating, opts) {
44
44
  const placement = opts.placement === 'auto'
45
45
  ? bestPlacement(trigger, floating, offset)
46
46
  : maybeFlip(opts.placement, trigger, floating, offset);
47
- // Main-axis offset
48
47
  let left = 0, top = 0;
49
48
  switch (placement) {
50
49
  case 'top':
@@ -60,7 +59,6 @@ function computePosition(trigger, floating, opts) {
60
59
  left = trigger.right + offset;
61
60
  break;
62
61
  }
63
- // Cross-axis alignment
64
62
  if (placement === 'top' || placement === 'bottom') {
65
63
  switch (align) {
66
64
  case 'start':
@@ -87,13 +85,11 @@ function computePosition(trigger, floating, opts) {
87
85
  break;
88
86
  }
89
87
  }
90
- // Clamp to viewport
91
88
  const l = Math.max(margin, Math.min(window.innerWidth - floating.width - margin, left));
92
89
  const t = Math.max(margin, Math.min(window.innerHeight - floating.height - margin, top));
93
- // Arrow offset: keep arrow centred on the trigger even after viewport clamping
94
90
  let arrowOffset;
95
91
  if (opts.arrowSize !== undefined) {
96
- const minOff = opts.arrowSize + 8; // min distance from rounded corner
92
+ const minOff = opts.arrowSize + 8;
97
93
  if (placement === 'top' || placement === 'bottom') {
98
94
  const raw = trigger.left + trigger.width / 2 - l;
99
95
  arrowOffset = Math.max(minOff, Math.min(floating.width - minOff, raw));
package/js/push-menu.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /** DOM element references managed by the PushMenu static class. */
1
2
  interface PushMenuElements {
2
3
  navigation: HTMLElement | null;
3
4
  content: HTMLElement | null;
@@ -6,6 +7,7 @@ interface PushMenuElements {
6
7
  controlIcon: HTMLElement | null;
7
8
  backdrop: HTMLElement | null;
8
9
  }
10
+ /** Static class that manages a push-style side navigation panel. */
9
11
  declare class PushMenu {
10
12
  private static elements;
11
13
  private static initialized;
@@ -19,7 +21,6 @@ declare class PushMenu {
19
21
  private static resetPanels;
20
22
  private static handleNavigationChange;
21
23
  static pushToggle(): void;
22
- private static toggleClass;
23
24
  private static clickNav;
24
25
  private static handleBackdropClick;
25
26
  static open(): void;