@ship-ui/core 0.17.15 → 0.17.17
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.
|
@@ -1062,9 +1062,15 @@
|
|
|
1062
1062
|
"outputs": [],
|
|
1063
1063
|
"methods": [
|
|
1064
1064
|
{
|
|
1065
|
-
"name": "
|
|
1066
|
-
"parameters": "
|
|
1067
|
-
"returnType": "
|
|
1065
|
+
"name": "while",
|
|
1066
|
+
"parameters": "parent",
|
|
1067
|
+
"returnType": "any",
|
|
1068
|
+
"description": ""
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
"name": "return",
|
|
1072
|
+
"parameters": "pos.left >= 0 &&\n pos.top >= 0 &&\n pos.left + m.width <= window.innerWidth &&\n pos.top + m.height <= window.innerHeight\n );\n }\n\n #clampToViewport(pos: { left: number; top: number }, m: DOMRect",
|
|
1073
|
+
"returnType": "any",
|
|
1068
1074
|
"description": ""
|
|
1069
1075
|
},
|
|
1070
1076
|
{
|
|
@@ -3258,7 +3264,7 @@
|
|
|
3258
3264
|
},
|
|
3259
3265
|
{
|
|
3260
3266
|
"name": "search-menu-example",
|
|
3261
|
-
"html": "<sh-menu [searchable]=\"true\">\n <button shButton class=\"outlined\">Open searchable menu</button>\n <ng-container menu>\n @for (item of filteredItems; track item.value) {\n <button (click)=\"select(item)\" [class.selected]=\"selected === item.value\">\n <p>\n hello world\n <br />\n {{ item.label }}\n </p>\n </button>\n }\n </ng-container>\n</sh-menu>\n",
|
|
3267
|
+
"html": "<sh-menu [searchable]=\"true\">\n <button shButton class=\"outlined\">Open searchable menu</button>\n <ng-container menu>\n @for (item of filteredItems; track item.value) {\n <button (click)=\"select(item)\" [class.selected]=\"selected === item.value\">\n <p>\n hello world\n <br />\n {{ item.label }}\n </p>\n </button>\n }\n </ng-container>\n</sh-menu>\n\n<sh-menu [searchable]=\"true\">\n <button shButton class=\"outlined\">Open searchable menu</button>\n <ng-container menu>\n @for (item of filteredItems; track item.value) {\n <button (click)=\"select(item)\" [class.selected]=\"selected === item.value\">\n <div class=\"option-col\">\n {{ item.label }} asdlkjadskljjkladsjkldaljkdaslkjad jklsjkl dasjkld as\n <p>hello world but im extra long so i should wrap to the next line</p>\n </div>\n </button>\n }\n </ng-container>\n</sh-menu>\n",
|
|
3262
3268
|
"ts": "import { Component } from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { ShipButton, ShipMenu } from 'ship-ui';\n\n@Component({\n selector: 'sh-search-menu-example',\n templateUrl: './search-menu-example.html',\n styleUrls: ['./search-menu-example.scss'],\n imports: [FormsModule, ShipMenu, ShipButton],\n standalone: true,\n})\nexport class SearchMenuExample {\n menuItems = [\n { label: 'Dashboard', value: 'dashboard' },\n { label: 'Users', value: 'users' },\n { label: 'Settings', value: 'settings' },\n { label: 'Billing', value: 'billing' },\n { label: 'Support', value: 'support' },\n ];\n search = '';\n selected: string | null = null;\n\n get filteredItems() {\n return this.menuItems.filter((item) => item.label.toLowerCase().includes(this.search.toLowerCase()));\n }\n\n select(item: any) {\n this.selected = item.value;\n }\n}\n"
|
|
3263
3269
|
},
|
|
3264
3270
|
{
|
|
@@ -7014,31 +7014,56 @@ class ShipTooltipWrapper {
|
|
|
7014
7014
|
close: () => this.close()(),
|
|
7015
7015
|
};
|
|
7016
7016
|
this.isTemplate = computed(() => this.content() instanceof TemplateRef, ...(ngDevMode ? [{ debugName: "isTemplate" }] : []));
|
|
7017
|
+
this.#document = inject(DOCUMENT);
|
|
7017
7018
|
this.#selfRef = inject((ElementRef));
|
|
7018
7019
|
this.#renderer = inject(Renderer2);
|
|
7019
7020
|
this.#positionAbort = null;
|
|
7020
|
-
this.SUPPORTS_ANCHOR = typeof CSS !== 'undefined' && CSS.supports('position-anchor', '--abc');
|
|
7021
|
+
this.SUPPORTS_ANCHOR = typeof CSS !== 'undefined' && CSS.supports('position-anchor', '--abc') && CSS.supports('anchor-name', '--abc');
|
|
7021
7022
|
this.isBelow = signal(false, ...(ngDevMode ? [{ debugName: "isBelow" }] : []));
|
|
7022
7023
|
this.openEffect = effect(() => {
|
|
7023
7024
|
if (this.isOpen()) {
|
|
7024
|
-
|
|
7025
|
-
this.#selfRef.nativeElement
|
|
7026
|
-
|
|
7025
|
+
queueMicrotask(() => {
|
|
7026
|
+
const tooltipEl = this.#selfRef.nativeElement;
|
|
7027
|
+
if (!tooltipEl || !tooltipEl.isConnected)
|
|
7028
|
+
return;
|
|
7029
|
+
if (this.#positionAbort) {
|
|
7030
|
+
this.#positionAbort.abort();
|
|
7031
|
+
}
|
|
7032
|
+
this.#positionAbort = new AbortController();
|
|
7033
|
+
tooltipEl.showPopover();
|
|
7034
|
+
if (!this.SUPPORTS_ANCHOR) {
|
|
7035
|
+
setTimeout(() => {
|
|
7036
|
+
const scrollableParent = this.#findScrollableParent(tooltipEl);
|
|
7037
|
+
scrollableParent.addEventListener('scroll', () => this.calculateTooltipPosition(), {
|
|
7038
|
+
signal: this.#positionAbort?.signal,
|
|
7039
|
+
passive: true,
|
|
7040
|
+
});
|
|
7041
|
+
window?.addEventListener('resize', () => this.calculateTooltipPosition(), {
|
|
7042
|
+
signal: this.#positionAbort?.signal,
|
|
7043
|
+
passive: true,
|
|
7044
|
+
});
|
|
7045
|
+
this.calculateTooltipPosition();
|
|
7046
|
+
});
|
|
7047
|
+
}
|
|
7027
7048
|
});
|
|
7028
7049
|
}
|
|
7029
7050
|
else {
|
|
7030
|
-
this.#selfRef.nativeElement
|
|
7051
|
+
const tooltipEl = this.#selfRef.nativeElement;
|
|
7052
|
+
if (tooltipEl) {
|
|
7053
|
+
try {
|
|
7054
|
+
if (tooltipEl.matches(':popover-open')) {
|
|
7055
|
+
tooltipEl.hidePopover();
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
catch (e) {
|
|
7059
|
+
// Ignore if already hidden or other errors
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
this.#positionAbort?.abort();
|
|
7063
|
+
this.#positionAbort = null;
|
|
7031
7064
|
}
|
|
7032
7065
|
}, ...(ngDevMode ? [{ debugName: "openEffect" }] : []));
|
|
7033
|
-
this.rafId = null;
|
|
7034
|
-
this.schedulePositionUpdate = () => {
|
|
7035
|
-
if (this.rafId !== null) {
|
|
7036
|
-
cancelAnimationFrame(this.rafId);
|
|
7037
|
-
}
|
|
7038
|
-
this.rafId = requestAnimationFrame(this.calculateTooltipPosition);
|
|
7039
|
-
};
|
|
7040
7066
|
this.calculateTooltipPosition = () => {
|
|
7041
|
-
this.rafId = null;
|
|
7042
7067
|
if (!this.anchorEl())
|
|
7043
7068
|
return;
|
|
7044
7069
|
const hostRect = this.anchorEl().nativeElement.getBoundingClientRect();
|
|
@@ -7046,56 +7071,71 @@ class ShipTooltipWrapper {
|
|
|
7046
7071
|
const tooltipRect = tooltipEl.getBoundingClientRect();
|
|
7047
7072
|
if (tooltipRect.width === 0 && tooltipRect.height === 0)
|
|
7048
7073
|
return;
|
|
7049
|
-
const
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
}
|
|
7071
|
-
if (tooltipEl.style.top !== topStyle) {
|
|
7072
|
-
this.#renderer.setStyle(tooltipEl, 'top', topStyle);
|
|
7073
|
-
}
|
|
7074
|
-
if (tooltipEl.style.position !== 'fixed') {
|
|
7075
|
-
this.#renderer.setStyle(tooltipEl, 'position', 'fixed');
|
|
7074
|
+
const BASE_SPACE = 8;
|
|
7075
|
+
// Position generators
|
|
7076
|
+
const topCenter = (t, m) => ({
|
|
7077
|
+
left: t.left + t.width / 2 - m.width / 2,
|
|
7078
|
+
top: t.top - m.height - BASE_SPACE,
|
|
7079
|
+
});
|
|
7080
|
+
const bottomCenter = (t, m) => ({
|
|
7081
|
+
left: t.left + t.width / 2 - m.width / 2,
|
|
7082
|
+
top: t.bottom + BASE_SPACE,
|
|
7083
|
+
});
|
|
7084
|
+
const topSpanLeft = (t, m) => ({ left: t.right - m.width, top: t.top - m.height - BASE_SPACE });
|
|
7085
|
+
const topSpanRight = (t, m) => ({ left: t.left, top: t.top - m.height - BASE_SPACE });
|
|
7086
|
+
const bottomSpanLeft = (t, m) => ({ left: t.right - m.width, top: t.bottom + BASE_SPACE });
|
|
7087
|
+
const bottomSpanRight = (t, m) => ({ left: t.left, top: t.bottom + BASE_SPACE });
|
|
7088
|
+
const tryOrder = [topCenter, topSpanLeft, topSpanRight, bottomCenter, bottomSpanLeft, bottomSpanRight];
|
|
7089
|
+
for (const positionFn of tryOrder) {
|
|
7090
|
+
const pos = positionFn(hostRect, tooltipRect);
|
|
7091
|
+
if (this.#fitsInViewport(pos, tooltipRect)) {
|
|
7092
|
+
this.#applyPosition(pos, tooltipEl);
|
|
7093
|
+
this.isBelow.set(pos.top > hostRect.top);
|
|
7094
|
+
return;
|
|
7076
7095
|
}
|
|
7077
7096
|
}
|
|
7097
|
+
const fallback = this.#clampToViewport(topCenter(hostRect, tooltipRect), tooltipRect);
|
|
7098
|
+
this.#applyPosition(fallback, tooltipEl);
|
|
7099
|
+
this.isBelow.set(fallback.top > hostRect.top);
|
|
7078
7100
|
};
|
|
7079
7101
|
}
|
|
7102
|
+
#document;
|
|
7080
7103
|
#selfRef;
|
|
7081
7104
|
#renderer;
|
|
7082
7105
|
#positionAbort;
|
|
7083
|
-
ngAfterViewInit() {
|
|
7084
|
-
this.#positionAbort = new AbortController();
|
|
7085
|
-
const options = { signal: this.#positionAbort.signal, capture: true, passive: true };
|
|
7086
|
-
window?.addEventListener('scroll', this.schedulePositionUpdate, options);
|
|
7087
|
-
window?.addEventListener('resize', this.schedulePositionUpdate, {
|
|
7088
|
-
signal: this.#positionAbort.signal,
|
|
7089
|
-
passive: true,
|
|
7090
|
-
});
|
|
7091
|
-
this.schedulePositionUpdate();
|
|
7092
|
-
}
|
|
7093
7106
|
ngOnDestroy() {
|
|
7094
7107
|
this.#positionAbort?.abort();
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
7108
|
+
this.#positionAbort = null;
|
|
7109
|
+
}
|
|
7110
|
+
#findScrollableParent(element) {
|
|
7111
|
+
const SCROLLABLE_STYLES = ['scroll', 'auto'];
|
|
7112
|
+
let parent = element.parentElement;
|
|
7113
|
+
while (parent) {
|
|
7114
|
+
if (SCROLLABLE_STYLES.indexOf(window?.getComputedStyle(parent).overflowY) > -1 &&
|
|
7115
|
+
parent.scrollHeight > parent.clientHeight) {
|
|
7116
|
+
return parent;
|
|
7117
|
+
}
|
|
7118
|
+
parent = parent.parentElement;
|
|
7098
7119
|
}
|
|
7120
|
+
return this.#document.documentElement;
|
|
7121
|
+
}
|
|
7122
|
+
#applyPosition(pos, element) {
|
|
7123
|
+
this.#renderer.setStyle(element, 'left', `${pos.left}px`);
|
|
7124
|
+
this.#renderer.setStyle(element, 'top', `${pos.top}px`);
|
|
7125
|
+
this.#renderer.setStyle(element, 'position', 'fixed');
|
|
7126
|
+
this.#renderer.setStyle(element, 'margin', '0');
|
|
7127
|
+
}
|
|
7128
|
+
#fitsInViewport(pos, m) {
|
|
7129
|
+
return (pos.left >= 0 &&
|
|
7130
|
+
pos.top >= 0 &&
|
|
7131
|
+
pos.left + m.width <= window.innerWidth &&
|
|
7132
|
+
pos.top + m.height <= window.innerHeight);
|
|
7133
|
+
}
|
|
7134
|
+
#clampToViewport(pos, m) {
|
|
7135
|
+
return {
|
|
7136
|
+
left: Math.max(0, Math.min(pos.left, window.innerWidth - m.width)),
|
|
7137
|
+
top: Math.max(0, Math.min(pos.top, window.innerHeight - m.height)),
|
|
7138
|
+
};
|
|
7099
7139
|
}
|
|
7100
7140
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.0", ngImport: i0, type: ShipTooltipWrapper, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
7101
7141
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.0", type: ShipTooltipWrapper, isStandalone: true, selector: "ship-tooltip-wrapper", inputs: { positionAnchorName: { classPropertyName: "positionAnchorName", publicName: "positionAnchorName", isSignal: true, isRequired: true, transformFunction: null }, anchorEl: { classPropertyName: "anchorEl", publicName: "anchorEl", isSignal: true, isRequired: true, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, close: { classPropertyName: "close", publicName: "close", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "tooltip" }, properties: { "attr.popover": "\"manual\"", "style.position-anchor": "positionAnchorName()", "class.below": "isBelow()" } }, ngImport: i0, template: `
|
|
@@ -7147,7 +7187,7 @@ class ShipTooltip {
|
|
|
7147
7187
|
this.#elementRef = inject((ElementRef));
|
|
7148
7188
|
this.#viewContainerRef = inject(ViewContainerRef);
|
|
7149
7189
|
this.#environmentInjector = inject(EnvironmentInjector);
|
|
7150
|
-
this
|
|
7190
|
+
this.debounceTimer = null;
|
|
7151
7191
|
this.DEBOUNCE_DELAY = 500;
|
|
7152
7192
|
this.anchorName = `--${generateUniqueId()}`;
|
|
7153
7193
|
this.isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
@@ -7156,7 +7196,6 @@ class ShipTooltip {
|
|
|
7156
7196
|
#elementRef;
|
|
7157
7197
|
#viewContainerRef;
|
|
7158
7198
|
#environmentInjector;
|
|
7159
|
-
#renderer;
|
|
7160
7199
|
onMouseEnter() {
|
|
7161
7200
|
if (openRef?.component.anchorName !== this.anchorName) {
|
|
7162
7201
|
this.cleanupTooltip();
|