Skip to content

Commit

Permalink
fix(Autocomplete): forward all events including child components
Browse files Browse the repository at this point in the history
docs(autocomplete): add prop allowNonListValue and example
  • Loading branch information
N00nDay committed Oct 14, 2022
1 parent 7e3f73a commit a2525fc
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 9 deletions.
23 changes: 21 additions & 2 deletions src/lib/components/autocomplete/Autocomplete.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
import { slide, scale } from 'svelte/transition';
import { clickOutside } from '../../actions';
import { onMount, setContext } from 'svelte';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
import { writable, type Writable } from 'svelte/store';
const forwardEvents = forwardEventsBuilder(get_current_component());
export let leading: MaterialIcon | undefined = undefined;
export let name: string;
Expand All @@ -17,10 +23,12 @@
export let value: string | undefined = undefined;
export let autofocus = false;
export let handleLeadingClick: (() => void) | undefined = undefined;
export let allowNonListValue = false;
let visible = false;
let input: HTMLInputElement;
let button: HTMLButtonElement;
let options: Writable<string[]> = writable([]);
function handleOpen() {
visible = true;
Expand All @@ -34,6 +42,10 @@
if (visible) {
if (!value) {
visible = false;
} else if ($options.includes(value)) {
visible = false;
} else if (allowNonListValue) {
visible = false;
} else {
input.value = '';
value = undefined;
Expand Down Expand Up @@ -62,11 +74,18 @@
setContext(AUTOCOMPLETE_CONTEXT_ID, {
autocomplete: true,
handleSelect
handleSelect,
options
});
</script>

<div class={$$props.class} style={$$props.style} use:clickOutside={handleClose}>
<div
class={$$props.class}
use:clickOutside={handleClose}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
>
<!-- TODO: label slot -->
{#if label}
<label
Expand Down
14 changes: 13 additions & 1 deletion src/lib/components/autocomplete/EmptyOption.svelte
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
<script lang="ts">
import { twMerge } from 'tailwind-merge';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
const defaultClass =
'group text-light-content dark:text-dark-content select-none p-0.5 w-full relative py-1.5 pl-2.5 pr-7 w-full rounded-md overflow-hidden';
$: finalClass = twMerge(defaultClass, $$props.class);
</script>

<li class={finalClass} style={$$props.style} role="option" aria-selected="false">
<li
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
role="option"
aria-selected="false"
>
<span class="font-normal block truncate">
{#if $$slots.default}<slot />{:else}No Options Available{/if}
</span>
Expand Down
9 changes: 8 additions & 1 deletion src/lib/components/autocomplete/List.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
import { twMerge } from 'tailwind-merge';
import { useContext } from '../../utils/useContext';
import { AUTOCOMPLETE_CONTEXT_ID } from './Autocomplete.svelte';
import { get_current_component } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
const forwardEvents = forwardEventsBuilder(get_current_component());
useContext({
context_id: AUTOCOMPLETE_CONTEXT_ID,
Expand All @@ -26,7 +31,9 @@

<ul
class={finalClass}
style={$$props.style}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
in:scale={{ start: 0.9, duration: 150 }}
out:scale={{ start: 0.95, duration: 150 }}
role="listbox"
Expand Down
23 changes: 21 additions & 2 deletions src/lib/components/autocomplete/Option.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
import { twMerge } from 'tailwind-merge';
import { useContext } from '$lib/utils/useContext';
import HoverBackground from '../HoverBackground.svelte';
import { get_current_component, onMount } from 'svelte/internal';
import { forwardEventsBuilder, useActions, type ActionArray } from '../../actions';
export let use: ActionArray = [];
import { exclude } from '../../utils/exclude';
import type { Writable } from 'svelte/store';
const forwardEvents = forwardEventsBuilder(get_current_component());
export let value: string;
export let selected = false;
Expand All @@ -23,17 +29,30 @@
});
const {
handleSelect
handleSelect,
options
}: {
handleSelect: (option: string) => void;
options: Writable<string[]>;
} = getContext(AUTOCOMPLETE_CONTEXT_ID);
const defaultClass =
'group text-light-content dark:text-dark-content cursor-pointer select-none p-0.5 w-full';
$: finalClass = twMerge(defaultClass, $$props.class);
onMount(() => {
options.update((current) => [...current, value]);
});
</script>

<li class={finalClass} role="option" aria-selected={selected}>
<li
class={finalClass}
use:useActions={use}
use:forwardEvents
{...exclude($$props, ['use', 'class'])}
role="option"
aria-selected={selected}
>
<button on:click={() => handleSelect(value)} class="w-full text-left">
<div class="relative py-1.5 pl-2.5 pr-7 w-full rounded-md overflow-hidden">
<span class="font-normal block truncate" class:font-semibold={selected}>
Expand Down
58 changes: 55 additions & 3 deletions src/routes/autocomplete/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
<script lang="ts">
import { Autocomplete, Card, Col } from '../../lib';
import { example, props, slots, listSlots, optionProps, emptyOptionSlots } from './examples';
import {
example,
allowNonOptionExample,
props,
slots,
listSlots,
optionProps,
emptyOptionSlots
} from './examples';
import { PropsTable, SlotsTable, BetaComponent, CodeBlock } from '../../docs';
let value1: string;
let value2: string;
let value3: string;
let value4 = 'I am not in the list!';
let options1 = ['Option 1', 'Option 2', 'Option 3'];
let options2 = ['Option 1', 'Option 2', 'Option 3'];
let options3 = ['Option 1', 'Option 2', 'Option 3'];
let options4 = ['Option 1', 'Option 2', 'Option 3'];
let filtered1 = ['Option 1', 'Option 2', 'Option 3'];
let filtered2 = ['Option 1', 'Option 2', 'Option 3'];
let filtered3 = ['Option 1', 'Option 2', 'Option 3'];
let filtered4 = ['Option 1', 'Option 2', 'Option 3'];
function filter1(e: Event) {
const target = e.target as HTMLInputElement;
Expand All @@ -30,7 +41,12 @@
filtered3 = options3.filter((opt) => opt.toLowerCase().includes(target.value.toLowerCase()));
}
function filterOptions(option: string, key: 1 | 2 | 3) {
function filter4(e: Event) {
const target = e.target as HTMLInputElement;
filtered4 = options4.filter((opt) => opt.toLowerCase().includes(target.value.toLowerCase()));
}
function filterOptions(option: string, key: 1 | 2 | 3 | 4) {
if (key === 1) {
if (option) {
filtered1 = options1.filter((opt) => opt.toLowerCase().includes(option.toLowerCase()));
Expand All @@ -43,18 +59,25 @@
} else {
filtered2 = options2;
}
} else {
} else if (key === 3) {
if (option) {
filtered3 = options3.filter((opt) => opt.toLowerCase().includes(option.toLowerCase()));
} else {
filtered3 = options3;
}
} else {
if (option) {
filtered4 = options4.filter((opt) => opt.toLowerCase().includes(option.toLowerCase()));
} else {
filtered4 = options4;
}
}
}
$: filterOptions(value1, 1);
$: filterOptions(value2, 2);
$: filterOptions(value3, 3);
$: filterOptions(value4, 4);
</script>

<Col class="col-24">
Expand Down Expand Up @@ -123,6 +146,35 @@
</Card>
</Col>

<Col class="col-24 md:col-12">
<Card bordered={false}>
<Card.Header slot="header">Autocomplete</Card.Header>
<Card.Content slot="content" class="p-4">
<Autocomplete
name="select-4"
placeholder="Basic"
bind:value={value4}
on:input={filter4}
allowNonListValue
>
<Autocomplete.List slot="list">
{#if filtered4.length > 0}
{#each filtered4 as option}
<Autocomplete.List.Option value={option} selected={value4 === option} />
{/each}
{:else}
<Autocomplete.List.EmptyOption />
{/if}
</Autocomplete.List>
</Autocomplete>

<br />

<CodeBlock language="svelte" code={allowNonOptionExample} />
</Card.Content>
</Card>
</Col>

<Col class="col-24">
<PropsTable component="Autocomplete" {props} />
</Col>
Expand Down
42 changes: 42 additions & 0 deletions src/routes/autocomplete/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ export const props: Prop[] = [
prop: 'handleLeadingClick',
type: '() => void',
default: ''
},
{
id: '10',
prop: 'allowNonListValue',
type: 'boolean',
default: 'false'
}
];

Expand Down Expand Up @@ -136,3 +142,39 @@ export const example = `
{/if}
</Autocomplete.List>
</Autocomplete>`;

export const allowNonOptionExample = `
<script lang="ts">
import { Autocomplete } from 'stwui';
let value = 'I am not in the list!';
let options = ['Option 1', 'Option 2', 'Option 3'];
function filter(e: Event) {
const target = e.target as HTMLInputElement;
filtered = options.filter((opt) => opt.toLowerCase().includes(target.value.toLowerCase()));
}
function filterOptions(option: string) {
if (option) {
filtered = options.filter((opt) => opt.toLowerCase().includes(option.toLowerCase()));
} else {
filtered = options;
}
}
$: filterOptions(value);
</script>
<Autocomplete name="autocomplete" placeholder="Basic" bind:value={value} on:input={filter} allowNonListValue>
<Autocomplete.List slot="list">
{#if filtered.length > 0}
{#each filtered as option}
<Autocomplete.List.Option value={option} selected={value === option} />
{/each}
{:else}
<Autocomplete.List.EmptyOption />
{/if}
</Autocomplete.List>
</Autocomplete>`;

0 comments on commit a2525fc

Please sign in to comment.