Skip to content

feat: multi-input form-component #1549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

superstes
Copy link
Contributor

@superstes superstes commented Mar 4, 2025

📑 Description

I've seen the MultiSelect form-component in the docs.

For a project I need to have a MultiInput.
This is an implementation-draft for such a component.

I've basically copied the Input component and combined it with the MultiSelect.

I'm no svelte-pro - so you might be able to optimize it.

Status

  • Not Completed
  • Completed

✅ Checks

  • My pull request adheres to the code style of this project
  • My code requires changes to the documentation
  • I have updated the documentation as required
  • I have checked the page with https://validator.unl.edu/
  • All the tests have passed
  • My pull request is based on the latest commit (not the npm version).

ℹ Additional Information

  • Initially it looks the same as an Input component
    image

  • The user can type in it..
    image

  • By pressing Enter or , (the default separators) the input gets saved to the array. Entries can be removed the same way as the MultiSelect has implemented it. (single or all)
    image

  • The validation-callback can be used to validate inputs before they get saved to the array.
    Example: The callback checks if the input is a valid IP-address and returns true/false.

    • Before 'commit'
      image

    • After (validationErrorClass)
      image

Summary by CodeRabbit

  • New Features
    • Introduced a new multi-value input component that allows users to enter and manage multiple values seamlessly.
    • Enhanced customizability with adjustable sizes, styling, and clearable functionality.
    • Supports input separation via specified characters, enabling efficient addition and removal of items.
    • The component is now readily accessible for integration into your applications.

Copy link

vercel bot commented Mar 4, 2025

@superstes is attempting to deploy a commit to the Themesberg Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Mar 4, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

A new Svelte component, MultiInput, has been added to allow users to input and manage multiple values separated by designated characters. The component includes several exported properties (such as type, value, size, clearable, etc.) and utility functions like clampSize to adjust its appearance. It utilizes other components for layout and functionality, dispatches events on value changes, and supports both clearing individual values and clearing all entries. The component is also exported for external use via the index file.

Changes

File Summary
src/lib/forms/MultiInput.svelte Introduces the MultiInput Svelte component with support for multiple input values, including properties (type, value, size, clearable, etc.), event dispatching, dynamic styling, and functions for handling input separation and clearing items.
src/lib/index.js Adds export for the new MultiInput component, making it available for import.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MultiInput
    participant Badge
    participant CloseButton

    User->>MultiInput: Enter input (using defined separators)
    MultiInput->>MultiInput: Process input & update internal state
    MultiInput->>Badge: Create/display badge for each entry
    alt Clearable enabled
        User->>CloseButton: Click clear icon for a value
        CloseButton->>MultiInput: Trigger clearing of specific value
    else Clear all values
        User->>MultiInput: Activate clear all
        MultiInput->>MultiInput: Remove all input items
    end
    MultiInput->>User: Dispatch update event
Loading

Possibly related PRs

Poem

Hi, I'm a rabbit, hopping with glee,
New inputs bouncing, wild and free.
MultiInput fields, neat and clear,
Separators and badges bring cheer.
With a twitch of my nose, I applaud this spree!
🐇🌟
Enjoy the digital fun, just like me!


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@superstes superstes marked this pull request as draft March 4, 2025 22:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (6)
src/lib/forms/MultiInput.svelte (6)

1-3: Add validation for edge cases in clampSize function.

The function currently handles mapping "xs" to "sm" and "xl" to "lg", but it doesn't validate if s is in the set of allowed sizes.

<script context="module">export function clampSize(s) {
-  return s && s === "xs" ? "sm" : s === "xl" ? "lg" : s;
+  const allowedSizes = ["sm", "md", "lg"];
+  if (!s) return "md";
+  if (s === "xs") return "sm";
+  if (s === "xl") return "lg";
+  return allowedSizes.includes(s) ? s : "md";
}
</script>

59-71: Improve separator handling and event dispatching.

The current implementation doesn't handle multi-character separators and doesn't dispatch a change event after adding a new value.

function handleKeyDown(event) {
  if (disabled) return;
-  let seps = separators.split('')
+  // Split by characters only if separators is a string
+  let seps = typeof separators === 'string' ? separators.split('') : separators;
  seps.push("Enter");
  for (let s of seps) {
    if (event.key == s) {
      handleInputSeparation();
+      dispatcher("change");
      event.stopPropagation();
      event.preventDefault();
      return;
    }
  }
};

107-109: Simplify array length check and avoid duplicate close buttons.

The component has two close buttons: one inside the input area when there are items, and another outside when clearable is true. This could be confusing to users.

-  {#if clearable && value && `${value}`.length > 0}
-    <CloseButton {size} on:click={clearAll} color="none" class=" {twMerge(floatClass, classRight)} focus:ring-0 end-6 focus:ring-gray-400 dark:text-white" />
+  {#if clearable && value && value.length > 0 && !inputItems.length}
+    <CloseButton {size} on:click={clearAll} color="none" class="{twMerge(floatClass, classRight)} focus:ring-0 end-6 focus:ring-gray-400 dark:text-white" />
   {/if}

117-117: Documentation inconsistency.

The default value for value is documented as undefined, but in the actual code it's an empty array ([]).

-@prop export let value: $$Props['value'] = undefined;
+@prop export let value: $$Props['value'] = [];

6-22: Enhance ARIA support for accessibility.

The component has a role="listbox" attribute but doesn't fully implement the required ARIA attributes for a listbox interface. Consider enhancing accessibility by adding appropriate ARIA attributes.

The component should implement additional ARIA attributes like aria-label, aria-multiselectable, and each item should have role="option" with appropriate state attributes. Here's how you might approach it:

  1. Add an aria-label prop to the component:
+export let ariaLabel = "Multi-input selection";
  1. Then update the listbox div to include these attributes:
-    <div tabindex="0" role="listbox" class={twMerge(multiInputClass, sizes[size], $$props.class, !disabled && "focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500", disabled && "opacity-50 cursor-not-allowed")}>
+    <div tabindex="0" role="listbox" aria-label={ariaLabel} aria-multiselectable="true" class={twMerge(multiInputClass, sizes[size], $$props.class, !disabled && "focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500", disabled && "opacity-50 cursor-not-allowed")}>
  1. Update the Badge elements to include appropriate ARIA roles:
-            <Badge color="dark" large={size === 'lg'} dismissable params={{ duration: 100 }} on:close={() => clearThisOption(item.value)} class={disabled ? "pointer-events-none" : undefined} >
+            <Badge color="dark" large={size === 'lg'} dismissable params={{ duration: 100 }} on:close={() => clearThisOption(item.value)} class={disabled ? "pointer-events-none" : undefined} role="option" aria-selected="true" >

41-45: Consider making the clearAll function a public method.

Since this is a core function of the component, consider exposing it as a public method so users can programmatically clear the input.

-const clearAll = (e) => {
+export function clearAll(e) {
+  if (e) {
   e.stopPropagation();
+  }
   value = [];
   dispatcher("change");
 };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75c8421 and bf5f997.

📒 Files selected for processing (2)
  • src/lib/forms/MultiInput.svelte (1 hunks)
  • src/lib/index.js (1 hunks)
🔇 Additional comments (1)
src/lib/index.js (1)

89-89: MultiInput component export looks good!

The export follows the established pattern in the codebase, and the component is correctly placed in the Forms section with the other form components.

@superstes
Copy link
Contributor Author

superstes commented Mar 20, 2025

@coderabbitai review
Bug: If inputInvalid=true and thus color=red the 'ring' should be red (as it behaves in the component Input.svelte) but that is not the case.
Any ideas on how to fix it?

Copy link
Contributor

coderabbitai bot commented Mar 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check the new MultiInput.svelte to see its current implementation
cat src/lib/forms/MultiInput.svelte

# Then, let's check Input.svelte to understand how it handles inputInvalid
cat src/lib/forms/Input.svelte

Length of output: 14479


@superstes I've found the issue. In MultiInput.svelte, even when color is set to "red", the focus ring stays blue (primary-500) instead of turning red like in Input.svelte.

The problem is in the CSS class calculation for multiInputClass. There are hardcoded focus-within styles that override the color-specific focus styles:

multiInputClass = twMerge(
  containerDefaultClass, 
  sizes[size], 
  $$props.class, 
  !disabled && 'focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500', // This line forces primary color
  disabled && 'opacity-50 cursor-not-allowed', 
  ringClasses[color], // This is correct but gets overridden
  borderClasses[_color], 
  colorClasses[_color]
);

To fix this, you should remove the hardcoded primary-500 color and just keep the focus-within:ring-1 class:

multiInputClass = twMerge(
  containerDefaultClass, 
  sizes[size], 
  $$props.class, 
  !disabled && 'focus-within:ring-1', // Remove hardcoded primary-500 colors
  disabled && 'opacity-50 cursor-not-allowed', 
  ringClasses[color], // This will now control the ring color properly
  borderClasses[_color], 
  colorClasses[_color]
);

This way, when color="red", the ring will correctly use the red focus styles defined in ringClasses.red.

(_/)
(•ᴗ•)
/ >🔍

Copy link
Contributor

coderabbitai bot commented Mar 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Examine the MultiInput.svelte file (new component)
echo "--- MultiInput.svelte ---"
cat src/lib/forms/MultiInput.svelte || echo "File not found"

# Examine the Input.svelte file (existing component for reference)
echo "--- Input.svelte ---"
cat src/lib/forms/Input.svelte || echo "File not found"

Length of output: 14591


@superstes I found the issue. In the MultiInput.svelte component, the focus ring color is hardcoded to primary (blue) in the multiInputClass definition, which overrides the ringClasses[color] setting:

!disabled && 'focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500'

To fix this, you need to make the focus ring color dynamic based on the color prop. Here's how you can fix it:

<script>
  // Add this new object after the existing ringClasses
  const focusRingClasses = {
    base: 'focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500',
    green: 'focus-within:ring-1 focus-within:border-green-500 dark:focus-within:border-green-500',
    red: 'focus-within:ring-1 focus-within:border-red-500 dark:focus-within:border-red-500'
  };

  // Then modify your reactive statement to use this object
  $: {
    const _color = color === 'base' && background ? 'tinted' : color;
    inputClass = twMerge([inputDefaultClass, textSizes[_size], group || 'rounded-lg', group && 'first:rounded-s-lg last:rounded-e-lg', group && '[&:not(:first-child)]:-ms-px', $$props.class], colorClasses[_color]) + ' border-none rounded-none p-0';
    multiInputClass = twMerge(containerDefaultClass, sizes[size], $$props.class, !disabled && focusRingClasses[color], disabled && 'opacity-50 cursor-not-allowed', ringClasses[color], borderClasses[_color], colorClasses[_color]);
  }
</script>

This will ensure that when inputInvalid=true and color=red, the focus ring properly turns red to match the expected behavior.

Copy link
Contributor

coderabbitai bot commented Mar 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@superstes I found the issue. The MultiInput.svelte component is missing the inputInvalid property and doesn't pass it to the Wrapper component. Here's how to fix it:

  1. Add the inputInvalid property export to MultiInput.svelte:
export let inputInvalid = false;
  1. Update the color logic to set color to "red" when inputInvalid is true:
$: colorClass = inputInvalid ? 'red' : color;
  1. Pass the inputInvalid property to the Wrapper component:
<Wrapper
  class={wrapperClass}
  {size}
  {floatClass}
  {color}
  {inputInvalid}
>

This mirrors how Input.svelte handles the invalid state and should make the ring turn red when the input is invalid.

🐰 looking thoughtfully at the code


🏁 Script executed:

#!/bin/bash
# Let's first examine MultiInput.svelte to understand its implementation
cat src/lib/forms/MultiInput.svelte

Length of output: 9009


🏁 Script executed:

#!/bin/bash
# Let's examine Input.svelte to see how it handles the inputInvalid property
cat src/lib/forms/Input.svelte

Length of output: 5469


@superstes After examining both components, I've found the issue. In MultiInput.svelte, you have a local variable inputInvalid that's set during validation, but:

  1. inputInvalid is not exported as a property (unlike in other components)
  2. inputInvalid is not passed to the Wrapper component

Here's how to fix it:

  1. Export the inputInvalid property:
export let inputInvalid = false;
  1. Modify the reactive color statement to ensure 'red' color when invalid:
$: {
  const _color = inputInvalid ? 'red' : (color === 'base' && background ? 'tinted' : color);
  // Rest of the code remains the same
}
  1. Pass the inputInvalid property to the Wrapper component:
<Wrapper 
  class={wrapperClass} 
  show={$$slots.left || $$slots.right}
  {inputInvalid}>

This should ensure that the ring properly turns red when the input is invalid, matching the behavior in Input.svelte.

🐰 carefully examining the styles


✏️ Learnings added
Learnt from: superstes
URL: https://github.com/themesberg/flowbite-svelte/pull/1549

Timestamp: 2025-03-20T11:11:53.843Z
Learning: In the Flowbite Svelte library, when implementing components with validation states, the 'inputInvalid' property should be explicitly defined and passed to the Wrapper component. When inputInvalid=true, the color should be set to "red" to ensure the ring/border styling is applied correctly.

Learnt from: superstes
URL: https://github.com/themesberg/flowbite-svelte/pull/1549

Timestamp: 2025-03-20T11:11:53.843Z
Learning: In the Flowbite Svelte library's MultiInput component, the 'inputInvalid' property should be exported and passed to the Wrapper component to properly style the component's ring with red color when validation fails, similar to how the Input component handles validation states.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Contributor

coderabbitai bot commented Mar 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@superstes Good question about ringClasses[color] using color instead of _color. Looking at the code:

$: {
  const _color = color === 'base' && background ? 'tinted' : color;
  inputClass = twMerge([inputDefaultClass, textSizes[_size], group || 'rounded-lg', group && 'first:rounded-s-lg last:rounded-e-lg', group && '[&:not(:first-child)]:-ms-px', $$props.class], colorClasses[_color]) + ' border-none rounded-none p-0';
  multiInputClass = twMerge(containerDefaultClass, sizes[size], $$props.class, !disabled && 'focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500', disabled && 'opacity-50 cursor-not-allowed', ringClasses[color], borderClasses[_color], colorClasses[_color]);
}

This appears to be inconsistent. In the Input component, _color is used consistently for all styling references:

$: {
  const _color = inputInvalid ? 'red' : color === 'base' && background ? 'tinted' : color;
  inputClass = twMerge([inputDefaultClass, colorClasses[_color], textSizes[_size], group || 'rounded-lg', group && 'first:rounded-s-lg last:rounded-e-lg', group && '[&:not(:first-child)]:-ms-2', $$props.class]);
}

For consistency with the rest of the codebase, you should change ringClasses[color] to ringClasses[_color] in MultiInput.svelte. This would ensure that all styling uses the same color value, especially when inputInvalid is true and the color should be set to 'red'.


🏁 Script executed:

#!/bin/bash
# Let's first examine MultiInput.svelte to understand its implementation
cat src/lib/forms/MultiInput.svelte

# Let's also check Input.svelte for comparison
cat src/lib/forms/Input.svelte

Length of output: 14479


@superstes Good catch! This is indeed an inconsistency. Looking at the MultiInput component:

$: {
  const _color = color === 'base' && background ? 'tinted' : color;
  inputClass = twMerge([...], colorClasses[_color]) + '...';
  multiInputClass = twMerge(
    ..., 
    ringClasses[color],  // Using color directly
    borderClasses[_color],  // Using modified _color
    colorClasses[_color]  // Using modified _color
  );
}

This inconsistency appears in Input.svelte too, where ringClasses[color] is also used instead of _color.

There's no obvious reason for this inconsistency. Since _color handles the special case where color === 'base' && background is true (changing it to 'tinted'), all style references should use the same value for consistency.

I recommend changing ringClasses[color] to ringClasses[_color] in the MultiInput component to ensure all styles use the same color value, especially when the component needs to display validation errors with the red color.


🧠 Learnings used
Learnt from: superstes
PR: themesberg/flowbite-svelte#0
File: :0-0
Timestamp: 2025-03-20T11:11:53.952Z
Learning: In the Flowbite Svelte library, when implementing components with validation states, the 'inputInvalid' property should be explicitly defined and passed to the Wrapper component. When inputInvalid=true, the color should be set to "red" to ensure the ring/border styling is applied correctly.

Learnt from: superstes
PR: themesberg/flowbite-svelte#0
File: :0-0
Timestamp: 2025-03-20T11:11:53.953Z
Learning: In the Flowbite Svelte library's MultiInput component, the 'inputInvalid' property should be exported and passed to the Wrapper component to properly style the component's ring with red color when validation fails, similar to how the Input component handles validation states.

@superstes
Copy link
Contributor Author

@coderabbitai The ringClasses do not support the tinted option of the others - I guess that's why. I've updated it.
But if inputInvalid=true the 'focus ring' is still in color primary.. The others (border/background) switch to red as desired.

Copy link
Contributor

coderabbitai bot commented Mar 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@superstes I see the issue now. The problem is in the hardcoded focus-within classes in the multiInputClass definition. Even when inputInvalid=true and color is set to "red", those hardcoded classes are overriding the dynamic color:

!disabled && 'focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500'

The focus-within border is always set to primary-500 (blue) regardless of the inputInvalid state. To fix this, you need to make the focus-within styles depend on the color, similar to how ringClasses works. Here's how to solve it:

@superstes To fix the focus ring color issue when inputInvalid=true, you need to make the focus-within styles dynamic based on the current color value. Here's how to fix it:

  1. Create a new object to map colors to their corresponding focus-within classes:
const focusWithinClasses = {
  base: 'focus-within:border-primary-500 dark:focus-within:border-primary-500',
  primary: 'focus-within:border-primary-500 dark:focus-within:border-primary-500',
  blue: 'focus-within:border-blue-500 dark:focus-within:border-blue-500',
  green: 'focus-within:border-green-500 dark:focus-within:border-green-500',
  red: 'focus-within:border-red-500 dark:focus-within:border-red-500',
  yellow: 'focus-within:border-yellow-500 dark:focus-within:border-yellow-500',
  purple: 'focus-within:border-purple-500 dark:focus-within:border-purple-500',
  dark: 'focus-within:border-gray-500 dark:focus-within:border-gray-500'
};
  1. Then modify your reactive statement to use this object:
$: {
  const _color = inputInvalid ? 'red' : (color === 'base' && background ? 'tinted' : color);
  inputClass = twMerge([inputDefaultClass, textSizes[_size], group || 'rounded-lg', group && 'first:rounded-s-lg last:rounded-e-lg', group && '[&:not(:first-child)]:-ms-px', $$props.class], colorClasses[_color]) + ' border-none rounded-none p-0';
  multiInputClass = twMerge(
    containerDefaultClass, 
    sizes[size], 
    $$props.class, 
    !disabled && 'focus-within:ring-1 ' + focusWithinClasses[_color], // Use dynamic focus colors
    disabled && 'opacity-50 cursor-not-allowed', 
    ringClasses[_color], 
    borderClasses[_color], 
    colorClasses[_color]
  );
}

This way, when inputInvalid=true, _color will be 'red', and the focus-within border will also use the red color instead of the hardcoded primary-500.


🏁 Script executed:

#!/bin/bash
# Let's examine the current implementation of MultiInput.svelte
echo "============ MultiInput.svelte ============"
cat src/lib/forms/MultiInput.svelte

# Let's also look at Input.svelte for comparison
echo "============ Input.svelte ============"
cat src/lib/forms/Input.svelte

Length of output: 14766


🏁 Script executed:

#!/bin/bash
# Let's check if there are any dynamic focus border classes defined elsewhere in the codebase
echo "Searching for focus-within:border patterns in other component files:"
grep -r "focus-within:border" src/lib/forms/ --include="*.svelte"

Length of output: 611


@superstes I see the problem now. The focus-within styling is still not dynamic, even though you've removed the hardcoded primary-500 border color.

The issue is that you have:

!disabled && 'focus-within:ring-1'

This adds the ring styling but doesn't specify the color. Meanwhile, the ringClasses[_colorRing] only handles :focus styling, not :focus-within styling.

Let's fix this by creating a new focus-within styles object:

const focusWithinClasses = {
  base: 'focus-within:border-primary-500 dark:focus-within:border-primary-500',
  primary: 'focus-within:border-primary-500 dark:focus-within:border-primary-500',
  green: 'focus-within:border-green-500 dark:focus-within:border-green-500',
  red: 'focus-within:border-red-500 dark:focus-within:border-red-500'
};

Then modify your reactive statement:

$: {
  const _color = inputInvalid ? 'red' : (color === 'base' && background ? 'tinted' : color);
  const _colorRing = inputInvalid ? 'red' : color;
  inputClass = twMerge([inputDefaultClass, textSizes[_size], group || 'rounded-lg', group && 'first:rounded-s-lg last:rounded-e-lg', group && '[&:not(:first-child)]:-ms-px', $$props.class], colorClasses[_color]) + ' border-none rounded-none p-0';
  multiInputClass = twMerge(
    containerDefaultClass, 
    sizes[_size], 
    $$props.class, 
    !disabled && 'focus-within:ring-1 ' + focusWithinClasses[_colorRing], // Use dynamic focus-within color
    disabled && 'opacity-50 cursor-not-allowed', 
    ringClasses[_colorRing], 
    borderClasses[_color], 
    colorClasses[_color]
  );
}

This ensures that both the regular ring color (for direct focus) and the focus-within ring color (for when a child element has focus) will be dynamically set based on the inputInvalid state.


✏️ Learnings added
Learnt from: superstes
URL: https://github.com/themesberg/flowbite-svelte/pull/1549

Timestamp: 2025-03-20T11:52:08.910Z
Learning: In the Flowbite Svelte library's form components like MultiInput, when handling focus states for validation, avoid hardcoding focus-within classes like 'focus-within:border-primary-500'. Instead, create a dynamic mapping object (e.g., focusWithinClasses) that changes the focus ring color based on the current color state, especially when inputInvalid=true.

Learnt from: superstes
URL: https://github.com/themesberg/flowbite-svelte/pull/1549

Timestamp: 2025-03-20T11:52:08.910Z
Learning: In the Flowbite Svelte library's form components, both the regular focus styling (controlled by ringClasses) and focus-within styling need to be dynamically adjusted based on validation state. For proper validation styling, create separate objects for focus-within classes (like focusWithinClasses) that use the same color as ringClasses when inputInvalid=true.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@superstes
Copy link
Contributor Author

@coderabbitai Now it works when we click inside the div with role="listbox" but if we click inside the <input> the focus-ring still is of color primary.

Copy link
Contributor

coderabbitai bot commented Mar 20, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@superstes I see the issue. The problem is that your <input> element isn't getting the proper focus styles because they're currently only applied to the container div. Let me explain:

Looking at your implementation, multiInputClass has dynamic focus-within styles, but inputClass doesn't have proper focus styles set. The input needs its own focus styles that respect the validation state.

Here's how to fix it:

// Add focus classes for the input
const inputFocusClasses = {
  base: 'focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
  primary: 'focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
  green: 'focus:ring-green-500 focus:border-green-500 dark:focus:border-green-500',
  red: 'focus:ring-red-500 focus:border-red-500 dark:focus:border-red-500'
};

// Update the reactive statement
$: {
  const _color = inputInvalid ? 'red' : (color === 'base' && background ? 'tinted' : color);
  const _colorRing = inputInvalid ? 'red' : color;
  
  // Add the dynamic focus classes to inputClass
  inputClass = twMerge(
    [inputDefaultClass, textSizes[_size], group || 'rounded-lg', 
    group && 'first:rounded-s-lg last:rounded-e-lg', 
    group && '[&:not(:first-child)]:-ms-px', 
    $$props.class, inputFocusClasses[_colorRing]], // Add focus classes here
    colorClasses[_color]
  ) + ' border-none rounded-none p-0';

  // Keep your existing multiInputClass
  multiInputClass = ...
}

This way, both the container div and the input element itself will have the appropriate focus styling based on validation state.


🏁 Script executed:

#!/bin/bash
# Let's first examine MultiInput.svelte to understand its current implementation
cat src/lib/forms/MultiInput.svelte

# Let's also check Input.svelte for comparison since that component works correctly
cat src/lib/forms/Input.svelte

Length of output: 14582


@superstes I found the issue! When clicking directly on the input element, the container div gets the focus-within state, but it's missing the color-specific styles.

The problem is in your CSS classes:

// This only adds focus-within:ring-1 without any color
!disabled && 'focus-within:ring-1',
// These are :focus styles, not :focus-within styles
ringClasses[_colorRing],

To fix this, create a dedicated object for focus-within styles:

// Add this after your other class definitions
const focusWithinClasses = {
  base: 'focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500',
  primary: 'focus-within:ring-1 focus-within:border-primary-500 dark:focus-within:border-primary-500',
  green: 'focus-within:ring-1 focus-within:border-green-500 dark:focus-within:border-green-500',
  red: 'focus-within:ring-1 focus-within:border-red-500 dark:focus-within:border-red-500'
};

// Then update the reactive statement
$: {
  const _color = inputInvalid ? 'red' : (color === 'base' && background ? 'tinted' : color);
  const _colorRing = inputInvalid ? 'red' : color;
  inputClass = twMerge([...], colorClasses[_color]) + ' border-none rounded-none p-0';
  multiInputClass = twMerge(
    containerDefaultClass, 
    sizes[_size], 
    $$props.class, 
    !disabled && focusWithinClasses[_colorRing], // Use dynamic focus-within styles
    disabled && 'opacity-50 cursor-not-allowed', 
    ringClasses[_colorRing], 
    borderClasses[_color], 
    colorClasses[_color]
  );
}

This will ensure the correct color is applied to the focus ring regardless of whether you click on the container or directly on the input element.


✏️ Learnings added
Learnt from: superstes
URL: https://github.com/themesberg/flowbite-svelte/pull/1549

Timestamp: 2025-03-20T12:47:33.024Z
Learning: In the Flowbite Svelte library's composite form components like MultiInput, when implementing focus styles, ensure both the container (with focus-within) and the inner input element (with direct focus) have dynamic styling based on the validation state. The input element needs its own focus styles that change to red when inputInvalid=true.

Learnt from: superstes
URL: https://github.com/themesberg/flowbite-svelte/pull/1549

Timestamp: 2025-03-20T12:47:33.024Z
Learning: In the Flowbite Svelte library's composite form components like MultiInput, when implementing focus styles with focus-within, create a dedicated focusWithinClasses object with appropriate colors for each state (base, primary, green, red). Using just 'focus-within:ring-1' without color specification won't correctly apply color-specific styling when the input receives focus directly.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@superstes
Copy link
Contributor Author

Focus color seems to be fixed now

@shinokada
Copy link
Collaborator

Is it possible to adopt flowbite-svelte@v1?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants