@penn-libraries/web 1.1.0 → 1.1.1-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 (61) hide show
  1. package/dist/cjs/{index-4ixBJUwu.js → index-C0qvW4Ra.js} +7 -2
  2. package/dist/cjs/index-C0qvW4Ra.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_3.cjs.entry.js +1 -1
  6. package/dist/cjs/pennlibs-banner.cjs.entry.js +1 -1
  7. package/dist/cjs/pennlibs-chat.cjs.entry.js +1 -1
  8. package/dist/cjs/pennlibs-fallback-img.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/pennlibs-iiif-img.cjs.entry.js +132 -0
  12. package/dist/cjs/pennlibs-iiif-img.entry.cjs.js.map +1 -0
  13. package/dist/cjs/web.cjs.js +2 -2
  14. package/dist/collection/collection-manifest.json +2 -1
  15. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.css +37 -0
  16. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.js +310 -0
  17. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.js.map +1 -0
  18. package/dist/components/index.d.ts +2 -0
  19. package/dist/components/index.js +1 -0
  20. package/dist/components/index.js.map +1 -1
  21. package/dist/components/pennlibs-fallback-img.js +1 -32
  22. package/dist/components/pennlibs-fallback-img.js.map +1 -1
  23. package/dist/components/pennlibs-fallback-img2.js +37 -0
  24. package/dist/components/pennlibs-fallback-img2.js.map +1 -0
  25. package/dist/components/pennlibs-iiif-img.d.ts +11 -0
  26. package/dist/components/pennlibs-iiif-img.js +168 -0
  27. package/dist/components/pennlibs-iiif-img.js.map +1 -0
  28. package/dist/docs.json +239 -3
  29. package/dist/{web/p-Di48-Vtw.js → esm/index-D9dYrmUF.js} +7 -2
  30. package/dist/esm/index-D9dYrmUF.js.map +1 -0
  31. package/dist/esm/index.js +1 -1
  32. package/dist/esm/loader.js +3 -3
  33. package/dist/esm/pennlibs-autocomplete_3.entry.js +1 -1
  34. package/dist/esm/pennlibs-banner.entry.js +1 -1
  35. package/dist/esm/pennlibs-chat.entry.js +1 -1
  36. package/dist/esm/pennlibs-fallback-img.entry.js +1 -1
  37. package/dist/esm/pennlibs-feedback.entry.js +1 -1
  38. package/dist/esm/pennlibs-hero.entry.js +1 -1
  39. package/dist/esm/pennlibs-iiif-img.entry.js +130 -0
  40. package/dist/esm/pennlibs-iiif-img.entry.js.map +1 -0
  41. package/dist/esm/web.js +3 -3
  42. package/dist/types/components/pennlibs-iiif-img/pennlibs-iiif-img.d.ts +89 -0
  43. package/dist/types/components.d.ts +119 -0
  44. package/dist/web/index.esm.js +1 -1
  45. package/dist/web/{p-7cbd16c0.entry.js → p-43d9c2d4.entry.js} +1 -1
  46. package/dist/{esm/index-Di48-Vtw.js → web/p-D9dYrmUF.js} +7 -2
  47. package/dist/web/p-D9dYrmUF.js.map +1 -0
  48. package/dist/web/{p-783e2a87.entry.js → p-ad92090a.entry.js} +1 -1
  49. package/dist/web/{p-56b9f10c.entry.js → p-b4b58af0.entry.js} +1 -1
  50. package/dist/web/p-c4074cf1.entry.js +130 -0
  51. package/dist/web/{p-6503bb09.entry.js → p-cb2584da.entry.js} +1 -1
  52. package/dist/web/{p-09e4a02c.entry.js → p-ce97059c.entry.js} +1 -1
  53. package/dist/web/{p-269363bf.entry.js → p-e6188c30.entry.js} +1 -1
  54. package/dist/web/pennlibs-iiif-img.entry.esm.js.map +1 -0
  55. package/dist/web/web.esm.js +3 -3
  56. package/hydrate/index.js +162 -0
  57. package/hydrate/index.mjs +162 -0
  58. package/package.json +1 -1
  59. package/dist/cjs/index-4ixBJUwu.js.map +0 -1
  60. package/dist/esm/index-Di48-Vtw.js.map +0 -1
  61. package/dist/web/p-Di48-Vtw.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
 
6
6
 
@@ -1,11 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  const defineCustomElements = async (win, options) => {
6
6
  if (typeof window === 'undefined') return undefined;
7
7
  await index.globalScripts();
8
- return index.bootstrapLazy([["pennlibs-autocomplete_3.cjs",[[257,"pennlibs-autocomplete",{"for":[1],"showSuggestions":[32],"currentIndex":[32],"originalValue":[32],"options":[32]},[[0,"slotchange","handleSlotChange"],[16,"input","handleInputEvent"],[16,"focus","handleFocusEvent"],[16,"blur","handleBlurEvent"],[0,"focusout","handleFocusOut"],[4,"keydown","handleKeyDown"]]],[257,"pennlibs-footer",{"navigationByChildren":[32]}],[257,"pennlibs-header",{"serviceName":[1,"service-name"],"serviceLede":[1,"service-lede"],"serviceHref":[1,"service-href"],"theme":[1],"isMenuOpen":[32],"navigation":[32]}]]],["pennlibs-banner.cjs",[[257,"pennlibs-banner"]]],["pennlibs-chat.cjs",[[257,"pennlibs-chat",{"href":[32]}]]],["pennlibs-fallback-img.cjs",[[257,"pennlibs-fallback-img"]]],["pennlibs-feedback.cjs",[[257,"pennlibs-feedback",{"error":[32],"answer":[32]}]]],["pennlibs-hero.cjs",[[257,"pennlibs-hero",{"heroPictureElement":[32],"heroHeadingElement":[32],"heroParagraphElement":[32],"heroSrc":[32]}]]]], options);
8
+ return index.bootstrapLazy([["pennlibs-iiif-img.cjs",[[257,"pennlibs-iiif-img",{"uuid":[1],"alt":[1],"region":[1],"rotation":[1],"quality":[1],"loading":[1],"showFallback":[4,"show-fallback"],"isLoaded":[32],"hasError":[32]}]]],["pennlibs-autocomplete_3.cjs",[[257,"pennlibs-autocomplete",{"for":[1],"showSuggestions":[32],"currentIndex":[32],"originalValue":[32],"options":[32]},[[0,"slotchange","handleSlotChange"],[16,"input","handleInputEvent"],[16,"focus","handleFocusEvent"],[16,"blur","handleBlurEvent"],[0,"focusout","handleFocusOut"],[4,"keydown","handleKeyDown"]]],[257,"pennlibs-footer",{"navigationByChildren":[32]}],[257,"pennlibs-header",{"serviceName":[1,"service-name"],"serviceLede":[1,"service-lede"],"serviceHref":[1,"service-href"],"theme":[1],"isMenuOpen":[32],"navigation":[32]}]]],["pennlibs-banner.cjs",[[257,"pennlibs-banner"]]],["pennlibs-chat.cjs",[[257,"pennlibs-chat",{"href":[32]}]]],["pennlibs-feedback.cjs",[[257,"pennlibs-feedback",{"error":[32],"answer":[32]}]]],["pennlibs-hero.cjs",[[257,"pennlibs-hero",{"heroPictureElement":[32],"heroHeadingElement":[32],"heroParagraphElement":[32],"heroSrc":[32]}]]],["pennlibs-fallback-img.cjs",[[257,"pennlibs-fallback-img"]]]], options);
9
9
  };
10
10
 
11
11
  exports.setNonce = index.setNonce;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  const pennlibsAutocompleteCss = ":host {\n display: block;\n width: 100%;\n border-radius: 1.25rem;\n padding: 0;\n border-top: 0;\n position: relative;\n}\n\n[role=listbox] {\n position: absolute;\n margin-top: var(--pl-space-xs);\n background: var(--pl-color-bg-default);\n border-radius: 1.25rem;\n box-shadow: rgba(140, 149, 159, 0.3) 0px 8px 24px 0px;\n width: 100%;\n overflow: hidden;\n z-index: 1;\n\n display: flex;\n flex-direction: column;\n}\n\np {\n margin: 0;\n font-size: var(--pl-font-size-s);\n color: var(--pl-color-fg-subtle);\n padding: var(--pl-space-xs) calc(var(--pl-space-m) + var(--pl-space-2xs));\n font-size: var(--pl-font-size-s);\n order: 2;\n font-weight: 500;\n background: var(--pl-color-bg-subtle);\n border-radius: 0 0 1.25rem 1.25rem;\n\n display: flex;\n gap: var(--pl-space-s) var(--pl-space-l);\n flex-wrap: wrap;\n}\n\nol {\n list-style: none;\n margin: 0;\n padding: var(--pl-space-xs) 0;\n order: 1;\n}\n\n[role=option] {\n color: var(--pl-color-fg-default);\n padding: var(--pl-space-s) calc(var(--pl-space-m) + var(--pl-space-2xs));\n text-decoration: none;\n font-weight: 700; \n\n &:hover {\n cursor: pointer;\n }\n\n &:hover,\n &:focus {\n text-decoration-thickness: 2px;\n text-underline-offset: 2px;\n text-decoration: underline;\n }\n}\n\n[aria-selected=true] {\n text-decoration-thickness: 2px;\n text-underline-offset: 2px;\n text-decoration: underline;\n}\n\nmark,\nb {\n background: none;\n font-weight: 400;\n}\n\n.suggestion--border {\n border-bottom: solid 1px rgb(from var(--pl-color-fg-default) r g b / 0.2);\n padding-bottom: calc(var(--pl-space-2xs) + var(--pl-space-s));\n margin-bottom: var(--pl-space-2xs);\n}";
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  const pennlibsBannerCss = ":host{font-family:var(--pl-font-family);font-size:var(--pl-font-size);--max-width:1080px}.viewport-margins{max-width:calc(var(--max-width) + 0.5em);margin:0 auto;padding:0 var(--pl-viewport-margins-gutter, 1em)}.skip-to-content-link{position:absolute;transform:translateY(-300%);background:var(--pl-color-bg-default);left:0.5em;padding:0.5em 1em;margin-top:0.5em;position:absolute}.skip-to-content-link:focus{transform:translateY(0%);color:var(--pl-color-fg-default)}.universal-nav{background:var(--pl-color-penn-blue)}.universal-nav ul{display:flex;align-items:baseline;flex-wrap:wrap;scrollbar-color:var(--pl-color-penn-red);list-style:none;padding:0;margin:0}.universal-nav li{display:inline-block}.universal-nav a{display:inline-block;text-transform:uppercase;font-size:0.75em;letter-spacing:0.075em;font-weight:600;color:var(--pl-color-fg-subtle-on-emphasis);padding:0.5em;text-decoration:none}.universal-nav a:hover{text-decoration:underline;text-decoration-thickness:2px;text-underline-offset:.15em}.universal-nav__shield-image{vertical-align:sub;height:1em;padding-right:0.5em;height:auto}";
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  const pennlibsChatCss = ":host {\n font-family: var(--pl-font-family);\n font-size: var(--pl-font-size);\n line-height: normal;\n}\n\n@media print {\n :host {\n display: none;\n }\n}\n\na {\n display: flex;\n align-items: center;\n background: linear-gradient(45deg, #faa755, #fcca99);\n border-radius: 1em;\n position: fixed;\n bottom: 0.75em;\n right: 0.75em;\n box-shadow: rgba(0,0,0,0.3) 0px 2px 16px 0px;\n color: var(--pl-color-penn-blue);\n text-decoration: none;\n font-weight: 500;\n padding: 0.15em;\n font-size: 1em;\n\n @media (min-width: 820px) {\n bottom: 1.5em;\n right: 1.5em;\n width: auto;\n padding: 0.15em 0.75em;\n padding-left: 0.5em;\n }\n}\n\na:hover {\n text-decoration: underline;\n text-underline-offset: var(--pl-link-text-underline-offset);\n text-decoration-thickness: var(--pl-link-hover-text-decoration-thickness);\n}\n\na:hover,\na:focus {\n outline: none;\n box-shadow: 0 0 0 2px var(--pl-color-bg-attention),0 0 0 4px var(--pl-color-penn-blue), rgba(0,0,0,0.3) 0px 2px 16px 0px;;\n}\n\nsvg {\n width: 2.5em;\n height: 2.5em;\n}\n\nspan {\n display: none;\n font-size: 1.1em;\n\n @media (min-width: 820px) {\n display: inline;\n }\n}";
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  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)}";
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  const pennlibsFeedbackCss = ":host{display:block;background:#eeeff4;padding:1.5em 1em;font-family:var(--pl-font-family);font-size:var(--pl-font-size);color:var(--pl-color-fg-default);line-height:1.4;box-sizing:border-box}@media print{:host{display:none}}.feedback-container{display:flex;align-items:center;flex-wrap:wrap;gap:0.5em;width:100%;justify-content:center}@media (max-width: 26em){.feedback-container{justify-content:center}}h2{display:inline-block;margin:0 0.5em 0 0;font-family:var(--pl-font-sans-serif);font-size:1em;font-weight:600}p{margin:0;padding:calc(0.5em + 1px) 0}strong{font-weight:600}a{color:var(--pl-color-fg-accent);text-decoration:underline;text-underline-offset:var(--pl-link-text-underline-offset);text-decoration-thickness:var(--pl-link-text-decoration-thickness)}button{all:unset;display:flex;align-items:center;gap:0.5em;padding:0.5em 1.5em;font-family:var(--pl-font-family);font-weight:500;line-height:1.4;color:var(--pl-color-fg-default);background:var(--pl-color-bg-default);border:solid 1px var(--pl-color-fg-subtle);border-radius:1em;box-sizing:border-box}button:hover{cursor:pointer}button:hover span{text-decoration:underline}*:focus{outline:0;box-shadow:0 0 0 2px var(--pl-color-bg-attention),\n 0 0 0 3px var(--pl-color-bg-emphasis)}p:focus{outline:none;box-shadow:0 0 0 2px var(--pl-color-bg-attention), 0 0 0 3px var(--pl-color-bg-emphasis)}@media (max-width: 26em){h2{display:block;width:100%;text-align:center}}.help-us{display:flex;flex-direction:column;gap:0.5em}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.visually-hidden:focus,.visually-hidden:active{clip:auto;clip-path:none;height:auto;overflow:visible;position:static;white-space:normal;width:auto}";
6
6
 
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  const pennlibsHeroCss = ":host{--pl-hero-height:clamp(42vh, 32rem, 26rem);--pl-hero-heading-font:var(--pl-font-serif);--pl-hero-color:var(--pl-color-fg-on-emphasis)}*,*:before,*:after{box-sizing:inherit}.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.viewport-margins{width:100%;max-width:var(--pl-viewport-margins-max-width);margin:0 auto;padding:0 var(--pl-viewport-margins-gutter, 1em)}.hero{position:relative;min-height:var(--pl-hero-height);height:100%;background-size:cover;background-repeat:no-repeat;background-position:50% 33%;display:flex}.hero::before{content:\"\";display:flex;width:100%;height:100%;top:0;position:absolute;background:linear-gradient(360deg, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.7) 20%, rgba(0, 0, 0, 0.4) 40%, rgba(0, 0, 0, 0.05) 100%);z-index:0}.hero::after{content:\"\";display:flex;width:100%;height:100%;top:0;position:absolute;background:linear-gradient(180deg, rgba(1, 31, 91, 1) 0%, rgba(1, 31, 91, 0.9) 10%, rgba(1, 31, 91, 0.8) 20%, rgba(1, 31, 91, 0.1) 50%, rgba(1, 31, 91, 0.0) 100%);z-index:0}.hero__content{position:relative;display:flex;flex-direction:column;width:100%;z-index:1}.hero__heading-container{margin-top:auto;padding-top:var(--pl-space-3xl);padding-bottom:var(--pl-space-3xl)}.hero__heading{text-shadow:1px 1px 2px var(--pl-color-fg-default);line-height:1.1;font-size:3em;font-weight:bold;font-family:var(--pl-hero-heading-font);text-wrap:pretty;max-width:30ch;margin:0;color:var(--pl-hero-color)}@media (max-width: 920px){.hero__heading{font-size:2.5em}}.hero__sub-heading{font-size:1.25em;font-family:var(--pl-font-family);font-weight:500;color:var(--pl-hero-color);max-width:52ch;text-wrap:pretty;margin-top:1em;margin-bottom:0}.hero__sub-heading a{text-decoration:underline;text-underline-offset:var(--pl-link-text-underline-offset);text-decoration-thickness:var(--pl-link-text-decoration-thickness);color:var(--pl-hero-color)}.hero__sub-heading a:hover{text-decoration-thickness:var(--pl-link-hover-text-decoration-thickness)}.hero__sub-heading strong{font-weight:bold}@media (max-width: 620px){.hero__heading{font-size:2.75em}.hero__sub-heading{font-size:1em}}";
6
6
 
@@ -0,0 +1,132 @@
1
+ 'use strict';
2
+
3
+ var index = require('./index-C0qvW4Ra.js');
4
+
5
+ const pennlibsIiifImgCss = ":host{display:block;width:100%;max-width:100%}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:auto;min-height:200px}.fallback-container pennlibs-fallback-img{width:100%;height:100%}";
6
+
7
+ const PIXEL_DENSITY_MULTIPLIERS = [0.5, 0.75, 1, 1.5, 2];
8
+ const DEFAULT_IMAGE_SIZES = [400, 600, 800, 1200, 1600, 2400];
9
+ const IIIFImg = class {
10
+ constructor(hostRef) {
11
+ index.registerInstance(this, hostRef);
12
+ /**
13
+ * The IIIF [region](https://iiif.io/api/image/3.0/#41-region) of the image to display.
14
+ * Defines the rectangular portion of the underlying image to return.
15
+ *
16
+ * `full`: The full image is returned, without any cropping.
17
+ *
18
+ * `square`: A square area where width and height equal the shorter dimension.
19
+ *
20
+ * `x,y,w,h`: Absolute pixel coordinates (x, y position; w, h dimensions).
21
+ *
22
+ * `pct:x,y,w,h`: Percentage-based coordinates of full image dimensions.
23
+ *
24
+ * @default 'full'
25
+ */
26
+ this.region = 'full';
27
+ /**
28
+ * The IIIF [rotation](https://iiif.io/api/image/3.0/#44-rotation) to apply to the image.
29
+ * Specifies mirroring and clockwise rotation in degrees (0-360).
30
+ *
31
+ * `n`: Rotation in degrees only.
32
+ *
33
+ * `!n`: Mirror the image vertically, then rotate by n degrees.
34
+ *
35
+ * @default '0'
36
+ */
37
+ this.rotation = '0';
38
+ /**
39
+ * The IIIF [quality](https://iiif.io/api/image/3.0/#quality) of the image.
40
+ * Controls the color delivery mode.
41
+ *
42
+ * `default`: The server's default quality.
43
+ *
44
+ * `color`: Full color information.
45
+ *
46
+ * `gray`: Grayscale rendering.
47
+ *
48
+ * `bitonal`: Black and white only.
49
+ *
50
+ * @default 'default'
51
+ */
52
+ this.quality = 'default';
53
+ /**
54
+ * Native browser lazy loading behavior. Use "lazy" to defer loading until the image is near the viewport,
55
+ * or "eager" to load immediately.
56
+ * @default 'lazy'
57
+ */
58
+ this.loading = 'lazy';
59
+ /**
60
+ * Whether to display a fallback placeholder image when the IIIF image fails to load.
61
+ * @default true
62
+ */
63
+ this.showFallback = true;
64
+ this.isLoaded = false;
65
+ this.hasError = false;
66
+ this.baseUrl = 'https://iiif-images.library.upenn.edu/iiif/3';
67
+ this.handleLoad = () => {
68
+ this.isLoaded = true;
69
+ };
70
+ this.handleError = () => {
71
+ this.hasError = true;
72
+ };
73
+ }
74
+ buildIIIFUrl(width, format = 'jpg') {
75
+ const sizeParam = width ? `${width},` : 'max';
76
+ return `${this.baseUrl}/${this.uuid}/${this.region}/${sizeParam}/${this.rotation}/${this.quality}.${format}`;
77
+ }
78
+ generateSrcset(sizes, format) {
79
+ const uniqueSizes = [...new Set(sizes)].sort((a, b) => a - b);
80
+ const srcsetEntries = uniqueSizes.map(size => {
81
+ const url = this.buildIIIFUrl(size, format);
82
+ return `${url} ${size}w`;
83
+ });
84
+ return srcsetEntries.join(', ');
85
+ }
86
+ getOptimalSizes() {
87
+ if (this.observedWidth) {
88
+ const baseSizes = PIXEL_DENSITY_MULTIPLIERS.map(multiplier => Math.round(this.observedWidth * multiplier));
89
+ return baseSizes;
90
+ }
91
+ return [...DEFAULT_IMAGE_SIZES];
92
+ }
93
+ componentDidLoad() {
94
+ if ('ResizeObserver' in window) {
95
+ this.resizeObserver = new ResizeObserver(entries => {
96
+ for (const entry of entries) {
97
+ this.observedWidth = Math.round(entry.contentRect.width);
98
+ if (this.resizeObserver) {
99
+ this.resizeObserver.disconnect();
100
+ this.resizeObserver = undefined;
101
+ }
102
+ // Trigger re-render with optimized sizes
103
+ this.isLoaded = this.isLoaded;
104
+ }
105
+ });
106
+ this.resizeObserver.observe(this.hostElement);
107
+ }
108
+ }
109
+ disconnectedCallback() {
110
+ if (this.resizeObserver) {
111
+ this.resizeObserver.disconnect();
112
+ this.resizeObserver = undefined;
113
+ }
114
+ }
115
+ render() {
116
+ const sizes = this.getOptimalSizes();
117
+ if (this.hasError && this.showFallback) {
118
+ return (index.h("div", { class: "fallback-container" }, index.h("pennlibs-fallback-img", null)));
119
+ }
120
+ const imgClasses = {
121
+ 'iiif-img': true,
122
+ 'loaded': this.isLoaded
123
+ };
124
+ const defaultSize = sizes[0];
125
+ return (index.h("picture", null, index.h("source", { type: "image/webp", srcSet: this.generateSrcset(sizes, 'webp'), sizes: "100vw" }), index.h("img", { class: imgClasses, src: this.buildIIIFUrl(defaultSize, 'jpg'), srcSet: this.generateSrcset(sizes, 'jpg'), sizes: "100vw", alt: this.alt, loading: this.loading, onLoad: this.handleLoad, onError: this.handleError })));
126
+ }
127
+ get hostElement() { return index.getElement(this); }
128
+ };
129
+ IIIFImg.style = pennlibsIiifImgCss;
130
+
131
+ exports.pennlibs_iiif_img = IIIFImg;
132
+ //# sourceMappingURL=pennlibs-iiif-img.entry.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pennlibs-iiif-img.entry.cjs.js","sources":["src/components/pennlibs-iiif-img/pennlibs-iiif-img.css?tag=pennlibs-iiif-img&encapsulation=shadow","src/components/pennlibs-iiif-img/pennlibs-iiif-img.tsx"],"sourcesContent":[":host {\n display: block;\n width: 100%;\n max-width: 100%;\n}\n\npicture {\n display: block;\n line-height: 0;\n}\n\n.iiif-img {\n display: block;\n width: 100%;\n max-width: 100%;\n height: auto;\n opacity: 0;\n filter: blur(5px);\n transition: opacity 0.15s ease-in, filter 0.15s ease-in;\n}\n\n.iiif-img.loaded {\n opacity: 1;\n filter: blur(0);\n}\n\n.fallback-container {\n display: block;\n width: 100%;\n height: auto;\n min-height: 200px;\n}\n\n.fallback-container pennlibs-fallback-img {\n width: 100%;\n height: 100%;\n}\n","import { Component, h, Prop, State, Element } from '@stencil/core';\n\ntype ImageFormat = 'jpg' | 'webp';\n\nconst PIXEL_DENSITY_MULTIPLIERS = [0.5, 0.75, 1, 1.5, 2] as const;\nconst DEFAULT_IMAGE_SIZES = [400, 600, 800, 1200, 1600, 2400] as const;\n\n/**\n * Display responsive, high-quality images from Penn Libraries' IIIF Image API 3.0 server.\n * Automatically generates optimal image sources for different viewport sizes and pixel densities,\n * with modern WebP format and JPG fallback support.\n *\n * @component\n * @example\n * <pennlibs-iiif-img\n * uuid=\"063ff35c-bbdd-4b63-bbdd-6206590e20d5\"\n * alt=\"\">\n * </pennlibs-iiif-img>\n */\n@Component({\n tag: 'pennlibs-iiif-img',\n styleUrl: 'pennlibs-iiif-img.css',\n shadow: true,\n})\nexport class IIIFImg {\n private resizeObserver?: ResizeObserver;\n\n @Element() hostElement!: HTMLElement;\n\n /**\n * The IIIF [UUID identifier](https://iiif.io/api/image/3.0/#3-identifier) of the image resource on the Penn Libraries IIIF server.\n */\n @Prop() uuid!: string;\n\n /**\n * Alternative text that describes the image content for screen readers and when images fail to load.\n * Use an empty string for purely decorative images.\n */\n @Prop() alt!: string;\n\n /**\n * The IIIF [region](https://iiif.io/api/image/3.0/#41-region) of the image to display.\n * Defines the rectangular portion of the underlying image to return.\n * \n * `full`: The full image is returned, without any cropping.\n * \n * `square`: A square area where width and height equal the shorter dimension.\n * \n * `x,y,w,h`: Absolute pixel coordinates (x, y position; w, h dimensions).\n * \n * `pct:x,y,w,h`: Percentage-based coordinates of full image dimensions.\n * \n * @default 'full'\n */\n @Prop() region?: string = 'full';\n\n /**\n * The IIIF [rotation](https://iiif.io/api/image/3.0/#44-rotation) to apply to the image.\n * Specifies mirroring and clockwise rotation in degrees (0-360).\n * \n * `n`: Rotation in degrees only.\n * \n * `!n`: Mirror the image vertically, then rotate by n degrees.\n * \n * @default '0'\n */\n @Prop() rotation?: string = '0';\n\n /**\n * The IIIF [quality](https://iiif.io/api/image/3.0/#quality) of the image.\n * Controls the color delivery mode.\n * \n * `default`: The server's default quality.\n * \n * `color`: Full color information.\n * \n * `gray`: Grayscale rendering.\n * \n * `bitonal`: Black and white only.\n * \n * @default 'default'\n */\n @Prop() quality?: string = 'default';\n\n /**\n * Native browser lazy loading behavior. Use \"lazy\" to defer loading until the image is near the viewport,\n * or \"eager\" to load immediately.\n * @default 'lazy'\n */\n @Prop() loading?: 'lazy' | 'eager' = 'lazy';\n\n /**\n * Whether to display a fallback placeholder image when the IIIF image fails to load.\n * @default true\n */\n @Prop() showFallback?: boolean = true;\n\n @State() isLoaded: boolean = false;\n @State() hasError: boolean = false;\n\n private observedWidth?: number;\n\n private readonly baseUrl = 'https://iiif-images.library.upenn.edu/iiif/3';\n\n private buildIIIFUrl(width?: number, format: ImageFormat = 'jpg'): string {\n const sizeParam = width ? `${width},` : 'max';\n return `${this.baseUrl}/${this.uuid}/${this.region}/${sizeParam}/${this.rotation}/${this.quality}.${format}`;\n }\n\n private generateSrcset(sizes: number[], format: ImageFormat): string {\n const uniqueSizes = [...new Set(sizes)].sort((a, b) => a - b);\n\n const srcsetEntries = uniqueSizes.map(size => {\n const url = this.buildIIIFUrl(size, format);\n return `${url} ${size}w`;\n });\n\n return srcsetEntries.join(', ');\n }\n\n private getOptimalSizes(): number[] {\n if (this.observedWidth) {\n const baseSizes = PIXEL_DENSITY_MULTIPLIERS.map(multiplier =>\n Math.round(this.observedWidth! * multiplier)\n );\n\n return baseSizes;\n }\n\n return [...DEFAULT_IMAGE_SIZES];\n }\n\n private handleLoad = () => {\n this.isLoaded = true;\n };\n\n private handleError = () => {\n this.hasError = true;\n };\n\n componentDidLoad() {\n if ('ResizeObserver' in window) {\n this.resizeObserver = new ResizeObserver(entries => {\n for (const entry of entries) {\n this.observedWidth = Math.round(entry.contentRect.width);\n\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = undefined;\n }\n\n // Trigger re-render with optimized sizes\n this.isLoaded = this.isLoaded;\n }\n });\n\n this.resizeObserver.observe(this.hostElement);\n }\n }\n\n disconnectedCallback() {\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = undefined;\n }\n }\n\n render() {\n const sizes = this.getOptimalSizes();\n\n if (this.hasError && this.showFallback) {\n return (\n <div class=\"fallback-container\">\n <pennlibs-fallback-img />\n </div>\n );\n }\n\n const imgClasses = {\n 'iiif-img': true,\n 'loaded': this.isLoaded\n };\n\n const defaultSize = sizes[0];\n\n return (\n <picture>\n <source\n type=\"image/webp\"\n srcSet={this.generateSrcset(sizes, 'webp')}\n sizes=\"100vw\"\n />\n\n <img\n class={imgClasses}\n src={this.buildIIIFUrl(defaultSize, 'jpg')}\n srcSet={this.generateSrcset(sizes, 'jpg')}\n sizes=\"100vw\"\n alt={this.alt}\n loading={this.loading}\n onLoad={this.handleLoad}\n onError={this.handleError}\n />\n </picture>\n );\n }\n}\n"],"names":["h"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var index = require('./index-4ixBJUwu.js');
3
+ var index = require('./index-C0qvW4Ra.js');
4
4
 
5
5
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
6
  /*
@@ -18,7 +18,7 @@ var patchBrowser = () => {
18
18
 
19
19
  patchBrowser().then(async (options) => {
20
20
  await index.globalScripts();
21
- return index.bootstrapLazy([["pennlibs-autocomplete_3.cjs",[[257,"pennlibs-autocomplete",{"for":[1],"showSuggestions":[32],"currentIndex":[32],"originalValue":[32],"options":[32]},[[0,"slotchange","handleSlotChange"],[16,"input","handleInputEvent"],[16,"focus","handleFocusEvent"],[16,"blur","handleBlurEvent"],[0,"focusout","handleFocusOut"],[4,"keydown","handleKeyDown"]]],[257,"pennlibs-footer",{"navigationByChildren":[32]}],[257,"pennlibs-header",{"serviceName":[1,"service-name"],"serviceLede":[1,"service-lede"],"serviceHref":[1,"service-href"],"theme":[1],"isMenuOpen":[32],"navigation":[32]}]]],["pennlibs-banner.cjs",[[257,"pennlibs-banner"]]],["pennlibs-chat.cjs",[[257,"pennlibs-chat",{"href":[32]}]]],["pennlibs-fallback-img.cjs",[[257,"pennlibs-fallback-img"]]],["pennlibs-feedback.cjs",[[257,"pennlibs-feedback",{"error":[32],"answer":[32]}]]],["pennlibs-hero.cjs",[[257,"pennlibs-hero",{"heroPictureElement":[32],"heroHeadingElement":[32],"heroParagraphElement":[32],"heroSrc":[32]}]]]], options);
21
+ return index.bootstrapLazy([["pennlibs-iiif-img.cjs",[[257,"pennlibs-iiif-img",{"uuid":[1],"alt":[1],"region":[1],"rotation":[1],"quality":[1],"loading":[1],"showFallback":[4,"show-fallback"],"isLoaded":[32],"hasError":[32]}]]],["pennlibs-autocomplete_3.cjs",[[257,"pennlibs-autocomplete",{"for":[1],"showSuggestions":[32],"currentIndex":[32],"originalValue":[32],"options":[32]},[[0,"slotchange","handleSlotChange"],[16,"input","handleInputEvent"],[16,"focus","handleFocusEvent"],[16,"blur","handleBlurEvent"],[0,"focusout","handleFocusOut"],[4,"keydown","handleKeyDown"]]],[257,"pennlibs-footer",{"navigationByChildren":[32]}],[257,"pennlibs-header",{"serviceName":[1,"service-name"],"serviceLede":[1,"service-lede"],"serviceHref":[1,"service-href"],"theme":[1],"isMenuOpen":[32],"navigation":[32]}]]],["pennlibs-banner.cjs",[[257,"pennlibs-banner"]]],["pennlibs-chat.cjs",[[257,"pennlibs-chat",{"href":[32]}]]],["pennlibs-feedback.cjs",[[257,"pennlibs-feedback",{"error":[32],"answer":[32]}]]],["pennlibs-hero.cjs",[[257,"pennlibs-hero",{"heroPictureElement":[32],"heroHeadingElement":[32],"heroParagraphElement":[32],"heroSrc":[32]}]]],["pennlibs-fallback-img.cjs",[[257,"pennlibs-fallback-img"]]]], options);
22
22
  });
23
23
 
24
24
  exports.setNonce = index.setNonce;
@@ -7,7 +7,8 @@
7
7
  "components/pennlibs-feedback/pennlibs-feedback.js",
8
8
  "components/pennlibs-footer/pennlibs-footer.js",
9
9
  "components/pennlibs-header/pennlibs-header.js",
10
- "components/pennlibs-hero/pennlibs-hero.js"
10
+ "components/pennlibs-hero/pennlibs-hero.js",
11
+ "components/pennlibs-iiif-img/pennlibs-iiif-img.js"
11
12
  ],
12
13
  "compiler": {
13
14
  "name": "@stencil/core",
@@ -0,0 +1,37 @@
1
+ :host {
2
+ display: block;
3
+ width: 100%;
4
+ max-width: 100%;
5
+ }
6
+
7
+ picture {
8
+ display: block;
9
+ line-height: 0;
10
+ }
11
+
12
+ .iiif-img {
13
+ display: block;
14
+ width: 100%;
15
+ max-width: 100%;
16
+ height: auto;
17
+ opacity: 0;
18
+ filter: blur(5px);
19
+ transition: opacity 0.15s ease-in, filter 0.15s ease-in;
20
+ }
21
+
22
+ .iiif-img.loaded {
23
+ opacity: 1;
24
+ filter: blur(0);
25
+ }
26
+
27
+ .fallback-container {
28
+ display: block;
29
+ width: 100%;
30
+ height: auto;
31
+ min-height: 200px;
32
+ }
33
+
34
+ .fallback-container pennlibs-fallback-img {
35
+ width: 100%;
36
+ height: 100%;
37
+ }
@@ -0,0 +1,310 @@
1
+ import { h } from "@stencil/core";
2
+ const PIXEL_DENSITY_MULTIPLIERS = [0.5, 0.75, 1, 1.5, 2];
3
+ const DEFAULT_IMAGE_SIZES = [400, 600, 800, 1200, 1600, 2400];
4
+ /**
5
+ * Display responsive, high-quality images from Penn Libraries' IIIF Image API 3.0 server.
6
+ * Automatically generates optimal image sources for different viewport sizes and pixel densities,
7
+ * with modern WebP format and JPG fallback support.
8
+ *
9
+ * @component
10
+ * @example
11
+ * <pennlibs-iiif-img
12
+ * uuid="063ff35c-bbdd-4b63-bbdd-6206590e20d5"
13
+ * alt="">
14
+ * </pennlibs-iiif-img>
15
+ */
16
+ export class IIIFImg {
17
+ constructor() {
18
+ /**
19
+ * The IIIF [region](https://iiif.io/api/image/3.0/#41-region) of the image to display.
20
+ * Defines the rectangular portion of the underlying image to return.
21
+ *
22
+ * `full`: The full image is returned, without any cropping.
23
+ *
24
+ * `square`: A square area where width and height equal the shorter dimension.
25
+ *
26
+ * `x,y,w,h`: Absolute pixel coordinates (x, y position; w, h dimensions).
27
+ *
28
+ * `pct:x,y,w,h`: Percentage-based coordinates of full image dimensions.
29
+ *
30
+ * @default 'full'
31
+ */
32
+ this.region = 'full';
33
+ /**
34
+ * The IIIF [rotation](https://iiif.io/api/image/3.0/#44-rotation) to apply to the image.
35
+ * Specifies mirroring and clockwise rotation in degrees (0-360).
36
+ *
37
+ * `n`: Rotation in degrees only.
38
+ *
39
+ * `!n`: Mirror the image vertically, then rotate by n degrees.
40
+ *
41
+ * @default '0'
42
+ */
43
+ this.rotation = '0';
44
+ /**
45
+ * The IIIF [quality](https://iiif.io/api/image/3.0/#quality) of the image.
46
+ * Controls the color delivery mode.
47
+ *
48
+ * `default`: The server's default quality.
49
+ *
50
+ * `color`: Full color information.
51
+ *
52
+ * `gray`: Grayscale rendering.
53
+ *
54
+ * `bitonal`: Black and white only.
55
+ *
56
+ * @default 'default'
57
+ */
58
+ this.quality = 'default';
59
+ /**
60
+ * Native browser lazy loading behavior. Use "lazy" to defer loading until the image is near the viewport,
61
+ * or "eager" to load immediately.
62
+ * @default 'lazy'
63
+ */
64
+ this.loading = 'lazy';
65
+ /**
66
+ * Whether to display a fallback placeholder image when the IIIF image fails to load.
67
+ * @default true
68
+ */
69
+ this.showFallback = true;
70
+ this.isLoaded = false;
71
+ this.hasError = false;
72
+ this.baseUrl = 'https://iiif-images.library.upenn.edu/iiif/3';
73
+ this.handleLoad = () => {
74
+ this.isLoaded = true;
75
+ };
76
+ this.handleError = () => {
77
+ this.hasError = true;
78
+ };
79
+ }
80
+ buildIIIFUrl(width, format = 'jpg') {
81
+ const sizeParam = width ? `${width},` : 'max';
82
+ return `${this.baseUrl}/${this.uuid}/${this.region}/${sizeParam}/${this.rotation}/${this.quality}.${format}`;
83
+ }
84
+ generateSrcset(sizes, format) {
85
+ const uniqueSizes = [...new Set(sizes)].sort((a, b) => a - b);
86
+ const srcsetEntries = uniqueSizes.map(size => {
87
+ const url = this.buildIIIFUrl(size, format);
88
+ return `${url} ${size}w`;
89
+ });
90
+ return srcsetEntries.join(', ');
91
+ }
92
+ getOptimalSizes() {
93
+ if (this.observedWidth) {
94
+ const baseSizes = PIXEL_DENSITY_MULTIPLIERS.map(multiplier => Math.round(this.observedWidth * multiplier));
95
+ return baseSizes;
96
+ }
97
+ return [...DEFAULT_IMAGE_SIZES];
98
+ }
99
+ componentDidLoad() {
100
+ if ('ResizeObserver' in window) {
101
+ this.resizeObserver = new ResizeObserver(entries => {
102
+ for (const entry of entries) {
103
+ this.observedWidth = Math.round(entry.contentRect.width);
104
+ if (this.resizeObserver) {
105
+ this.resizeObserver.disconnect();
106
+ this.resizeObserver = undefined;
107
+ }
108
+ // Trigger re-render with optimized sizes
109
+ this.isLoaded = this.isLoaded;
110
+ }
111
+ });
112
+ this.resizeObserver.observe(this.hostElement);
113
+ }
114
+ }
115
+ disconnectedCallback() {
116
+ if (this.resizeObserver) {
117
+ this.resizeObserver.disconnect();
118
+ this.resizeObserver = undefined;
119
+ }
120
+ }
121
+ render() {
122
+ const sizes = this.getOptimalSizes();
123
+ if (this.hasError && this.showFallback) {
124
+ return (h("div", { class: "fallback-container" }, h("pennlibs-fallback-img", null)));
125
+ }
126
+ const imgClasses = {
127
+ 'iiif-img': true,
128
+ 'loaded': this.isLoaded
129
+ };
130
+ const defaultSize = sizes[0];
131
+ return (h("picture", null, h("source", { type: "image/webp", srcSet: this.generateSrcset(sizes, 'webp'), sizes: "100vw" }), h("img", { class: imgClasses, src: this.buildIIIFUrl(defaultSize, 'jpg'), srcSet: this.generateSrcset(sizes, 'jpg'), sizes: "100vw", alt: this.alt, loading: this.loading, onLoad: this.handleLoad, onError: this.handleError })));
132
+ }
133
+ static get is() { return "pennlibs-iiif-img"; }
134
+ static get encapsulation() { return "shadow"; }
135
+ static get originalStyleUrls() {
136
+ return {
137
+ "$": ["pennlibs-iiif-img.css"]
138
+ };
139
+ }
140
+ static get styleUrls() {
141
+ return {
142
+ "$": ["pennlibs-iiif-img.css"]
143
+ };
144
+ }
145
+ static get properties() {
146
+ return {
147
+ "uuid": {
148
+ "type": "string",
149
+ "mutable": false,
150
+ "complexType": {
151
+ "original": "string",
152
+ "resolved": "string",
153
+ "references": {}
154
+ },
155
+ "required": true,
156
+ "optional": false,
157
+ "docs": {
158
+ "tags": [],
159
+ "text": "The IIIF [UUID identifier](https://iiif.io/api/image/3.0/#3-identifier) of the image resource on the Penn Libraries IIIF server."
160
+ },
161
+ "getter": false,
162
+ "setter": false,
163
+ "reflect": false,
164
+ "attribute": "uuid"
165
+ },
166
+ "alt": {
167
+ "type": "string",
168
+ "mutable": false,
169
+ "complexType": {
170
+ "original": "string",
171
+ "resolved": "string",
172
+ "references": {}
173
+ },
174
+ "required": true,
175
+ "optional": false,
176
+ "docs": {
177
+ "tags": [],
178
+ "text": "Alternative text that describes the image content for screen readers and when images fail to load.\nUse an empty string for purely decorative images."
179
+ },
180
+ "getter": false,
181
+ "setter": false,
182
+ "reflect": false,
183
+ "attribute": "alt"
184
+ },
185
+ "region": {
186
+ "type": "string",
187
+ "mutable": false,
188
+ "complexType": {
189
+ "original": "string",
190
+ "resolved": "string",
191
+ "references": {}
192
+ },
193
+ "required": false,
194
+ "optional": true,
195
+ "docs": {
196
+ "tags": [{
197
+ "name": "default",
198
+ "text": "'full'"
199
+ }],
200
+ "text": "The IIIF [region](https://iiif.io/api/image/3.0/#41-region) of the image to display.\nDefines the rectangular portion of the underlying image to return.\n\n`full`: The full image is returned, without any cropping.\n\n`square`: A square area where width and height equal the shorter dimension.\n\n`x,y,w,h`: Absolute pixel coordinates (x, y position; w, h dimensions).\n\n`pct:x,y,w,h`: Percentage-based coordinates of full image dimensions."
201
+ },
202
+ "getter": false,
203
+ "setter": false,
204
+ "reflect": false,
205
+ "attribute": "region",
206
+ "defaultValue": "'full'"
207
+ },
208
+ "rotation": {
209
+ "type": "string",
210
+ "mutable": false,
211
+ "complexType": {
212
+ "original": "string",
213
+ "resolved": "string",
214
+ "references": {}
215
+ },
216
+ "required": false,
217
+ "optional": true,
218
+ "docs": {
219
+ "tags": [{
220
+ "name": "default",
221
+ "text": "'0'"
222
+ }],
223
+ "text": "The IIIF [rotation](https://iiif.io/api/image/3.0/#44-rotation) to apply to the image.\nSpecifies mirroring and clockwise rotation in degrees (0-360).\n\n`n`: Rotation in degrees only.\n\n`!n`: Mirror the image vertically, then rotate by n degrees."
224
+ },
225
+ "getter": false,
226
+ "setter": false,
227
+ "reflect": false,
228
+ "attribute": "rotation",
229
+ "defaultValue": "'0'"
230
+ },
231
+ "quality": {
232
+ "type": "string",
233
+ "mutable": false,
234
+ "complexType": {
235
+ "original": "string",
236
+ "resolved": "string",
237
+ "references": {}
238
+ },
239
+ "required": false,
240
+ "optional": true,
241
+ "docs": {
242
+ "tags": [{
243
+ "name": "default",
244
+ "text": "'default'"
245
+ }],
246
+ "text": "The IIIF [quality](https://iiif.io/api/image/3.0/#quality) of the image.\nControls the color delivery mode.\n\n`default`: The server's default quality.\n\n`color`: Full color information.\n\n`gray`: Grayscale rendering.\n\n`bitonal`: Black and white only."
247
+ },
248
+ "getter": false,
249
+ "setter": false,
250
+ "reflect": false,
251
+ "attribute": "quality",
252
+ "defaultValue": "'default'"
253
+ },
254
+ "loading": {
255
+ "type": "string",
256
+ "mutable": false,
257
+ "complexType": {
258
+ "original": "'lazy' | 'eager'",
259
+ "resolved": "\"eager\" | \"lazy\"",
260
+ "references": {}
261
+ },
262
+ "required": false,
263
+ "optional": true,
264
+ "docs": {
265
+ "tags": [{
266
+ "name": "default",
267
+ "text": "'lazy'"
268
+ }],
269
+ "text": "Native browser lazy loading behavior. Use \"lazy\" to defer loading until the image is near the viewport,\nor \"eager\" to load immediately."
270
+ },
271
+ "getter": false,
272
+ "setter": false,
273
+ "reflect": false,
274
+ "attribute": "loading",
275
+ "defaultValue": "'lazy'"
276
+ },
277
+ "showFallback": {
278
+ "type": "boolean",
279
+ "mutable": false,
280
+ "complexType": {
281
+ "original": "boolean",
282
+ "resolved": "boolean",
283
+ "references": {}
284
+ },
285
+ "required": false,
286
+ "optional": true,
287
+ "docs": {
288
+ "tags": [{
289
+ "name": "default",
290
+ "text": "true"
291
+ }],
292
+ "text": "Whether to display a fallback placeholder image when the IIIF image fails to load."
293
+ },
294
+ "getter": false,
295
+ "setter": false,
296
+ "reflect": false,
297
+ "attribute": "show-fallback",
298
+ "defaultValue": "true"
299
+ }
300
+ };
301
+ }
302
+ static get states() {
303
+ return {
304
+ "isLoaded": {},
305
+ "hasError": {}
306
+ };
307
+ }
308
+ static get elementRef() { return "hostElement"; }
309
+ }
310
+ //# sourceMappingURL=pennlibs-iiif-img.js.map