@aquera/nile-elements 1.2.8 → 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.
- package/README.md +4 -0
- package/dist/{fixture-dff65c89.cjs.js → fixture-1c49c014.cjs.js} +2 -2
- package/dist/{fixture-dff65c89.cjs.js.map → fixture-1c49c014.cjs.js.map} +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +371 -370
- package/dist/nile-accordion/nile-accordian.test.cjs.js +1 -1
- package/dist/nile-auto-complete/index.cjs.js +1 -1
- package/dist/nile-auto-complete/index.esm.js +1 -1
- package/dist/nile-auto-complete/nile-auto-complete.cjs.js +1 -1
- package/dist/nile-auto-complete/nile-auto-complete.cjs.js.map +1 -1
- package/dist/nile-auto-complete/nile-auto-complete.esm.js +4 -4
- package/dist/nile-auto-complete/nile-auto-complete.test.cjs.js +1 -1
- package/dist/nile-auto-complete/nile-auto-complete.test.cjs.js.map +1 -1
- package/dist/nile-auto-complete/nile-auto-complete.test.esm.js +1 -1
- package/dist/nile-auto-complete/portal-manager.cjs.js +1 -1
- package/dist/nile-auto-complete/portal-manager.cjs.js.map +1 -1
- package/dist/nile-auto-complete/portal-manager.esm.js +1 -1
- package/dist/nile-auto-complete/portal-utils.cjs.js +1 -1
- package/dist/nile-auto-complete/portal-utils.cjs.js.map +1 -1
- package/dist/nile-auto-complete/portal-utils.esm.js +1 -1
- package/dist/nile-avatar/nile-avatar.test.cjs.js +1 -1
- package/dist/nile-badge/nile-badge.test.cjs.js +1 -1
- package/dist/nile-button/nile-button.test.cjs.js +1 -1
- package/dist/nile-button-toggle-group/nile-button-toggle-group.test.cjs.js +1 -1
- package/dist/nile-calendar/nile-calendar.test.cjs.js +1 -1
- package/dist/nile-card/nile-card.test.cjs.js +1 -1
- package/dist/nile-checkbox/nile-checkbox.test.cjs.js +1 -1
- package/dist/nile-chip/nile-chip.cjs.js +1 -1
- package/dist/nile-chip/nile-chip.cjs.js.map +1 -1
- package/dist/nile-chip/nile-chip.esm.js +1 -1
- package/dist/nile-chip/nile-chip.test.cjs.js +1 -1
- package/dist/nile-chip/nile-chip.test.cjs.js.map +1 -1
- package/dist/nile-chip/nile-chip.test.esm.js +1 -1
- package/dist/nile-dialog/nile-dialog.test.cjs.js +1 -1
- package/dist/nile-drawer/nile-drawer.test.cjs.js +1 -1
- package/dist/nile-dropdown/index.cjs.js +1 -1
- package/dist/nile-dropdown/index.esm.js +1 -1
- package/dist/nile-dropdown/nile-dropdown.cjs.js +1 -1
- package/dist/nile-dropdown/nile-dropdown.cjs.js.map +1 -1
- package/dist/nile-dropdown/nile-dropdown.esm.js +3 -2
- package/dist/nile-dropdown/nile-dropdown.test.cjs.js +1 -1
- package/dist/nile-dropdown/nile-dropdown.test.cjs.js.map +1 -1
- package/dist/nile-dropdown/nile-dropdown.test.esm.js +1 -1
- package/dist/nile-dropdown/portal-manager.cjs.js +2 -0
- package/dist/nile-dropdown/portal-manager.cjs.js.map +1 -0
- package/dist/nile-dropdown/portal-manager.esm.js +1 -0
- package/dist/nile-dropdown/portal-utils.cjs.js +2 -0
- package/dist/nile-dropdown/portal-utils.cjs.js.map +1 -0
- package/dist/nile-dropdown/portal-utils.esm.js +1 -0
- package/dist/nile-empty-state/nile-empty-state.test.cjs.js +1 -1
- package/dist/nile-error-message/nile-error-message.test.cjs.js +1 -1
- package/dist/nile-file-preview/nile-file-preview.test.cjs.js +1 -1
- package/dist/nile-file-upload/index.cjs.js +1 -1
- package/dist/nile-file-upload/nile-file-upload.cjs.js +1 -1
- package/dist/nile-file-upload/nile-file-upload.template.cjs.js +1 -1
- package/dist/nile-file-upload/nile-file-upload.test.cjs.js +1 -1
- package/dist/nile-filter-chip/nile-filter-chip.test.cjs.js +1 -1
- package/dist/nile-form-group/nile-form-group.test.cjs.js +1 -1
- package/dist/nile-form-help-text/nile-form-help-text.test.cjs.js +1 -1
- package/dist/nile-hero/nile-hero.test.cjs.js +1 -1
- package/dist/nile-icon/nile-icon.test.cjs.js +1 -1
- package/dist/nile-input/nile-input.test.cjs.js +1 -1
- package/dist/nile-link/nile-link.test.cjs.js +1 -1
- package/dist/nile-lite-tooltip/nile-lite-tooltip.cjs.js +1 -1
- package/dist/nile-lite-tooltip/nile-lite-tooltip.cjs.js.map +1 -1
- package/dist/nile-lite-tooltip/nile-lite-tooltip.esm.js +1 -1
- package/dist/nile-loader/nile-loader.test.cjs.js +1 -1
- package/dist/nile-popover/index.cjs.js +1 -1
- package/dist/nile-popover/nile-popover.cjs.js +1 -1
- package/dist/nile-popover/nile-popover.test.cjs.js +1 -1
- package/dist/nile-popup/nile-popup.test.cjs.js +1 -1
- package/dist/nile-progress-bar/nile-progress-bar.test.cjs.js +1 -1
- package/dist/nile-radio/nile-radio.test.cjs.js +1 -1
- package/dist/nile-radio-group/nile-radio-group.test.cjs.js +1 -1
- package/dist/nile-select/nile-select.test.cjs.js +1 -1
- package/dist/nile-slide-toggle/nile-slide-toggle.test.cjs.js +1 -1
- package/dist/nile-tab-group/nile-tab-group.test.cjs.js +1 -1
- package/dist/nile-textarea/nile-textarea.test.cjs.js +1 -1
- package/dist/nile-virtual-select/nile-virtual-select.test.cjs.js +1 -1
- package/dist/{scopedElementsWrapper-1bff26ef.cjs.js → scopedElementsWrapper-5ea5834f.cjs.js} +2 -2
- package/dist/{scopedElementsWrapper-1bff26ef.cjs.js.map → scopedElementsWrapper-5ea5834f.cjs.js.map} +1 -1
- package/dist/src/nile-auto-complete/nile-auto-complete.js.map +1 -1
- package/dist/src/nile-auto-complete/portal-utils.js +5 -5
- package/dist/src/nile-auto-complete/portal-utils.js.map +1 -1
- package/dist/src/nile-chip/nile-chip.js +24 -13
- package/dist/src/nile-chip/nile-chip.js.map +1 -1
- package/dist/src/nile-dropdown/nile-dropdown.d.ts +8 -1
- package/dist/src/nile-dropdown/nile-dropdown.js +67 -11
- package/dist/src/nile-dropdown/nile-dropdown.js.map +1 -1
- package/dist/src/nile-dropdown/portal-manager.d.ts +43 -0
- package/dist/src/nile-dropdown/portal-manager.js +374 -0
- package/dist/src/nile-dropdown/portal-manager.js.map +1 -0
- package/dist/src/nile-dropdown/portal-utils.d.ts +32 -0
- package/dist/src/nile-dropdown/portal-utils.js +212 -0
- package/dist/src/nile-dropdown/portal-utils.js.map +1 -0
- package/dist/src/nile-lite-tooltip/nile-lite-tooltip.d.ts +1 -0
- package/dist/src/nile-lite-tooltip/nile-lite-tooltip.js +37 -19
- package/dist/src/nile-lite-tooltip/nile-lite-tooltip.js.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/nile-auto-complete/nile-auto-complete.ts +0 -2
- package/src/nile-auto-complete/portal-utils.ts +5 -5
- package/src/nile-chip/nile-chip.ts +23 -13
- package/src/nile-dropdown/nile-dropdown.ts +75 -14
- package/src/nile-dropdown/portal-manager.ts +488 -0
- package/src/nile-dropdown/portal-utils.ts +269 -0
- package/src/nile-lite-tooltip/nile-lite-tooltip.ts +42 -21
- package/vscode-html-custom-data.json +12 -2
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import {
|
|
2
|
+
autoUpdate,
|
|
3
|
+
computePosition,
|
|
4
|
+
flip,
|
|
5
|
+
offset,
|
|
6
|
+
shift,
|
|
7
|
+
size,
|
|
8
|
+
platform,
|
|
9
|
+
type Placement,
|
|
10
|
+
type MiddlewareData,
|
|
11
|
+
type ComputePositionConfig
|
|
12
|
+
} from '@floating-ui/dom';
|
|
13
|
+
import { PortalUtils, PortalContentUtils, PortalEventUtils } from './portal-utils';
|
|
14
|
+
|
|
15
|
+
export class DropdownPortalManager {
|
|
16
|
+
private portalContainer: HTMLElement | null = null;
|
|
17
|
+
private measuredPanelHeight: number | null = null;
|
|
18
|
+
private component: any;
|
|
19
|
+
private clonedPanel: HTMLElement | null = null;
|
|
20
|
+
private cleanupAutoUpdate: (() => void) | null = null;
|
|
21
|
+
private currentPlacement: Placement = 'bottom-start';
|
|
22
|
+
private currentMiddlewareData: MiddlewareData | null = null;
|
|
23
|
+
private boundHandleWindowResize: (() => void) | null = null;
|
|
24
|
+
|
|
25
|
+
constructor(component: any) {
|
|
26
|
+
this.component = component;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private createPortalAppendContainer(): HTMLElement {
|
|
30
|
+
const container = document.createElement('div');
|
|
31
|
+
container.style.position = 'absolute';
|
|
32
|
+
container.style.zIndex = '9999';
|
|
33
|
+
container.style.pointerEvents = 'none';
|
|
34
|
+
container.style.width = 'auto';
|
|
35
|
+
container.style.maxWidth = 'none';
|
|
36
|
+
container.style.minWidth = 'auto';
|
|
37
|
+
container.className = 'nile-dropdown-portal-append';
|
|
38
|
+
return container;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
positionPortalAppend(): void {
|
|
42
|
+
if (!this.portalContainer || !this.component.open) return;
|
|
43
|
+
|
|
44
|
+
this.measurePanelHeight();
|
|
45
|
+
this.computeFloatingUIPosition();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private measurePanelHeight(): void {
|
|
49
|
+
if (this.measuredPanelHeight || !this.portalContainer) return;
|
|
50
|
+
|
|
51
|
+
this.portalContainer.style.position = 'absolute';
|
|
52
|
+
this.portalContainer.style.visibility = 'hidden';
|
|
53
|
+
this.portalContainer.style.top = '0px';
|
|
54
|
+
this.portalContainer.style.left = '0px';
|
|
55
|
+
|
|
56
|
+
this.portalContainer.offsetHeight;
|
|
57
|
+
|
|
58
|
+
this.measuredPanelHeight = this.portalContainer.offsetHeight;
|
|
59
|
+
|
|
60
|
+
this.portalContainer.style.visibility = '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private async computeFloatingUIPosition(): Promise<void> {
|
|
64
|
+
if (!this.portalContainer) return;
|
|
65
|
+
|
|
66
|
+
const referenceElement = this.component.shadowRoot?.querySelector('[slot="anchor"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
67
|
+
this.component.querySelector('[slot="trigger"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
68
|
+
this.component;
|
|
69
|
+
const floatingElement = this.portalContainer;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const { x, y, placement, middlewareData } = await this.calculateFloatingUIPosition(
|
|
73
|
+
referenceElement,
|
|
74
|
+
floatingElement
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
this.applyFloatingUIPosition(floatingElement, referenceElement, x, y, placement, middlewareData);
|
|
78
|
+
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.warn('Floating UI positioning failed, falling back to simple positioning:', error);
|
|
81
|
+
this.fallbackPositioning();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async calculateFloatingUIPosition(
|
|
86
|
+
referenceElement: HTMLElement,
|
|
87
|
+
floatingElement: HTMLElement
|
|
88
|
+
): Promise<{ x: number; y: number; placement: Placement; middlewareData: MiddlewareData }> {
|
|
89
|
+
const boundary = PortalUtils.findBoundaryElements(referenceElement);
|
|
90
|
+
const initialPlacement = this.getInitialPlacement();
|
|
91
|
+
const middleware = this.createFloatingUIMiddleware(boundary);
|
|
92
|
+
|
|
93
|
+
return await computePosition(referenceElement, floatingElement, {
|
|
94
|
+
placement: initialPlacement,
|
|
95
|
+
strategy: 'fixed',
|
|
96
|
+
middleware,
|
|
97
|
+
platform: this.createCustomPlatform()
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private getInitialPlacement(): Placement {
|
|
102
|
+
return this.component.placement || 'bottom-start';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private createFloatingUIMiddleware(boundary: Element[] | undefined): ComputePositionConfig['middleware'] {
|
|
106
|
+
const middleware: ComputePositionConfig['middleware'] = [
|
|
107
|
+
offset(this.component.distance || 0),
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
// Add size middleware if sync is specified
|
|
111
|
+
if (this.component.sync) {
|
|
112
|
+
middleware.push(
|
|
113
|
+
size({
|
|
114
|
+
apply: this.handleSizeMiddleware.bind(this),
|
|
115
|
+
padding: 10,
|
|
116
|
+
boundary: boundary
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Add flip middleware
|
|
122
|
+
middleware.push(
|
|
123
|
+
flip({
|
|
124
|
+
fallbackPlacements: this.getFallbackPlacements(),
|
|
125
|
+
fallbackStrategy: 'bestFit',
|
|
126
|
+
padding: 10,
|
|
127
|
+
boundary: boundary
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Add shift middleware
|
|
132
|
+
middleware.push(
|
|
133
|
+
shift({
|
|
134
|
+
padding: 10,
|
|
135
|
+
crossAxis: true,
|
|
136
|
+
boundary: boundary
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return middleware;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private handleSizeMiddleware({ availableWidth, availableHeight, elements, rects }: {
|
|
144
|
+
availableWidth: number;
|
|
145
|
+
availableHeight: number;
|
|
146
|
+
elements: { floating: HTMLElement };
|
|
147
|
+
rects: { reference: { x: number; y: number; width: number; height: number } };
|
|
148
|
+
}): void {
|
|
149
|
+
const maxHeight = PortalUtils.calculateOptimalHeight(
|
|
150
|
+
rects.reference,
|
|
151
|
+
window.innerHeight,
|
|
152
|
+
this.currentPlacement
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (this.component.sync === 'width' || this.component.sync === 'both') {
|
|
156
|
+
elements.floating.style.width = `${rects.reference.width}px`;
|
|
157
|
+
}
|
|
158
|
+
if (this.component.sync === 'height' || this.component.sync === 'both') {
|
|
159
|
+
elements.floating.style.height = `${rects.reference.height}px`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
elements.floating.style.maxHeight = `${maxHeight}px`;
|
|
163
|
+
elements.floating.style.setProperty('--auto-size-available-width', `${availableWidth}px`);
|
|
164
|
+
elements.floating.style.setProperty('--auto-size-available-height', `${maxHeight}px`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private getFallbackPlacements(): Placement[] {
|
|
168
|
+
const basePlacement = this.component.placement || 'bottom-start';
|
|
169
|
+
const placements: Placement[] = [];
|
|
170
|
+
|
|
171
|
+
if (basePlacement.startsWith('top')) {
|
|
172
|
+
placements.push('bottom-start', 'bottom', 'bottom-end', 'top', 'top-end', 'right-start', 'left-start');
|
|
173
|
+
} else if (basePlacement.startsWith('bottom')) {
|
|
174
|
+
placements.push('top-start', 'top', 'top-end', 'bottom', 'bottom-end', 'right-start', 'left-start');
|
|
175
|
+
} else if (basePlacement.startsWith('left')) {
|
|
176
|
+
placements.push('right-start', 'right', 'right-end', 'left', 'left-end', 'top-start', 'bottom-start');
|
|
177
|
+
} else if (basePlacement.startsWith('right')) {
|
|
178
|
+
placements.push('left-start', 'left', 'left-end', 'right', 'right-end', 'top-start', 'bottom-start');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return placements;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private createCustomPlatform() {
|
|
185
|
+
return platform;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private applyFloatingUIPosition(
|
|
189
|
+
floatingElement: HTMLElement,
|
|
190
|
+
referenceElement: HTMLElement,
|
|
191
|
+
x: number,
|
|
192
|
+
y: number,
|
|
193
|
+
placement: Placement,
|
|
194
|
+
middlewareData: MiddlewareData
|
|
195
|
+
): void {
|
|
196
|
+
const referenceRect = referenceElement.getBoundingClientRect();
|
|
197
|
+
|
|
198
|
+
Object.assign(floatingElement.style, {
|
|
199
|
+
left: `${x}px`,
|
|
200
|
+
top: `${y}px`,
|
|
201
|
+
position: 'fixed',
|
|
202
|
+
pointerEvents: 'auto',
|
|
203
|
+
width: 'auto',
|
|
204
|
+
minWidth: 'auto'
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Apply sync width if needed
|
|
208
|
+
if (this.component.sync === 'width' || this.component.sync === 'both') {
|
|
209
|
+
floatingElement.style.width = `${referenceRect.width}px`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
this.currentPlacement = placement;
|
|
213
|
+
this.currentMiddlewareData = middlewareData;
|
|
214
|
+
|
|
215
|
+
PortalUtils.applyCollisionData(floatingElement, middlewareData, placement);
|
|
216
|
+
|
|
217
|
+
const placementClass = placement.split('-')[0];
|
|
218
|
+
floatingElement.className = `nile-dropdown-portal-append dropdown--open dropdown__panel--${placementClass}`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private fallbackPositioning(): void {
|
|
222
|
+
if (!this.portalContainer) return;
|
|
223
|
+
|
|
224
|
+
const referenceElement = this.component.shadowRoot?.querySelector('[slot="anchor"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
225
|
+
this.component.querySelector('[slot="trigger"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
226
|
+
this.component;
|
|
227
|
+
const rect = referenceElement.getBoundingClientRect();
|
|
228
|
+
const viewportHeight = window.innerHeight;
|
|
229
|
+
const viewportWidth = window.innerWidth;
|
|
230
|
+
|
|
231
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
232
|
+
const spaceAbove = rect.top;
|
|
233
|
+
const spaceRight = viewportWidth - rect.right;
|
|
234
|
+
const spaceLeft = rect.left;
|
|
235
|
+
|
|
236
|
+
let topPosition: number;
|
|
237
|
+
let leftPosition: number = rect.left;
|
|
238
|
+
let placementClass: string;
|
|
239
|
+
let maxHeight: number;
|
|
240
|
+
|
|
241
|
+
const placement = this.component.placement || 'bottom-start';
|
|
242
|
+
|
|
243
|
+
if (placement.startsWith('bottom')) {
|
|
244
|
+
maxHeight = Math.max(spaceBelow - 20, 100);
|
|
245
|
+
topPosition = rect.bottom + (this.component.distance || 0);
|
|
246
|
+
placementClass = 'bottom';
|
|
247
|
+
} else if (placement.startsWith('top')) {
|
|
248
|
+
maxHeight = Math.max(spaceAbove - 20, 100);
|
|
249
|
+
topPosition = Math.max(rect.top - maxHeight - (this.component.distance || 0), 10);
|
|
250
|
+
placementClass = 'top';
|
|
251
|
+
} else if (placement.startsWith('right')) {
|
|
252
|
+
maxHeight = Math.max(Math.min(spaceRight, spaceBelow, spaceAbove) - 20, 100);
|
|
253
|
+
topPosition = rect.top;
|
|
254
|
+
leftPosition = rect.right + (this.component.distance || 0);
|
|
255
|
+
placementClass = 'right';
|
|
256
|
+
} else {
|
|
257
|
+
maxHeight = Math.max(Math.min(spaceLeft, spaceBelow, spaceAbove) - 20, 100);
|
|
258
|
+
topPosition = rect.top;
|
|
259
|
+
leftPosition = Math.max(rect.left - 200 - (this.component.distance || 0), 10);
|
|
260
|
+
placementClass = 'left';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.portalContainer.style.left = `${leftPosition}px`;
|
|
264
|
+
this.portalContainer.style.top = `${topPosition}px`;
|
|
265
|
+
this.portalContainer.style.maxHeight = `${maxHeight}px`;
|
|
266
|
+
this.portalContainer.style.pointerEvents = 'auto';
|
|
267
|
+
this.portalContainer.style.width = 'auto';
|
|
268
|
+
this.portalContainer.style.minWidth = 'auto';
|
|
269
|
+
|
|
270
|
+
if (this.component.sync === 'width' || this.component.sync === 'both') {
|
|
271
|
+
this.portalContainer.style.width = `${rect.width}px`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.portalContainer.className = `nile-dropdown-portal-append dropdown__panel--${placementClass}`;
|
|
275
|
+
|
|
276
|
+
this.calculateAndSetAutoSizeProperties(rect, topPosition, placementClass);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private calculateAndSetAutoSizeProperties(rect: DOMRect, topPosition: number, placementClass: string): void {
|
|
280
|
+
if (!this.portalContainer) return;
|
|
281
|
+
|
|
282
|
+
const viewportHeight = window.innerHeight;
|
|
283
|
+
const viewportWidth = window.innerWidth;
|
|
284
|
+
|
|
285
|
+
let availableHeight: number;
|
|
286
|
+
let availableWidth: number = viewportWidth - rect.left - 10;
|
|
287
|
+
|
|
288
|
+
if (placementClass === 'top') {
|
|
289
|
+
availableHeight = rect.top - 10;
|
|
290
|
+
} else if (placementClass === 'bottom') {
|
|
291
|
+
availableHeight = viewportHeight - rect.bottom - 10;
|
|
292
|
+
} else if (placementClass === 'left') {
|
|
293
|
+
availableHeight = Math.min(viewportHeight - rect.top, rect.bottom);
|
|
294
|
+
availableWidth = rect.left - 10;
|
|
295
|
+
} else {
|
|
296
|
+
availableHeight = Math.min(viewportHeight - rect.top, rect.bottom);
|
|
297
|
+
availableWidth = viewportWidth - rect.right - 10;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.portalContainer.style.setProperty('--auto-size-available-height', `${Math.max(availableHeight, 100)}px`);
|
|
301
|
+
this.portalContainer.style.setProperty('--auto-size-available-width', `${Math.max(availableWidth, 200)}px`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
updatePortalAppendPosition(): void {
|
|
305
|
+
if (this.component.portal && this.portalContainer) {
|
|
306
|
+
this.positionPortalAppend();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
handleWindowResize(): void {
|
|
311
|
+
if (this.component.portal && this.portalContainer) {
|
|
312
|
+
this.positionPortalAppend();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private setupAutoUpdatePositioning(): void {
|
|
317
|
+
if (!this.portalContainer || !this.component) return;
|
|
318
|
+
|
|
319
|
+
this.cleanupAutoUpdatePositioning();
|
|
320
|
+
|
|
321
|
+
const referenceElement = this.component.shadowRoot?.querySelector('[slot="anchor"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
322
|
+
this.component.querySelector('[slot="trigger"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
323
|
+
this.component;
|
|
324
|
+
|
|
325
|
+
this.cleanupAutoUpdate = autoUpdate(
|
|
326
|
+
referenceElement,
|
|
327
|
+
this.portalContainer,
|
|
328
|
+
() => {
|
|
329
|
+
this.computeFloatingUIPosition();
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
ancestorScroll: true,
|
|
333
|
+
ancestorResize: true,
|
|
334
|
+
elementResize: true,
|
|
335
|
+
layoutShift: true,
|
|
336
|
+
animationFrame: true
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private cleanupAutoUpdatePositioning(): void {
|
|
342
|
+
if (this.cleanupAutoUpdate) {
|
|
343
|
+
this.cleanupAutoUpdate();
|
|
344
|
+
this.cleanupAutoUpdate = null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private injectStylesToDocument(): void {
|
|
349
|
+
if (!this.portalContainer) return;
|
|
350
|
+
|
|
351
|
+
const styleId = PortalUtils.generateStyleId();
|
|
352
|
+
|
|
353
|
+
if (document.getElementById(styleId)) return;
|
|
354
|
+
|
|
355
|
+
const componentStyles = (this.component.constructor as any).styles;
|
|
356
|
+
if (!componentStyles) return;
|
|
357
|
+
|
|
358
|
+
const styleElement = document.createElement('style');
|
|
359
|
+
styleElement.id = styleId;
|
|
360
|
+
styleElement.textContent = PortalUtils.extractStylesAsCSS(componentStyles);
|
|
361
|
+
|
|
362
|
+
document.head.appendChild(styleElement);
|
|
363
|
+
|
|
364
|
+
(this.portalContainer as any).__injectedStyleId = styleId;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private adoptStylesToPortalAppend(): void {
|
|
368
|
+
if (!this.portalContainer) return;
|
|
369
|
+
this.injectStylesToDocument();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
setupPortalAppend(): void {
|
|
373
|
+
if (!this.component.portal) return;
|
|
374
|
+
|
|
375
|
+
this.component.updateComplete.then(() => {
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
if (this.component.open) {
|
|
378
|
+
this.clonedPanel = this.createPortalPanel();
|
|
379
|
+
|
|
380
|
+
this.portalContainer = this.createPortalAppendContainer();
|
|
381
|
+
this.portalContainer.appendChild(this.clonedPanel);
|
|
382
|
+
document.body.appendChild(this.portalContainer);
|
|
383
|
+
|
|
384
|
+
this.adoptStylesToPortalAppend();
|
|
385
|
+
|
|
386
|
+
this.clonedPanel.style.display = '';
|
|
387
|
+
this.positionPortalAppend();
|
|
388
|
+
|
|
389
|
+
this.setupPortalEventListeners();
|
|
390
|
+
|
|
391
|
+
this.setupAutoUpdatePositioning();
|
|
392
|
+
|
|
393
|
+
this.boundHandleWindowResize = this.handleWindowResize.bind(this);
|
|
394
|
+
window.addEventListener('resize', this.boundHandleWindowResize);
|
|
395
|
+
}
|
|
396
|
+
}, 10);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private createPortalPanel(): HTMLElement {
|
|
401
|
+
return PortalContentUtils.createPortalPanel(this.component);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private setupPortalEventListeners(): void {
|
|
405
|
+
PortalEventUtils.setupPortalEventListeners(this.clonedPanel!, this.component);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
cleanupPortalAppend(): void {
|
|
409
|
+
this.cleanupAutoUpdatePositioning();
|
|
410
|
+
|
|
411
|
+
if (this.portalContainer && this.portalContainer.parentNode) {
|
|
412
|
+
const injectedStyleId = (this.portalContainer as any).__injectedStyleId;
|
|
413
|
+
if (injectedStyleId) {
|
|
414
|
+
const styleElement = document.getElementById(injectedStyleId);
|
|
415
|
+
if (styleElement) {
|
|
416
|
+
styleElement.remove();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this.portalContainer.parentNode.removeChild(this.portalContainer);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (this.boundHandleWindowResize) {
|
|
424
|
+
window.removeEventListener('resize', this.boundHandleWindowResize);
|
|
425
|
+
this.boundHandleWindowResize = null;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
this.portalContainer = null;
|
|
429
|
+
this.clonedPanel = null;
|
|
430
|
+
this.measuredPanelHeight = null;
|
|
431
|
+
this.currentPlacement = 'bottom-start';
|
|
432
|
+
this.currentMiddlewareData = null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
get portalContainerElement(): HTMLElement | null {
|
|
436
|
+
return this.portalContainer;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
resetMeasuredHeight(): void {
|
|
440
|
+
this.measuredPanelHeight = null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
forceReposition(): void {
|
|
444
|
+
if (this.portalContainer) {
|
|
445
|
+
this.computeFloatingUIPosition();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
getCurrentPlacement(): Placement {
|
|
450
|
+
return this.currentPlacement;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
getCurrentMiddlewareData(): MiddlewareData | null {
|
|
454
|
+
return this.currentMiddlewareData;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
isUsingFloatingUI(): boolean {
|
|
458
|
+
return this.cleanupAutoUpdate !== null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
isPositioningOptimal(): boolean {
|
|
462
|
+
if (!this.portalContainer || !this.currentMiddlewareData) return true;
|
|
463
|
+
|
|
464
|
+
const referenceElement = this.component.shadowRoot?.querySelector('[slot="anchor"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
465
|
+
this.component.querySelector('[slot="trigger"]')?.assignedElements({ flatten: true })[0] as HTMLElement ||
|
|
466
|
+
this.component;
|
|
467
|
+
const rect = referenceElement.getBoundingClientRect();
|
|
468
|
+
const viewportHeight = window.innerHeight;
|
|
469
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
470
|
+
const spaceAbove = rect.top;
|
|
471
|
+
|
|
472
|
+
const isAbove = this.currentPlacement.startsWith('top');
|
|
473
|
+
const isBelow = this.currentPlacement.startsWith('bottom');
|
|
474
|
+
|
|
475
|
+
if (isAbove && spaceBelow > spaceAbove) return false;
|
|
476
|
+
if (isBelow && spaceAbove > spaceBelow) return false;
|
|
477
|
+
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
updatePortalPanel(): void {
|
|
482
|
+
if (this.portalContainer && this.clonedPanel) {
|
|
483
|
+
PortalContentUtils.updatePortalPanel(this.clonedPanel, this.component);
|
|
484
|
+
this.forceReposition();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|