@penn-libraries/web 1.2.0-dev.0 → 1.2.0-dev.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.
Files changed (73) hide show
  1. package/dist/cjs/{index-DJo51Q03.js → index-Ag6DtE87.js} +3 -14
  2. package/dist/cjs/index-Ag6DtE87.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-feedback.pennlibs-footer.pennlibs-header.entry.cjs.js.map +1 -0
  6. package/dist/cjs/{pennlibs-autocomplete_5.cjs.entry.js → pennlibs-autocomplete_4.cjs.entry.js} +50 -258
  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-fallback-img.cjs.entry.js +20 -0
  10. package/dist/cjs/pennlibs-fallback-img.entry.cjs.js.map +1 -0
  11. package/dist/cjs/pennlibs-hero.cjs.entry.js +2 -2
  12. package/dist/cjs/pennlibs-iiif-img.cjs.entry.js +249 -0
  13. package/dist/cjs/pennlibs-iiif-img.entry.cjs.js.map +1 -0
  14. package/dist/cjs/web.cjs.js +2 -2
  15. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.css +23 -14
  16. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.js +50 -17
  17. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.js.map +1 -1
  18. package/dist/collection/components/pennlibs-footer/pennlibs-footer.css +13 -1
  19. package/dist/collection/components/pennlibs-footer/pennlibs-footer.js +3 -3
  20. package/dist/collection/components/pennlibs-footer/pennlibs-footer.js.map +1 -1
  21. package/dist/collection/components/pennlibs-header/pennlibs-header.js +1 -1
  22. package/dist/collection/components/pennlibs-hero/pennlibs-hero.js +1 -1
  23. package/dist/components/pennlibs-feedback.js +16 -17
  24. package/dist/components/pennlibs-feedback.js.map +1 -1
  25. package/dist/components/pennlibs-footer.js +4 -4
  26. package/dist/components/pennlibs-footer.js.map +1 -1
  27. package/dist/components/pennlibs-header.js +1 -1
  28. package/dist/components/pennlibs-hero.js +1 -1
  29. package/dist/docs.json +44 -5
  30. package/dist/{web/p-RqnbThKP.js → esm/index-CNQ9-Wwh.js} +4 -15
  31. package/dist/esm/index-CNQ9-Wwh.js.map +1 -0
  32. package/dist/esm/index.js +1 -1
  33. package/dist/esm/loader.js +3 -3
  34. package/dist/esm/pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.js.map +1 -0
  35. package/dist/esm/{pennlibs-autocomplete_5.entry.js → pennlibs-autocomplete_4.entry.js} +50 -257
  36. package/dist/esm/pennlibs-banner.entry.js +1 -1
  37. package/dist/esm/pennlibs-chat.entry.js +1 -1
  38. package/dist/esm/pennlibs-fallback-img.entry.js +18 -0
  39. package/dist/esm/pennlibs-fallback-img.entry.js.map +1 -0
  40. package/dist/esm/pennlibs-hero.entry.js +2 -2
  41. package/dist/esm/pennlibs-iiif-img.entry.js +247 -0
  42. package/dist/esm/pennlibs-iiif-img.entry.js.map +1 -0
  43. package/dist/esm/web.js +3 -3
  44. package/dist/types/components/pennlibs-feedback/pennlibs-feedback.d.ts +19 -3
  45. package/dist/types/components.d.ts +41 -0
  46. package/dist/web/index.esm.js +1 -1
  47. package/dist/web/p-662e4125.entry.js +18 -0
  48. package/dist/{esm/index-RqnbThKP.js → web/p-CNQ9-Wwh.js} +4 -15
  49. package/dist/web/p-CNQ9-Wwh.js.map +1 -0
  50. package/dist/web/{p-8ac5ef70.entry.js → p-a34e1e3e.entry.js} +1 -1
  51. package/dist/web/{p-b7b01d67.entry.js → p-b6e686d1.entry.js} +2 -2
  52. package/dist/web/{p-f37f3865.entry.js → p-b9538aed.entry.js} +50 -257
  53. package/dist/web/{p-ce09ae2e.entry.js → p-c96a3643.entry.js} +1 -1
  54. package/dist/web/p-eb633f4d.entry.js +247 -0
  55. package/dist/web/pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.esm.js.map +1 -0
  56. package/dist/web/pennlibs-fallback-img.entry.esm.js.map +1 -0
  57. package/dist/web/pennlibs-iiif-img.entry.esm.js.map +1 -0
  58. package/dist/web/web.esm.js +3 -3
  59. package/hydrate/index.js +34 -35
  60. package/hydrate/index.mjs +34 -35
  61. package/package.json +1 -1
  62. package/dist/cjs/index-DJo51Q03.js.map +0 -1
  63. package/dist/cjs/pennlibs-autocomplete.pennlibs-fallback-img.pennlibs-footer.pennlibs-header.pennlibs-iiif-img.entry.cjs.js.map +0 -1
  64. package/dist/cjs/pennlibs-feedback.cjs.entry.js +0 -58
  65. package/dist/cjs/pennlibs-feedback.entry.cjs.js.map +0 -1
  66. package/dist/esm/index-RqnbThKP.js.map +0 -1
  67. package/dist/esm/pennlibs-autocomplete.pennlibs-fallback-img.pennlibs-footer.pennlibs-header.pennlibs-iiif-img.entry.js.map +0 -1
  68. package/dist/esm/pennlibs-feedback.entry.js +0 -56
  69. package/dist/esm/pennlibs-feedback.entry.js.map +0 -1
  70. package/dist/web/p-780e656e.entry.js +0 -56
  71. package/dist/web/p-RqnbThKP.js.map +0 -1
  72. package/dist/web/pennlibs-autocomplete.pennlibs-fallback-img.pennlibs-footer.pennlibs-header.pennlibs-iiif-img.entry.esm.js.map +0 -1
  73. package/dist/web/pennlibs-feedback.entry.esm.js.map +0 -1
@@ -0,0 +1,247 @@
1
+ import { r as registerInstance, h, c as getElement } from './p-CNQ9-Wwh.js';
2
+
3
+ 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%}";
4
+
5
+ const PIXEL_DENSITIES = [1, 1.5, 2];
6
+ const IIIFImg = class {
7
+ constructor(hostRef) {
8
+ registerInstance(this, hostRef);
9
+ /**
10
+ * The IIIF [region](https://iiif.io/api/image/3.0/#41-region) of the image to display.
11
+ * Defines the rectangular portion of the underlying image to return.
12
+ *
13
+ * `full`: The full image is returned, without any cropping.
14
+ *
15
+ * `square`: A square area where width and height equal the shorter dimension.
16
+ *
17
+ * `width:height`: Any aspect ratio format (e.g., `16:9`, `4:3`, `3:2`, `21:9`) applies
18
+ * a centered crop based on the source image dimensions and sets the CSS aspect-ratio
19
+ * property for layout reservation.
20
+ *
21
+ * `x,y,w,h`: Absolute pixel coordinates (x, y position; w, h dimensions).
22
+ *
23
+ * `pct:x,y,w,h`: Percentage-based coordinates of full image dimensions.
24
+ *
25
+ * @default 'full'
26
+ */
27
+ this.region = 'full';
28
+ /**
29
+ * The IIIF [rotation](https://iiif.io/api/image/3.0/#44-rotation) to apply to the image.
30
+ * Specifies mirroring and clockwise rotation in degrees (0-360).
31
+ *
32
+ * `n`: Rotation in degrees only.
33
+ *
34
+ * `!n`: Mirror the image vertically, then rotate by n degrees.
35
+ *
36
+ * @default '0'
37
+ */
38
+ this.rotation = '0';
39
+ /**
40
+ * The IIIF [quality](https://iiif.io/api/image/3.0/#quality) of the image.
41
+ * Controls the color delivery mode.
42
+ *
43
+ * `default`: The server's default quality.
44
+ *
45
+ * `color`: Full color information.
46
+ *
47
+ * `gray`: Grayscale rendering.
48
+ *
49
+ * `bitonal`: Black and white only.
50
+ *
51
+ * @default 'default'
52
+ */
53
+ this.quality = 'default';
54
+ /**
55
+ * Native browser lazy loading behavior. Use "lazy" to defer loading until the image is near the viewport,
56
+ * or "eager" to load immediately.
57
+ * @default 'lazy'
58
+ */
59
+ this.loading = 'lazy';
60
+ /**
61
+ * Whether to display a fallback placeholder image when the IIIF image fails to load.
62
+ * @default true
63
+ */
64
+ this.showFallback = true;
65
+ this.isLoaded = false;
66
+ this.hasError = false;
67
+ this.baseUrl = 'https://iiif-images.library.upenn.edu/iiif/3';
68
+ this.handleLoad = () => {
69
+ this.isLoaded = true;
70
+ };
71
+ this.handleError = () => {
72
+ this.hasError = true;
73
+ };
74
+ }
75
+ /**
76
+ * Fetch IIIF image info to get actual dimensions.
77
+ */
78
+ async fetchImageInfo() {
79
+ try {
80
+ const currentUuid = this.uuid; // Capture current UUID
81
+ const infoUrl = `${this.baseUrl}/${currentUuid}/info.json`;
82
+ const response = await fetch(infoUrl);
83
+ if (!response.ok) {
84
+ console.error(`Failed to fetch IIIF info.json for ${currentUuid}`);
85
+ return;
86
+ }
87
+ const info = await response.json();
88
+ // Only update dimensions if UUID hasn't changed during fetch
89
+ // (protects against race conditions if component updates)
90
+ if (this.uuid === currentUuid) {
91
+ this.imageDimensions = {
92
+ width: info.width,
93
+ height: info.height,
94
+ };
95
+ this.fetchedUuid = currentUuid;
96
+ }
97
+ }
98
+ catch (error) {
99
+ console.error(`Error fetching IIIF image info for ${this.uuid}:`, error);
100
+ }
101
+ }
102
+ /**
103
+ * Check if a region value matches aspect ratio format (e.g., "16:9", "4:3", "3:2").
104
+ */
105
+ isAspectRatio(value) {
106
+ return /^\d+:\d+$/.test(value);
107
+ }
108
+ /**
109
+ * Set CSS custom property for aspect ratio on the host element.
110
+ */
111
+ setAspectRatioCss(aspectRatio) {
112
+ const [widthRatio, heightRatio] = aspectRatio.split(':');
113
+ this.hostElement.style.setProperty('--aspect-ratio', `${widthRatio} / ${heightRatio}`);
114
+ }
115
+ /**
116
+ * Clear CSS custom property for aspect ratio from the host element.
117
+ */
118
+ clearAspectRatioCss() {
119
+ this.hostElement.style.removeProperty('--aspect-ratio');
120
+ }
121
+ /**
122
+ * Format a number for IIIF percentage values (remove trailing zeros).
123
+ * IIIF spec requires no trailing zeros per section 4.7.
124
+ */
125
+ formatPercent(value) {
126
+ return value.toFixed(2).replace(/\.?0+$/, '');
127
+ }
128
+ /**
129
+ * Calculate a centered crop region for a given aspect ratio.
130
+ * Requires actual image dimensions to calculate correctly.
131
+ * @param aspectRatio - The target aspect ratio (e.g., "3:2" or "2:3")
132
+ * @returns A IIIF percentage-based region string (e.g., "pct:0,16.67,100,66.67")
133
+ */
134
+ calculateCenteredCrop(aspectRatio) {
135
+ if (!this.imageDimensions) {
136
+ throw new Error('Image dimensions required for aspect ratio calculation');
137
+ }
138
+ const [widthRatio, heightRatio] = aspectRatio.split(':').map(Number);
139
+ const targetRatio = widthRatio / heightRatio;
140
+ const sourceRatio = this.imageDimensions.width / this.imageDimensions.height;
141
+ // Determine if we need to crop width or height
142
+ if (sourceRatio > targetRatio) {
143
+ // Source is wider than target - crop left and right
144
+ const widthPercent = (targetRatio / sourceRatio) * 100;
145
+ const xOffset = (100 - widthPercent) / 2;
146
+ return `pct:${this.formatPercent(xOffset)},0,${this.formatPercent(widthPercent)},100`;
147
+ }
148
+ else {
149
+ // Source is taller than target - crop top and bottom
150
+ const heightPercent = (sourceRatio / targetRatio) * 100;
151
+ const yOffset = (100 - heightPercent) / 2;
152
+ return `pct:0,${this.formatPercent(yOffset)},100,${this.formatPercent(heightPercent)}`;
153
+ }
154
+ }
155
+ /**
156
+ * Get the region parameter for the IIIF URL.
157
+ */
158
+ getRegionParam() {
159
+ // Handle custom aspect ratios
160
+ let regionParam = this.region || 'full';
161
+ if (this.isAspectRatio(regionParam)) {
162
+ // Only apply crop if we have dimensions for the current UUID
163
+ if (this.imageDimensions && this.fetchedUuid === this.uuid) {
164
+ regionParam = this.calculateCenteredCrop(regionParam);
165
+ }
166
+ else {
167
+ // Use full image until dimensions are loaded
168
+ regionParam = 'full';
169
+ }
170
+ }
171
+ return regionParam;
172
+ }
173
+ buildIIIFUrl(width, format = 'jpg') {
174
+ const sizeParam = width ? `${width},` : 'max';
175
+ const regionParam = this.getRegionParam();
176
+ return `${this.baseUrl}/${this.uuid}/${regionParam}/${sizeParam}/${this.rotation}/${this.quality}.${format}`;
177
+ }
178
+ generateSrcset(baseWidth, format) {
179
+ return PIXEL_DENSITIES.map(density => {
180
+ const width = Math.round(baseWidth * density);
181
+ const url = this.buildIIIFUrl(width, format);
182
+ return `${url} ${density}x`;
183
+ }).join(', ');
184
+ }
185
+ handleRegionChange(newValue, oldValue) {
186
+ // Only process if region actually changed
187
+ if (newValue === oldValue) {
188
+ return;
189
+ }
190
+ // If new region is an aspect ratio, set CSS and fetch info
191
+ if (newValue && this.isAspectRatio(newValue)) {
192
+ this.setAspectRatioCss(newValue);
193
+ this.fetchImageInfo();
194
+ }
195
+ else {
196
+ // Clear aspect ratio CSS if no longer using aspect ratio format
197
+ this.clearAspectRatioCss();
198
+ }
199
+ }
200
+ componentDidLoad() {
201
+ // Set CSS aspect ratio and fetch image info if using aspect ratio format
202
+ if (this.region && this.isAspectRatio(this.region)) {
203
+ this.setAspectRatioCss(this.region);
204
+ this.fetchImageInfo();
205
+ }
206
+ if ('ResizeObserver' in window) {
207
+ this.resizeObserver = new ResizeObserver(entries => {
208
+ for (const entry of entries) {
209
+ this.observedWidth = Math.round(entry.contentRect.width);
210
+ if (this.resizeObserver) {
211
+ this.resizeObserver.disconnect();
212
+ this.resizeObserver = undefined;
213
+ }
214
+ }
215
+ });
216
+ this.resizeObserver.observe(this.hostElement);
217
+ }
218
+ }
219
+ disconnectedCallback() {
220
+ if (this.resizeObserver) {
221
+ this.resizeObserver.disconnect();
222
+ this.resizeObserver = undefined;
223
+ }
224
+ }
225
+ render() {
226
+ // Show placeholder until we've measured the component width
227
+ if (!this.observedWidth) {
228
+ return h("pennlibs-fallback-img", null);
229
+ }
230
+ if (this.hasError && this.showFallback) {
231
+ return (h("div", { class: "fallback-container" }, h("pennlibs-fallback-img", null)));
232
+ }
233
+ const imgClasses = {
234
+ 'iiif-img': true,
235
+ 'loaded': this.isLoaded
236
+ };
237
+ return (h("picture", null, h("source", { type: "image/webp", srcSet: this.generateSrcset(this.observedWidth, 'webp') }), h("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 })));
238
+ }
239
+ get hostElement() { return getElement(this); }
240
+ static get watchers() { return {
241
+ "region": ["handleRegionChange"]
242
+ }; }
243
+ };
244
+ IIIFImg.style = pennlibsIiifImgCss;
245
+
246
+ export { IIIFImg as pennlibs_iiif_img };
247
+ //# sourceMappingURL=pennlibs-iiif-img.entry.esm.js.map