oasys-lib 2.32.0-rc.0 → 2.32.0-rc.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.
- package/fesm2022/oasys-lib.mjs +97 -35
- package/fesm2022/oasys-lib.mjs.map +1 -1
- package/index.d.ts +6 -2
- 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
|
@@ -928,7 +928,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.21", ngImpo
|
|
|
928
928
|
}]
|
|
929
929
|
}] });
|
|
930
930
|
|
|
931
|
-
const UNMEASURED_LAYOUT_WIDTH_PX = 300;
|
|
932
931
|
/**
|
|
933
932
|
* ImageComponent is a reusable component. Necessary accessibility attributes are set by default for a decorative image.
|
|
934
933
|
* alt text and airia described by text can be set to make the image non-decorative.
|
|
@@ -955,6 +954,8 @@ class ImageComponent {
|
|
|
955
954
|
measuredWidthPx = signal(null, ...(ngDevMode ? [{ debugName: "measuredWidthPx" }] : []));
|
|
956
955
|
resizeObserver = null;
|
|
957
956
|
intersectionObserver = null;
|
|
957
|
+
lazySourcesApplied = false;
|
|
958
|
+
viewInitialized = false;
|
|
958
959
|
get class() {
|
|
959
960
|
return this.image_fill ? 'imageFill' : '';
|
|
960
961
|
}
|
|
@@ -963,10 +964,11 @@ class ImageComponent {
|
|
|
963
964
|
*/
|
|
964
965
|
ngOnInit() {
|
|
965
966
|
this.createImage();
|
|
966
|
-
void this.setupLazyLoading();
|
|
967
967
|
}
|
|
968
968
|
ngAfterViewInit() {
|
|
969
|
+
this.viewInitialized = true;
|
|
969
970
|
this.setupResizeObserver();
|
|
971
|
+
void this.setupLazyLoading();
|
|
970
972
|
}
|
|
971
973
|
/**
|
|
972
974
|
* On changes, handle any changes in input properties.
|
|
@@ -974,16 +976,18 @@ class ImageComponent {
|
|
|
974
976
|
ngOnChanges(changes) {
|
|
975
977
|
if ('image_src' in changes || 'image_width' in changes || 'preload_aspect_ratio' in changes) {
|
|
976
978
|
if ('image_width' in changes) {
|
|
979
|
+
this.measuredWidthPx.set(null);
|
|
977
980
|
if (this.image_width) {
|
|
978
981
|
this.cleanupResizeObserver();
|
|
979
982
|
}
|
|
980
|
-
else {
|
|
981
|
-
this.setupResizeObserver();
|
|
982
|
-
}
|
|
983
983
|
}
|
|
984
|
-
this.
|
|
984
|
+
this.lazySourcesApplied = false;
|
|
985
985
|
this.cleanupObserver();
|
|
986
|
-
|
|
986
|
+
this.createImage();
|
|
987
|
+
if (this.viewInitialized) {
|
|
988
|
+
this.setupResizeObserver();
|
|
989
|
+
void this.setupLazyLoading();
|
|
990
|
+
}
|
|
987
991
|
}
|
|
988
992
|
}
|
|
989
993
|
/**
|
|
@@ -1007,36 +1011,42 @@ class ImageComponent {
|
|
|
1007
1011
|
fill: this.image_fill,
|
|
1008
1012
|
fetchpriority: this.fetchpriority,
|
|
1009
1013
|
preloadAspectRatio: this.preload_aspect_ratio,
|
|
1010
|
-
width: this.
|
|
1014
|
+
width: this.image_width ? `${this.image_width}px` : null,
|
|
1011
1015
|
altText: this.image_alt_text,
|
|
1012
1016
|
ariaDescribedby: this.image_aria_describedby ?? null,
|
|
1013
1017
|
};
|
|
1014
1018
|
this.image.set(newImage);
|
|
1015
1019
|
}
|
|
1016
|
-
|
|
1020
|
+
resolvedSizes() {
|
|
1017
1021
|
if (this.image_width) {
|
|
1018
1022
|
return `${this.image_width}px`;
|
|
1019
1023
|
}
|
|
1020
1024
|
const measured = this.measuredWidthPx();
|
|
1021
1025
|
if (measured && measured > 0) {
|
|
1022
|
-
return `${
|
|
1026
|
+
return `${measured}px`;
|
|
1023
1027
|
}
|
|
1024
|
-
return
|
|
1028
|
+
return this.imageSizes;
|
|
1025
1029
|
}
|
|
1026
1030
|
setupResizeObserver() {
|
|
1027
|
-
if (this.image_width || typeof ResizeObserver === 'undefined') {
|
|
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);
|
|
1028
1038
|
return;
|
|
1029
1039
|
}
|
|
1030
1040
|
this.resizeObserver = new ResizeObserver((entries) => {
|
|
1031
|
-
const width = entries[0]
|
|
1032
|
-
const rounded = Math.
|
|
1033
|
-
if (rounded === this.measuredWidthPx()) {
|
|
1041
|
+
const width = entries[0].borderBoxSize[0].inlineSize;
|
|
1042
|
+
const rounded = Math.round(width);
|
|
1043
|
+
if (rounded <= 0 || rounded === this.measuredWidthPx()) {
|
|
1034
1044
|
return;
|
|
1035
1045
|
}
|
|
1036
|
-
this.measuredWidthPx.set(rounded
|
|
1037
|
-
this.
|
|
1046
|
+
this.measuredWidthPx.set(rounded);
|
|
1047
|
+
this.cleanupResizeObserver();
|
|
1038
1048
|
});
|
|
1039
|
-
this.resizeObserver.observe(
|
|
1049
|
+
this.resizeObserver.observe(host);
|
|
1040
1050
|
}
|
|
1041
1051
|
cleanupResizeObserver() {
|
|
1042
1052
|
this.resizeObserver?.disconnect();
|
|
@@ -1051,29 +1061,23 @@ class ImageComponent {
|
|
|
1051
1061
|
setupLazyLoading() {
|
|
1052
1062
|
return Promise.resolve().then(() => {
|
|
1053
1063
|
// Defer setup until after the view is initialized to ensure the <img> element exists.
|
|
1064
|
+
if (this.intersectionObserver) {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1054
1067
|
const imageElement = this.el.nativeElement.querySelector('img');
|
|
1055
1068
|
if (!imageElement) {
|
|
1056
1069
|
return;
|
|
1057
1070
|
}
|
|
1071
|
+
if (this.loading === 'eager' || this.fetchpriority === 'high') {
|
|
1072
|
+
this.loadImageSources(imageElement);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1058
1075
|
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
1059
1076
|
entries.forEach((entry) => {
|
|
1060
1077
|
if (entry.isIntersecting) {
|
|
1061
|
-
|
|
1062
|
-
const source = img.parentElement?.querySelector('source');
|
|
1063
|
-
// Move the URL from data-src to src
|
|
1064
|
-
const dataSrc = img.getAttribute('data-src');
|
|
1065
|
-
if (dataSrc) {
|
|
1066
|
-
img.setAttribute('src', dataSrc);
|
|
1067
|
-
}
|
|
1068
|
-
// Move the URLs from data-srcset to srcset
|
|
1069
|
-
if (source) {
|
|
1070
|
-
const dataSrcset = source.getAttribute('data-srcset');
|
|
1071
|
-
if (dataSrcset) {
|
|
1072
|
-
source.setAttribute('srcset', dataSrcset);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1078
|
+
this.loadImageSources(entry.target);
|
|
1075
1079
|
// Stop observing this image once it's loaded.
|
|
1076
|
-
this.intersectionObserver?.unobserve(
|
|
1080
|
+
this.intersectionObserver?.unobserve(entry.target);
|
|
1077
1081
|
}
|
|
1078
1082
|
});
|
|
1079
1083
|
}, {
|
|
@@ -1084,6 +1088,64 @@ class ImageComponent {
|
|
|
1084
1088
|
this.intersectionObserver.observe(imageElement);
|
|
1085
1089
|
});
|
|
1086
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
|
+
}
|
|
1087
1149
|
/**
|
|
1088
1150
|
* Cleans up the IntersectionObserver to prevent memory leaks.
|
|
1089
1151
|
* This method disconnects the observer and sets it to null.
|
|
@@ -1095,11 +1157,11 @@ class ImageComponent {
|
|
|
1095
1157
|
}
|
|
1096
1158
|
}
|
|
1097
1159
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.21", ngImport: i0, type: ImageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1098
|
-
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"] }] });
|
|
1099
1161
|
}
|
|
1100
1162
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.21", ngImport: i0, type: ImageComponent, decorators: [{
|
|
1101
1163
|
type: Component,
|
|
1102
|
-
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"] }]
|
|
1103
1165
|
}], propDecorators: { image_src: [{
|
|
1104
1166
|
type: Input,
|
|
1105
1167
|
args: [{ required: true }]
|