@penn-libraries/web 1.1.0 → 1.1.1-dev.2

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.
Files changed (64) hide show
  1. package/dist/cjs/{index-4ixBJUwu.js → index-DVr0pLZy.js} +62 -3
  2. package/dist/cjs/index-DVr0pLZy.js.map +1 -0
  3. package/dist/cjs/index.cjs.js +1 -1
  4. package/dist/cjs/loader.cjs.js +2 -2
  5. package/dist/cjs/pennlibs-autocomplete.pennlibs-fallback-img.pennlibs-footer.pennlibs-header.pennlibs-iiif-img.entry.cjs.js.map +1 -0
  6. package/dist/cjs/{pennlibs-autocomplete_3.cjs.entry.js → pennlibs-autocomplete_5.cjs.entry.js} +261 -2
  7. package/dist/cjs/pennlibs-banner.cjs.entry.js +1 -1
  8. package/dist/cjs/pennlibs-chat.cjs.entry.js +1 -1
  9. package/dist/cjs/pennlibs-feedback.cjs.entry.js +1 -1
  10. package/dist/cjs/pennlibs-hero.cjs.entry.js +1 -1
  11. package/dist/cjs/web.cjs.js +2 -2
  12. package/dist/collection/collection-manifest.json +2 -1
  13. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.css +44 -0
  14. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.js +432 -0
  15. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.js.map +1 -0
  16. package/dist/components/index.d.ts +2 -0
  17. package/dist/components/index.js +1 -0
  18. package/dist/components/index.js.map +1 -1
  19. package/dist/components/pennlibs-fallback-img.js +1 -32
  20. package/dist/components/pennlibs-fallback-img.js.map +1 -1
  21. package/dist/components/pennlibs-fallback-img2.js +37 -0
  22. package/dist/components/pennlibs-fallback-img2.js.map +1 -0
  23. package/dist/components/pennlibs-iiif-img.d.ts +11 -0
  24. package/dist/components/pennlibs-iiif-img.js +289 -0
  25. package/dist/components/pennlibs-iiif-img.js.map +1 -0
  26. package/dist/docs.json +239 -3
  27. package/dist/{web/p-Di48-Vtw.js → esm/index-Cst_89-s.js} +62 -3
  28. package/dist/esm/index-Cst_89-s.js.map +1 -0
  29. package/dist/esm/index.js +1 -1
  30. package/dist/esm/loader.js +3 -3
  31. package/dist/esm/pennlibs-autocomplete.pennlibs-fallback-img.pennlibs-footer.pennlibs-header.pennlibs-iiif-img.entry.js.map +1 -0
  32. package/dist/esm/{pennlibs-autocomplete_3.entry.js → pennlibs-autocomplete_5.entry.js} +260 -3
  33. package/dist/esm/pennlibs-banner.entry.js +1 -1
  34. package/dist/esm/pennlibs-chat.entry.js +1 -1
  35. package/dist/esm/pennlibs-feedback.entry.js +1 -1
  36. package/dist/esm/pennlibs-hero.entry.js +1 -1
  37. package/dist/esm/web.js +3 -3
  38. package/dist/types/components/pennlibs-iiif-img/pennlibs-iiif-img.d.ts +130 -0
  39. package/dist/types/components.d.ts +119 -0
  40. package/dist/web/index.esm.js +1 -1
  41. package/dist/web/{p-6503bb09.entry.js → p-4ffdbc93.entry.js} +1 -1
  42. package/dist/web/{p-56b9f10c.entry.js → p-621f166e.entry.js} +1 -1
  43. package/dist/web/{p-269363bf.entry.js → p-6e0c2de9.entry.js} +260 -3
  44. package/dist/web/{p-783e2a87.entry.js → p-848d9acc.entry.js} +1 -1
  45. package/dist/{esm/index-Di48-Vtw.js → web/p-Cst_89-s.js} +62 -3
  46. package/dist/web/p-Cst_89-s.js.map +1 -0
  47. package/dist/web/{p-7cbd16c0.entry.js → p-a9c79310.entry.js} +1 -1
  48. package/dist/web/pennlibs-autocomplete.pennlibs-fallback-img.pennlibs-footer.pennlibs-header.pennlibs-iiif-img.entry.esm.js.map +1 -0
  49. package/dist/web/web.esm.js +3 -3
  50. package/hydrate/index.js +323 -2
  51. package/hydrate/index.mjs +323 -2
  52. package/package.json +1 -1
  53. package/dist/cjs/index-4ixBJUwu.js.map +0 -1
  54. package/dist/cjs/pennlibs-autocomplete.pennlibs-footer.pennlibs-header.entry.cjs.js.map +0 -1
  55. package/dist/cjs/pennlibs-fallback-img.cjs.entry.js +0 -20
  56. package/dist/cjs/pennlibs-fallback-img.entry.cjs.js.map +0 -1
  57. package/dist/esm/index-Di48-Vtw.js.map +0 -1
  58. package/dist/esm/pennlibs-autocomplete.pennlibs-footer.pennlibs-header.entry.js.map +0 -1
  59. package/dist/esm/pennlibs-fallback-img.entry.js +0 -18
  60. package/dist/esm/pennlibs-fallback-img.entry.js.map +0 -1
  61. package/dist/web/p-09e4a02c.entry.js +0 -18
  62. package/dist/web/p-Di48-Vtw.js.map +0 -1
  63. package/dist/web/pennlibs-autocomplete.pennlibs-footer.pennlibs-header.entry.esm.js.map +0 -1
  64. package/dist/web/pennlibs-fallback-img.entry.esm.js.map +0 -1
package/hydrate/index.js CHANGED
@@ -127,7 +127,7 @@ function hydrateFactory($stencilWindow, $stencilHydrateOpts, $stencilHydrateResu
127
127
 
128
128
 
129
129
  const NAMESPACE = 'web';
130
- const BUILD = /* web */ { hydratedSelectorName: "hydrated", propChangeCallback: false, slotRelocation: true, updatable: true};
130
+ const BUILD = /* web */ { hydratedSelectorName: "hydrated", slotRelocation: true, updatable: true};
131
131
 
132
132
  /*
133
133
  Stencil Hydrate Platform v4.38.1 | MIT Licensed | https://stenciljs.com
@@ -1530,6 +1530,11 @@ var parsePropertyValue = (propValue, propType, isFormAssociated) => {
1530
1530
  return propValue;
1531
1531
  }
1532
1532
  if (propValue != null && !isComplexType(propValue)) {
1533
+ if (propType & 4 /* Boolean */) {
1534
+ {
1535
+ return propValue === "false" ? false : propValue === "" || !!propValue;
1536
+ }
1537
+ }
1533
1538
  if (propType & 1 /* String */) {
1534
1539
  return String(propValue);
1535
1540
  }
@@ -2100,6 +2105,14 @@ var renderVdom = (hostRef, renderFnResults, isInitialLoad = false) => {
2100
2105
  const isHostElement = isHost(renderFnResults);
2101
2106
  const rootVnode = isHostElement ? renderFnResults : h(null, null, renderFnResults);
2102
2107
  hostTagName = hostElm.tagName;
2108
+ if (cmpMeta.$attrsToReflect$) {
2109
+ rootVnode.$attrs$ = rootVnode.$attrs$ || {};
2110
+ cmpMeta.$attrsToReflect$.forEach(([propName, attribute]) => {
2111
+ {
2112
+ rootVnode.$attrs$[attribute] = hostElm[propName];
2113
+ }
2114
+ });
2115
+ }
2103
2116
  if (isInitialLoad && rootVnode.$attrs$) {
2104
2117
  for (const key of Object.keys(rootVnode.$attrs$)) {
2105
2118
  if (hostElm.hasAttribute(key) && !["key", "ref", "style", "class"].includes(key)) {
@@ -2401,6 +2414,7 @@ var setValue = (ref, propName, newVal, cmpMeta) => {
2401
2414
  `Couldn't find host element for "${cmpMeta.$tagName$}" as it is unknown to this Stencil runtime. This usually happens when integrating a 3rd party Stencil component with another Stencil component or application. Please reach out to the maintainers of the 3rd party Stencil component or report this on the Stencil Discord server (https://chat.stenciljs.com) or comment on this similar [GitHub issue](https://github.com/stenciljs/core/issues/5457).`
2402
2415
  );
2403
2416
  }
2417
+ const elm = hostRef.$hostElement$ ;
2404
2418
  const oldVal = hostRef.$instanceValues$.get(propName);
2405
2419
  const flags = hostRef.$flags$;
2406
2420
  const instance = hostRef.$lazyInstance$ ;
@@ -2412,6 +2426,18 @@ var setValue = (ref, propName, newVal, cmpMeta) => {
2412
2426
  if ((!(flags & 8 /* isConstructingInstance */) || oldVal === void 0) && didValueChange) {
2413
2427
  hostRef.$instanceValues$.set(propName, newVal);
2414
2428
  if (instance) {
2429
+ if (cmpMeta.$watchers$ && flags & 128 /* isWatchReady */) {
2430
+ const watchMethods = cmpMeta.$watchers$[propName];
2431
+ if (watchMethods) {
2432
+ watchMethods.map((watchMethodName) => {
2433
+ try {
2434
+ instance[watchMethodName](newVal, oldVal, propName);
2435
+ } catch (e) {
2436
+ consoleError(e, elm);
2437
+ }
2438
+ });
2439
+ }
2440
+ }
2415
2441
  if ((flags & (2 /* hasRendered */ | 16 /* isQueuedForUpdate */)) === 2 /* hasRendered */) {
2416
2442
  if (instance.componentShouldUpdate) {
2417
2443
  if (instance.componentShouldUpdate(newVal, oldVal, propName) === false) {
@@ -2428,7 +2454,18 @@ var setValue = (ref, propName, newVal, cmpMeta) => {
2428
2454
  var proxyComponent = (Cstr, cmpMeta, flags) => {
2429
2455
  var _a;
2430
2456
  const prototype = Cstr.prototype;
2431
- if (cmpMeta.$members$ || BUILD.propChangeCallback) {
2457
+ {
2458
+ {
2459
+ if (Cstr.watchers && !cmpMeta.$watchers$) {
2460
+ cmpMeta.$watchers$ = Cstr.watchers;
2461
+ }
2462
+ if (Cstr.deserializers && !cmpMeta.$deserializers$) {
2463
+ cmpMeta.$deserializers$ = Cstr.deserializers;
2464
+ }
2465
+ if (Cstr.serializers && !cmpMeta.$serializers$) {
2466
+ cmpMeta.$serializers$ = Cstr.serializers;
2467
+ }
2468
+ }
2432
2469
  const members = Object.entries((_a = cmpMeta.$members$) != null ? _a : {});
2433
2470
  members.map(([memberName, [memberFlags]]) => {
2434
2471
  if ((memberFlags & 31 /* Prop */ || memberFlags & 32 /* State */)) {
@@ -2505,6 +2542,11 @@ var initializeComponent = async (elm, hostRef, cmpMeta, hmrVersionId) => {
2505
2542
  throw new Error(`Constructor for "${cmpMeta.$tagName$}#${hostRef.$modeName$}" was not found`);
2506
2543
  }
2507
2544
  if (!Cstr.isProxied) {
2545
+ {
2546
+ cmpMeta.$watchers$ = Cstr.watchers;
2547
+ cmpMeta.$serializers$ = Cstr.serializers;
2548
+ cmpMeta.$deserializers$ = Cstr.deserializers;
2549
+ }
2508
2550
  proxyComponent(Cstr, cmpMeta);
2509
2551
  Cstr.isProxied = true;
2510
2552
  }
@@ -2520,6 +2562,9 @@ var initializeComponent = async (elm, hostRef, cmpMeta, hmrVersionId) => {
2520
2562
  {
2521
2563
  hostRef.$flags$ &= -9 /* isConstructingInstance */;
2522
2564
  }
2565
+ {
2566
+ hostRef.$flags$ |= 128 /* isWatchReady */;
2567
+ }
2523
2568
  endNewInstance();
2524
2569
  fireConnectedCallback(hostRef.$lazyInstance$, elm);
2525
2570
  } else {
@@ -3870,6 +3915,281 @@ class Hero {
3870
3915
  }; }
3871
3916
  }
3872
3917
 
3918
+ const pennlibsIiifImgCss = ":host{display:block;width:100%;max-width:100%;align-self:flex-start;aspect-ratio:var(--aspect-ratio, auto)}picture{display:block;line-height:0}.iiif-img{display:block;width:100%;max-width:100%;height:auto;opacity:0;filter:blur(5px);transition:opacity 0.15s ease-in, filter 0.15s ease-in}.iiif-img.loaded{opacity:1;filter:blur(0)}.fallback-container{display:block;width:100%;height:100%}.fallback-container pennlibs-fallback-img{width:100%;height:100%}pennlibs-fallback-img{display:block;width:100%;height:100%}";
3919
+
3920
+ const PIXEL_DENSITIES = [1, 1.5, 2];
3921
+ /**
3922
+ * Display responsive, high-quality images from Penn Libraries' IIIF Image API 3.0 server.
3923
+ * Measures its own rendered width and automatically generates optimal image sources at
3924
+ * multiple pixel densities (1x, 1.5x, 2x) with modern WebP format and JPG fallback support.
3925
+ *
3926
+ * @component
3927
+ * @example
3928
+ * <pennlibs-iiif-img
3929
+ * uuid="063ff35c-bbdd-4b63-bbdd-6206590e20d5"
3930
+ * alt="">
3931
+ * </pennlibs-iiif-img>
3932
+ */
3933
+ class IIIFImg {
3934
+ constructor(hostRef) {
3935
+ registerInstance(this, hostRef);
3936
+ /**
3937
+ * The IIIF [region](https://iiif.io/api/image/3.0/#41-region) of the image to display.
3938
+ * Defines the rectangular portion of the underlying image to return.
3939
+ *
3940
+ * `full`: The full image is returned, without any cropping.
3941
+ *
3942
+ * `square`: A square area where width and height equal the shorter dimension.
3943
+ *
3944
+ * `width:height`: Any aspect ratio format (e.g., `16:9`, `4:3`, `3:2`, `21:9`) applies
3945
+ * a centered crop based on the source image dimensions and sets the CSS aspect-ratio
3946
+ * property for layout reservation.
3947
+ *
3948
+ * `x,y,w,h`: Absolute pixel coordinates (x, y position; w, h dimensions).
3949
+ *
3950
+ * `pct:x,y,w,h`: Percentage-based coordinates of full image dimensions.
3951
+ *
3952
+ * @default 'full'
3953
+ */
3954
+ this.region = 'full';
3955
+ /**
3956
+ * The IIIF [rotation](https://iiif.io/api/image/3.0/#44-rotation) to apply to the image.
3957
+ * Specifies mirroring and clockwise rotation in degrees (0-360).
3958
+ *
3959
+ * `n`: Rotation in degrees only.
3960
+ *
3961
+ * `!n`: Mirror the image vertically, then rotate by n degrees.
3962
+ *
3963
+ * @default '0'
3964
+ */
3965
+ this.rotation = '0';
3966
+ /**
3967
+ * The IIIF [quality](https://iiif.io/api/image/3.0/#quality) of the image.
3968
+ * Controls the color delivery mode.
3969
+ *
3970
+ * `default`: The server's default quality.
3971
+ *
3972
+ * `color`: Full color information.
3973
+ *
3974
+ * `gray`: Grayscale rendering.
3975
+ *
3976
+ * `bitonal`: Black and white only.
3977
+ *
3978
+ * @default 'default'
3979
+ */
3980
+ this.quality = 'default';
3981
+ /**
3982
+ * Native browser lazy loading behavior. Use "lazy" to defer loading until the image is near the viewport,
3983
+ * or "eager" to load immediately.
3984
+ * @default 'lazy'
3985
+ */
3986
+ this.loading = 'lazy';
3987
+ /**
3988
+ * Whether to display a fallback placeholder image when the IIIF image fails to load.
3989
+ * @default true
3990
+ */
3991
+ this.showFallback = true;
3992
+ this.isLoaded = false;
3993
+ this.hasError = false;
3994
+ this.baseUrl = 'https://iiif-images.library.upenn.edu/iiif/3';
3995
+ this.handleLoad = () => {
3996
+ this.isLoaded = true;
3997
+ };
3998
+ this.handleError = () => {
3999
+ this.hasError = true;
4000
+ };
4001
+ }
4002
+ /**
4003
+ * Fetch IIIF image info to get actual dimensions.
4004
+ */
4005
+ async fetchImageInfo() {
4006
+ try {
4007
+ const currentUuid = this.uuid; // Capture current UUID
4008
+ const infoUrl = `${this.baseUrl}/${currentUuid}/info.json`;
4009
+ const response = await fetch(infoUrl);
4010
+ if (!response.ok) {
4011
+ console.error(`Failed to fetch IIIF info.json for ${currentUuid}`);
4012
+ return;
4013
+ }
4014
+ const info = await response.json();
4015
+ // Only update dimensions if UUID hasn't changed during fetch
4016
+ // (protects against race conditions if component updates)
4017
+ if (this.uuid === currentUuid) {
4018
+ this.imageDimensions = {
4019
+ width: info.width,
4020
+ height: info.height,
4021
+ };
4022
+ this.fetchedUuid = currentUuid;
4023
+ }
4024
+ }
4025
+ catch (error) {
4026
+ console.error(`Error fetching IIIF image info for ${this.uuid}:`, error);
4027
+ }
4028
+ }
4029
+ /**
4030
+ * Check if a region value matches aspect ratio format (e.g., "16:9", "4:3", "3:2").
4031
+ */
4032
+ isAspectRatio(value) {
4033
+ return /^\d+:\d+$/.test(value);
4034
+ }
4035
+ /**
4036
+ * Set CSS custom property for aspect ratio on the host element.
4037
+ */
4038
+ setAspectRatioCss(aspectRatio) {
4039
+ const [widthRatio, heightRatio] = aspectRatio.split(':');
4040
+ this.hostElement.style.setProperty('--aspect-ratio', `${widthRatio} / ${heightRatio}`);
4041
+ }
4042
+ /**
4043
+ * Clear CSS custom property for aspect ratio from the host element.
4044
+ */
4045
+ clearAspectRatioCss() {
4046
+ this.hostElement.style.removeProperty('--aspect-ratio');
4047
+ }
4048
+ /**
4049
+ * Format a number for IIIF percentage values (remove trailing zeros).
4050
+ * IIIF spec requires no trailing zeros per section 4.7.
4051
+ */
4052
+ formatPercent(value) {
4053
+ return value.toFixed(2).replace(/\.?0+$/, '');
4054
+ }
4055
+ /**
4056
+ * Calculate a centered crop region for a given aspect ratio.
4057
+ * Requires actual image dimensions to calculate correctly.
4058
+ * @param aspectRatio - The target aspect ratio (e.g., "3:2" or "2:3")
4059
+ * @returns A IIIF percentage-based region string (e.g., "pct:0,16.67,100,66.67")
4060
+ */
4061
+ calculateCenteredCrop(aspectRatio) {
4062
+ if (!this.imageDimensions) {
4063
+ throw new Error('Image dimensions required for aspect ratio calculation');
4064
+ }
4065
+ const [widthRatio, heightRatio] = aspectRatio.split(':').map(Number);
4066
+ const targetRatio = widthRatio / heightRatio;
4067
+ const sourceRatio = this.imageDimensions.width / this.imageDimensions.height;
4068
+ // Determine if we need to crop width or height
4069
+ if (sourceRatio > targetRatio) {
4070
+ // Source is wider than target - crop left and right
4071
+ const widthPercent = (targetRatio / sourceRatio) * 100;
4072
+ const xOffset = (100 - widthPercent) / 2;
4073
+ return `pct:${this.formatPercent(xOffset)},0,${this.formatPercent(widthPercent)},100`;
4074
+ }
4075
+ else {
4076
+ // Source is taller than target - crop top and bottom
4077
+ const heightPercent = (sourceRatio / targetRatio) * 100;
4078
+ const yOffset = (100 - heightPercent) / 2;
4079
+ return `pct:0,${this.formatPercent(yOffset)},100,${this.formatPercent(heightPercent)}`;
4080
+ }
4081
+ }
4082
+ /**
4083
+ * Get the region parameter for the IIIF URL.
4084
+ */
4085
+ getRegionParam() {
4086
+ // Handle custom aspect ratios
4087
+ let regionParam = this.region || 'full';
4088
+ if (this.isAspectRatio(regionParam)) {
4089
+ // Only apply crop if we have dimensions for the current UUID
4090
+ if (this.imageDimensions && this.fetchedUuid === this.uuid) {
4091
+ regionParam = this.calculateCenteredCrop(regionParam);
4092
+ }
4093
+ else {
4094
+ // Use full image until dimensions are loaded
4095
+ regionParam = 'full';
4096
+ }
4097
+ }
4098
+ return regionParam;
4099
+ }
4100
+ buildIIIFUrl(width, format = 'jpg') {
4101
+ const sizeParam = width ? `${width},` : 'max';
4102
+ const regionParam = this.getRegionParam();
4103
+ return `${this.baseUrl}/${this.uuid}/${regionParam}/${sizeParam}/${this.rotation}/${this.quality}.${format}`;
4104
+ }
4105
+ generateSrcset(baseWidth, format) {
4106
+ return PIXEL_DENSITIES.map(density => {
4107
+ const width = Math.round(baseWidth * density);
4108
+ const url = this.buildIIIFUrl(width, format);
4109
+ return `${url} ${density}x`;
4110
+ }).join(', ');
4111
+ }
4112
+ handleRegionChange(newValue, oldValue) {
4113
+ // Only process if region actually changed
4114
+ if (newValue === oldValue) {
4115
+ return;
4116
+ }
4117
+ // If new region is an aspect ratio, set CSS and fetch info
4118
+ if (newValue && this.isAspectRatio(newValue)) {
4119
+ this.setAspectRatioCss(newValue);
4120
+ this.fetchImageInfo();
4121
+ }
4122
+ else {
4123
+ // Clear aspect ratio CSS if no longer using aspect ratio format
4124
+ this.clearAspectRatioCss();
4125
+ }
4126
+ }
4127
+ componentDidLoad() {
4128
+ // Set CSS aspect ratio and fetch image info if using aspect ratio format
4129
+ if (this.region && this.isAspectRatio(this.region)) {
4130
+ this.setAspectRatioCss(this.region);
4131
+ this.fetchImageInfo();
4132
+ }
4133
+ if ('ResizeObserver' in window) {
4134
+ this.resizeObserver = new ResizeObserver(entries => {
4135
+ for (const entry of entries) {
4136
+ this.observedWidth = Math.round(entry.contentRect.width);
4137
+ if (this.resizeObserver) {
4138
+ this.resizeObserver.disconnect();
4139
+ this.resizeObserver = undefined;
4140
+ }
4141
+ }
4142
+ });
4143
+ this.resizeObserver.observe(this.hostElement);
4144
+ }
4145
+ }
4146
+ disconnectedCallback() {
4147
+ if (this.resizeObserver) {
4148
+ this.resizeObserver.disconnect();
4149
+ this.resizeObserver = undefined;
4150
+ }
4151
+ }
4152
+ render() {
4153
+ // Show placeholder until we've measured the component width
4154
+ if (!this.observedWidth) {
4155
+ return hAsync("pennlibs-fallback-img", null);
4156
+ }
4157
+ if (this.hasError && this.showFallback) {
4158
+ return (hAsync("div", { class: "fallback-container" }, hAsync("pennlibs-fallback-img", null)));
4159
+ }
4160
+ const imgClasses = {
4161
+ 'iiif-img': true,
4162
+ 'loaded': this.isLoaded
4163
+ };
4164
+ return (hAsync("picture", null, hAsync("source", { type: "image/webp", srcSet: this.generateSrcset(this.observedWidth, 'webp') }), hAsync("img", { class: imgClasses, src: this.buildIIIFUrl(this.observedWidth, 'jpg'), srcSet: this.generateSrcset(this.observedWidth, 'jpg'), alt: this.alt, loading: this.loading, onLoad: this.handleLoad, onError: this.handleError })));
4165
+ }
4166
+ get hostElement() { return getElement(this); }
4167
+ static get watchers() { return {
4168
+ "region": ["handleRegionChange"]
4169
+ }; }
4170
+ static get style() { return pennlibsIiifImgCss; }
4171
+ static get cmpMeta() { return {
4172
+ "$flags$": 265,
4173
+ "$tagName$": "pennlibs-iiif-img",
4174
+ "$members$": {
4175
+ "uuid": [1],
4176
+ "alt": [1],
4177
+ "region": [513],
4178
+ "rotation": [1],
4179
+ "quality": [1],
4180
+ "loading": [1],
4181
+ "showFallback": [4, "show-fallback"],
4182
+ "isLoaded": [32],
4183
+ "hasError": [32],
4184
+ "imageDimensions": [32],
4185
+ "observedWidth": [32]
4186
+ },
4187
+ "$listeners$": undefined,
4188
+ "$lazyBundleId$": "-",
4189
+ "$attrsToReflect$": [["region", "region"]]
4190
+ }; }
4191
+ }
4192
+
3873
4193
  const pennlibsFallbackImgCss = ":host{font-family:var(--pl-font-family);font-size:var(--pl-font-size);display:flex;align-items:center;justify-content:center;padding:1rem;background:var(--pl-color-bg-subtle);aspect-ratio:3/2}.no-image__img{width:100%;max-width:150px;filter:grayscale(1) opacity(0.3)}";
3874
4194
 
3875
4195
  class NoImage {
@@ -3962,6 +4282,7 @@ registerComponents([
3962
4282
  Footer,
3963
4283
  Header,
3964
4284
  Hero,
4285
+ IIIFImg,
3965
4286
  NoImage,
3966
4287
  PennlibsFeedback,
3967
4288
  ]);