@penn-libraries/web 1.1.0-dev.7 → 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 (112) hide show
  1. package/dist/cjs/{index-C0A2bnrZ.js → index-C0qvW4Ra.js} +289 -191
  2. package/dist/cjs/index-C0qvW4Ra.js.map +1 -0
  3. package/dist/cjs/index.cjs.js +1 -3
  4. package/dist/cjs/loader.cjs.js +3 -6
  5. package/dist/cjs/loader.cjs.js.map +1 -1
  6. package/dist/cjs/pennlibs-autocomplete.pennlibs-footer.pennlibs-header.entry.cjs.js.map +1 -1
  7. package/dist/cjs/pennlibs-autocomplete_3.cjs.entry.js +25 -36
  8. package/dist/cjs/pennlibs-banner.cjs.entry.js +1 -3
  9. package/dist/cjs/pennlibs-chat.cjs.entry.js +1 -3
  10. package/dist/cjs/pennlibs-fallback-img.cjs.entry.js +1 -3
  11. package/dist/cjs/pennlibs-feedback.cjs.entry.js +1 -3
  12. package/dist/cjs/pennlibs-hero.cjs.entry.js +1 -3
  13. package/dist/cjs/pennlibs-iiif-img.cjs.entry.js +132 -0
  14. package/dist/cjs/pennlibs-iiif-img.entry.cjs.js.map +1 -0
  15. package/dist/cjs/web.cjs.js +4 -7
  16. package/dist/cjs/web.cjs.js.map +1 -1
  17. package/dist/collection/collection-manifest.json +4 -3
  18. package/dist/collection/components/pennlibs-autocomplete/pennlibs-autocomplete.js +40 -23
  19. package/dist/collection/components/pennlibs-autocomplete/pennlibs-autocomplete.js.map +1 -1
  20. package/dist/collection/components/pennlibs-header/pennlibs-header.js +6 -6
  21. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.css +37 -0
  22. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.js +310 -0
  23. package/dist/collection/components/pennlibs-iiif-img/pennlibs-iiif-img.js.map +1 -0
  24. package/dist/components/index.d.ts +2 -0
  25. package/dist/components/index.js +2 -1
  26. package/dist/components/index.js.map +1 -1
  27. package/dist/components/pennlibs-autocomplete.js +28 -20
  28. package/dist/components/pennlibs-autocomplete.js.map +1 -1
  29. package/dist/components/pennlibs-banner.js +5 -3
  30. package/dist/components/pennlibs-banner.js.map +1 -1
  31. package/dist/components/pennlibs-chat.js +5 -3
  32. package/dist/components/pennlibs-chat.js.map +1 -1
  33. package/dist/components/pennlibs-fallback-img.js +1 -30
  34. package/dist/components/pennlibs-fallback-img.js.map +1 -1
  35. package/dist/components/pennlibs-fallback-img2.js +37 -0
  36. package/dist/components/pennlibs-fallback-img2.js.map +1 -0
  37. package/dist/components/pennlibs-feedback.js +5 -3
  38. package/dist/components/pennlibs-feedback.js.map +1 -1
  39. package/dist/components/pennlibs-footer.js +5 -3
  40. package/dist/components/pennlibs-footer.js.map +1 -1
  41. package/dist/components/pennlibs-header.js +6 -19
  42. package/dist/components/pennlibs-header.js.map +1 -1
  43. package/dist/components/pennlibs-hero.js +5 -3
  44. package/dist/components/pennlibs-hero.js.map +1 -1
  45. package/dist/components/pennlibs-iiif-img.d.ts +11 -0
  46. package/dist/components/pennlibs-iiif-img.js +168 -0
  47. package/dist/components/pennlibs-iiif-img.js.map +1 -0
  48. package/dist/docs.json +268 -12
  49. package/dist/esm/{index-CE_ILdTo.js → index-D9dYrmUF.js} +289 -192
  50. package/dist/esm/index-D9dYrmUF.js.map +1 -0
  51. package/dist/esm/index.js +1 -3
  52. package/dist/esm/loader.js +3 -6
  53. package/dist/esm/loader.js.map +1 -1
  54. package/dist/esm/pennlibs-autocomplete.pennlibs-footer.pennlibs-header.entry.js.map +1 -1
  55. package/dist/esm/pennlibs-autocomplete_3.entry.js +25 -36
  56. package/dist/esm/pennlibs-banner.entry.js +1 -3
  57. package/dist/esm/pennlibs-chat.entry.js +1 -3
  58. package/dist/esm/pennlibs-fallback-img.entry.js +1 -3
  59. package/dist/esm/pennlibs-feedback.entry.js +1 -3
  60. package/dist/esm/pennlibs-hero.entry.js +1 -3
  61. package/dist/esm/pennlibs-iiif-img.entry.js +130 -0
  62. package/dist/esm/pennlibs-iiif-img.entry.js.map +1 -0
  63. package/dist/esm/web.js +4 -7
  64. package/dist/esm/web.js.map +1 -1
  65. package/dist/types/components/pennlibs-autocomplete/pennlibs-autocomplete.d.ts +17 -1
  66. package/dist/types/components/pennlibs-iiif-img/pennlibs-iiif-img.d.ts +89 -0
  67. package/dist/types/components.d.ts +143 -0
  68. package/dist/types/stencil-public-runtime.d.ts +73 -9
  69. package/dist/web/index.esm.js +1 -3
  70. package/dist/web/loader.esm.js.map +1 -1
  71. package/dist/web/{p-20c0bd7a.entry.js → p-43d9c2d4.entry.js} +1 -3
  72. package/dist/web/{p-CE_ILdTo.js → p-D9dYrmUF.js} +289 -192
  73. package/dist/web/p-D9dYrmUF.js.map +1 -0
  74. package/dist/web/{p-cbae5952.entry.js → p-ad92090a.entry.js} +1 -3
  75. package/dist/web/{p-370e32b1.entry.js → p-b4b58af0.entry.js} +1 -3
  76. package/dist/web/p-c4074cf1.entry.js +130 -0
  77. package/dist/web/{p-07ad051f.entry.js → p-cb2584da.entry.js} +1 -3
  78. package/dist/web/{p-5e385536.entry.js → p-ce97059c.entry.js} +1 -3
  79. package/dist/web/{p-e1215158.entry.js → p-e6188c30.entry.js} +25 -36
  80. package/dist/web/pennlibs-autocomplete.pennlibs-footer.pennlibs-header.entry.esm.js.map +1 -1
  81. package/dist/web/pennlibs-iiif-img.entry.esm.js.map +1 -0
  82. package/dist/web/web.esm.js +4 -7
  83. package/dist/web/web.esm.js.map +1 -1
  84. package/hydrate/index.d.ts +28 -24
  85. package/hydrate/index.js +609 -241
  86. package/hydrate/index.mjs +609 -241
  87. package/package.json +3 -9
  88. package/dist/cjs/app-globals-V2Kpy_OQ.js +0 -8
  89. package/dist/cjs/app-globals-V2Kpy_OQ.js.map +0 -1
  90. package/dist/cjs/index-C0A2bnrZ.js.map +0 -1
  91. package/dist/cjs/pennlibs-autocomplete_3.cjs.entry.js.map +0 -1
  92. package/dist/cjs/pennlibs-banner.cjs.entry.js.map +0 -1
  93. package/dist/cjs/pennlibs-chat.cjs.entry.js.map +0 -1
  94. package/dist/cjs/pennlibs-fallback-img.cjs.entry.js.map +0 -1
  95. package/dist/cjs/pennlibs-feedback.cjs.entry.js.map +0 -1
  96. package/dist/cjs/pennlibs-hero.cjs.entry.js.map +0 -1
  97. package/dist/collection/utils/utils.js +0 -4
  98. package/dist/collection/utils/utils.js.map +0 -1
  99. package/dist/esm/app-globals-DQuL1Twl.js +0 -6
  100. package/dist/esm/app-globals-DQuL1Twl.js.map +0 -1
  101. package/dist/esm/index-CE_ILdTo.js.map +0 -1
  102. package/dist/esm/pennlibs-autocomplete_3.entry.js.map +0 -1
  103. package/dist/types/utils/utils.d.ts +0 -1
  104. package/dist/web/p-07ad051f.entry.js.map +0 -1
  105. package/dist/web/p-20c0bd7a.entry.js.map +0 -1
  106. package/dist/web/p-370e32b1.entry.js.map +0 -1
  107. package/dist/web/p-5e385536.entry.js.map +0 -1
  108. package/dist/web/p-CE_ILdTo.js.map +0 -1
  109. package/dist/web/p-DQuL1Twl.js +0 -6
  110. package/dist/web/p-DQuL1Twl.js.map +0 -1
  111. package/dist/web/p-cbae5952.entry.js.map +0 -1
  112. package/dist/web/p-e1215158.entry.js.map +0 -1
@@ -1,4 +1,9 @@
1
1
  import { h } from "@stencil/core";
2
+ /**
3
+ * Offer predictive suggestions while typing your search query.
4
+ *
5
+ * @slot start - Content to display at the start (top).
6
+ */
2
7
  export class Autocomplete {
3
8
  constructor() {
4
9
  this.showSuggestions = false;
@@ -137,22 +142,19 @@ export class Autocomplete {
137
142
  }
138
143
  handleEscape() {
139
144
  this.reset();
140
- this.syncInputState();
141
- this.syncInputValueToSelection();
145
+ this.syncAll();
142
146
  }
143
147
  handleArrowDown() {
144
148
  if (!this.showSuggestions)
145
149
  return;
146
150
  this.moveNext();
147
- this.syncInputState();
148
- this.syncInputValueToSelection();
151
+ this.syncAll();
149
152
  }
150
153
  handleArrowUp() {
151
154
  if (!this.showSuggestions)
152
155
  return;
153
156
  this.movePrevious();
154
- this.syncInputState();
155
- this.syncInputValueToSelection();
157
+ this.syncAll();
156
158
  }
157
159
  handleEnter() {
158
160
  if (this.canSelect())
@@ -174,16 +176,22 @@ export class Autocomplete {
174
176
  if (this.currentIndex < 0)
175
177
  return;
176
178
  const selectedOption = this.options[this.currentIndex];
179
+ this.applySelection(selectedOption);
180
+ this.emitActivation(selectedOption);
181
+ this.closeSuggestions();
182
+ }
183
+ applySelection(option) {
177
184
  const input = this.getInput();
178
- if (input) {
179
- input.value = selectedOption.value;
180
- this.originalValue = selectedOption.value;
181
- }
185
+ if (!input)
186
+ return;
187
+ input.value = option.value;
188
+ this.originalValue = option.value;
189
+ }
190
+ emitActivation(option) {
182
191
  this.activated.emit({
183
192
  index: this.currentIndex,
184
- value: selectedOption.value
193
+ value: option.value
185
194
  });
186
- this.closeSuggestions();
187
195
  }
188
196
  openSuggestions() {
189
197
  if (!this.isInputFocused())
@@ -195,12 +203,11 @@ export class Autocomplete {
195
203
  }
196
204
  showSuggestionsPanel() {
197
205
  this.showSuggestions = true;
198
- this.syncInputState();
206
+ this.syncAriaAttributes();
199
207
  }
200
208
  closeSuggestions() {
201
209
  this.reset();
202
- this.syncInputState();
203
- this.syncInputValueToSelection();
210
+ this.syncAll();
204
211
  }
205
212
  deferCloseSuggestions() {
206
213
  if (this.blurTimeout)
@@ -225,7 +232,7 @@ export class Autocomplete {
225
232
  const active = document.activeElement;
226
233
  return !((_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(active)) && active !== this.getInput();
227
234
  }
228
- syncInputState() {
235
+ syncAriaAttributes() {
229
236
  const input = this.getInput();
230
237
  if (!input)
231
238
  return;
@@ -240,6 +247,10 @@ export class Autocomplete {
240
247
  ? this.options[this.currentIndex].value
241
248
  : this.originalValue;
242
249
  }
250
+ syncAll() {
251
+ this.syncAriaAttributes();
252
+ this.syncInputValueToSelection();
253
+ }
243
254
  updateAriaExpanded(input) {
244
255
  input.setAttribute('aria-expanded', this.showSuggestions.toString());
245
256
  }
@@ -258,7 +269,7 @@ export class Autocomplete {
258
269
  return this.showSuggestions && this.options.length > 0 && this.isInputFocused();
259
270
  }
260
271
  render() {
261
- return (h("div", { key: 'd7c11d48e354937eedc76f608ef3b3b8762ddb4b' }, h("slot", { key: 'a3c65cf477aa9f575c3310e364c1dd2a5dca9e57', name: "start" }), this.shouldShowListbox() && (h("ol", { key: 'dc035d276f14da3ad0246924c345bc86ff5c68f9', role: "listbox", id: "listbox" }, this.options.map((option, index) => (h("li", { role: "option", id: option.id, "aria-selected": this.currentIndex === index ? 'true' : 'false', tabindex: "-1", onClick: () => this.handleOptionClick(index), innerHTML: option.html })))))));
272
+ return (h("div", { key: 'ee4ead1b5bec44a561901a29d3d5e119da0d09f8' }, h("slot", { key: '7d7128ab7968544ab25125d30a648136f8d8a566', name: "start" }), this.shouldShowListbox() && (h("ol", { key: '95d45d524847f83b646853a8d919bc88f471f150', role: "listbox", id: "listbox" }, this.options.map((option, index) => (h("li", { role: "option", id: option.id, "aria-selected": this.currentIndex === index ? 'true' : 'false', tabindex: "-1", onClick: () => this.handleOptionClick(index), innerHTML: option.html })))))));
262
273
  }
263
274
  static get is() { return "pennlibs-autocomplete"; }
264
275
  static get encapsulation() { return "shadow"; }
@@ -276,7 +287,6 @@ export class Autocomplete {
276
287
  return {
277
288
  "for": {
278
289
  "type": "string",
279
- "attribute": "for",
280
290
  "mutable": false,
281
291
  "complexType": {
282
292
  "original": "string",
@@ -286,12 +296,16 @@ export class Autocomplete {
286
296
  "required": false,
287
297
  "optional": true,
288
298
  "docs": {
289
- "tags": [],
290
- "text": ""
299
+ "tags": [{
300
+ "name": "prop",
301
+ "text": "for"
302
+ }],
303
+ "text": "The `id` of the input that this autocomplete is attached to."
291
304
  },
292
305
  "getter": false,
293
306
  "setter": false,
294
- "reflect": false
307
+ "reflect": false,
308
+ "attribute": "for"
295
309
  }
296
310
  };
297
311
  }
@@ -311,8 +325,11 @@ export class Autocomplete {
311
325
  "cancelable": true,
312
326
  "composed": true,
313
327
  "docs": {
314
- "tags": [],
315
- "text": ""
328
+ "tags": [{
329
+ "name": "event",
330
+ "text": "pl:activated"
331
+ }],
332
+ "text": "Emitted when a user activates (selects) an autocomplete suggestion."
316
333
  },
317
334
  "complexType": {
318
335
  "original": "ActivatedEvent",
@@ -1 +1 @@
1
- {"version":3,"file":"pennlibs-autocomplete.js","sourceRoot":"","sources":["../../../src/components/pennlibs-autocomplete/pennlibs-autocomplete.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, h, Element, State, Listen, Event, EventEmitter, Prop } from '@stencil/core';\n\ninterface ListboxOption {\n id: string;\n html: string;\n value: string;\n}\n\nexport interface ActivatedEvent {\n value: string;\n index: number;\n}\n\n@Component({\n tag: 'pennlibs-autocomplete',\n styleUrl: 'pennlibs-autocomplete.css',\n shadow: true\n})\nexport class Autocomplete {\n @Element() el!: HTMLElement;\n\n @Prop() for?: string;\n\n @State() showSuggestions: boolean = false;\n @State() currentIndex: number = -1;\n @State() originalValue: string = '';\n @State() options: ListboxOption[] = [];\n\n @Event({ eventName: 'pl:activated' }) activated: EventEmitter<ActivatedEvent>;\n\n private blurTimeout: number;\n private mutationObserver: MutationObserver;\n private readonly keyHandlerMap = {\n 'Escape': () => this.handleEscape(),\n 'ArrowDown': () => this.handleArrowDown(),\n 'ArrowUp': () => this.handleArrowUp(),\n 'Enter': () => this.handleEnter()\n };\n\n componentWillLoad() {\n this.options = this.parseOptionsFromDOM();\n }\n\n componentDidLoad() {\n this.setupInput();\n this.setupMutationObserver();\n }\n\n disconnectedCallback() {\n if (this.blurTimeout) clearTimeout(this.blurTimeout);\n if (this.mutationObserver) this.mutationObserver.disconnect();\n }\n\n private setupMutationObserver(): void {\n this.mutationObserver = new MutationObserver(() => {\n this.options = this.parseOptionsFromDOM();\n });\n\n this.mutationObserver.observe(this.el, {\n childList: true,\n subtree: true,\n characterData: true\n });\n }\n\n private parseOptionsFromDOM(): ListboxOption[] {\n const listbox = this.findListbox();\n if (!listbox) return [];\n\n const elements = Array.from(listbox.querySelectorAll('[role=\"option\"]')) as HTMLElement[];\n return elements.map((el, i) => this.createOption(el, i));\n }\n\n private findListbox(): HTMLOListElement | null {\n return Array.from(this.el.children).find(child =>\n child.matches('ol[role=\"listbox\"]')\n ) as HTMLOListElement;\n }\n\n private createOption(element: HTMLElement, index: number): ListboxOption {\n return {\n id: element.id || `option-${index}`,\n html: element.innerHTML,\n value: element.getAttribute('data-pl-value') || element.textContent?.trim() || ''\n };\n }\n\n private getInput(): HTMLInputElement | null {\n if (!this.for) return null;\n\n const slot = this.el.shadowRoot?.querySelector('slot[name=\"start\"]') as HTMLSlotElement;\n if (!slot) return null;\n\n const assignedElements = slot.assignedElements();\n if (assignedElements.length === 0) return null;\n\n const input = assignedElements[0].querySelector(`input#${this.for}`) as HTMLInputElement;\n return input || null;\n }\n\n private isInputFocused(): boolean {\n return this.getInput() === document.activeElement;\n }\n\n private isTrackedInput(input: HTMLInputElement): boolean {\n return input?.getAttribute('data-pl-autocomplete-connected') === 'true' &&\n this.el.contains(input);\n }\n\n private setupInput(): void {\n if (!this.for) {\n console.warn('<pennlibs-autocomplete> Missing \"for\" attribute. Please add for=\"input-id\" to specify which input to attach to.');\n return;\n }\n\n const input = this.getInput();\n if (!input) {\n console.warn(`<pennlibs-autocomplete> No input element found with id=\"${this.for}\". Ensure an input with this id exists.`);\n return;\n }\n\n this.setComboboxAttributes(input);\n input.setAttribute('data-pl-autocomplete-connected', 'true');\n }\n\n private setComboboxAttributes(input: HTMLInputElement): void {\n input.setAttribute('role', 'combobox');\n input.setAttribute('aria-autocomplete', 'list');\n input.setAttribute('aria-expanded', 'false');\n input.setAttribute('aria-controls', 'listbox');\n }\n\n @Listen('slotchange')\n handleSlotChange() {\n this.options = this.parseOptionsFromDOM();\n this.setupInput();\n }\n\n @Listen('input', { target: 'body' })\n handleInputEvent(event: Event) {\n const input = event.target as HTMLInputElement;\n if (this.isTrackedInput(input)) {\n this.currentIndex = -1;\n this.openSuggestions();\n }\n }\n\n @Listen('focus', { target: 'body' })\n handleFocusEvent(event: Event) {\n const input = event.target as HTMLInputElement;\n if (this.isTrackedInput(input)) this.showSuggestionsPanel();\n }\n\n @Listen('blur', { target: 'body' })\n handleBlurEvent(event: Event) {\n const input = event.target as HTMLInputElement;\n if (this.isTrackedInput(input)) this.deferCloseSuggestions();\n }\n\n @Listen('focusout')\n handleFocusOut(event: FocusEvent) {\n if (this.isFocusLeavingComponent(event)) this.closeSuggestions();\n }\n\n @Listen('keydown', { target: 'document' })\n handleKeyDown(event: KeyboardEvent) {\n if (!this.isInputFocused()) return;\n if (event.metaKey || event.ctrlKey) return; // Ignore keyboard shortcuts\n\n const handler = this.keyHandlerMap[event.key];\n if (handler) {\n // Don't prevent default for Enter - allow form submission\n if (event.key !== 'Enter') {\n event.preventDefault();\n }\n handler();\n }\n }\n\n private handleEscape() {\n this.reset();\n this.syncInputState();\n this.syncInputValueToSelection();\n }\n\n private handleArrowDown() {\n if (!this.showSuggestions) return;\n this.moveNext();\n this.syncInputState();\n this.syncInputValueToSelection();\n }\n\n private handleArrowUp() {\n if (!this.showSuggestions) return;\n this.movePrevious();\n this.syncInputState();\n this.syncInputValueToSelection();\n }\n\n private handleEnter() {\n if (this.canSelect()) this.selectCurrent();\n }\n\n // Navigation\n private moveNext(): void {\n const lastIndex = this.options.length - 1;\n this.currentIndex = this.currentIndex < lastIndex ? this.currentIndex + 1 : -1;\n }\n\n private movePrevious(): void {\n const lastIndex = this.options.length - 1;\n this.currentIndex = this.currentIndex > -1 ? this.currentIndex - 1 : lastIndex;\n }\n\n private canSelect(): boolean {\n return this.showSuggestions && this.currentIndex >= 0;\n }\n\n private selectCurrent(): void {\n if (this.currentIndex < 0) return;\n\n const selectedOption = this.options[this.currentIndex];\n const input = this.getInput();\n\n if (input) {\n input.value = selectedOption.value;\n this.originalValue = selectedOption.value;\n }\n\n this.activated.emit({\n index: this.currentIndex,\n value: selectedOption.value\n });\n\n this.closeSuggestions();\n }\n\n private handleOptionClick = (index: number): void => {\n this.currentIndex = index;\n this.selectCurrent();\n }\n\n private openSuggestions(): void {\n if (!this.isInputFocused()) return;\n\n const input = this.getInput();\n if (input) this.originalValue = input.value;\n\n this.showSuggestionsPanel();\n }\n\n private showSuggestionsPanel(): void {\n this.showSuggestions = true;\n this.syncInputState();\n }\n\n private closeSuggestions(): void {\n this.reset();\n this.syncInputState();\n this.syncInputValueToSelection();\n }\n\n private deferCloseSuggestions(): void {\n if (this.blurTimeout) clearTimeout(this.blurTimeout);\n this.blurTimeout = setTimeout(() => {\n if (this.isFocusOutsideComponent()) this.closeSuggestions();\n }, 150) as unknown as number;\n }\n\n private reset(): void {\n this.showSuggestions = false;\n this.currentIndex = -1;\n }\n\n private isFocusLeavingComponent(event: FocusEvent): boolean {\n const target = event.relatedTarget as HTMLElement;\n return !target ||\n (!this.el.contains(target) && !this.el.shadowRoot?.contains(target));\n }\n\n private isFocusOutsideComponent(): boolean {\n const active = document.activeElement;\n return !this.el.shadowRoot?.contains(active) && active !== this.getInput();\n }\n\n private syncInputState(): void {\n const input = this.getInput();\n if (!input) return;\n\n this.updateAriaExpanded(input);\n this.updateAriaActiveDescendant(input);\n }\n\n private syncInputValueToSelection(): void {\n const input = this.getInput();\n if (!input) return;\n\n input.value = this.currentIndex >= 0 && this.currentIndex < this.options.length\n ? this.options[this.currentIndex].value\n : this.originalValue;\n }\n\n private updateAriaExpanded(input: HTMLInputElement): void {\n input.setAttribute('aria-expanded', this.showSuggestions.toString());\n }\n\n private updateAriaActiveDescendant(input: HTMLInputElement): void {\n const hasSelection = this.showSuggestions &&\n this.currentIndex >= 0 &&\n this.currentIndex < this.options.length;\n if (hasSelection) {\n input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);\n } else {\n input.removeAttribute('aria-activedescendant');\n }\n }\n\n private shouldShowListbox(): boolean {\n return this.showSuggestions && this.options.length > 0 && this.isInputFocused();\n }\n\n render() {\n return (\n <div>\n <slot name=\"start\" />\n {this.shouldShowListbox() && (\n <ol role=\"listbox\" id=\"listbox\">\n {this.options.map((option, index) => (\n <li\n role=\"option\"\n id={option.id}\n aria-selected={this.currentIndex === index ? 'true' : 'false'}\n tabindex=\"-1\"\n onClick={() => this.handleOptionClick(index)}\n innerHTML={option.html}\n />\n ))}\n </ol>\n )}\n </div>\n )\n }\n}\n"]}
1
+ {"version":3,"file":"pennlibs-autocomplete.js","sourceRoot":"","sources":["../../../src/components/pennlibs-autocomplete/pennlibs-autocomplete.tsx"],"names":[],"mappings":"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","sourcesContent":["import { Component, h, Element, State, Listen, Event, EventEmitter, Prop } from '@stencil/core';\n\ninterface ListboxOption {\n id: string;\n html: string;\n value: string;\n}\n\nexport interface ActivatedEvent {\n value: string;\n index: number;\n}\n\n/**\n * Offer predictive suggestions while typing your search query.\n *\n * @slot start - Content to display at the start (top).\n */\n@Component({\n tag: 'pennlibs-autocomplete',\n styleUrl: 'pennlibs-autocomplete.css',\n shadow: true\n})\nexport class Autocomplete {\n @Element() el!: HTMLElement;\n\n /**\n * The `id` of the input that this autocomplete is attached to.\n * @prop for\n */\n @Prop() for?: string;\n\n @State() showSuggestions: boolean = false;\n @State() currentIndex: number = -1;\n @State() originalValue: string = '';\n @State() options: ListboxOption[] = [];\n\n /**\n * Emitted when a user activates (selects) an autocomplete suggestion.\n * @event pl:activated\n */\n @Event({ eventName: 'pl:activated' }) activated: EventEmitter<ActivatedEvent>;\n\n private blurTimeout: number;\n private mutationObserver: MutationObserver;\n private readonly keyHandlerMap = {\n 'Escape': () => this.handleEscape(),\n 'ArrowDown': () => this.handleArrowDown(),\n 'ArrowUp': () => this.handleArrowUp(),\n 'Enter': () => this.handleEnter()\n };\n\n componentWillLoad() {\n this.options = this.parseOptionsFromDOM();\n }\n\n componentDidLoad() {\n this.setupInput();\n this.setupMutationObserver();\n }\n\n disconnectedCallback() {\n if (this.blurTimeout) clearTimeout(this.blurTimeout);\n if (this.mutationObserver) this.mutationObserver.disconnect();\n }\n\n private setupMutationObserver(): void {\n this.mutationObserver = new MutationObserver(() => {\n this.options = this.parseOptionsFromDOM();\n });\n\n this.mutationObserver.observe(this.el, {\n childList: true,\n subtree: true,\n characterData: true\n });\n }\n\n private parseOptionsFromDOM(): ListboxOption[] {\n const listbox = this.findListbox();\n if (!listbox) return [];\n\n const elements = Array.from(listbox.querySelectorAll('[role=\"option\"]')) as HTMLElement[];\n return elements.map((el, i) => this.createOption(el, i));\n }\n\n private findListbox(): HTMLOListElement | null {\n return Array.from(this.el.children).find(child =>\n child.matches('ol[role=\"listbox\"]')\n ) as HTMLOListElement;\n }\n\n private createOption(element: HTMLElement, index: number): ListboxOption {\n return {\n id: element.id || `option-${index}`,\n html: element.innerHTML,\n value: element.getAttribute('data-pl-value') || element.textContent?.trim() || ''\n };\n }\n\n private getInput(): HTMLInputElement | null {\n if (!this.for) return null;\n\n const slot = this.el.shadowRoot?.querySelector('slot[name=\"start\"]') as HTMLSlotElement;\n if (!slot) return null;\n\n const assignedElements = slot.assignedElements();\n if (assignedElements.length === 0) return null;\n\n const input = assignedElements[0].querySelector(`input#${this.for}`) as HTMLInputElement;\n return input || null;\n }\n\n private isInputFocused(): boolean {\n return this.getInput() === document.activeElement;\n }\n\n private isTrackedInput(input: HTMLInputElement): boolean {\n return input?.getAttribute('data-pl-autocomplete-connected') === 'true' &&\n this.el.contains(input);\n }\n\n private setupInput(): void {\n if (!this.for) {\n console.warn('<pennlibs-autocomplete> Missing \"for\" attribute. Please add for=\"input-id\" to specify which input to attach to.');\n return;\n }\n\n const input = this.getInput();\n if (!input) {\n console.warn(`<pennlibs-autocomplete> No input element found with id=\"${this.for}\". Ensure an input with this id exists.`);\n return;\n }\n\n this.setComboboxAttributes(input);\n input.setAttribute('data-pl-autocomplete-connected', 'true');\n }\n\n private setComboboxAttributes(input: HTMLInputElement): void {\n input.setAttribute('role', 'combobox');\n input.setAttribute('aria-autocomplete', 'list');\n input.setAttribute('aria-expanded', 'false');\n input.setAttribute('aria-controls', 'listbox');\n }\n\n @Listen('slotchange')\n handleSlotChange() {\n this.options = this.parseOptionsFromDOM();\n this.setupInput();\n }\n\n @Listen('input', { target: 'body' })\n handleInputEvent(event: Event) {\n const input = event.target as HTMLInputElement;\n if (this.isTrackedInput(input)) {\n this.currentIndex = -1;\n this.openSuggestions();\n }\n }\n\n @Listen('focus', { target: 'body' })\n handleFocusEvent(event: Event) {\n const input = event.target as HTMLInputElement;\n if (this.isTrackedInput(input)) this.showSuggestionsPanel();\n }\n\n @Listen('blur', { target: 'body' })\n handleBlurEvent(event: Event) {\n const input = event.target as HTMLInputElement;\n if (this.isTrackedInput(input)) this.deferCloseSuggestions();\n }\n\n @Listen('focusout')\n handleFocusOut(event: FocusEvent) {\n if (this.isFocusLeavingComponent(event)) this.closeSuggestions();\n }\n\n @Listen('keydown', { target: 'document' })\n handleKeyDown(event: KeyboardEvent) {\n if (!this.isInputFocused()) return;\n if (event.metaKey || event.ctrlKey) return; // Ignore keyboard shortcuts\n\n const handler = this.keyHandlerMap[event.key];\n if (handler) {\n // Don't prevent default for Enter - allow form submission\n if (event.key !== 'Enter') {\n event.preventDefault();\n }\n handler();\n }\n }\n\n private handleEscape() {\n this.reset();\n this.syncAll();\n }\n\n private handleArrowDown() {\n if (!this.showSuggestions) return;\n this.moveNext();\n this.syncAll();\n }\n\n private handleArrowUp() {\n if (!this.showSuggestions) return;\n this.movePrevious();\n this.syncAll();\n }\n\n private handleEnter() {\n if (this.canSelect()) this.selectCurrent();\n }\n\n // Navigation\n private moveNext(): void {\n const lastIndex = this.options.length - 1;\n this.currentIndex = this.currentIndex < lastIndex ? this.currentIndex + 1 : -1;\n }\n\n private movePrevious(): void {\n const lastIndex = this.options.length - 1;\n this.currentIndex = this.currentIndex > -1 ? this.currentIndex - 1 : lastIndex;\n }\n\n private canSelect(): boolean {\n return this.showSuggestions && this.currentIndex >= 0;\n }\n\n private selectCurrent(): void {\n if (this.currentIndex < 0) return;\n\n const selectedOption = this.options[this.currentIndex];\n this.applySelection(selectedOption);\n this.emitActivation(selectedOption);\n this.closeSuggestions();\n }\n\n private applySelection(option: ListboxOption): void {\n const input = this.getInput();\n if (!input) return;\n\n input.value = option.value;\n this.originalValue = option.value;\n }\n\n private emitActivation(option: ListboxOption): void {\n this.activated.emit({\n index: this.currentIndex,\n value: option.value\n });\n }\n\n private handleOptionClick = (index: number): void => {\n this.currentIndex = index;\n this.selectCurrent();\n }\n\n private openSuggestions(): void {\n if (!this.isInputFocused()) return;\n\n const input = this.getInput();\n if (input) this.originalValue = input.value;\n\n this.showSuggestionsPanel();\n }\n\n private showSuggestionsPanel(): void {\n this.showSuggestions = true;\n this.syncAriaAttributes();\n }\n\n private closeSuggestions(): void {\n this.reset();\n this.syncAll();\n }\n\n private deferCloseSuggestions(): void {\n if (this.blurTimeout) clearTimeout(this.blurTimeout);\n this.blurTimeout = setTimeout(() => {\n if (this.isFocusOutsideComponent()) this.closeSuggestions();\n }, 150) as unknown as number;\n }\n\n private reset(): void {\n this.showSuggestions = false;\n this.currentIndex = -1;\n }\n\n private isFocusLeavingComponent(event: FocusEvent): boolean {\n const target = event.relatedTarget as HTMLElement;\n return !target ||\n (!this.el.contains(target) && !this.el.shadowRoot?.contains(target));\n }\n\n private isFocusOutsideComponent(): boolean {\n const active = document.activeElement;\n return !this.el.shadowRoot?.contains(active) && active !== this.getInput();\n }\n\n private syncAriaAttributes(): void {\n const input = this.getInput();\n if (!input) return;\n\n this.updateAriaExpanded(input);\n this.updateAriaActiveDescendant(input);\n }\n\n private syncInputValueToSelection(): void {\n const input = this.getInput();\n if (!input) return;\n\n input.value = this.currentIndex >= 0 && this.currentIndex < this.options.length\n ? this.options[this.currentIndex].value\n : this.originalValue;\n }\n\n private syncAll(): void {\n this.syncAriaAttributes();\n this.syncInputValueToSelection();\n }\n\n private updateAriaExpanded(input: HTMLInputElement): void {\n input.setAttribute('aria-expanded', this.showSuggestions.toString());\n }\n\n private updateAriaActiveDescendant(input: HTMLInputElement): void {\n const hasSelection = this.showSuggestions &&\n this.currentIndex >= 0 &&\n this.currentIndex < this.options.length;\n if (hasSelection) {\n input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);\n } else {\n input.removeAttribute('aria-activedescendant');\n }\n }\n\n private shouldShowListbox(): boolean {\n return this.showSuggestions && this.options.length > 0 && this.isInputFocused();\n }\n\n render() {\n return (\n <div>\n <slot name=\"start\" />\n {this.shouldShowListbox() && (\n <ol role=\"listbox\" id=\"listbox\">\n {this.options.map((option, index) => (\n <li\n role=\"option\"\n id={option.id}\n aria-selected={this.currentIndex === index ? 'true' : 'false'}\n tabindex=\"-1\"\n onClick={() => this.handleOptionClick(index)}\n innerHTML={option.html}\n />\n ))}\n </ol>\n )}\n </div>\n )\n }\n}\n"]}
@@ -82,7 +82,6 @@ export class Header {
82
82
  return {
83
83
  "serviceName": {
84
84
  "type": "string",
85
- "attribute": "service-name",
86
85
  "mutable": false,
87
86
  "complexType": {
88
87
  "original": "string",
@@ -97,11 +96,11 @@ export class Header {
97
96
  },
98
97
  "getter": false,
99
98
  "setter": false,
100
- "reflect": false
99
+ "reflect": false,
100
+ "attribute": "service-name"
101
101
  },
102
102
  "serviceLede": {
103
103
  "type": "string",
104
- "attribute": "service-lede",
105
104
  "mutable": false,
106
105
  "complexType": {
107
106
  "original": "string",
@@ -116,11 +115,11 @@ export class Header {
116
115
  },
117
116
  "getter": false,
118
117
  "setter": false,
119
- "reflect": false
118
+ "reflect": false,
119
+ "attribute": "service-lede"
120
120
  },
121
121
  "serviceHref": {
122
122
  "type": "string",
123
- "attribute": "service-href",
124
123
  "mutable": false,
125
124
  "complexType": {
126
125
  "original": "string",
@@ -139,11 +138,11 @@ export class Header {
139
138
  "getter": false,
140
139
  "setter": false,
141
140
  "reflect": false,
141
+ "attribute": "service-href",
142
142
  "defaultValue": "\"/\""
143
143
  },
144
144
  "theme": {
145
145
  "type": "string",
146
- "attribute": "theme",
147
146
  "mutable": false,
148
147
  "complexType": {
149
148
  "original": "'light' | 'dark'",
@@ -162,6 +161,7 @@ export class Header {
162
161
  "getter": false,
163
162
  "setter": false,
164
163
  "reflect": false,
164
+ "attribute": "theme",
165
165
  "defaultValue": "'light'"
166
166
  }
167
167
  };
@@ -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