@penn-libraries/web 1.2.0-dev.1 → 1.2.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 (53) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/pennlibs-autocomplete.cjs.entry.js +281 -0
  3. package/dist/cjs/pennlibs-autocomplete.entry.cjs.js.map +1 -0
  4. package/dist/cjs/pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.cjs.js.map +1 -0
  5. package/dist/cjs/{pennlibs-autocomplete_4.cjs.entry.js → pennlibs-feedback_3.cjs.entry.js} +33 -301
  6. package/dist/cjs/web.cjs.js +1 -1
  7. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.css +22 -1
  8. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.js +25 -21
  9. package/dist/collection/components/pennlibs-feedback/pennlibs-feedback.js.map +1 -1
  10. package/dist/collection/components/pennlibs-footer/pennlibs-footer.css +12 -3
  11. package/dist/collection/components/pennlibs-footer/pennlibs-footer.js +33 -3
  12. package/dist/collection/components/pennlibs-footer/pennlibs-footer.js.map +1 -1
  13. package/dist/components/pennlibs-feedback.js +26 -22
  14. package/dist/components/pennlibs-feedback.js.map +1 -1
  15. package/dist/components/pennlibs-footer.js +9 -4
  16. package/dist/components/pennlibs-footer.js.map +1 -1
  17. package/dist/docs.json +32 -2
  18. package/dist/esm/{index-CNQ9-Wwh.js → index-mkVX9lU0.js} +3 -3
  19. package/dist/esm/{index-CNQ9-Wwh.js.map → index-mkVX9lU0.js.map} +1 -1
  20. package/dist/esm/index.js +1 -1
  21. package/dist/esm/loader.js +3 -3
  22. package/dist/esm/pennlibs-autocomplete.entry.js +279 -0
  23. package/dist/esm/pennlibs-autocomplete.entry.js.map +1 -0
  24. package/dist/esm/pennlibs-banner.entry.js +1 -1
  25. package/dist/esm/pennlibs-chat.entry.js +1 -1
  26. package/dist/esm/pennlibs-fallback-img.entry.js +1 -1
  27. package/dist/esm/pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.js.map +1 -0
  28. package/dist/esm/{pennlibs-autocomplete_4.entry.js → pennlibs-feedback_3.entry.js} +35 -302
  29. package/dist/esm/pennlibs-hero.entry.js +1 -1
  30. package/dist/esm/pennlibs-iiif-img.entry.js +1 -1
  31. package/dist/esm/web.js +3 -3
  32. package/dist/types/components/pennlibs-feedback/pennlibs-feedback.d.ts +1 -1
  33. package/dist/types/components/pennlibs-footer/pennlibs-footer.d.ts +4 -0
  34. package/dist/types/components.d.ts +10 -0
  35. package/dist/web/index.esm.js +1 -1
  36. package/dist/web/{p-a34e1e3e.entry.js → p-0c1113f4.entry.js} +1 -1
  37. package/dist/web/{p-b9538aed.entry.js → p-10931f0a.entry.js} +35 -302
  38. package/dist/web/{p-b6e686d1.entry.js → p-154851d1.entry.js} +1 -1
  39. package/dist/web/{p-c96a3643.entry.js → p-190f0293.entry.js} +1 -1
  40. package/dist/web/{p-eb633f4d.entry.js → p-4fb3e5a4.entry.js} +1 -1
  41. package/dist/web/p-7c6382d0.entry.js +279 -0
  42. package/dist/web/{p-662e4125.entry.js → p-e426fb61.entry.js} +1 -1
  43. package/dist/web/{p-CNQ9-Wwh.js → p-mkVX9lU0.js} +3 -3
  44. package/dist/web/{p-CNQ9-Wwh.js.map → p-mkVX9lU0.js.map} +1 -1
  45. package/dist/web/pennlibs-autocomplete.entry.esm.js.map +1 -0
  46. package/dist/web/pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.esm.js.map +1 -0
  47. package/dist/web/web.esm.js +3 -3
  48. package/hydrate/index.js +35 -26
  49. package/hydrate/index.mjs +35 -26
  50. package/package.json +1 -1
  51. package/dist/cjs/pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.cjs.js.map +0 -1
  52. package/dist/esm/pennlibs-autocomplete.pennlibs-feedback.pennlibs-footer.pennlibs-header.entry.js.map +0 -1
  53. 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-mkVX9lU0.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,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-mkVX9lU0.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
 
@@ -2257,7 +2257,7 @@ var hostListenerOpts = (flags) => supportsListenerOptions ? {
2257
2257
  // src/runtime/nonce.ts
2258
2258
  var setNonce = (nonce) => plt.$nonce$ = nonce;
2259
2259
 
2260
- export { getAssetPath as a, bootstrapLazy as b, getElement as c, createEvent as d, setAssetPath as e, globalScripts as g, h, promiseResolve as p, registerInstance as r, setNonce as s };
2261
- //# sourceMappingURL=p-CNQ9-Wwh.js.map
2260
+ export { getElement as a, bootstrapLazy as b, createEvent as c, getAssetPath as d, setAssetPath as e, globalScripts as g, h, promiseResolve as p, registerInstance as r, setNonce as s };
2261
+ //# sourceMappingURL=p-mkVX9lU0.js.map
2262
2262
 
2263
- //# sourceMappingURL=p-CNQ9-Wwh.js.map
2263
+ //# sourceMappingURL=p-mkVX9lU0.js.map