@dile/ui 3.4.6 → 3.5.1

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 (20) hide show
  1. package/components/select/__screenshots__/select-ajax-overlay.spec.js/dile-select-ajax-overlay-Selecting-a-result-clear---does-not-re-select-the-same-item--regression--infinite-add-loop-under-addOnSelect--1.png +0 -0
  2. package/components/select/__screenshots__/select-ajax-overlay.spec.js/dile-select-ajax-overlay-Selecting-a-result-clearing-the-selection-returns-to-the-search-field-1.png +0 -0
  3. package/components/select/__screenshots__/select-ajax-overlay.spec.js/dile-select-ajax-overlay-Selecting-a-result-selecting-an-option-sets-value-selectedText--closes-the-popup-and-fires-events-1.png +0 -0
  4. package/components/select/__screenshots__/select-ajax-overlay.spec.js/dile-select-ajax-overlay-Selecting-a-result-shows-the-read-only-field-with-the-clear-icon-after-selecting-1.png +0 -0
  5. package/components/select/__screenshots__/select-ajax-overlay.spec.js/dile-select-ajax-overlay-static-mode-loads-all-data-on-init-and-renders-it-inline-without-requiring-a-search-1.png +0 -0
  6. package/components/select/__screenshots__/select-ajax-overlay.spec.js/dile-select-ajax-overlay-static-mode-shows-a-visible--clickable-trigger-instead-of-hiding-it--there-is-no-separate-search-field-to-compose-under--1.png +0 -0
  7. package/components/select/__screenshots__/select-overlay.spec.js/dile-select-overlay-Positioning-keeps-the-same-moveTop-tightened-gap-when-flipping-above-the-trigger-as-when-sitting-below-it-1.png +0 -0
  8. package/components/select/__screenshots__/select-overlay.spec.js/dile-select-overlay-Rendering-throws-when-no-select-element-is-slotted-1.png +0 -0
  9. package/components/select/__screenshots__/select-overlay.spec.js/dile-select-overlay-hideTrigger-handleExternalKeydown---drives-navigation-without-a-visible-trigger-1.png +0 -0
  10. package/components/select/__screenshots__/zzdebug2.spec.js/debug-static-debug-1.png +0 -0
  11. package/components/select/__screenshots__/zzdebug3.spec.js/debug-rapid-close-reopen-reproduces-blur-then-quick-refocus-1.png +0 -0
  12. package/components/select/index.js +3 -1
  13. package/components/select/select-ajax-overlay.js +6 -0
  14. package/components/select/select-ajax-overlay.spec.js +422 -0
  15. package/components/select/select-overlay.js +5 -0
  16. package/components/select/select-overlay.spec.js +507 -0
  17. package/components/select/src/DileSelectAjaxOverlay.js +531 -0
  18. package/components/select/src/DileSelectOverlay.js +511 -0
  19. package/mixins/overlay/src/DileOverlay.js +9 -6
  20. package/package.json +2 -2
@@ -0,0 +1,511 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { ifDefined } from 'lit/directives/if-defined.js';
3
+ import { DileEmmitChange } from '../../../mixins/form/index.js';
4
+ import { DileOverlay } from '../../../mixins/overlay/index.js';
5
+ import { DileCloseDocumentClick } from '../../../mixins/close-document-click/index.js';
6
+ import { labelStyles, messageStyles } from '../../input/index.js';
7
+
8
+ const TYPEAHEAD_RESET_DELAY = 500;
9
+
10
+ export class DileSelectOverlay extends DileOverlay(DileCloseDocumentClick(DileEmmitChange(LitElement))) {
11
+ static get styles() {
12
+ return [
13
+ labelStyles,
14
+ messageStyles,
15
+ css`
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+ :host {
20
+ display: block;
21
+ margin-bottom: 10px;
22
+ }
23
+ .select-wrapper {
24
+ position: relative;
25
+ display: block;
26
+ width: 100%;
27
+ }
28
+ #trigger {
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: space-between;
32
+ gap: 0.5rem;
33
+ width: var(--dile-input-width, 100%);
34
+ padding: var(--dile-input-padding, 7px 5px);
35
+ line-height: var(--dile-input-line-height, 1.5em);
36
+ font-size: var(--dile-select-font-size, 0.875em);
37
+ font-family: inherit;
38
+ border-radius: var(--dile-input-border-radius, 5px);
39
+ border: var(--dile-input-border-width, 1px) solid var(--dile-input-border-color, #888);
40
+ background-color: var(--dile-input-background-color, #fff);
41
+ color: var(--dile-input-color, #303030);
42
+ cursor: pointer;
43
+ text-align: left;
44
+ }
45
+ #trigger:disabled {
46
+ background-color: #f5f5f5;
47
+ border-color: var(--dile-input-disabled-border-color, #eee);
48
+ cursor: not-allowed;
49
+ }
50
+ #trigger:focus-visible {
51
+ outline: var(--dile-input-focus-ring-width, 3px) solid var(--dile-input-focus-ring-color, rgba(102, 170, 255, 0.5));
52
+ outline-offset: calc(-1 * var(--dile-input-focus-ring-width, 3px));
53
+ border-color: var(--dile-input-focus-border-color, var(--dile-link-color, #6af));
54
+ }
55
+ #trigger.errored {
56
+ border-color: var(--dile-input-error-border-color, #c00);
57
+ }
58
+ :host([hideTrigger]) #trigger {
59
+ /* Keep the full width (used to size/position the popup) but collapse
60
+ vertically and hide visually, since a parent composer supplies its
61
+ own visible control and drives open()/close() directly. */
62
+ height: 0;
63
+ padding-top: 0;
64
+ padding-bottom: 0;
65
+ border-top: 0;
66
+ border-bottom: 0;
67
+ overflow: hidden;
68
+ opacity: 0;
69
+ pointer-events: none;
70
+ }
71
+ .trigger-text {
72
+ overflow: hidden;
73
+ text-overflow: ellipsis;
74
+ white-space: nowrap;
75
+ }
76
+ .trigger-text.placeholder {
77
+ color: var(--dile-input-placeholder-color, #ccc);
78
+ }
79
+ .arrow-icon {
80
+ width: 18px;
81
+ height: 18px;
82
+ flex: none;
83
+ display: flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ }
87
+ .arrow-icon svg {
88
+ width: 100%;
89
+ height: 100%;
90
+ }
91
+ .arrow-icon path {
92
+ fill: var(--dile-select-arrow-color, #303030);
93
+ }
94
+ #overlay {
95
+ margin: 0;
96
+ list-style: none;
97
+ display: none;
98
+ position: absolute;
99
+ opacity: 0;
100
+ box-sizing: border-box;
101
+ z-index: var(--dile-select-overlay-z-index, 999);
102
+ color: var(--dile-select-overlay-color, #303030);
103
+ background-color: var(--dile-select-overlay-background-color, #fff);
104
+ border-radius: var(--dile-select-overlay-border-radius, 5px);
105
+ max-height: var(--dile-select-overlay-max-height, 260px);
106
+ overflow-y: auto;
107
+ box-shadow: var(--dile-select-overlay-box-shadow, 0 4px 10px rgba(0, 0, 0, 0.15));
108
+ padding: var(--dile-select-overlay-padding, 4px 0);
109
+ transition: var(--dile-select-overlay-transition, ease 0.2s);
110
+ transition-property: transform, opacity;
111
+ transform: translateY(-6px);
112
+ }
113
+ #overlay.opened {
114
+ opacity: 1;
115
+ transform: translateY(0);
116
+ }
117
+ .option {
118
+ padding: var(--dile-select-overlay-option-padding, 7px 10px);
119
+ cursor: pointer;
120
+ color: var(--dile-select-overlay-color, #303030);
121
+ }
122
+ .option.highlighted {
123
+ background-color: var(--dile-select-overlay-highlighted-background-color, #eef6ff);
124
+ }
125
+ .option[aria-selected="true"] {
126
+ font-weight: var(--dile-select-overlay-selected-font-weight, 600);
127
+ }
128
+ @media (forced-colors: active) {
129
+ #trigger {
130
+ border-width: var(--dile-input-border-width, 1px);
131
+ }
132
+ #trigger:focus-visible {
133
+ outline: 3px solid;
134
+ }
135
+ .arrow-icon {
136
+ forced-color-adjust: none;
137
+ color: CanvasText;
138
+ }
139
+ }
140
+ `];
141
+ }
142
+
143
+ static get formAssociated() {
144
+ return true;
145
+ }
146
+
147
+ static get properties() {
148
+ return {
149
+ label: { type: String },
150
+ value: { type: String },
151
+ name: { type: String, reflect: true },
152
+ disabled: { type: Boolean },
153
+ errored: { type: Boolean },
154
+ message: { type: String },
155
+ hideErrorOnInput: { type: Boolean },
156
+ quietOnStart: { type: Boolean },
157
+ /** Text shown on the trigger when no option is selected. Empty by default, matching
158
+ * dile-select's native <select> (which shows whichever option ends up selected). */
159
+ placeholder: { type: String },
160
+ /** Suppresses the visible trigger box, letting a parent component drive open()/close()
161
+ * and forward keyboard events via handleExternalKeydown() while supplying its own visible control. */
162
+ hideTrigger: { type: Boolean, reflect: true },
163
+ _activeIndex: { state: true },
164
+ /** Display text of the currently highlighted option, readable by an external composer. */
165
+ highlightedText: { state: true },
166
+ };
167
+ }
168
+
169
+ constructor() {
170
+ super();
171
+ this.errored = false;
172
+ this.hideErrorOnInput = false;
173
+ this.hideTrigger = false;
174
+ this.placeholder = '';
175
+ // Sit closer to the trigger than DileOverlay's 10px default gap.
176
+ this.moveTop = -8;
177
+ this.quiet = false;
178
+ this._activeIndex = -1;
179
+ this.highlightedText = '';
180
+ this._typeaheadQuery = '';
181
+ this._typeaheadTimeout = null;
182
+ this.internals = this.attachInternals();
183
+ this.selectId = `dile-select-overlay-${Math.random().toString(36).substr(2, 9)}`;
184
+ }
185
+
186
+ get elselect() {
187
+ return this.querySelector("select");
188
+ }
189
+
190
+ get opened() {
191
+ return this._overlayClass === 'opened' || this._opening;
192
+ }
193
+
194
+ get options() {
195
+ return this.elselect
196
+ ? Array.from(this.elselect.options).map(o => ({ value: o.value, text: o.textContent }))
197
+ : [];
198
+ }
199
+
200
+ get selectedOption() {
201
+ return this.options.find(o => o.value === (this.value ?? ''));
202
+ }
203
+
204
+ connectedCallback() {
205
+ super.connectedCallback();
206
+ if (!this.elselect) {
207
+ throw new Error('Use dile-select-overlay with a select element in the slot "select"');
208
+ }
209
+ if (!this.elselect.id) {
210
+ this.elselect.id = this.selectId;
211
+ }
212
+ // Unlike dile-select, the native <select> is never slotted for display (it's only
213
+ // read as the data source), so browser-native slot projection can't keep the popup
214
+ // in sync when a consumer regenerates its <option> children (e.g. dile-select-ajax-overlay
215
+ // re-rendering options after each search). Watch it directly instead.
216
+ this._optionsObserver = new MutationObserver(() => {
217
+ this.requestUpdate();
218
+ // The option count/height can change (e.g. a search narrowing from many results down
219
+ // to one), but DileOverlay only repositions on open/scroll/resize — without this, a
220
+ // popup that flipped above the trigger for a tall list stays anchored at that old top
221
+ // position once it shrinks, leaving it floating detached from the trigger.
222
+ this.updateComplete.then(() => {
223
+ if (this.opened) {
224
+ this.updatePosition();
225
+ }
226
+ });
227
+ });
228
+ this._optionsObserver.observe(this.elselect, { childList: true, subtree: true, characterData: true });
229
+ }
230
+
231
+ disconnectedCallback() {
232
+ super.disconnectedCallback();
233
+ if (this._optionsObserver) {
234
+ this._optionsObserver.disconnect();
235
+ }
236
+ if (this._typeaheadTimeout) {
237
+ clearTimeout(this._typeaheadTimeout);
238
+ }
239
+ }
240
+
241
+ firstUpdated() {
242
+ super.firstUpdated();
243
+ this.quiet = this.quietOnStart;
244
+ if (this.value) {
245
+ this.elselect.value = this.value;
246
+ } else {
247
+ this.value = this.elselect.value;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Points positioning (open()/updatePosition()'s flip-to-top-or-bottom math) at an external
253
+ * element instead of this component's own trigger. Meant for hideTrigger compositions where
254
+ * a parent's own visible control (e.g. a search field) is the thing the popup should actually
255
+ * avoid covering — the internal trigger is a zero-height point, so flipping "above" it doesn't
256
+ * know to leave room for the parent's real, taller control sitting right before it.
257
+ * Pass no argument (or a falsy value) to revert to this component's own trigger.
258
+ */
259
+ setTriggerElement(el) {
260
+ this.trigger = el || this.shadowRoot.getElementById('trigger');
261
+ }
262
+
263
+ updatePosition() {
264
+ // DileOverlay switches #overlay to position:fixed, so a CSS percentage width
265
+ // would resolve against the viewport instead of the trigger. Match it in JS instead.
266
+ if (this.trigger) {
267
+ this.overlay.style.width = `${this.trigger.getBoundingClientRect().width}px`;
268
+ }
269
+ super.updatePosition();
270
+ }
271
+
272
+ updated(changedProperties) {
273
+ if (changedProperties.has("value")) {
274
+ if (this.elselect) {
275
+ this.elselect.value = this.value ?? '';
276
+ }
277
+ if (this.quiet) {
278
+ this.quiet = false;
279
+ } else {
280
+ this.emmitChange();
281
+ }
282
+ this.internals.setFormValue(this.value);
283
+ }
284
+ }
285
+
286
+ render() {
287
+ const messageId = this.message ? `${this.selectId}-message` : null;
288
+ return html`
289
+ <div>${this.label
290
+ ? html`<label for="trigger">${this.label}</label>`
291
+ : ""
292
+ }<div class="select-wrapper">
293
+ ${this.triggerTemplate}
294
+ ${this.overlayTemplate}
295
+ </div></div>
296
+ ${this.message
297
+ ? html`<div class="message ${this.errored ? 'errored-msg' : ''}" id="${messageId}" role="status" aria-live="polite"><span>${this.message}</span></div>`
298
+ : ''
299
+ }
300
+ `;
301
+ }
302
+
303
+ get triggerTemplate() {
304
+ const selected = this.selectedOption;
305
+ return html`
306
+ <button
307
+ type="button"
308
+ id="trigger"
309
+ role="combobox"
310
+ aria-haspopup="listbox"
311
+ aria-expanded="${this.opened}"
312
+ aria-controls="overlay"
313
+ aria-activedescendant="${ifDefined(this._activeIndex >= 0 ? this._optionId(this._activeIndex) : undefined)}"
314
+ aria-invalid="${this.errored}"
315
+ aria-describedby="${ifDefined(this.message ? `${this.selectId}-message` : undefined)}"
316
+ ?disabled="${this.disabled}"
317
+ tabindex="${this.hideTrigger ? '-1' : '0'}"
318
+ class="${this.errored ? 'errored' : ''}"
319
+ @click="${this._onTriggerClick}"
320
+ @keydown="${this.handleExternalKeydown}"
321
+ >
322
+ <span class="trigger-text ${selected ? '' : 'placeholder'}">${selected ? selected.text : this.placeholder}</span>
323
+ <span class="arrow-icon" aria-hidden="true">
324
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 448" enable-background="new 0 0 256 448">
325
+ <path d="M255.9 168c0-4.2-1.6-7.9-4.8-11.2-3.2-3.2-6.9-4.8-11.2-4.8H16c-4.2 0-7.9 1.6-11.2 4.8S0 163.8 0 168c0 4.4 1.6 8.2 4.8 11.4l112 112c3.1 3.1 6.8 4.6 11.2 4.6 4.4 0 8.2-1.5 11.4-4.6l112-112c3-3.2 4.5-7 4.5-11.4z"></path>
326
+ </svg>
327
+ </span>
328
+ </button>
329
+ `;
330
+ }
331
+
332
+ get overlayTemplate() {
333
+ const options = this.options;
334
+ return html`
335
+ <ul id="overlay" class="${this._overlayClass}" role="listbox">
336
+ ${options.map((opt, index) => html`
337
+ <li
338
+ id="${this._optionId(index)}"
339
+ role="option"
340
+ class="option ${index === this._activeIndex ? 'highlighted' : ''}"
341
+ aria-selected="${opt.value === (this.value ?? '')}"
342
+ @click="${() => this.selectOption(index)}"
343
+ @mousemove="${() => this._setActiveIndex(index, false)}"
344
+ >${opt.text}</li>
345
+ `)}
346
+ </ul>
347
+ `;
348
+ }
349
+
350
+ _optionId(index) {
351
+ return `${this.selectId}-option-${index}`;
352
+ }
353
+
354
+ _indexForValue(value) {
355
+ return this.options.findIndex(o => o.value === (value ?? ''));
356
+ }
357
+
358
+ _setActiveIndex(index, scroll = true) {
359
+ this._activeIndex = index;
360
+ const opt = this.options[index];
361
+ this.highlightedText = opt ? opt.text : '';
362
+ if (scroll) {
363
+ this.updateComplete.then(() => {
364
+ const el = this.shadowRoot.getElementById(this._optionId(index));
365
+ if (el) {
366
+ el.scrollIntoView({ block: 'nearest' });
367
+ }
368
+ });
369
+ }
370
+ }
371
+
372
+ _moveActiveIndex(delta, options = this.options) {
373
+ if (!options.length) return;
374
+ let next = this._activeIndex;
375
+ if (next < 0) {
376
+ next = delta > 0 ? 0 : options.length - 1;
377
+ } else {
378
+ next = Math.min(Math.max(next + delta, 0), options.length - 1);
379
+ }
380
+ this._setActiveIndex(next);
381
+ }
382
+
383
+ _onTriggerClick() {
384
+ if (this.disabled) return;
385
+ const wasOpen = this.opened;
386
+ this.toggle();
387
+ if (!wasOpen) {
388
+ this._setActiveIndex(this._indexForValue(this.value), false);
389
+ }
390
+ }
391
+
392
+ handleExternalKeydown(e) {
393
+ if (this.disabled) return;
394
+ const options = this.options;
395
+
396
+ switch (e.key) {
397
+ case 'ArrowDown':
398
+ case 'ArrowUp': {
399
+ e.preventDefault();
400
+ const wasOpen = this.opened;
401
+ if (!wasOpen) {
402
+ this.open();
403
+ this._setActiveIndex(this._indexForValue(this.value), false);
404
+ }
405
+ this._moveActiveIndex(e.key === 'ArrowDown' ? 1 : -1, options);
406
+ break;
407
+ }
408
+ case 'Home':
409
+ e.preventDefault();
410
+ if (!this.opened) this.open();
411
+ this._setActiveIndex(0);
412
+ break;
413
+ case 'End':
414
+ e.preventDefault();
415
+ if (!this.opened) this.open();
416
+ this._setActiveIndex(options.length - 1);
417
+ break;
418
+ case 'Enter':
419
+ case ' ':
420
+ e.preventDefault();
421
+ if (this.opened) {
422
+ if (this._activeIndex >= 0) {
423
+ this.selectOption(this._activeIndex);
424
+ } else {
425
+ this.close();
426
+ }
427
+ } else {
428
+ this._onTriggerClick();
429
+ }
430
+ break;
431
+ case 'Escape':
432
+ if (this.opened) {
433
+ e.preventDefault();
434
+ this.close();
435
+ }
436
+ break;
437
+ case 'Tab':
438
+ this.close();
439
+ break;
440
+ default:
441
+ if (e.key && e.key.length === 1 && /\S/.test(e.key)) {
442
+ this._handleTypeahead(e.key, options);
443
+ }
444
+ break;
445
+ }
446
+ }
447
+
448
+ _handleTypeahead(char, options = this.options) {
449
+ if (!options.length) return;
450
+ if (this._typeaheadTimeout) {
451
+ clearTimeout(this._typeaheadTimeout);
452
+ }
453
+ this._typeaheadQuery += char.toLowerCase();
454
+ this._typeaheadTimeout = setTimeout(() => {
455
+ this._typeaheadQuery = '';
456
+ }, TYPEAHEAD_RESET_DELAY);
457
+
458
+ const currentIndex = this.opened ? this._activeIndex : this._indexForValue(this.value);
459
+ const startFrom = currentIndex >= 0 ? currentIndex + 1 : 0;
460
+ const ordered = [...options.slice(startFrom), ...options.slice(0, startFrom)];
461
+ const relativeMatch = ordered.findIndex(o => o.text.toLowerCase().startsWith(this._typeaheadQuery));
462
+ if (relativeMatch < 0) return;
463
+
464
+ const matchIndex = (startFrom + relativeMatch) % options.length;
465
+ if (this.opened) {
466
+ this._setActiveIndex(matchIndex);
467
+ } else {
468
+ this.selectOption(matchIndex);
469
+ }
470
+ }
471
+
472
+ selectOption(index) {
473
+ const opt = this.options[index];
474
+ if (!opt) return;
475
+ this.value = opt.value;
476
+ this.close();
477
+ this.hideErrorOnInteraction();
478
+ }
479
+
480
+ hideErrorOnInteraction() {
481
+ if (this.hideErrorOnInput && this.errored) {
482
+ this.errored = false;
483
+ this.message = '';
484
+ }
485
+ }
486
+
487
+ close() {
488
+ super.close();
489
+ this._activeIndex = -1;
490
+ }
491
+
492
+ clear() {
493
+ this.value = undefined;
494
+ }
495
+
496
+ getOptionByValue(id) {
497
+ let options = this.elselect.options;
498
+ for (let i = 0; i < options.length; i++) {
499
+ if (options[i].value === id) {
500
+ return options[i];
501
+ }
502
+ }
503
+ }
504
+
505
+ quietChange(value) {
506
+ if (value != this.value) {
507
+ this.quiet = true;
508
+ this.value = value;
509
+ }
510
+ }
511
+ }
@@ -73,7 +73,7 @@ export const DileOverlay = function(superClass) {
73
73
  }
74
74
  }
75
75
  cancelDelay() {
76
- if(this.reardo) {
76
+ if(this.delayId) {
77
77
  clearTimeout(this.delayId);
78
78
  }
79
79
  this.delayId = null;
@@ -152,13 +152,13 @@ export const DileOverlay = function(superClass) {
152
152
 
153
153
  switch(this.verticalAlign) {
154
154
  case 'top':
155
- this.setOverlayStyleTop(triggerRect.top - overlayHeight + moveTop, triggerRect, overlayHeight, 'top');
155
+ this.setOverlayStyleTop(triggerRect.top - overlayHeight + moveTop, triggerRect, overlayHeight, 'top', moveTop);
156
156
  break;
157
157
  case 'center':
158
- this.setOverlayStyleTop(triggerRect.top - ((overlayHeight - (triggerRect.height / 2)) / 2) - moveTop, triggerRect, overlayHeight, 'center');
158
+ this.setOverlayStyleTop(triggerRect.top - ((overlayHeight - (triggerRect.height / 2)) / 2) - moveTop, triggerRect, overlayHeight, 'center', moveTop);
159
159
  break;
160
160
  default:
161
- this.setOverlayStyleTop(triggerRect.bottom + 10 + moveTop, triggerRect, overlayHeight, 'bottom');
161
+ this.setOverlayStyleTop(triggerRect.bottom + 10 + moveTop, triggerRect, overlayHeight, 'bottom', moveTop);
162
162
  }
163
163
 
164
164
  switch(this.horizontalAlign) {
@@ -181,11 +181,14 @@ export const DileOverlay = function(superClass) {
181
181
  this.overlay.style.display = lastDisplay;
182
182
  }
183
183
 
184
- setOverlayStyleTop(topValue, triggerRect, overlayHeight, direction) {
184
+ setOverlayStyleTop(topValue, triggerRect, overlayHeight, direction, moveTop = 0) {
185
185
  const viewportHeight = document.documentElement.clientHeight;
186
186
  const margin = 10;
187
187
  if (direction === 'bottom' && topValue + overlayHeight > viewportHeight - margin) {
188
- const flipped = triggerRect.top - overlayHeight - margin;
188
+ // Keep the same moveTop-driven distance to the trigger when flipping above it as when
189
+ // sitting below it (topValue above already folds moveTop into the unflipped position),
190
+ // otherwise a tightened gap below reverts to the default 10px once it flips to above.
191
+ const flipped = triggerRect.top - overlayHeight - margin - moveTop;
189
192
  topValue = flipped >= margin ? flipped : viewportHeight - overlayHeight - margin;
190
193
  } else if (direction === 'top' && topValue < margin) {
191
194
  const flipped = triggerRect.bottom + margin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/ui",
3
- "version": "3.4.6",
3
+ "version": "3.5.1",
4
4
  "description": "UI Core components from dile-components.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -28,5 +28,5 @@
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
- "gitHead": "57a54e50de1a24d64dcf478fa6d39624e1f775eb"
31
+ "gitHead": "7bcf203fb23acc24867e9f3df4f3ca2367eabd97"
32
32
  }