@penn-libraries/web 1.2.0-dev.2 → 1.2.1-dev.0

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 (57) hide show
  1. package/dist/cjs/{index-Ag6DtE87.js → index-C2lJ0X_b.js} +64 -2
  2. package/dist/cjs/index-C2lJ0X_b.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.cjs.entry.js +281 -0
  6. package/dist/cjs/pennlibs-autocomplete.entry.cjs.js.map +1 -0
  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 +1 -1
  10. package/dist/cjs/pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.cjs.js.map +1 -0
  11. package/dist/cjs/{pennlibs-autocomplete_4.cjs.entry.js → pennlibs-feedback_3.cjs.entry.js} +29 -299
  12. package/dist/cjs/pennlibs-hero.cjs.entry.js +1 -1
  13. package/dist/cjs/pennlibs-iiif-img.cjs.entry.js +1 -1
  14. package/dist/cjs/web.cjs.js +2 -2
  15. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.css +22 -1
  16. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.js +29 -23
  17. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.js.map +1 -1
  18. package/dist/components/pennlibs-feedback.js +30 -25
  19. package/dist/components/pennlibs-feedback.js.map +1 -1
  20. package/dist/docs.json +2 -2
  21. package/dist/{web/p-CNQ9-Wwh.js → esm/index-kmszRyIe.js} +65 -3
  22. package/dist/esm/index-kmszRyIe.js.map +1 -0
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/loader.js +3 -3
  25. package/dist/esm/pennlibs-autocomplete.entry.js +279 -0
  26. package/dist/esm/pennlibs-autocomplete.entry.js.map +1 -0
  27. package/dist/esm/pennlibs-banner.entry.js +1 -1
  28. package/dist/esm/pennlibs-chat.entry.js +1 -1
  29. package/dist/esm/pennlibs-fallback-img.entry.js +1 -1
  30. package/dist/esm/pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.js.map +1 -0
  31. package/dist/esm/{pennlibs-autocomplete_4.entry.js → pennlibs-feedback_3.entry.js} +30 -299
  32. package/dist/esm/pennlibs-hero.entry.js +1 -1
  33. package/dist/esm/pennlibs-iiif-img.entry.js +1 -1
  34. package/dist/esm/web.js +3 -3
  35. package/dist/types/components/pennlibs-feedback/pennlibs-feedback.d.ts +2 -2
  36. package/dist/web/index.esm.js +1 -1
  37. package/dist/web/p-1c0dd1ec.entry.js +279 -0
  38. package/dist/web/{p-3da26275.entry.js → p-37644cf5.entry.js} +30 -299
  39. package/dist/web/{p-662e4125.entry.js → p-51bc70b8.entry.js} +1 -1
  40. package/dist/web/{p-eb633f4d.entry.js → p-906263a3.entry.js} +1 -1
  41. package/dist/web/{p-c96a3643.entry.js → p-badb334a.entry.js} +1 -1
  42. package/dist/web/{p-b6e686d1.entry.js → p-d392e98a.entry.js} +1 -1
  43. package/dist/web/{p-a34e1e3e.entry.js → p-e1947e84.entry.js} +1 -1
  44. package/dist/{esm/index-CNQ9-Wwh.js → web/p-kmszRyIe.js} +65 -3
  45. package/dist/web/p-kmszRyIe.js.map +1 -0
  46. package/dist/web/pennlibs-autocomplete.entry.esm.js.map +1 -0
  47. package/dist/web/pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.esm.js.map +1 -0
  48. package/dist/web/web.esm.js +3 -3
  49. package/hydrate/index.js +59 -24
  50. package/hydrate/index.mjs +59 -24
  51. package/package.json +1 -1
  52. package/dist/cjs/index-Ag6DtE87.js.map +0 -1
  53. package/dist/cjs/pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.cjs.js.map +0 -1
  54. package/dist/esm/index-CNQ9-Wwh.js.map +0 -1
  55. package/dist/esm/pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.js.map +0 -1
  56. package/dist/web/p-CNQ9-Wwh.js.map +0 -1
  57. package/dist/web/pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.esm.js.map +0 -1
@@ -0,0 +1,279 @@
1
+ import { r as registerInstance, c as createEvent, h, a as getElement } from './p-kmszRyIe.js';
2
+
3
+ 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}";
4
+
5
+ const Autocomplete = class {
6
+ constructor(hostRef) {
7
+ registerInstance(this, hostRef);
8
+ this.activated = createEvent(this, "pl:activated");
9
+ this.showSuggestions = false;
10
+ this.currentIndex = -1;
11
+ this.originalValue = '';
12
+ this.options = [];
13
+ this.keyHandlerMap = {
14
+ 'Escape': () => this.handleEscape(),
15
+ 'ArrowDown': () => this.handleArrowDown(),
16
+ 'ArrowUp': () => this.handleArrowUp(),
17
+ 'Enter': () => this.handleEnter()
18
+ };
19
+ this.handleOptionClick = (index) => {
20
+ this.currentIndex = index;
21
+ this.selectCurrent();
22
+ };
23
+ }
24
+ componentWillLoad() {
25
+ this.options = this.parseOptionsFromDOM();
26
+ }
27
+ componentDidLoad() {
28
+ this.setupInput();
29
+ this.setupMutationObserver();
30
+ }
31
+ disconnectedCallback() {
32
+ if (this.blurTimeout)
33
+ clearTimeout(this.blurTimeout);
34
+ if (this.mutationObserver)
35
+ this.mutationObserver.disconnect();
36
+ }
37
+ setupMutationObserver() {
38
+ this.mutationObserver = new MutationObserver(() => {
39
+ this.options = this.parseOptionsFromDOM();
40
+ });
41
+ this.mutationObserver.observe(this.el, {
42
+ childList: true,
43
+ subtree: true,
44
+ characterData: true
45
+ });
46
+ }
47
+ parseOptionsFromDOM() {
48
+ const listbox = this.findListbox();
49
+ if (!listbox)
50
+ return [];
51
+ const elements = Array.from(listbox.querySelectorAll('[role="option"]'));
52
+ return elements.map((el, i) => this.createOption(el, i));
53
+ }
54
+ findListbox() {
55
+ return Array.from(this.el.children).find(child => child.matches('ol[role="listbox"]'));
56
+ }
57
+ createOption(element, index) {
58
+ var _a;
59
+ return {
60
+ id: element.id || `option-${index}`,
61
+ html: element.innerHTML,
62
+ value: element.getAttribute('data-pl-value') || ((_a = element.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || ''
63
+ };
64
+ }
65
+ getInput() {
66
+ var _a;
67
+ if (!this.for)
68
+ return null;
69
+ const slot = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="start"]');
70
+ if (!slot)
71
+ return null;
72
+ const assignedElements = slot.assignedElements();
73
+ if (assignedElements.length === 0)
74
+ return null;
75
+ const input = assignedElements[0].querySelector(`input#${this.for}`);
76
+ return input || null;
77
+ }
78
+ isInputFocused() {
79
+ return this.getInput() === document.activeElement;
80
+ }
81
+ isTrackedInput(input) {
82
+ return (input === null || input === void 0 ? void 0 : input.getAttribute('data-pl-autocomplete-connected')) === 'true' &&
83
+ this.el.contains(input);
84
+ }
85
+ setupInput() {
86
+ if (!this.for) {
87
+ console.warn('<pennlibs-autocomplete> Missing "for" attribute. Please add for="input-id" to specify which input to attach to.');
88
+ return;
89
+ }
90
+ const input = this.getInput();
91
+ if (!input) {
92
+ console.warn(`<pennlibs-autocomplete> No input element found with id="${this.for}". Ensure an input with this id exists.`);
93
+ return;
94
+ }
95
+ this.setComboboxAttributes(input);
96
+ input.setAttribute('data-pl-autocomplete-connected', 'true');
97
+ }
98
+ setComboboxAttributes(input) {
99
+ input.setAttribute('role', 'combobox');
100
+ input.setAttribute('aria-autocomplete', 'list');
101
+ input.setAttribute('aria-expanded', 'false');
102
+ input.setAttribute('aria-controls', 'listbox');
103
+ }
104
+ handleSlotChange() {
105
+ this.options = this.parseOptionsFromDOM();
106
+ this.setupInput();
107
+ }
108
+ handleInputEvent(event) {
109
+ const input = event.target;
110
+ if (this.isTrackedInput(input)) {
111
+ this.currentIndex = -1;
112
+ this.openSuggestions();
113
+ }
114
+ }
115
+ handleFocusEvent(event) {
116
+ const input = event.target;
117
+ if (this.isTrackedInput(input))
118
+ this.showSuggestionsPanel();
119
+ }
120
+ handleBlurEvent(event) {
121
+ const input = event.target;
122
+ if (this.isTrackedInput(input))
123
+ this.deferCloseSuggestions();
124
+ }
125
+ handleFocusOut(event) {
126
+ if (this.isFocusLeavingComponent(event))
127
+ this.closeSuggestions();
128
+ }
129
+ handleKeyDown(event) {
130
+ if (!this.isInputFocused())
131
+ return;
132
+ if (event.metaKey || event.ctrlKey)
133
+ return; // Ignore keyboard shortcuts
134
+ const handler = this.keyHandlerMap[event.key];
135
+ if (handler) {
136
+ // Don't prevent default for Enter - allow form submission
137
+ if (event.key !== 'Enter') {
138
+ event.preventDefault();
139
+ }
140
+ handler();
141
+ }
142
+ }
143
+ handleEscape() {
144
+ this.reset();
145
+ this.syncAll();
146
+ }
147
+ handleArrowDown() {
148
+ if (!this.showSuggestions)
149
+ return;
150
+ this.moveNext();
151
+ this.syncAll();
152
+ }
153
+ handleArrowUp() {
154
+ if (!this.showSuggestions)
155
+ return;
156
+ this.movePrevious();
157
+ this.syncAll();
158
+ }
159
+ handleEnter() {
160
+ if (this.canSelect())
161
+ this.selectCurrent();
162
+ }
163
+ // Navigation
164
+ moveNext() {
165
+ const lastIndex = this.options.length - 1;
166
+ this.currentIndex = this.currentIndex < lastIndex ? this.currentIndex + 1 : -1;
167
+ }
168
+ movePrevious() {
169
+ const lastIndex = this.options.length - 1;
170
+ this.currentIndex = this.currentIndex > -1 ? this.currentIndex - 1 : lastIndex;
171
+ }
172
+ canSelect() {
173
+ return this.showSuggestions && this.currentIndex >= 0;
174
+ }
175
+ selectCurrent() {
176
+ if (this.currentIndex < 0)
177
+ return;
178
+ const selectedOption = this.options[this.currentIndex];
179
+ this.applySelection(selectedOption);
180
+ this.emitActivation(selectedOption);
181
+ this.closeSuggestions();
182
+ }
183
+ applySelection(option) {
184
+ const input = this.getInput();
185
+ if (!input)
186
+ return;
187
+ input.value = option.value;
188
+ this.originalValue = option.value;
189
+ }
190
+ emitActivation(option) {
191
+ this.activated.emit({
192
+ index: this.currentIndex,
193
+ value: option.value
194
+ });
195
+ }
196
+ openSuggestions() {
197
+ if (!this.isInputFocused())
198
+ return;
199
+ const input = this.getInput();
200
+ if (input)
201
+ this.originalValue = input.value;
202
+ this.showSuggestionsPanel();
203
+ }
204
+ showSuggestionsPanel() {
205
+ this.showSuggestions = true;
206
+ this.syncAriaAttributes();
207
+ }
208
+ closeSuggestions() {
209
+ this.reset();
210
+ this.syncAll();
211
+ }
212
+ deferCloseSuggestions() {
213
+ if (this.blurTimeout)
214
+ clearTimeout(this.blurTimeout);
215
+ this.blurTimeout = setTimeout(() => {
216
+ if (this.isFocusOutsideComponent())
217
+ this.closeSuggestions();
218
+ }, 150);
219
+ }
220
+ reset() {
221
+ this.showSuggestions = false;
222
+ this.currentIndex = -1;
223
+ }
224
+ isFocusLeavingComponent(event) {
225
+ var _a;
226
+ const target = event.relatedTarget;
227
+ return !target ||
228
+ (!this.el.contains(target) && !((_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(target)));
229
+ }
230
+ isFocusOutsideComponent() {
231
+ var _a;
232
+ const active = document.activeElement;
233
+ return !((_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(active)) && active !== this.getInput();
234
+ }
235
+ syncAriaAttributes() {
236
+ const input = this.getInput();
237
+ if (!input)
238
+ return;
239
+ this.updateAriaExpanded(input);
240
+ this.updateAriaActiveDescendant(input);
241
+ }
242
+ syncInputValueToSelection() {
243
+ const input = this.getInput();
244
+ if (!input)
245
+ return;
246
+ input.value = this.currentIndex >= 0 && this.currentIndex < this.options.length
247
+ ? this.options[this.currentIndex].value
248
+ : this.originalValue;
249
+ }
250
+ syncAll() {
251
+ this.syncAriaAttributes();
252
+ this.syncInputValueToSelection();
253
+ }
254
+ updateAriaExpanded(input) {
255
+ input.setAttribute('aria-expanded', this.showSuggestions.toString());
256
+ }
257
+ updateAriaActiveDescendant(input) {
258
+ const hasSelection = this.showSuggestions &&
259
+ this.currentIndex >= 0 &&
260
+ this.currentIndex < this.options.length;
261
+ if (hasSelection) {
262
+ input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);
263
+ }
264
+ else {
265
+ input.removeAttribute('aria-activedescendant');
266
+ }
267
+ }
268
+ shouldShowListbox() {
269
+ return this.showSuggestions && this.options.length > 0 && this.isInputFocused();
270
+ }
271
+ render() {
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 })))))));
273
+ }
274
+ get el() { return getElement(this); }
275
+ };
276
+ Autocomplete.style = pennlibsAutocompleteCss;
277
+
278
+ export { Autocomplete as pennlibs_autocomplete };
279
+ //# sourceMappingURL=pennlibs-autocomplete.entry.esm.js.map
@@ -1,287 +1,10 @@
1
- import { r as registerInstance, d as createEvent, h, c as getElement, a as getAssetPath } from './p-CNQ9-Wwh.js';
1
+ import { r as registerInstance, c as createEvent, h, d as getAssetPath, a as getElement } from './p-kmszRyIe.js';
2
2
 
3
- 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}";
3
+ const pennlibsFeedbackCss = ".feedback__container.sc-pennlibs-feedback{display:block;background:#eeeff4;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;padding:0.75em 0}@media print{.sc-pennlibs-feedback-h{display:none}}*.sc-pennlibs-feedback,*.sc-pennlibs-feedback::before,*.sc-pennlibs-feedback::after{box-sizing:border-box}.viewport-margins.sc-pennlibs-feedback{max-width:var(--pl-viewport-margins-max-width);margin:0 auto;padding:0 var(--pl-viewport-margins-gutter, 1em)}.feedback__content.sc-pennlibs-feedback{display:flex;justify-content:space-between;gap:var(--pl-space-s);flex-wrap:wrap}.feedback__inline-question.sc-pennlibs-feedback{display:flex;flex-wrap:wrap;gap:0.5em;align-items:center}h2.sc-pennlibs-feedback{display:inline-block;margin:0;font-family:var(--pl-font-sans-serif);font-size:1em;font-weight:400}p.sc-pennlibs-feedback{margin:0;padding:calc(0.5em + 1px) 0}strong.sc-pennlibs-feedback{font-weight:600}a.sc-pennlibs-feedback{color:var(--pl-color-fg-accent);text-decoration:underline;text-underline-offset:var(--pl-link-text-underline-offset);text-decoration-thickness:var(--pl-link-hover-text-decoration-thickness)}button.sc-pennlibs-feedback{all:unset;display:flex;align-items:center;gap:0.5em;padding:0.25em 1.25em;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.sc-pennlibs-feedback:hover{cursor:pointer}button.sc-pennlibs-feedback:hover span.sc-pennlibs-feedback{text-decoration:underline}button.selected.sc-pennlibs-feedback{background:var(--pl-color-bg-accent-emphasis);color:var(--pl-color-fg-on-emphasis);border-color:var(--pl-color-bg-accent-emphasis)}.feedback__inline-question.sc-pennlibs-feedback+.feedback__share-feedback.sc-pennlibs-feedback,p.sc-pennlibs-feedback{animation:none}.feedback__content.sc-pennlibs-feedback>p.sc-pennlibs-feedback{animation:fadeIn 300ms ease-in}@keyframes fadeIn{from{opacity:0}to{opacity:1}}*.sc-pennlibs-feedback: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.sc-pennlibs-feedback: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.sc-pennlibs-feedback{display:block;width:100%;text-align:center}}.help-us.sc-pennlibs-feedback{display:flex;flex-direction:column;gap:0.5em}.visually-hidden.sc-pennlibs-feedback{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}.visually-hidden.sc-pennlibs-feedback:focus,.visually-hidden.sc-pennlibs-feedback:active{clip:auto;clip-path:none;height:auto;overflow:visible;position:static;white-space:normal;width:auto}";
4
4
 
5
- const Autocomplete = class {
6
- constructor(hostRef) {
7
- registerInstance(this, hostRef);
8
- this.activated = createEvent(this, "pl:activated");
9
- this.showSuggestions = false;
10
- this.currentIndex = -1;
11
- this.originalValue = '';
12
- this.options = [];
13
- this.keyHandlerMap = {
14
- 'Escape': () => this.handleEscape(),
15
- 'ArrowDown': () => this.handleArrowDown(),
16
- 'ArrowUp': () => this.handleArrowUp(),
17
- 'Enter': () => this.handleEnter()
18
- };
19
- this.handleOptionClick = (index) => {
20
- this.currentIndex = index;
21
- this.selectCurrent();
22
- };
23
- }
24
- componentWillLoad() {
25
- this.options = this.parseOptionsFromDOM();
26
- }
27
- componentDidLoad() {
28
- this.setupInput();
29
- this.setupMutationObserver();
30
- }
31
- disconnectedCallback() {
32
- if (this.blurTimeout)
33
- clearTimeout(this.blurTimeout);
34
- if (this.mutationObserver)
35
- this.mutationObserver.disconnect();
36
- }
37
- setupMutationObserver() {
38
- this.mutationObserver = new MutationObserver(() => {
39
- this.options = this.parseOptionsFromDOM();
40
- });
41
- this.mutationObserver.observe(this.el, {
42
- childList: true,
43
- subtree: true,
44
- characterData: true
45
- });
46
- }
47
- parseOptionsFromDOM() {
48
- const listbox = this.findListbox();
49
- if (!listbox)
50
- return [];
51
- const elements = Array.from(listbox.querySelectorAll('[role="option"]'));
52
- return elements.map((el, i) => this.createOption(el, i));
53
- }
54
- findListbox() {
55
- return Array.from(this.el.children).find(child => child.matches('ol[role="listbox"]'));
56
- }
57
- createOption(element, index) {
58
- var _a;
59
- return {
60
- id: element.id || `option-${index}`,
61
- html: element.innerHTML,
62
- value: element.getAttribute('data-pl-value') || ((_a = element.textContent) === null || _a === void 0 ? void 0 : _a.trim()) || ''
63
- };
64
- }
65
- getInput() {
66
- var _a;
67
- if (!this.for)
68
- return null;
69
- const slot = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="start"]');
70
- if (!slot)
71
- return null;
72
- const assignedElements = slot.assignedElements();
73
- if (assignedElements.length === 0)
74
- return null;
75
- const input = assignedElements[0].querySelector(`input#${this.for}`);
76
- return input || null;
77
- }
78
- isInputFocused() {
79
- return this.getInput() === document.activeElement;
80
- }
81
- isTrackedInput(input) {
82
- return (input === null || input === void 0 ? void 0 : input.getAttribute('data-pl-autocomplete-connected')) === 'true' &&
83
- this.el.contains(input);
84
- }
85
- setupInput() {
86
- if (!this.for) {
87
- console.warn('<pennlibs-autocomplete> Missing "for" attribute. Please add for="input-id" to specify which input to attach to.');
88
- return;
89
- }
90
- const input = this.getInput();
91
- if (!input) {
92
- console.warn(`<pennlibs-autocomplete> No input element found with id="${this.for}". Ensure an input with this id exists.`);
93
- return;
94
- }
95
- this.setComboboxAttributes(input);
96
- input.setAttribute('data-pl-autocomplete-connected', 'true');
97
- }
98
- setComboboxAttributes(input) {
99
- input.setAttribute('role', 'combobox');
100
- input.setAttribute('aria-autocomplete', 'list');
101
- input.setAttribute('aria-expanded', 'false');
102
- input.setAttribute('aria-controls', 'listbox');
103
- }
104
- handleSlotChange() {
105
- this.options = this.parseOptionsFromDOM();
106
- this.setupInput();
107
- }
108
- handleInputEvent(event) {
109
- const input = event.target;
110
- if (this.isTrackedInput(input)) {
111
- this.currentIndex = -1;
112
- this.openSuggestions();
113
- }
114
- }
115
- handleFocusEvent(event) {
116
- const input = event.target;
117
- if (this.isTrackedInput(input))
118
- this.showSuggestionsPanel();
119
- }
120
- handleBlurEvent(event) {
121
- const input = event.target;
122
- if (this.isTrackedInput(input))
123
- this.deferCloseSuggestions();
124
- }
125
- handleFocusOut(event) {
126
- if (this.isFocusLeavingComponent(event))
127
- this.closeSuggestions();
128
- }
129
- handleKeyDown(event) {
130
- if (!this.isInputFocused())
131
- return;
132
- if (event.metaKey || event.ctrlKey)
133
- return; // Ignore keyboard shortcuts
134
- const handler = this.keyHandlerMap[event.key];
135
- if (handler) {
136
- // Don't prevent default for Enter - allow form submission
137
- if (event.key !== 'Enter') {
138
- event.preventDefault();
139
- }
140
- handler();
141
- }
142
- }
143
- handleEscape() {
144
- this.reset();
145
- this.syncAll();
146
- }
147
- handleArrowDown() {
148
- if (!this.showSuggestions)
149
- return;
150
- this.moveNext();
151
- this.syncAll();
152
- }
153
- handleArrowUp() {
154
- if (!this.showSuggestions)
155
- return;
156
- this.movePrevious();
157
- this.syncAll();
158
- }
159
- handleEnter() {
160
- if (this.canSelect())
161
- this.selectCurrent();
162
- }
163
- // Navigation
164
- moveNext() {
165
- const lastIndex = this.options.length - 1;
166
- this.currentIndex = this.currentIndex < lastIndex ? this.currentIndex + 1 : -1;
167
- }
168
- movePrevious() {
169
- const lastIndex = this.options.length - 1;
170
- this.currentIndex = this.currentIndex > -1 ? this.currentIndex - 1 : lastIndex;
171
- }
172
- canSelect() {
173
- return this.showSuggestions && this.currentIndex >= 0;
174
- }
175
- selectCurrent() {
176
- if (this.currentIndex < 0)
177
- return;
178
- const selectedOption = this.options[this.currentIndex];
179
- this.applySelection(selectedOption);
180
- this.emitActivation(selectedOption);
181
- this.closeSuggestions();
182
- }
183
- applySelection(option) {
184
- const input = this.getInput();
185
- if (!input)
186
- return;
187
- input.value = option.value;
188
- this.originalValue = option.value;
189
- }
190
- emitActivation(option) {
191
- this.activated.emit({
192
- index: this.currentIndex,
193
- value: option.value
194
- });
195
- }
196
- openSuggestions() {
197
- if (!this.isInputFocused())
198
- return;
199
- const input = this.getInput();
200
- if (input)
201
- this.originalValue = input.value;
202
- this.showSuggestionsPanel();
203
- }
204
- showSuggestionsPanel() {
205
- this.showSuggestions = true;
206
- this.syncAriaAttributes();
207
- }
208
- closeSuggestions() {
209
- this.reset();
210
- this.syncAll();
211
- }
212
- deferCloseSuggestions() {
213
- if (this.blurTimeout)
214
- clearTimeout(this.blurTimeout);
215
- this.blurTimeout = setTimeout(() => {
216
- if (this.isFocusOutsideComponent())
217
- this.closeSuggestions();
218
- }, 150);
219
- }
220
- reset() {
221
- this.showSuggestions = false;
222
- this.currentIndex = -1;
223
- }
224
- isFocusLeavingComponent(event) {
225
- var _a;
226
- const target = event.relatedTarget;
227
- return !target ||
228
- (!this.el.contains(target) && !((_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(target)));
229
- }
230
- isFocusOutsideComponent() {
231
- var _a;
232
- const active = document.activeElement;
233
- return !((_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.contains(active)) && active !== this.getInput();
234
- }
235
- syncAriaAttributes() {
236
- const input = this.getInput();
237
- if (!input)
238
- return;
239
- this.updateAriaExpanded(input);
240
- this.updateAriaActiveDescendant(input);
241
- }
242
- syncInputValueToSelection() {
243
- const input = this.getInput();
244
- if (!input)
245
- return;
246
- input.value = this.currentIndex >= 0 && this.currentIndex < this.options.length
247
- ? this.options[this.currentIndex].value
248
- : this.originalValue;
249
- }
250
- syncAll() {
251
- this.syncAriaAttributes();
252
- this.syncInputValueToSelection();
253
- }
254
- updateAriaExpanded(input) {
255
- input.setAttribute('aria-expanded', this.showSuggestions.toString());
256
- }
257
- updateAriaActiveDescendant(input) {
258
- const hasSelection = this.showSuggestions &&
259
- this.currentIndex >= 0 &&
260
- this.currentIndex < this.options.length;
261
- if (hasSelection) {
262
- input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);
263
- }
264
- else {
265
- input.removeAttribute('aria-activedescendant');
266
- }
267
- }
268
- shouldShowListbox() {
269
- return this.showSuggestions && this.options.length > 0 && this.isInputFocused();
270
- }
271
- render() {
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 })))))));
273
- }
274
- get el() { return getElement(this); }
275
- };
276
- Autocomplete.style = pennlibsAutocompleteCss;
277
-
278
- const pennlibsFeedbackCss = ":host{display:block;background:#eeeff4;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;padding:0.75em 0}@media print{:host{display:none}}*,*::before,*::after{box-sizing:border-box}.viewport-margins{max-width:var(--pl-viewport-margins-max-width);margin:0 auto;padding:0 var(--pl-viewport-margins-gutter, 1em)}.feedback__content{display:flex;justify-content:space-between;gap:var(--pl-space-s);flex-wrap:wrap}.feedback__inline-question{display:flex;flex-wrap:wrap;gap:0.5em;align-items:center}h2{display:inline-block;margin:0;font-family:var(--pl-font-sans-serif);font-size:1em;font-weight:400}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-hover-text-decoration-thickness)}button{all:unset;display:flex;align-items:center;gap:0.5em;padding:0.25em 1.25em;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}";
279
-
280
- const FEEDBACK_STORAGE_KEY = 'pennlibs-feedback-answer';
281
- const FEEDBACK_TIMESTAMP_KEY = 'pennlibs-feedback-timestamp';
5
+ const FEEDBACK_STORAGE_KEY = `pennlibs-feedback-${window.location.pathname}`;
282
6
  const FEEDBACK_TTL = 5 * 60 * 1000; // 5 minutes
283
- const FEEDBACK_QUESTION = 'Is this page useful?';
284
- const FEEDBACK_ERROR_STORAGE_KEY = 'pennlibs-feedback-error';
7
+ const FEEDBACK_QUESTION = 'Did you find what you need?';
285
8
  const PennlibsFeedback = class {
286
9
  constructor(hostRef) {
287
10
  registerInstance(this, hostRef);
@@ -290,27 +13,32 @@ const PennlibsFeedback = class {
290
13
  * When true, hides the yes/no question and only shows the "share your feedback" link.
291
14
  */
292
15
  this.hideQuestion = false;
293
- this.error = false;
294
16
  this.answer = undefined;
17
+ this.selected = undefined;
295
18
  }
296
19
  componentWillLoad() {
297
- const timestamp = sessionStorage.getItem(FEEDBACK_TIMESTAMP_KEY);
298
- if (timestamp && Date.now() - Number(timestamp) > FEEDBACK_TTL) {
299
- sessionStorage.removeItem(FEEDBACK_STORAGE_KEY);
300
- sessionStorage.removeItem(FEEDBACK_TIMESTAMP_KEY);
301
- }
302
- if (sessionStorage.getItem(FEEDBACK_STORAGE_KEY)) {
303
- this.answer = sessionStorage.getItem(FEEDBACK_STORAGE_KEY);
304
- }
305
- if (sessionStorage.getItem(FEEDBACK_ERROR_STORAGE_KEY)) {
306
- this.error = sessionStorage.getItem(FEEDBACK_ERROR_STORAGE_KEY) === 'true';
20
+ const stored = sessionStorage.getItem(FEEDBACK_STORAGE_KEY);
21
+ if (stored) {
22
+ try {
23
+ const { answer, timestamp } = JSON.parse(stored);
24
+ if (Date.now() - timestamp > FEEDBACK_TTL) {
25
+ sessionStorage.removeItem(FEEDBACK_STORAGE_KEY);
26
+ }
27
+ else {
28
+ this.answer = answer;
29
+ }
30
+ }
31
+ catch (_a) {
32
+ sessionStorage.removeItem(FEEDBACK_STORAGE_KEY);
33
+ }
307
34
  }
308
35
  }
309
- handleFeedback(answer) {
36
+ handleSubmit(event) {
310
37
  var _a;
311
- this.answer = answer;
312
- sessionStorage.setItem(FEEDBACK_STORAGE_KEY, answer);
313
- sessionStorage.setItem(FEEDBACK_TIMESTAMP_KEY, String(Date.now()));
38
+ event.preventDefault();
39
+ const answer = event.submitter.value;
40
+ this.selected = answer;
41
+ sessionStorage.setItem(FEEDBACK_STORAGE_KEY, JSON.stringify({ answer, timestamp: Date.now() }));
314
42
  this.feedbackEvent.emit({
315
43
  question: FEEDBACK_QUESTION,
316
44
  answer: answer,
@@ -318,9 +46,12 @@ const PennlibsFeedback = class {
318
46
  pageHeading: ((_a = document.querySelector('h1')) === null || _a === void 0 ? void 0 : _a.textContent) || undefined,
319
47
  pageTitle: document.title || undefined
320
48
  });
49
+ setTimeout(() => {
50
+ this.answer = answer;
51
+ }, 600);
321
52
  }
322
53
  render() {
323
- return (h("div", { key: '04053c96e964ee9f240ca1a77d7944ae0f53248d', class: "feedback__container" }, h("div", { key: 'f5f524b449d3473c1ecc2d454787034c54a811a0', class: "viewport-margins" }, h("div", { key: 'd0c832edc9361dac49d9cbfc143332fea04434ea', class: "feedback__content" }, !this.hideQuestion && (this.answer === 'yes' ? (h("p", null, "Thank you for letting us know.")) : this.answer === 'no' ? (h("p", null, "Thank you for letting us know. ", h("a", { href: "https://www.library.upenn.edu/contact-us" }, "Contact us"), " if you need help.")) : (h("div", { class: "feedback__inline-question" }, h("h2", null, "Did you find what you need?"), h("button", { onClick: () => this.handleFeedback('yes') }, h("span", null, "Yes"), h("span", { class: "visually-hidden" }, ", I found what I needed.")), h("button", { onClick: () => this.handleFeedback('no') }, h("span", null, "No"), h("span", { class: "visually-hidden" }, ", I did not find what I needed."))))), h("div", { key: '9d4de668c9e3696d8983c48fcc20ea89b2163ea1', class: "feedback__share-feedback" }, h("p", { key: '1377153ebba81c48b0a70756b190503b9cd33c61' }, "To help us improve, ", h("a", { key: '26f3f05b0af848ff58d959190a380d5b64db254a', href: "http://library.upenn.edu/feedback" }, "share your feedback"), "."))))));
54
+ return (h("div", { key: '013c3b140f249641d412bd0c19108a6c51d2f12e', class: "feedback__container" }, h("div", { key: 'ef51ab5c419dfdd8679b027882a59171480ea039', class: "viewport-margins" }, h("div", { key: '2c61b86b1e69037d7d63ecde5288bfcc0e1dad75', class: "feedback__content" }, !this.hideQuestion && (this.answer === 'yes' ? (h("p", null, "Thank you for letting us know.")) : this.answer === 'no' ? (h("p", null, "We'd like to help. ", h("a", { href: "https://www.library.upenn.edu/contact-us" }, "Contact us"), " and let us know what you're looking for.")) : (h("form", { class: "feedback__inline-question", onSubmit: (e) => this.handleSubmit(e) }, h("h2", null, "Did you find what you need?"), h("button", { type: "submit", name: "answer", value: "yes", class: { 'selected': this.selected === 'yes' }, disabled: !!this.selected }, h("span", null, "Yes"), h("span", { class: "visually-hidden" }, ", I found what I needed.")), h("button", { type: "submit", name: "answer", value: "no", class: { 'selected': this.selected === 'no' }, disabled: !!this.selected }, h("span", null, "No"), h("span", { class: "visually-hidden" }, ", I did not find what I needed."))))), h("div", { key: '90ccdacc682229570f83867f8d8f587a3e0c16aa', class: "feedback__share-feedback" }, h("p", { key: 'edd15c793ac6af5d31bd98863cd76fce27aed69a' }, "To help us improve, ", h("a", { key: '25f5c322d0fb717b8618b40625af978d57f805bd', href: `https://upenn.co1.qualtrics.com/jfe/form/SV_3xVrP7EKWS92zfE?reporting_from_url=${encodeURIComponent(window.location.href)}` }, "share your feedback"), "."))))));
324
55
  }
325
56
  };
326
57
  PennlibsFeedback.style = pennlibsFeedbackCss;
@@ -428,5 +159,5 @@ const Header = class {
428
159
  };
429
160
  Header.style = pennlibsHeaderCss;
430
161
 
431
- export { Autocomplete as pennlibs_autocomplete, PennlibsFeedback as pennlibs_feedback, Footer as pennlibs_footer, Header as pennlibs_header };
432
- //# sourceMappingURL=pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.esm.js.map
162
+ export { PennlibsFeedback as pennlibs_feedback, Footer as pennlibs_footer, Header as pennlibs_header };
163
+ //# sourceMappingURL=pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.esm.js.map
@@ -1,4 +1,4 @@
1
- import { r as registerInstance, a as getAssetPath, h } from './p-CNQ9-Wwh.js';
1
+ import { r as registerInstance, d as getAssetPath, h } from './p-kmszRyIe.js';
2
2
 
3
3
  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)}";
4
4
 
@@ -1,4 +1,4 @@
1
- import { r as registerInstance, h, c as getElement } from './p-CNQ9-Wwh.js';
1
+ import { r as registerInstance, h, a as getElement } from './p-kmszRyIe.js';
2
2
 
3
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
4