Skip to content

Commit 980596b

Browse files
Fix SelectField menu icons & toggle collapsing btn
Fix SelectField toggle button not collapsing.
1 parent f3bda28 commit 980596b

File tree

4 files changed

+58
-22
lines changed

4 files changed

+58
-22
lines changed

.changeset/bright-donuts-appear.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte-ux': patch
3+
---
4+
5+
Fix regression of SelectField MenuOption icons no longer being passed to MenuItems. Fix SelectField toggle button not collapsing. Add `element` and `iconSpan` exports to `Button` so that the elements can be directly accessed from ancestor contexts.

packages/svelte-ux/src/lib/components/Button.svelte

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
export let iconOnly = icon !== undefined && $$slots.default !== true;
2121
export let actions: Actions<HTMLAnchorElement | HTMLButtonElement> | undefined = undefined;
2222
23+
export let element: HTMLAnchorElement | HTMLButtonElement | undefined | null = undefined;
24+
export let iconSpan: HTMLSpanElement | undefined | null = undefined;
25+
2326
export let loading: boolean = false;
2427
export let disabled: boolean = false;
2528
export let rounded: boolean | 'full' | undefined = undefined; // default in reactive groupContext below
@@ -240,6 +243,7 @@
240243
<!-- svelte-ignore a11y-no-static-element-interactions -->
241244
<svelte:element
242245
this={href ? 'a' : 'button'}
246+
bind:this={element}
243247
{href}
244248
{target}
245249
{type}
@@ -255,11 +259,11 @@
255259
on:blur
256260
>
257261
{#if loading}
258-
<span transition:slide={{ axis: 'x', duration: 200 }}>
262+
<span bind:this={iconSpan} transition:slide={{ axis: 'x', duration: 200 }}>
259263
<ProgressCircle size={16} width={2} class={cls(theme.loading, classes.loading)} />
260264
</span>
261265
{:else if icon}
262-
<span in:slide={{ axis: 'x', duration: 200 }}>
266+
<span bind:this={iconSpan} in:slide={{ axis: 'x', duration: 200 }}>
263267
{#if typeof icon === 'string' || 'icon' in icon}
264268
<!-- font path/url/etc or font-awesome IconDefinition -->
265269
<Icon data={asIconData(icon)} class={cls('pointer-events-none', theme.icon, classes.icon)} />

packages/svelte-ux/src/lib/components/SelectField.svelte

+47-19
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
import type { MenuOption } from '$lib/types/options';
2020
import type { ScrollIntoViewOptions } from '$lib/actions';
2121
22+
type LogReason<T extends Event = any> = {
23+
reason: string,
24+
event?: T
25+
}
26+
2227
const dispatch = createEventDispatcher<{
2328
change: { value: any; option: any };
2429
inputChange: string;
@@ -51,6 +56,8 @@
5156
: undefined;
5257
5358
let originalIcon = icon;
59+
let toggleButtonElement: ComponentProps<Button>['element'] = undefined;
60+
let toggleButtonIconSpan: ComponentProps<Button>['iconElement'] = undefined;
5461
5562
export let scrollIntoView: Partial<ScrollIntoViewOptions> = {};
5663
@@ -194,17 +201,27 @@
194201
});
195202
}
196203
204+
function isToggleButtonClicked(ev: MouseEvent) {
205+
return toggleButtonIconSpan && toggleButtonIconSpan === ev.target;
206+
}
207+
208+
function isToggleButtonRelated(ev: MouseEvent|FocusEvent) {
209+
return toggleButtonElement && toggleButtonElement === ev.relatedTarget;
210+
}
211+
197212
function onChange(e: ComponentEvents<TextField>['change']) {
198213
logger.debug('onChange');
199214
200215
searchText = e.detail.inputValue as string;
201216
dispatch('inputChange', searchText);
202-
show();
217+
show({ reason: "onChange", event: e });
203218
}
204219
205-
function onFocus() {
206-
logger.debug('onFocus');
207-
show();
220+
function onFocus(event: FocusEvent) {
221+
if (isToggleButtonRelated(event)) {
222+
return;
223+
}
224+
show({ reason: "onFocus", event });
208225
}
209226
210227
function onBlur(e: FocusEvent|CustomEvent<any>) {
@@ -216,9 +233,10 @@
216233
fe.relatedTarget instanceof HTMLElement &&
217234
!menuOptionsEl?.contains(fe.relatedTarget) && // TODO: Oddly Safari does not set `relatedTarget` to the clicked on menu option (like Chrome and Firefox) but instead appears to take `tabindex` into consideration. Currently resolves to `.options` after setting `tabindex="-1"
218235
fe.relatedTarget !== menuOptionsEl?.offsetParent && // click on scroll bar
219-
!fe.relatedTarget.closest('menu > [slot=actions]') // click on action item
236+
!fe.relatedTarget.closest('menu > [slot=actions]') && // click on action item
237+
!isToggleButtonRelated(fe) // click on toggle button
220238
) {
221-
hide('blur');
239+
hide({ reason: 'blur', event: e });
222240
} else {
223241
logger.debug('ignoring blur');
224242
}
@@ -237,7 +255,7 @@
237255
break;
238256
239257
case 'ArrowDown':
240-
show();
258+
show({ reason: `onKeyDown: '${e.key}'`, event: e });
241259
if (highlightIndex < filteredOptions.length - 1) {
242260
highlightIndex++;
243261
} else {
@@ -247,7 +265,7 @@
247265
break;
248266
249267
case 'ArrowUp':
250-
show();
268+
show({ reason: `onKeyDown: '${e.key}'`, event: e });
251269
if (highlightIndex > 0) {
252270
highlightIndex--;
253271
} else {
@@ -259,7 +277,7 @@
259277
case 'Escape':
260278
if (open) {
261279
inputEl?.focus();
262-
hide('escape');
280+
hide({ reason: 'escape', event: e });
263281
}
264282
break;
265283
}
@@ -274,15 +292,18 @@
274292
}
275293
}
276294
277-
function onClick() {
278-
logger.debug('onClick');
279-
show();
295+
function onClick(event: MouseEvent) {
296+
if (isToggleButtonClicked(event) || isToggleButtonRelated(event)) {
297+
return;
298+
}
299+
show({ reason: 'onClick', event });
280300
}
281301
282-
function show() {
283-
logger.debug('show');
302+
function show<T extends LogReason = any>(reason: string|T = '') {
303+
const doShow = !disabled && !readonly;
304+
logger.debug('show', { ...(typeof(reason) === "string" ? { reason } : reason), openBefore: open, openAfter: doShow });
284305
285-
if (!disabled && !readonly) {
306+
if (doShow) {
286307
if (open === false && clearSearchOnOpen) {
287308
searchText = ''; // Show all options on open
288309
}
@@ -291,8 +312,8 @@
291312
}
292313
}
293314
294-
function hide(reason = '') {
295-
logger.debug('hide', { reason });
315+
function hide<T extends LogReason = any>(reason: string|T = '') {
316+
logger.debug('hide', { ...(typeof(reason) === "string" ? { reason } : reason), openBefore: open, openAfter: false });
296317
open = false;
297318
highlightIndex = -1;
298319
}
@@ -417,7 +438,13 @@
417438
icon={toggleIcon}
418439
class="text-black/50 p-1 transform {open ? 'rotate-180' : ''}"
419440
tabindex="-1"
420-
on:click={() => {logger.debug("toggleIcon clicked")}}
441+
bind:element={toggleButtonElement}
442+
bind:iconElement={toggleButtonIconSpan}
443+
on:click={(e) => {
444+
logger.debug("toggleIcon clicked", { event: e, open })
445+
const func = !open ? show : hide;
446+
func({ reason: "toggleIcon", event: e });
447+
}}
421448
/>
422449
{/if}
423450
</span>
@@ -434,7 +461,7 @@
434461
{disableTransition}
435462
moveFocus={false}
436463
bind:open
437-
on:close={() => hide('menu on:close')}
464+
on:close={e => hide({ reason: 'menu on:close', event: e})}
438465
{...menuProps}
439466
>
440467
<!-- TODO: Rework into hierarchy of snippets in v2.0 -->
@@ -492,6 +519,7 @@
492519
theme.option,
493520
classes.option
494521
)}
522+
icon={option.icon}
495523
scrollIntoView={{ condition: index === highlightIndex, onlyIfNeeded: inlineOptions, ...scrollIntoView }}
496524
role="option"
497525
aria-selected={option === selected ? "true" : "false"}

packages/svelte-ux/src/routes/docs/components/SelectField/+page.svelte

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
1414
import { delay } from '$lib/utils/promise';
1515
import { cls } from '$lib/utils/styles';
16-
import Icon from '$lib/components/Icon.svelte';
1716
import type { MenuOption } from '$lib/types/options';
1817
1918
let options: MenuOption[] = [

0 commit comments

Comments
 (0)