Skip to content

Commit

Permalink
feat/input-chip-add-remove-manually (#2347)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mahmoud-zino authored Jan 29, 2024
1 parent 3ff56db commit 69b6825
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-icons-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@skeletonlabs/skeleton": minor
---

feat: InputChip - Implemented function to add/remove chips programatically.
109 changes: 74 additions & 35 deletions packages/skeleton/src/lib/components/InputChip/InputChip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
type InputChipEvent = {
add: { event: SubmitEvent; chipIndex: number; chipValue: string };
remove: { event: MouseEvent; chipIndex: number; chipValue: string };
addManually: { chipIndex: number; chipValue: string };
removeManually: { chipValue: string };
invalid: { event: SubmitEvent; input: string };
invalidManually: { input: string };
};
const dispatch = createEventDispatcher<InputChipEvent>();
Expand Down Expand Up @@ -174,27 +177,53 @@
inputValid = true;
}
function validate(): boolean {
if (!input) return false;
function validateCustom(chip: string) {
return validation === undefined || validation(chip);
}
function validateCount() {
return max === -1 || value.length < max;
}
function validateLength(chip: string) {
return (minlength === -1 || chip.length >= minlength) && (maxlength === -1 || chip.length <= maxlength);
}
function validateWhiteList(chip: string) {
return whitelist.length === 0 || whitelist.includes(chip);
}
function validateDuplicates(chip: string) {
return allowDuplicates || !value.includes(chip);
}
function validate(chip: string = ''): boolean {
if (!chip && !input) return false;
// Format: trim value
input = input.trim();
// Custom validation
if (validation !== undefined && !validation(input)) return false;
// Maximum
if (max !== -1 && value.length >= max) return false;
// Minimum Character Length
if (minlength !== -1 && input.length < minlength) return false;
// Maximum Character Length
if (maxlength !== -1 && input.length > maxlength) return false;
// Whitelist (if available)
if (whitelist.length > 0 && !whitelist.includes(input)) return false;
// Value is unique
if (allowDuplicates === false && value.includes(input)) return false;
// State is valid
return true;
chip = chip !== '' ? chip.trim() : input.trim();
return validateCustom(chip) && validateCount() && validateLength(chip) && validateWhiteList(chip) && validateDuplicates(chip);
}
function addChipCommon(chip: string) {
// Format: to lowercase (if enabled)
chip = allowUpperCase ? chip : chip.toLowerCase();
// Append value to array
value.push(chip);
value = value;
chipValues.push({ val: chip, id: Math.random() });
chipValues = chipValues;
}
function removeChipCommon(chip: string) {
let chipIndex = value.indexOf(chip);
// Remove value from array
value.splice(chipIndex, 1);
value = value;
chipValues.splice(chipIndex, 1);
chipValues = chipValues;
}
function addChip(event: SvelteEvent<SubmitEvent, HTMLFormElement>): void {
function addChipInternally(event: SvelteEvent<SubmitEvent, HTMLFormElement>): void {
event.preventDefault();
// Validate
inputValid = validate();
Expand All @@ -204,30 +233,42 @@
dispatch('invalid', { event, input });
return;
}
// Format: to lowercase (if enabled)
input = allowUpperCase ? input : input.toLowerCase();
// Append value to array
value.push(input);
value = value;
chipValues.push({ val: input, id: Math.random() });
chipValues = chipValues;
addChipCommon(input);
/** @event {{ event: Event, chipIndex: number, chipValue: string }} add - Fires when a chip is added. */
dispatch('add', { event, chipIndex: value.length - 1, chipValue: input });
// Clear input value
input = '';
}
function removeChip(event: SvelteEvent<MouseEvent, HTMLButtonElement>, chipIndex: number, chipValue: string): void {
function removeChipInternally(event: SvelteEvent<MouseEvent, HTMLButtonElement>, chipIndex: number, chipValue: string): void {
if ($$restProps.disabled) return;
// Remove value from array
value.splice(chipIndex, 1);
value = value;
chipValues.splice(chipIndex, 1);
chipValues = chipValues;
removeChipCommon(chipValue);
/** @event {{ event: Event, chipIndex: number, chipValue: string }} remove - Fires when a chip is removed. */
dispatch('remove', { event, chipIndex, chipValue });
}
// Export functions
export function addChip(chip: string) {
// Validate
inputValid = validate(chip);
// When the onInvalid hook is present
if (inputValid === false) {
/** @event {{ input: string }} invalidManually - Fires when the manually added value is invalid. */
dispatch('invalidManually', { input: chip });
return;
}
addChipCommon(chip);
/** @event {{ chipIndex: number, chipValue: string }} addManually - Fires when a chip is added manually. */
dispatch('addManually', { chipIndex: value.length - 1, chipValue: chip });
}
export function removeChip(chip: string) {
if ($$restProps.disabled) return;
removeChipCommon(chip);
/** @event {{ chipValue: string }} removeManually - Fires when a chip is removed manually. */
dispatch('removeManually', { chipValue: chip });
}
// State
$: classesInvalid = inputValid === false ? invalid : '';
// Reactive
Expand All @@ -253,7 +294,7 @@
<!-- Chip Wrapper -->
<div class="input-chip-wrapper {classesChipWrapper}">
<!-- Input Field -->
<form on:submit={addChip}>
<form on:submit={addChipInternally}>
<input
type="text"
bind:value={input}
Expand All @@ -279,9 +320,7 @@
<button
type="button"
class="chip {chips}"
on:click={(e) => {
removeChip(e, i, val);
}}
on:click={(e) => removeChipInternally(e, i, val)}
on:click
on:keypress
on:keydown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
let emails = ['[email protected]', '[email protected]', '[email protected]'];
let musicalGenres = ['rock', 'r&b', 'pop'];
let musicalGenresWhitelist = ['rock', 'pop', 'hip-hop', 'metal', 'techno', 'r&b'];
let manualList: string[] = [];
let manualInputChip: InputChip;
const toastStore = getToastStore();
function isValidEmail(value: string): boolean {
Expand Down Expand Up @@ -161,6 +163,50 @@ function onInvalidHandler(event: any): void {
</svelte:fragment>
</DocsPreview>
</section>
<!-- Add/Remove Chips Programatically -->
<section class="space-y-4">
<h2 class="h2">Add / Remove Chips</h2>
<p>
To add or remove chips programatically, use the exported functions <code class="code">addChip</code> and
<code class="code">removeChip</code> after binding the ChipInput.
</p>
<DocsPreview background="neutral">
<svelte:fragment slot="preview">
<InputChip
bind:this={manualInputChip}
bind:value={manualList}
name="chips-example-programatic"
placeholder="Enter number..."
chips="variant-filled-tertiary"
/>
</svelte:fragment>
<svelte:fragment slot="footer">
<div class="text-center">
<button type="button" class="btn variant-filled" on:click={() => manualInputChip.removeChip(manualList[-1])}
>Remove LastChip</button
>
<button
type="button"
class="btn variant-filled"
on:click={() => manualInputChip.addChip((Math.floor(Math.random() * 10000) + 1).toString())}>Add Random Number</button
>
</div>
</svelte:fragment>
<svelte:fragment slot="source">
<CodeBlock
language="ts"
code={`
let inputChip: InputChip;
// add chip manually
inputChip.addChip('chipValue');
// remove chip manually
inputChip.removeChip('chipValue');
`}
/>
<CodeBlock language="html" code={`<InputChip ... bind:this={inputChip} />`} />
</svelte:fragment>
</DocsPreview>
</section>
<!-- Additional Settings -->
<section class="space-y-4">
<h2 class="h2">Additional Settings</h2>
Expand Down

0 comments on commit 69b6825

Please sign in to comment.