oasys-lib 2.30.1 → 2.31.0
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/fesm2022/oasys-lib.mjs +125 -19
- package/fesm2022/oasys-lib.mjs.map +1 -1
- package/index.d.ts +11 -1
- package/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/assets/bloomandwild/variables.css +1 -1
- package/src/assets/bloomon/variables.css +1 -1
- package/src/assets/global/scss-breakpoints.scss +1 -1
package/fesm2022/oasys-lib.mjs
CHANGED
|
@@ -951,7 +951,11 @@ class ImageComponent {
|
|
|
951
951
|
breakpoints = inject(IMAGE_BREAKPOINTS);
|
|
952
952
|
fallbackBreakpoint = 768;
|
|
953
953
|
imageSizes = this.breakpoints.map((size) => `(max-width: ${size}px) ${size}px`).join(', ');
|
|
954
|
+
measuredWidthPx = signal(null, ...(ngDevMode ? [{ debugName: "measuredWidthPx" }] : []));
|
|
955
|
+
resizeObserver = null;
|
|
954
956
|
intersectionObserver = null;
|
|
957
|
+
lazySourcesApplied = false;
|
|
958
|
+
viewInitialized = false;
|
|
955
959
|
get class() {
|
|
956
960
|
return this.image_fill ? 'imageFill' : '';
|
|
957
961
|
}
|
|
@@ -960,6 +964,10 @@ class ImageComponent {
|
|
|
960
964
|
*/
|
|
961
965
|
ngOnInit() {
|
|
962
966
|
this.createImage();
|
|
967
|
+
}
|
|
968
|
+
ngAfterViewInit() {
|
|
969
|
+
this.viewInitialized = true;
|
|
970
|
+
this.setupResizeObserver();
|
|
963
971
|
void this.setupLazyLoading();
|
|
964
972
|
}
|
|
965
973
|
/**
|
|
@@ -967,9 +975,19 @@ class ImageComponent {
|
|
|
967
975
|
*/
|
|
968
976
|
ngOnChanges(changes) {
|
|
969
977
|
if ('image_src' in changes || 'image_width' in changes || 'preload_aspect_ratio' in changes) {
|
|
970
|
-
|
|
978
|
+
if ('image_width' in changes) {
|
|
979
|
+
this.measuredWidthPx.set(null);
|
|
980
|
+
if (this.image_width) {
|
|
981
|
+
this.cleanupResizeObserver();
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
this.lazySourcesApplied = false;
|
|
971
985
|
this.cleanupObserver();
|
|
972
|
-
|
|
986
|
+
this.createImage();
|
|
987
|
+
if (this.viewInitialized) {
|
|
988
|
+
this.setupResizeObserver();
|
|
989
|
+
void this.setupLazyLoading();
|
|
990
|
+
}
|
|
973
991
|
}
|
|
974
992
|
}
|
|
975
993
|
/**
|
|
@@ -977,6 +995,7 @@ class ImageComponent {
|
|
|
977
995
|
*/
|
|
978
996
|
ngOnDestroy() {
|
|
979
997
|
this.cleanupObserver();
|
|
998
|
+
this.cleanupResizeObserver();
|
|
980
999
|
}
|
|
981
1000
|
/**
|
|
982
1001
|
* Creates the image object with the provided properties.
|
|
@@ -998,6 +1017,41 @@ class ImageComponent {
|
|
|
998
1017
|
};
|
|
999
1018
|
this.image.set(newImage);
|
|
1000
1019
|
}
|
|
1020
|
+
resolvedSizes() {
|
|
1021
|
+
if (this.image_width) {
|
|
1022
|
+
return `${this.image_width}px`;
|
|
1023
|
+
}
|
|
1024
|
+
const measured = this.measuredWidthPx();
|
|
1025
|
+
if (measured && measured > 0) {
|
|
1026
|
+
return `${measured}px`;
|
|
1027
|
+
}
|
|
1028
|
+
return this.imageSizes;
|
|
1029
|
+
}
|
|
1030
|
+
setupResizeObserver() {
|
|
1031
|
+
if (this.image_width || typeof ResizeObserver === 'undefined' || this.resizeObserver) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const host = this.el.nativeElement;
|
|
1035
|
+
const syncWidth = Math.round(host.getBoundingClientRect().width || host.offsetWidth || 0);
|
|
1036
|
+
if (syncWidth > 0) {
|
|
1037
|
+
this.measuredWidthPx.set(syncWidth);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
1041
|
+
const width = entries[0].borderBoxSize[0].inlineSize;
|
|
1042
|
+
const rounded = Math.round(width);
|
|
1043
|
+
if (rounded <= 0 || rounded === this.measuredWidthPx()) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
this.measuredWidthPx.set(rounded);
|
|
1047
|
+
this.cleanupResizeObserver();
|
|
1048
|
+
});
|
|
1049
|
+
this.resizeObserver.observe(host);
|
|
1050
|
+
}
|
|
1051
|
+
cleanupResizeObserver() {
|
|
1052
|
+
this.resizeObserver?.disconnect();
|
|
1053
|
+
this.resizeObserver = null;
|
|
1054
|
+
}
|
|
1001
1055
|
/**
|
|
1002
1056
|
* Sets up lazy loading for the image using IntersectionObserver.
|
|
1003
1057
|
* This method observes the image element and loads the image when it comes into view.
|
|
@@ -1007,29 +1061,23 @@ class ImageComponent {
|
|
|
1007
1061
|
setupLazyLoading() {
|
|
1008
1062
|
return Promise.resolve().then(() => {
|
|
1009
1063
|
// Defer setup until after the view is initialized to ensure the <img> element exists.
|
|
1064
|
+
if (this.intersectionObserver) {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1010
1067
|
const imageElement = this.el.nativeElement.querySelector('img');
|
|
1011
1068
|
if (!imageElement) {
|
|
1012
1069
|
return;
|
|
1013
1070
|
}
|
|
1071
|
+
if (this.loading === 'eager' || this.fetchpriority === 'high') {
|
|
1072
|
+
this.loadImageSources(imageElement);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1014
1075
|
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
1015
1076
|
entries.forEach((entry) => {
|
|
1016
1077
|
if (entry.isIntersecting) {
|
|
1017
|
-
|
|
1018
|
-
const source = img.parentElement?.querySelector('source');
|
|
1019
|
-
// Move the URL from data-src to src
|
|
1020
|
-
const dataSrc = img.getAttribute('data-src');
|
|
1021
|
-
if (dataSrc) {
|
|
1022
|
-
img.setAttribute('src', dataSrc);
|
|
1023
|
-
}
|
|
1024
|
-
// Move the URLs from data-srcset to srcset
|
|
1025
|
-
if (source) {
|
|
1026
|
-
const dataSrcset = source.getAttribute('data-srcset');
|
|
1027
|
-
if (dataSrcset) {
|
|
1028
|
-
source.setAttribute('srcset', dataSrcset);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1078
|
+
this.loadImageSources(entry.target);
|
|
1031
1079
|
// Stop observing this image once it's loaded.
|
|
1032
|
-
this.intersectionObserver?.unobserve(
|
|
1080
|
+
this.intersectionObserver?.unobserve(entry.target);
|
|
1033
1081
|
}
|
|
1034
1082
|
});
|
|
1035
1083
|
}, {
|
|
@@ -1040,6 +1088,64 @@ class ImageComponent {
|
|
|
1040
1088
|
this.intersectionObserver.observe(imageElement);
|
|
1041
1089
|
});
|
|
1042
1090
|
}
|
|
1091
|
+
loadImageSources(img) {
|
|
1092
|
+
if (this.lazySourcesApplied) {
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (!this.image_width) {
|
|
1096
|
+
const host = this.el.nativeElement;
|
|
1097
|
+
const width = Math.round(host.getBoundingClientRect().width || host.offsetWidth || 0);
|
|
1098
|
+
if (width > 0) {
|
|
1099
|
+
this.measuredWidthPx.set(width);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
const source = img.parentElement?.querySelector('source');
|
|
1103
|
+
const dataSrcset = source?.getAttribute('data-srcset');
|
|
1104
|
+
if (source) {
|
|
1105
|
+
source.setAttribute('sizes', this.resolvedSizes());
|
|
1106
|
+
if (dataSrcset) {
|
|
1107
|
+
// Move the URLs from data-srcset to srcset
|
|
1108
|
+
source.setAttribute('srcset', dataSrcset);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
const dataSrc = img.getAttribute('data-src');
|
|
1112
|
+
const slotWidthPx = this.image_width ?? this.measuredWidthPx() ?? 0;
|
|
1113
|
+
let imgSrc = dataSrc;
|
|
1114
|
+
if (dataSrcset && slotWidthPx > 0) {
|
|
1115
|
+
imgSrc = this.pickSrcsetUrlForSlot(dataSrcset, slotWidthPx) ?? dataSrc;
|
|
1116
|
+
}
|
|
1117
|
+
if (imgSrc) {
|
|
1118
|
+
// Move the URL from data-src to src
|
|
1119
|
+
img.setAttribute('src', imgSrc);
|
|
1120
|
+
}
|
|
1121
|
+
this.lazySourcesApplied = true;
|
|
1122
|
+
}
|
|
1123
|
+
pickSrcsetUrlForSlot(srcset, slotWidthPx) {
|
|
1124
|
+
const targetWidth = Math.ceil(slotWidthPx * (window.devicePixelRatio || 1));
|
|
1125
|
+
const candidates = srcset
|
|
1126
|
+
.split(',')
|
|
1127
|
+
.map((candidate) => candidate.trim())
|
|
1128
|
+
.map((candidate) => {
|
|
1129
|
+
const descriptorIndex = candidate.lastIndexOf(' ');
|
|
1130
|
+
if (descriptorIndex === -1) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
const url = candidate.slice(0, descriptorIndex).trim();
|
|
1134
|
+
const width = parseInt(candidate.slice(descriptorIndex + 1), 10);
|
|
1135
|
+
return url && !Number.isNaN(width) ? { url, width } : null;
|
|
1136
|
+
})
|
|
1137
|
+
.filter((candidate) => candidate !== null);
|
|
1138
|
+
if (candidates.length === 0) {
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
const atOrAbove = candidates
|
|
1142
|
+
.filter(({ width }) => width >= targetWidth)
|
|
1143
|
+
.sort((a, b) => a.width - b.width);
|
|
1144
|
+
if (atOrAbove.length > 0) {
|
|
1145
|
+
return atOrAbove[0].url;
|
|
1146
|
+
}
|
|
1147
|
+
return candidates.sort((a, b) => b.width - a.width)[0].url;
|
|
1148
|
+
}
|
|
1043
1149
|
/**
|
|
1044
1150
|
* Cleans up the IntersectionObserver to prevent memory leaks.
|
|
1045
1151
|
* This method disconnects the observer and sets it to null.
|
|
@@ -1051,11 +1157,11 @@ class ImageComponent {
|
|
|
1051
1157
|
}
|
|
1052
1158
|
}
|
|
1053
1159
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.21", ngImport: i0, type: ImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1054
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.21", type: ImageComponent, isStandalone: true, selector: "ui-image", inputs: { image_src: "image_src", image_alt_text: "image_alt_text", image_aria_describedby: "image_aria_describedby", image_fill: "image_fill", fetchpriority: "fetchpriority", loading: "loading", preload_aspect_ratio: "preload_aspect_ratio", image_width: "image_width" }, outputs: { image_loaded: "image_loaded" }, host: { properties: { "class": "this.class" } }, usesOnChanges: true, ngImport: i0, template: "@if (image()) {\n <picture>\n <source\n [attr.sizes]=\"
|
|
1160
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.21", type: ImageComponent, isStandalone: true, selector: "ui-image", inputs: { image_src: "image_src", image_alt_text: "image_alt_text", image_aria_describedby: "image_aria_describedby", image_fill: "image_fill", fetchpriority: "fetchpriority", loading: "loading", preload_aspect_ratio: "preload_aspect_ratio", image_width: "image_width" }, outputs: { image_loaded: "image_loaded" }, host: { properties: { "class": "this.class" } }, usesOnChanges: true, ngImport: i0, template: "@if (image()) {\n <picture>\n <source\n [attr.sizes]=\"resolvedSizes()\"\n [attr.srcset]=\"null\"\n [attr.data-srcset]=\"image().srcSetUrls\"\n type=\"image/webp\"\n />\n\n <img\n [class.imageFill]=\"image().fill\"\n [attr.width]=\"image().ratioWidth\"\n [attr.height]=\"image().ratioHeight\"\n [attr.data-src]=\"image().fallbackUrl\"\n [attr.loading]=\"image().loading\"\n [attr.fetchpriority]=\"image().fetchpriority\"\n [attr.alt]=\"image().altText\"\n [attr.aria-describedby]=\"image().ariaDescribedby\"\n [ngStyle]=\"{ 'aspect-ratio': image().preloadAspectRatio }\"\n (load)=\"image_loaded.emit()\"\n [attr.role]=\"image().altText?.length > 0 ? 'img' : 'presentation'\"\n />\n </picture>\n}\n", styles: [":host.imageFill,:host.imageFill picture,:host.imageFill img{display:block;height:100%}:host.imageFill img{object-fit:cover}img{display:block;width:100%;height:auto}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
|
|
1055
1161
|
}
|
|
1056
1162
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.21", ngImport: i0, type: ImageComponent, decorators: [{
|
|
1057
1163
|
type: Component,
|
|
1058
|
-
args: [{ standalone: true, imports: [NgStyle], selector: 'ui-image', template: "@if (image()) {\n <picture>\n <source\n [attr.sizes]=\"
|
|
1164
|
+
args: [{ standalone: true, imports: [NgStyle], selector: 'ui-image', template: "@if (image()) {\n <picture>\n <source\n [attr.sizes]=\"resolvedSizes()\"\n [attr.srcset]=\"null\"\n [attr.data-srcset]=\"image().srcSetUrls\"\n type=\"image/webp\"\n />\n\n <img\n [class.imageFill]=\"image().fill\"\n [attr.width]=\"image().ratioWidth\"\n [attr.height]=\"image().ratioHeight\"\n [attr.data-src]=\"image().fallbackUrl\"\n [attr.loading]=\"image().loading\"\n [attr.fetchpriority]=\"image().fetchpriority\"\n [attr.alt]=\"image().altText\"\n [attr.aria-describedby]=\"image().ariaDescribedby\"\n [ngStyle]=\"{ 'aspect-ratio': image().preloadAspectRatio }\"\n (load)=\"image_loaded.emit()\"\n [attr.role]=\"image().altText?.length > 0 ? 'img' : 'presentation'\"\n />\n </picture>\n}\n", styles: [":host.imageFill,:host.imageFill picture,:host.imageFill img{display:block;height:100%}:host.imageFill img{object-fit:cover}img{display:block;width:100%;height:auto}\n"] }]
|
|
1059
1165
|
}], propDecorators: { image_src: [{
|
|
1060
1166
|
type: Input,
|
|
1061
1167
|
args: [{ required: true }]
|