@aquera/nile-elements 1.7.1 → 1.7.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.
- package/README.md +7 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.js +1524 -419
- package/dist/nile-breadcrumb-item/nile-breadcrumb-item.cjs.js +1 -1
- package/dist/nile-breadcrumb-item/nile-breadcrumb-item.cjs.js.map +1 -1
- package/dist/nile-breadcrumb-item/nile-breadcrumb-item.esm.js +8 -6
- package/dist/nile-combobox/group-utils.cjs.js +2 -0
- package/dist/nile-combobox/group-utils.cjs.js.map +1 -0
- package/dist/nile-combobox/group-utils.esm.js +1 -0
- package/dist/nile-combobox/index.cjs.js +2 -0
- package/dist/nile-combobox/index.cjs.js.map +1 -0
- package/dist/nile-combobox/index.esm.js +1 -0
- package/dist/nile-combobox/nile-combobox.cjs.js +2 -0
- package/dist/nile-combobox/nile-combobox.cjs.js.map +1 -0
- package/dist/nile-combobox/nile-combobox.css.cjs.js +2 -0
- package/dist/nile-combobox/nile-combobox.css.cjs.js.map +1 -0
- package/dist/nile-combobox/nile-combobox.css.esm.js +715 -0
- package/dist/nile-combobox/nile-combobox.esm.js +238 -0
- package/dist/nile-combobox/portal-manager.cjs.js +2 -0
- package/dist/nile-combobox/portal-manager.cjs.js.map +1 -0
- package/dist/nile-combobox/portal-manager.esm.js +1 -0
- package/dist/nile-combobox/renderer.cjs.js +2 -0
- package/dist/nile-combobox/renderer.cjs.js.map +1 -0
- package/dist/nile-combobox/renderer.esm.js +147 -0
- package/dist/nile-combobox/search-manager.cjs.js +2 -0
- package/dist/nile-combobox/search-manager.cjs.js.map +1 -0
- package/dist/nile-combobox/search-manager.esm.js +1 -0
- package/dist/nile-combobox/selection-manager.cjs.js +2 -0
- package/dist/nile-combobox/selection-manager.cjs.js.map +1 -0
- package/dist/nile-combobox/selection-manager.esm.js +1 -0
- package/dist/nile-combobox/types.cjs.js +2 -0
- package/dist/nile-combobox/types.cjs.js.map +1 -0
- package/dist/nile-combobox/types.esm.js +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/nile-breadcrumb-item/nile-breadcrumb-item.js +4 -2
- package/dist/src/nile-breadcrumb-item/nile-breadcrumb-item.js.map +1 -1
- package/dist/src/nile-combobox/group-utils.d.ts +26 -0
- package/dist/src/nile-combobox/group-utils.js +140 -0
- package/dist/src/nile-combobox/group-utils.js.map +1 -0
- package/dist/src/nile-combobox/index.d.ts +1 -0
- package/dist/src/nile-combobox/index.js +2 -0
- package/dist/src/nile-combobox/index.js.map +1 -0
- package/dist/src/nile-combobox/nile-combobox.css.d.ts +9 -0
- package/dist/src/nile-combobox/nile-combobox.css.js +724 -0
- package/dist/src/nile-combobox/nile-combobox.css.js.map +1 -0
- package/dist/src/nile-combobox/nile-combobox.d.ts +320 -0
- package/dist/src/nile-combobox/nile-combobox.js +1739 -0
- package/dist/src/nile-combobox/nile-combobox.js.map +1 -0
- package/dist/src/nile-combobox/nile-combobox.test.d.ts +1 -0
- package/dist/src/nile-combobox/nile-combobox.test.js +551 -0
- package/dist/src/nile-combobox/nile-combobox.test.js.map +1 -0
- package/dist/src/nile-combobox/portal-manager.d.ts +26 -0
- package/dist/src/nile-combobox/portal-manager.js +218 -0
- package/dist/src/nile-combobox/portal-manager.js.map +1 -0
- package/dist/src/nile-combobox/renderer.d.ts +24 -0
- package/dist/src/nile-combobox/renderer.js +279 -0
- package/dist/src/nile-combobox/renderer.js.map +1 -0
- package/dist/src/nile-combobox/search-manager.d.ts +15 -0
- package/dist/src/nile-combobox/search-manager.js +41 -0
- package/dist/src/nile-combobox/search-manager.js.map +1 -0
- package/dist/src/nile-combobox/selection-manager.d.ts +12 -0
- package/dist/src/nile-combobox/selection-manager.js +44 -0
- package/dist/src/nile-combobox/selection-manager.js.map +1 -0
- package/dist/src/nile-combobox/types.d.ts +53 -0
- package/dist/src/nile-combobox/types.js +8 -0
- package/dist/src/nile-combobox/types.js.map +1 -0
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -1
- package/src/index.ts +1 -0
- package/src/nile-breadcrumb-item/nile-breadcrumb-item.ts +4 -2
- package/src/nile-combobox/group-utils.ts +157 -0
- package/src/nile-combobox/index.ts +1 -0
- package/src/nile-combobox/nile-combobox.css.ts +726 -0
- package/src/nile-combobox/nile-combobox.test.ts +704 -0
- package/src/nile-combobox/nile-combobox.ts +1816 -0
- package/src/nile-combobox/portal-manager.ts +263 -0
- package/src/nile-combobox/renderer.ts +466 -0
- package/src/nile-combobox/search-manager.ts +53 -0
- package/src/nile-combobox/selection-manager.ts +57 -0
- package/src/nile-combobox/types.ts +63 -0
- package/vscode-html-custom-data.json +311 -4
- package/web-dev-server.config.mjs +9 -0
- package/web-test-runner.config.mjs +11 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright Aquera Inc 2025
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the BSD-3-Clause license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
autoUpdate,
|
|
10
|
+
computePosition,
|
|
11
|
+
flip,
|
|
12
|
+
offset,
|
|
13
|
+
shift,
|
|
14
|
+
size,
|
|
15
|
+
platform,
|
|
16
|
+
type Placement,
|
|
17
|
+
type MiddlewareData,
|
|
18
|
+
type ComputePositionConfig,
|
|
19
|
+
} from '@floating-ui/dom';
|
|
20
|
+
import { PortalUtils } from '../nile-select/portal-utils';
|
|
21
|
+
|
|
22
|
+
export class ComboboxPortalManager {
|
|
23
|
+
private portalContainer: HTMLElement | null = null;
|
|
24
|
+
private originalListboxParent: HTMLElement | null = null;
|
|
25
|
+
private measuredPopupHeight: number | null = null;
|
|
26
|
+
private component: any;
|
|
27
|
+
private cleanupAutoUpdate: (() => void) | null = null;
|
|
28
|
+
private currentPlacement: Placement = 'bottom';
|
|
29
|
+
|
|
30
|
+
constructor(component: any) {
|
|
31
|
+
this.component = component;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get portalContainerElement(): HTMLElement | null {
|
|
35
|
+
return this.portalContainer;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private createPortalContainer(): HTMLElement {
|
|
39
|
+
const container = document.createElement('div');
|
|
40
|
+
container.style.position = 'absolute';
|
|
41
|
+
container.style.zIndex = '9999';
|
|
42
|
+
container.style.pointerEvents = 'none';
|
|
43
|
+
container.className = 'nile-combobox-portal';
|
|
44
|
+
return container;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async computePosition(): Promise<void> {
|
|
48
|
+
if (!this.portalContainer) return;
|
|
49
|
+
|
|
50
|
+
const referenceElement = this.component.combobox || this.component;
|
|
51
|
+
const floatingElement = this.portalContainer;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const boundary = PortalUtils.findBoundaryElements(referenceElement);
|
|
55
|
+
const initialPlacement = PortalUtils.getOptimalPlacement(referenceElement);
|
|
56
|
+
|
|
57
|
+
const { x, y, placement } = await computePosition(
|
|
58
|
+
referenceElement,
|
|
59
|
+
floatingElement,
|
|
60
|
+
{
|
|
61
|
+
placement: initialPlacement,
|
|
62
|
+
strategy: 'fixed',
|
|
63
|
+
middleware: [
|
|
64
|
+
offset(4),
|
|
65
|
+
size({
|
|
66
|
+
apply: ({ availableWidth, availableHeight, elements, rects }) => {
|
|
67
|
+
const maxHeight = PortalUtils.calculateOptimalHeight(
|
|
68
|
+
rects.reference,
|
|
69
|
+
window.innerHeight,
|
|
70
|
+
this.currentPlacement,
|
|
71
|
+
);
|
|
72
|
+
elements.floating.style.maxWidth = `${availableWidth}px`;
|
|
73
|
+
elements.floating.style.maxHeight = `${maxHeight}px`;
|
|
74
|
+
elements.floating.style.setProperty('--auto-size-available-width', `${availableWidth}px`);
|
|
75
|
+
elements.floating.style.setProperty('--auto-size-available-height', `${maxHeight}px`);
|
|
76
|
+
},
|
|
77
|
+
padding: 10,
|
|
78
|
+
boundary,
|
|
79
|
+
}),
|
|
80
|
+
flip({
|
|
81
|
+
fallbackPlacements: ['bottom', 'top', 'bottom-start', 'top-start'],
|
|
82
|
+
fallbackStrategy: 'bestFit',
|
|
83
|
+
padding: 10,
|
|
84
|
+
boundary,
|
|
85
|
+
}),
|
|
86
|
+
shift({ padding: 10, crossAxis: true, boundary }),
|
|
87
|
+
],
|
|
88
|
+
platform,
|
|
89
|
+
},
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const referenceRect = referenceElement.getBoundingClientRect();
|
|
93
|
+
Object.assign(floatingElement.style, {
|
|
94
|
+
left: `${x}px`,
|
|
95
|
+
top: `${y}px`,
|
|
96
|
+
position: 'fixed',
|
|
97
|
+
pointerEvents: 'auto',
|
|
98
|
+
width: `${referenceRect.width}px`,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this.currentPlacement = placement;
|
|
102
|
+
const placementClass = placement.split('-')[0];
|
|
103
|
+
floatingElement.className = `nile-combobox-portal combobox__listbox--${placementClass}`;
|
|
104
|
+
} catch {
|
|
105
|
+
this.fallbackPositioning();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private fallbackPositioning(): void {
|
|
110
|
+
if (!this.portalContainer) return;
|
|
111
|
+
|
|
112
|
+
const ref = this.component.combobox || this.component;
|
|
113
|
+
const rect = ref.getBoundingClientRect();
|
|
114
|
+
const vh = window.innerHeight;
|
|
115
|
+
const spaceBelow = vh - rect.bottom;
|
|
116
|
+
const spaceAbove = rect.top;
|
|
117
|
+
|
|
118
|
+
let top: number;
|
|
119
|
+
let maxH: number;
|
|
120
|
+
let cls: string;
|
|
121
|
+
|
|
122
|
+
if (spaceAbove > spaceBelow) {
|
|
123
|
+
maxH = Math.max(spaceAbove - 20, 100);
|
|
124
|
+
top = Math.max(rect.top - maxH - 4, 10);
|
|
125
|
+
cls = 'top';
|
|
126
|
+
} else {
|
|
127
|
+
maxH = Math.max(spaceBelow - 20, 100);
|
|
128
|
+
top = rect.bottom + 4;
|
|
129
|
+
cls = 'bottom';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Object.assign(this.portalContainer.style, {
|
|
133
|
+
left: `${rect.left}px`,
|
|
134
|
+
top: `${top}px`,
|
|
135
|
+
width: `${rect.width}px`,
|
|
136
|
+
maxHeight: `${maxH}px`,
|
|
137
|
+
pointerEvents: 'auto',
|
|
138
|
+
});
|
|
139
|
+
this.portalContainer.className = `nile-combobox-portal combobox__listbox--${cls}`;
|
|
140
|
+
this.portalContainer.style.setProperty('--auto-size-available-height', `${maxH}px`);
|
|
141
|
+
this.portalContainer.style.setProperty('--auto-size-available-width', `${rect.width}px`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private extractStylesAsCSS(styles: any): string {
|
|
145
|
+
if (typeof styles === 'string') return styles;
|
|
146
|
+
if (Array.isArray(styles)) return styles.map(s => this.extractStylesAsCSS(s)).join('\n');
|
|
147
|
+
if (styles && typeof styles === 'object' && styles.cssText) return styles.cssText;
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private injectStyles(): void {
|
|
152
|
+
if (!this.portalContainer) return;
|
|
153
|
+
const styleId = `nile-combobox-styles-${Math.random().toString(36).substring(2, 11)}`;
|
|
154
|
+
if (document.getElementById(styleId)) return;
|
|
155
|
+
|
|
156
|
+
const componentStyles = (this.component.constructor as any).styles;
|
|
157
|
+
if (!componentStyles) return;
|
|
158
|
+
|
|
159
|
+
const el = document.createElement('style');
|
|
160
|
+
el.id = styleId;
|
|
161
|
+
el.textContent = this.extractStylesAsCSS(componentStyles);
|
|
162
|
+
document.head.appendChild(el);
|
|
163
|
+
(this.portalContainer as any).__injectedStyleId = styleId;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setupPortal(): void {
|
|
167
|
+
if (!this.component.portal) return;
|
|
168
|
+
|
|
169
|
+
this.component.updateComplete.then(() => {
|
|
170
|
+
const listbox = this.component.shadowRoot?.querySelector('#listbox') as HTMLElement;
|
|
171
|
+
if (!listbox) return;
|
|
172
|
+
|
|
173
|
+
this.originalListboxParent = listbox.parentElement as HTMLElement;
|
|
174
|
+
this.portalContainer = this.createPortalContainer();
|
|
175
|
+
this.portalContainer.appendChild(listbox);
|
|
176
|
+
document.body.appendChild(this.portalContainer);
|
|
177
|
+
this.injectStyles();
|
|
178
|
+
listbox.style.display = '';
|
|
179
|
+
this.computePosition();
|
|
180
|
+
|
|
181
|
+
this.cleanupAutoUpdate = autoUpdate(
|
|
182
|
+
this.component,
|
|
183
|
+
this.portalContainer,
|
|
184
|
+
() => this.computePosition(),
|
|
185
|
+
{ ancestorScroll: true, ancestorResize: true, elementResize: true, layoutShift: true, animationFrame: true },
|
|
186
|
+
);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
cleanupPortal(): void {
|
|
191
|
+
if (this.cleanupAutoUpdate) {
|
|
192
|
+
this.cleanupAutoUpdate();
|
|
193
|
+
this.cleanupAutoUpdate = null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (this.portalContainer && this.portalContainer.parentNode) {
|
|
197
|
+
const listbox = this.portalContainer.querySelector('#listbox') as HTMLElement;
|
|
198
|
+
if (listbox && this.originalListboxParent) {
|
|
199
|
+
this.originalListboxParent.appendChild(listbox);
|
|
200
|
+
listbox.style.display = this.component.portal ? 'none' : '';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const sid = (this.portalContainer as any).__injectedStyleId;
|
|
204
|
+
if (sid) document.getElementById(sid)?.remove();
|
|
205
|
+
|
|
206
|
+
this.portalContainer.parentNode.removeChild(this.portalContainer);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.portalContainer = null;
|
|
210
|
+
this.originalListboxParent = null;
|
|
211
|
+
this.measuredPopupHeight = null;
|
|
212
|
+
this.currentPlacement = 'bottom';
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
updatePosition(): void {
|
|
216
|
+
if (this.component.portal && this.portalContainer) {
|
|
217
|
+
this.computePosition();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
resetMeasuredHeight(): void {
|
|
222
|
+
this.measuredPopupHeight = null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async resetScrollPosition(): Promise<void> {
|
|
226
|
+
await this.component.updateComplete;
|
|
227
|
+
|
|
228
|
+
requestAnimationFrame(() => {
|
|
229
|
+
let listbox: HTMLElement | null = null;
|
|
230
|
+
|
|
231
|
+
if (this.component.portal && this.portalContainer) {
|
|
232
|
+
listbox = this.portalContainer.querySelector('#listbox') as HTMLElement;
|
|
233
|
+
} else {
|
|
234
|
+
listbox = this.component.shadowRoot?.querySelector('#listbox') as HTMLElement;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!listbox || !listbox.isConnected) return;
|
|
238
|
+
listbox.scrollTop = 0;
|
|
239
|
+
|
|
240
|
+
const virtualized = listbox.querySelector('.virtualized') as HTMLElement;
|
|
241
|
+
if (virtualized && virtualized.isConnected) {
|
|
242
|
+
const fewItems = this.component.filteredData?.length < 5;
|
|
243
|
+
if (fewItems) {
|
|
244
|
+
virtualized.style.overflowY = 'hidden';
|
|
245
|
+
virtualized.style.maxHeight = 'none';
|
|
246
|
+
listbox.style.overflowY = 'hidden';
|
|
247
|
+
listbox.style.maxHeight = 'fit-content';
|
|
248
|
+
} else {
|
|
249
|
+
virtualized.style.overflowY = 'auto';
|
|
250
|
+
virtualized.style.maxHeight = '';
|
|
251
|
+
listbox.style.overflowY = 'auto';
|
|
252
|
+
listbox.style.maxHeight = '';
|
|
253
|
+
}
|
|
254
|
+
virtualized.scrollTop = 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const virtualizer = listbox.querySelector('lit-virtualizer') as HTMLElement;
|
|
258
|
+
if (virtualizer && virtualizer.isConnected) {
|
|
259
|
+
virtualizer.scrollTop = 0;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|