@vanduo-oss/framework 1.4.1 → 1.4.2

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.
@@ -128,6 +128,32 @@
128
128
  return x;
129
129
  }
130
130
 
131
+ function positionAnchoredPopup(anchor, popup, gap) {
132
+ const padding = 8;
133
+ const offset = gap != null ? gap : 4;
134
+ const rect = anchor.getBoundingClientRect();
135
+
136
+ popup.style.minWidth = Math.max(rect.width, 0) + 'px';
137
+
138
+ let top = rect.bottom + offset;
139
+ let left = rect.left;
140
+ popup.style.top = top + 'px';
141
+ popup.style.left = left + 'px';
142
+
143
+ const popRect = popup.getBoundingClientRect();
144
+ if (popRect.bottom > window.innerHeight - padding && rect.top - popRect.height > padding) {
145
+ top = rect.top - popRect.height - offset;
146
+ popup.style.top = top + 'px';
147
+ }
148
+
149
+ const alignedRect = popup.getBoundingClientRect();
150
+ left = rect.left;
151
+ if (left + alignedRect.width > window.innerWidth - padding) {
152
+ left = window.innerWidth - alignedRect.width - padding;
153
+ }
154
+ popup.style.left = Math.max(padding, left) + 'px';
155
+ }
156
+
131
157
  const Datepicker = {
132
158
  instances: new Map(),
133
159
 
@@ -221,7 +247,7 @@
221
247
  wrapper.style.display = 'inline-block';
222
248
  input.parentNode.insertBefore(wrapper, input);
223
249
  wrapper.appendChild(input);
224
- wrapper.appendChild(popup);
250
+ document.body.appendChild(popup);
225
251
 
226
252
  const isSameDay = (a, b) => a && b &&
227
253
  a.getFullYear() === b.getFullYear() &&
@@ -442,6 +468,10 @@
442
468
  }
443
469
  popup.appendChild(grid);
444
470
  }
471
+
472
+ if (popup.classList.contains('is-open')) {
473
+ requestAnimationFrame(positionPopup);
474
+ }
445
475
  };
446
476
 
447
477
  const handleGridKeydown = (e) => {
@@ -523,6 +553,15 @@
523
553
  requestAnimationFrame(focusFocusedDay);
524
554
  };
525
555
 
556
+ const positionPopup = () => {
557
+ if (!popup.classList.contains('is-open')) return;
558
+ positionAnchoredPopup(input, popup);
559
+ };
560
+
561
+ const repositionHandler = () => {
562
+ positionPopup();
563
+ };
564
+
526
565
  const open = () => {
527
566
  viewMode = 'days';
528
567
  if (selectedDate) {
@@ -542,7 +581,10 @@
542
581
  render();
543
582
  popup.classList.add('is-open');
544
583
  input.setAttribute('aria-expanded', 'true');
545
- requestAnimationFrame(focusFocusedDay);
584
+ requestAnimationFrame(() => {
585
+ positionPopup();
586
+ focusFocusedDay();
587
+ });
546
588
  };
547
589
 
548
590
  const close = () => {
@@ -559,7 +601,7 @@
559
601
  open();
560
602
  };
561
603
  const outsideHandler = (e) => {
562
- if (!wrapper.contains(e.target)) close();
604
+ if (!input.contains(e.target) && !popup.contains(e.target)) close();
563
605
  };
564
606
  const escHandler = (e) => {
565
607
  if (e.key === 'Escape' && popup.classList.contains('is-open')) {
@@ -573,6 +615,8 @@
573
615
  document.addEventListener('click', outsideHandler, true);
574
616
  document.addEventListener('keydown', escHandler);
575
617
  popup.addEventListener('keydown', handleGridKeydown);
618
+ window.addEventListener('resize', repositionHandler);
619
+ window.addEventListener('scroll', repositionHandler, true);
576
620
 
577
621
  input.setAttribute('aria-haspopup', 'dialog');
578
622
  input.setAttribute('aria-expanded', 'false');
@@ -582,7 +626,10 @@
582
626
  () => input.removeEventListener('focus', focusHandler),
583
627
  () => document.removeEventListener('click', outsideHandler, true),
584
628
  () => document.removeEventListener('keydown', escHandler),
585
- () => popup.removeEventListener('keydown', handleGridKeydown)
629
+ () => popup.removeEventListener('keydown', handleGridKeydown),
630
+ () => window.removeEventListener('resize', repositionHandler),
631
+ () => window.removeEventListener('scroll', repositionHandler, true),
632
+ () => popup.remove()
586
633
  );
587
634
 
588
635
  this.instances.set(input, { cleanup: cleanup, open: open, close: close, popup: popup });
@@ -6,6 +6,32 @@
6
6
  (function () {
7
7
  'use strict';
8
8
 
9
+ function positionAnchoredPopup(anchor, popup, gap) {
10
+ const padding = 8;
11
+ const offset = gap != null ? gap : 4;
12
+ const rect = anchor.getBoundingClientRect();
13
+
14
+ popup.style.minWidth = Math.max(rect.width, 0) + 'px';
15
+
16
+ let top = rect.bottom + offset;
17
+ let left = rect.left;
18
+ popup.style.top = top + 'px';
19
+ popup.style.left = left + 'px';
20
+
21
+ const popRect = popup.getBoundingClientRect();
22
+ if (popRect.bottom > window.innerHeight - padding && rect.top - popRect.height > padding) {
23
+ top = rect.top - popRect.height - offset;
24
+ popup.style.top = top + 'px';
25
+ }
26
+
27
+ const alignedRect = popup.getBoundingClientRect();
28
+ left = rect.left;
29
+ if (left + alignedRect.width > window.innerWidth - padding) {
30
+ left = window.innerWidth - alignedRect.width - padding;
31
+ }
32
+ popup.style.left = Math.max(padding, left) + 'px';
33
+ }
34
+
9
35
  const Timepicker = {
10
36
  instances: new Map(),
11
37
 
@@ -36,7 +62,7 @@
36
62
  const popup = document.createElement('div');
37
63
  popup.className = 'vd-timepicker-popup';
38
64
  popup.setAttribute('role', 'listbox');
39
- wrapper.appendChild(popup);
65
+ document.body.appendChild(popup);
40
66
 
41
67
  // Generate time slots
42
68
  const times = [];
@@ -84,13 +110,24 @@
84
110
  });
85
111
  };
86
112
 
113
+ const positionPopup = () => {
114
+ if (!popup.classList.contains('is-open')) return;
115
+ positionAnchoredPopup(input, popup);
116
+ };
117
+
118
+ const repositionHandler = () => {
119
+ positionPopup();
120
+ };
121
+
87
122
  const open = () => {
88
123
  render();
89
124
  popup.classList.add('is-open');
90
125
  input.setAttribute('aria-expanded', 'true');
91
- // Scroll to selected
92
- const selected = popup.querySelector('.is-selected');
93
- if (selected) selected.scrollIntoView({ block: 'center' });
126
+ requestAnimationFrame(() => {
127
+ positionPopup();
128
+ const selected = popup.querySelector('.is-selected');
129
+ if (selected) selected.scrollIntoView({ block: 'center' });
130
+ });
94
131
  };
95
132
 
96
133
  const close = () => {
@@ -100,13 +137,15 @@
100
137
 
101
138
  const focusHandler = () => open();
102
139
  const outsideHandler = (e) => {
103
- if (!wrapper.contains(e.target)) close();
140
+ if (!input.contains(e.target) && !popup.contains(e.target)) close();
104
141
  };
105
142
  const escHandler = (e) => { if (e.key === 'Escape') close(); };
106
143
 
107
144
  input.addEventListener('focus', focusHandler);
108
145
  document.addEventListener('click', outsideHandler, true);
109
146
  document.addEventListener('keydown', escHandler);
147
+ window.addEventListener('resize', repositionHandler);
148
+ window.addEventListener('scroll', repositionHandler, true);
110
149
  input.setAttribute('aria-haspopup', 'listbox');
111
150
  input.setAttribute('aria-expanded', 'false');
112
151
  input.setAttribute('autocomplete', 'off');
@@ -115,7 +154,10 @@
115
154
  cleanup.push(
116
155
  () => input.removeEventListener('focus', focusHandler),
117
156
  () => document.removeEventListener('click', outsideHandler, true),
118
- () => document.removeEventListener('keydown', escHandler)
157
+ () => document.removeEventListener('keydown', escHandler),
158
+ () => window.removeEventListener('resize', repositionHandler),
159
+ () => window.removeEventListener('scroll', repositionHandler, true),
160
+ () => popup.remove()
119
161
  );
120
162
 
121
163
  this.instances.set(input, { cleanup, open, close });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanduo-oss/framework",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Zero-dependency CSS/JS framework built on Fibonacci/Golden Ratio design system with Open Color integration",
5
5
  "keywords": [
6
6
  "css",