@penn-libraries/web 1.1.0-dev.5 → 1.1.0-dev.7

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.
@@ -68,7 +68,8 @@ ol {
68
68
  text-decoration: underline;
69
69
  }
70
70
 
71
- mark {
71
+ mark,
72
+ b {
72
73
  background: none;
73
74
  font-weight: 400;
74
75
  }
@@ -5,6 +5,12 @@ export class Autocomplete {
5
5
  this.currentIndex = -1;
6
6
  this.originalValue = '';
7
7
  this.options = [];
8
+ this.keyHandlerMap = {
9
+ 'Escape': () => this.handleEscape(),
10
+ 'ArrowDown': () => this.handleArrowDown(),
11
+ 'ArrowUp': () => this.handleArrowUp(),
12
+ 'Enter': () => this.handleEnter()
13
+ };
8
14
  this.handleOptionClick = (index) => {
9
15
  this.currentIndex = index;
10
16
  this.selectCurrent();
@@ -56,7 +62,12 @@ export class Autocomplete {
56
62
  if (!this.for)
57
63
  return null;
58
64
  const slot = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="start"]');
59
- const input = slot.assignedElements()[0].querySelector(`input#${this.for}`);
65
+ if (!slot)
66
+ return null;
67
+ const assignedElements = slot.assignedElements();
68
+ if (assignedElements.length === 0)
69
+ return null;
70
+ const input = assignedElements[0].querySelector(`input#${this.for}`);
60
71
  return input || null;
61
72
  }
62
73
  isInputFocused() {
@@ -115,35 +126,33 @@ export class Autocomplete {
115
126
  return;
116
127
  if (event.metaKey || event.ctrlKey)
117
128
  return; // Ignore keyboard shortcuts
118
- const handler = this.keyHandlers()[event.key];
129
+ const handler = this.keyHandlerMap[event.key];
119
130
  if (handler) {
120
- event.preventDefault();
131
+ // Don't prevent default for Enter - allow form submission
132
+ if (event.key !== 'Enter') {
133
+ event.preventDefault();
134
+ }
121
135
  handler();
122
136
  }
123
137
  }
124
- keyHandlers() {
125
- return {
126
- 'Escape': () => this.handleEscape(),
127
- 'ArrowDown': () => this.handleArrowDown(),
128
- 'ArrowUp': () => this.handleArrowUp(),
129
- 'Enter': () => this.handleEnter()
130
- };
131
- }
132
138
  handleEscape() {
133
139
  this.reset();
134
140
  this.syncInputState();
141
+ this.syncInputValueToSelection();
135
142
  }
136
143
  handleArrowDown() {
137
144
  if (!this.showSuggestions)
138
145
  return;
139
146
  this.moveNext();
140
147
  this.syncInputState();
148
+ this.syncInputValueToSelection();
141
149
  }
142
150
  handleArrowUp() {
143
151
  if (!this.showSuggestions)
144
152
  return;
145
153
  this.movePrevious();
146
154
  this.syncInputState();
155
+ this.syncInputValueToSelection();
147
156
  }
148
157
  handleEnter() {
149
158
  if (this.canSelect())
@@ -183,7 +192,6 @@ export class Autocomplete {
183
192
  if (input)
184
193
  this.originalValue = input.value;
185
194
  this.showSuggestionsPanel();
186
- this.syncInputState();
187
195
  }
188
196
  showSuggestionsPanel() {
189
197
  this.showSuggestions = true;
@@ -192,8 +200,11 @@ export class Autocomplete {
192
200
  closeSuggestions() {
193
201
  this.reset();
194
202
  this.syncInputState();
203
+ this.syncInputValueToSelection();
195
204
  }
196
205
  deferCloseSuggestions() {
206
+ if (this.blurTimeout)
207
+ clearTimeout(this.blurTimeout);
197
208
  this.blurTimeout = setTimeout(() => {
198
209
  if (this.isFocusOutsideComponent())
199
210
  this.closeSuggestions();
@@ -220,30 +231,34 @@ export class Autocomplete {
220
231
  return;
221
232
  this.updateAriaExpanded(input);
222
233
  this.updateAriaActiveDescendant(input);
223
- this.updateInputValue(input);
234
+ }
235
+ syncInputValueToSelection() {
236
+ const input = this.getInput();
237
+ if (!input)
238
+ return;
239
+ input.value = this.currentIndex >= 0 && this.currentIndex < this.options.length
240
+ ? this.options[this.currentIndex].value
241
+ : this.originalValue;
224
242
  }
225
243
  updateAriaExpanded(input) {
226
244
  input.setAttribute('aria-expanded', this.showSuggestions.toString());
227
245
  }
228
246
  updateAriaActiveDescendant(input) {
229
- const hasSelection = this.showSuggestions && this.currentIndex >= 0;
230
- if (hasSelection && this.options[this.currentIndex]) {
247
+ const hasSelection = this.showSuggestions &&
248
+ this.currentIndex >= 0 &&
249
+ this.currentIndex < this.options.length;
250
+ if (hasSelection) {
231
251
  input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);
232
252
  }
233
253
  else {
234
254
  input.removeAttribute('aria-activedescendant');
235
255
  }
236
256
  }
237
- updateInputValue(input) {
238
- input.value = this.currentIndex >= 0
239
- ? this.options[this.currentIndex].value
240
- : this.originalValue;
241
- }
242
257
  shouldShowListbox() {
243
258
  return this.showSuggestions && this.options.length > 0 && this.isInputFocused();
244
259
  }
245
260
  render() {
246
- return (h("div", { key: '71422b55533372a9d8697e648e49f87fe223f4e0' }, h("slot", { key: '951511fd2090b9816c2e6c1a5a40169ea08e72fd', name: "start" }), this.shouldShowListbox() && (h("ol", { key: 'e3f6f14686bc99b6e25d34d8e053f01174f23438', 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 })))))));
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 })))))));
247
262
  }
248
263
  static get is() { return "pennlibs-autocomplete"; }
249
264
  static get encapsulation() { return "shadow"; }
@@ -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\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 const input = slot.assignedElements()[0].querySelector(`input#${this.for}`) as HTMLInputElement;\n\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.keyHandlers()[event.key];\n if (handler) {\n event.preventDefault();\n handler();\n }\n }\n\n private keyHandlers() {\n return {\n 'Escape': () => this.handleEscape(),\n 'ArrowDown': () => this.handleArrowDown(),\n 'ArrowUp': () => this.handleArrowUp(),\n 'Enter': () => this.handleEnter()\n };\n }\n\n private handleEscape() {\n this.reset();\n this.syncInputState();\n }\n\n private handleArrowDown() {\n if (!this.showSuggestions) return;\n this.moveNext();\n this.syncInputState();\n }\n\n private handleArrowUp() {\n if (!this.showSuggestions) return;\n this.movePrevious();\n this.syncInputState();\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 this.syncInputState();\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 }\n\n private deferCloseSuggestions(): void {\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 this.updateInputValue(input);\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 && this.currentIndex >= 0;\n if (hasSelection && this.options[this.currentIndex]) {\n input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);\n } else {\n input.removeAttribute('aria-activedescendant');\n }\n }\n\n private updateInputValue(input: HTMLInputElement): void {\n input.value = this.currentIndex >= 0\n ? this.options[this.currentIndex].value\n : this.originalValue;\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@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,6 +1,6 @@
1
1
  import { proxyCustomElement, HTMLElement, createEvent, h } from '@stencil/core/internal/client';
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 {\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 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
4
 
5
5
  const Autocomplete = /*@__PURE__*/ proxyCustomElement(class Autocomplete extends HTMLElement {
6
6
  constructor() {
@@ -12,6 +12,12 @@ const Autocomplete = /*@__PURE__*/ proxyCustomElement(class Autocomplete extends
12
12
  this.currentIndex = -1;
13
13
  this.originalValue = '';
14
14
  this.options = [];
15
+ this.keyHandlerMap = {
16
+ 'Escape': () => this.handleEscape(),
17
+ 'ArrowDown': () => this.handleArrowDown(),
18
+ 'ArrowUp': () => this.handleArrowUp(),
19
+ 'Enter': () => this.handleEnter()
20
+ };
15
21
  this.handleOptionClick = (index) => {
16
22
  this.currentIndex = index;
17
23
  this.selectCurrent();
@@ -63,7 +69,12 @@ const Autocomplete = /*@__PURE__*/ proxyCustomElement(class Autocomplete extends
63
69
  if (!this.for)
64
70
  return null;
65
71
  const slot = (_a = this.el.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('slot[name="start"]');
66
- const input = slot.assignedElements()[0].querySelector(`input#${this.for}`);
72
+ if (!slot)
73
+ return null;
74
+ const assignedElements = slot.assignedElements();
75
+ if (assignedElements.length === 0)
76
+ return null;
77
+ const input = assignedElements[0].querySelector(`input#${this.for}`);
67
78
  return input || null;
68
79
  }
69
80
  isInputFocused() {
@@ -122,35 +133,33 @@ const Autocomplete = /*@__PURE__*/ proxyCustomElement(class Autocomplete extends
122
133
  return;
123
134
  if (event.metaKey || event.ctrlKey)
124
135
  return; // Ignore keyboard shortcuts
125
- const handler = this.keyHandlers()[event.key];
136
+ const handler = this.keyHandlerMap[event.key];
126
137
  if (handler) {
127
- event.preventDefault();
138
+ // Don't prevent default for Enter - allow form submission
139
+ if (event.key !== 'Enter') {
140
+ event.preventDefault();
141
+ }
128
142
  handler();
129
143
  }
130
144
  }
131
- keyHandlers() {
132
- return {
133
- 'Escape': () => this.handleEscape(),
134
- 'ArrowDown': () => this.handleArrowDown(),
135
- 'ArrowUp': () => this.handleArrowUp(),
136
- 'Enter': () => this.handleEnter()
137
- };
138
- }
139
145
  handleEscape() {
140
146
  this.reset();
141
147
  this.syncInputState();
148
+ this.syncInputValueToSelection();
142
149
  }
143
150
  handleArrowDown() {
144
151
  if (!this.showSuggestions)
145
152
  return;
146
153
  this.moveNext();
147
154
  this.syncInputState();
155
+ this.syncInputValueToSelection();
148
156
  }
149
157
  handleArrowUp() {
150
158
  if (!this.showSuggestions)
151
159
  return;
152
160
  this.movePrevious();
153
161
  this.syncInputState();
162
+ this.syncInputValueToSelection();
154
163
  }
155
164
  handleEnter() {
156
165
  if (this.canSelect())
@@ -190,7 +199,6 @@ const Autocomplete = /*@__PURE__*/ proxyCustomElement(class Autocomplete extends
190
199
  if (input)
191
200
  this.originalValue = input.value;
192
201
  this.showSuggestionsPanel();
193
- this.syncInputState();
194
202
  }
195
203
  showSuggestionsPanel() {
196
204
  this.showSuggestions = true;
@@ -199,8 +207,11 @@ const Autocomplete = /*@__PURE__*/ proxyCustomElement(class Autocomplete extends
199
207
  closeSuggestions() {
200
208
  this.reset();
201
209
  this.syncInputState();
210
+ this.syncInputValueToSelection();
202
211
  }
203
212
  deferCloseSuggestions() {
213
+ if (this.blurTimeout)
214
+ clearTimeout(this.blurTimeout);
204
215
  this.blurTimeout = setTimeout(() => {
205
216
  if (this.isFocusOutsideComponent())
206
217
  this.closeSuggestions();
@@ -227,30 +238,34 @@ const Autocomplete = /*@__PURE__*/ proxyCustomElement(class Autocomplete extends
227
238
  return;
228
239
  this.updateAriaExpanded(input);
229
240
  this.updateAriaActiveDescendant(input);
230
- this.updateInputValue(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;
231
249
  }
232
250
  updateAriaExpanded(input) {
233
251
  input.setAttribute('aria-expanded', this.showSuggestions.toString());
234
252
  }
235
253
  updateAriaActiveDescendant(input) {
236
- const hasSelection = this.showSuggestions && this.currentIndex >= 0;
237
- if (hasSelection && this.options[this.currentIndex]) {
254
+ const hasSelection = this.showSuggestions &&
255
+ this.currentIndex >= 0 &&
256
+ this.currentIndex < this.options.length;
257
+ if (hasSelection) {
238
258
  input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);
239
259
  }
240
260
  else {
241
261
  input.removeAttribute('aria-activedescendant');
242
262
  }
243
263
  }
244
- updateInputValue(input) {
245
- input.value = this.currentIndex >= 0
246
- ? this.options[this.currentIndex].value
247
- : this.originalValue;
248
- }
249
264
  shouldShowListbox() {
250
265
  return this.showSuggestions && this.options.length > 0 && this.isInputFocused();
251
266
  }
252
267
  render() {
253
- return (h("div", { key: '71422b55533372a9d8697e648e49f87fe223f4e0' }, h("slot", { key: '951511fd2090b9816c2e6c1a5a40169ea08e72fd', name: "start" }), this.shouldShowListbox() && (h("ol", { key: 'e3f6f14686bc99b6e25d34d8e053f01174f23438', 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 })))))));
268
+ 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 })))))));
254
269
  }
255
270
  get el() { return this; }
256
271
  static get style() { return pennlibsAutocompleteCss; }
@@ -1 +1 @@
1
- {"file":"pennlibs-autocomplete.js","mappings":"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","names":["__stencil_proxyCustomElement"],"sources":["src/components/pennlibs-autocomplete/pennlibs-autocomplete.css?tag=pennlibs-autocomplete&encapsulation=shadow","src/components/pennlibs-autocomplete/pennlibs-autocomplete.tsx"],"sourcesContent":[":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 {\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}","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\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 const input = slot.assignedElements()[0].querySelector(`input#${this.for}`) as HTMLInputElement;\n\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.keyHandlers()[event.key];\n if (handler) {\n event.preventDefault();\n handler();\n }\n }\n\n private keyHandlers() {\n return {\n 'Escape': () => this.handleEscape(),\n 'ArrowDown': () => this.handleArrowDown(),\n 'ArrowUp': () => this.handleArrowUp(),\n 'Enter': () => this.handleEnter()\n };\n }\n\n private handleEscape() {\n this.reset();\n this.syncInputState();\n }\n\n private handleArrowDown() {\n if (!this.showSuggestions) return;\n this.moveNext();\n this.syncInputState();\n }\n\n private handleArrowUp() {\n if (!this.showSuggestions) return;\n this.movePrevious();\n this.syncInputState();\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 this.syncInputState();\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 }\n\n private deferCloseSuggestions(): void {\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 this.updateInputValue(input);\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 && this.currentIndex >= 0;\n if (hasSelection && this.options[this.currentIndex]) {\n input.setAttribute('aria-activedescendant', this.options[this.currentIndex].id);\n } else {\n input.removeAttribute('aria-activedescendant');\n }\n }\n\n private updateInputValue(input: HTMLInputElement): void {\n input.value = this.currentIndex >= 0\n ? this.options[this.currentIndex].value\n : this.originalValue;\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"],"version":3}
1
+ {"file":"pennlibs-autocomplete.js","mappings":"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","names":["__stencil_proxyCustomElement"],"sources":["src/components/pennlibs-autocomplete/pennlibs-autocomplete.css?tag=pennlibs-autocomplete&encapsulation=shadow","src/components/pennlibs-autocomplete/pennlibs-autocomplete.tsx"],"sourcesContent":[":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}","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"],"version":3}
package/dist/docs.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2025-10-29T19:40:29",
2
+ "timestamp": "2025-11-11T21:11:26",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.29.3",