+
Skip to content
This repository was archived by the owner on Oct 14, 2025. It is now read-only.

Conversation

ynotdraw
Copy link
Collaborator

@ynotdraw ynotdraw commented Oct 24, 2024

🚀 Description

This PR is massive, but it's mostly rinse and repeat / copy paste across all of the form components.

  • Added a setCustomValidity() method to each form element to allow for users to provide custom validity messages for the components.
    • Like native, setCustomValidity() only sets the validity message. One must call reportValidity() for the message to appear and the error state to be visually displayed.
    • The validity message replaces the description slot.
    • It leverages the customError validity flag, just like the native elements
  • Added a setValidity() method to each form element to allow for users to have even more granular control over the validation state.
    • Like native, one must call reportValidity() for the message to appear and the error state to be visually displayed.
    • The validity message replaces the description slot.
  • Added the pattern attribute to Input
  • Updated both Input and Textarea to no longer be invalid when maxlength is exceeded.
    • Instead, consumers can check the length and then use setCustomValidity() and setValidity() to provide more information to the user.
  • Updated Checkbox so that removing the required attribute returns it to a valid state.
  • Checkbox's setValidity() no longer accepts a third anchor argument due to it not being utilized. The anchor is automatically set to itself.

📋 Checklist

🔬 How to Test

A few notes worth mentioning:

  • Each test below references $0. This is the host element. As an example, if you are testing glide-core-checkbox, $0 must be <glide-core-checkbox>.
  • As mentioned above, most of the changes are identical across the components. The test cases below for setValidity() and setCustomValidity() are copy/paste. So once you get through one, the others should go pretty quick once you get a feel of what we are testing!
  • After validity is reported via reportValidity(), it will react immediately to validation changes. It's best to refresh between each test.
  • Clicking into an element can cause a blur() to be called, which calls reportValidity(). This means the validity message may appear right away. If this happens, this is likely the cause. If you blur the element, you can try again, but from my troubleshooting in Storybook that always appears to be the cause. Seems to mostly happen with Input and Textarea when I right-click with DevTools and/or inspect.

1️⃣ Checkbox

Removing required

  • Go to Checkbox with required
    • (setting required: true for you!)
  • $0.checkValidity() should return false
  • Remove the required attribute $0.required = false;
  • $0.checkValidity() should now return true

setCustomValidity()

  • Go to Checkbox
  • Set a custom validation message on your input via $0.setCustomValidity('whatever you want here')
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setCustomValidity('')
  • Verify the error message is now gone and the component border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

setValidity()

  • Go to Checkbox
  • Set validity via $0.setValidity({ customError: true }, 'whatever you want here');
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setValidity({})
  • Verify the error message is now gone and the input border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

2️⃣ Checkbox Group

setCustomValidity()

  • Go to Checkbox Group
  • Set a custom validation message on your input via $0.setCustomValidity('whatever you want here')
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setCustomValidity('')
  • Verify the error message is now gone and the component border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

setValidity()

  • Go to Checkbox Group
  • Set validity via $0.setValidity({ customError: true }, 'whatever you want here');
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setValidity({})
  • Verify the error message is now gone and the input border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

3️⃣ Dropdown

setCustomValidity()

  • Go to Dropdown
  • Set a custom validation message on your input via $0.setCustomValidity('whatever you want here')
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setCustomValidity('')
  • Verify the error message is now gone and the component border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

setValidity()

  • Go to Dropdown
  • Set validity via $0.setValidity({ customError: true }, 'whatever you want here');
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setValidity({})
  • Verify the error message is now gone and the input border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

4️⃣ Input

pattern (test 1)

  • Go to Input
  • Set the attribute directly ( pattern="[a-z]{4,8}" ) on glide-core-input
    • Be sure not to click the label or else a blur() event will be called when you open DevTools!
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
  • Input should now have a red border
  • Type 4 characters into the input
  • The error state should disappear
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true

pattern (test 2)

  • Go to Input
  • Set the attribute directly ( pattern="[a-z]{4,8}" ) on glide-core-input
    • Be sure not to click the label or else a blur() event will be called when you open DevTools!
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
  • Input should now have a red border
  • Programmatically remove pattern via $0.pattern = undefined
  • The error state should disappear
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true

setCustomValidity()

  • Go to Input
  • Set a custom validation message on your input via $0.setCustomValidity('whatever you want here')
    • Rather than right-clicking the element and inspecting it, use the inspector tool in Dev Tools instead. If your cursor goes inside of the input at this step, below won't work as you'll blur the input and you'll prematurely call reportValidity().
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setCustomValidity('')
  • Verify the error message is now gone and the component border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

setValidity()

  • Go to Input
  • Set validity via $0.setValidity({ customError: true }, 'whatever you want here');
    • Rather than right-clicking the element and inspecting it, use the inspector tool in Dev Tools instead. If your cursor goes inside of the input at this step, below won't work as you'll blur the input and you'll prematurely call reportValidity().
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setValidity({})
  • Verify the error message is now gone and the input border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

combining invalid states (1)

  • Go to required input
  • $0.validity should only have one flag that is true - valueMissing
  • Now force an invalid state via $0.setCustomValidity('whatever you want here')
  • $0.validity should now have only valueMissing and customError set to true
  • Type into the input
  • $0.validity should now have all flags set to false

combining invalid states (2)

  • Go to required input
  • $0.validity should only have one flag that is true - valueMissing
  • Now force an invalid state via $0.setCustomValidity('whatever you want here')
  • $0.validity should now have only valueMissing and customError set to true
  • $0.setCustomValidity('')
  • $0.validity should have valueMissing as true only, and customError should now be false

5️⃣ Radio Group

setCustomValidity()

  • Go to Radio Group
  • Set a custom validation message on your input via $0.setCustomValidity('whatever you want here')
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setCustomValidity('')
  • Verify the error message is now gone and the component border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

setValidity()

  • Go to Radio Group
  • Set validity via $0.setValidity({ customError: true }, 'whatever you want here');
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setValidity({})
  • Verify the error message is now gone and the input border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

6️⃣ Textarea

setCustomValidity()

  • Go to Textarea
  • Set a custom validation message on your input via $0.setCustomValidity('whatever you want here')
    • Rather than right-clicking the element and inspecting it, use the inspector tool in Dev Tools instead. If your cursor goes inside of the input at this step, below won't work as you'll blur the input and you'll prematurely call reportValidity().
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setCustomValidity('')
  • Verify the error message is now gone and the component border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

setValidity()

  • Go to Textarea
  • Set validity via $0.setValidity({ customError: true }, 'whatever you want here');
    • Rather than right-clicking the element and inspecting it, use the inspector tool in Dev Tools instead. If your cursor goes inside of the input at this step, below won't work as you'll blur the input and you'll prematurely call reportValidity().
  • $0.checkValidity() should return false
  • $0.reportValidity() should return false
    • After calling reportValidity(), the string provided should be rendered where the description slot was and the component should have the error border
  • Now set the following $0.setValidity({})
  • Verify the error message is now gone and the input border has returned to normal
  • $0.checkValidity() should return true
  • $0.reportValidity() should return true
    • (There should be no additional UI changes when running the above two validity checks)

📸 Images/Videos of Functionality

Checkbox

Screenshot 2024-10-24 at 8 02 33 PM

Checkbox Group

Screenshot 2024-10-24 at 8 02 04 PM

Dropdown

Screenshot 2024-10-24 at 8 03 01 PM

Input

Screenshot 2024-10-24 at 8 04 38 PM

Radio Group

Screenshot 2024-10-24 at 8 03 27 PM

Textarea

Screenshot 2024-10-24 at 8 03 54 PM

@ynotdraw ynotdraw self-assigned this Oct 24, 2024
Copy link

changeset-bot bot commented Oct 24, 2024

🦋 Changeset detected

Latest commit: 78f2edd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@crowdstrike/glide-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

Comment on lines +7 to +14
```html
<glide-core-input label="Label" pattern="[a-z]{4,8}"></glide-core-input>
```

```js
// Returns `false`.
document.querySelector('glide-core-input').checkValidity();
```
Copy link
Collaborator Author

@ynotdraw ynotdraw Oct 25, 2024

Choose a reason for hiding this comment

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

These snippets could be removed, as folks should know how to use the native APIs, but I don't think they necessarily hurt anything. I also wanted to call it out, because we do deviate from native's behavior a bit here.

Having small snippets like this will make my job a little easier when talking about the changes with other groups.

Comment on lines +7 to +27
```js
const input = document.querySelector('glide-core-input');

// `setCustomValidity()` sets the validity message on the element
// and places the element in an invalid state.
input.setCustomValidity(
'Please enter a name that is greater than 1 character.',
);

// The element is now marked as invalid.
// Returns `false`.
input.checkValidity();

// Displays the validity message to the user.
// Returns `false`.
input.reportValidity();

// Like native, provide an empty string to change
// the validity state to valid.
input.setCustomValidity('');
```
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Another case where this shouldn't be needed, but it'll make my life a little easier when discussing the changes externally. Better in the release note than Storybook, I think.

Comment on lines +77 to +94
disabled: {
table: {
defaultValue: { summary: 'false' },
type: { summary: 'boolean' },
},
},
'hide-label': {
table: {
defaultValue: { summary: 'false' },
type: { summary: 'boolean' },
},
},
name: {
table: {
defaultValue: { summary: '""' },
type: { summary: 'string' },
},
},
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Reordered these to align with the order above so they match.


await elementUpdated(component);

// Like native, the validity message shouldn't display until `reportValidity()` is called.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Folks should know this if they use the platform, but it may be confusing to some, so I called it out - is this helpful or not?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I like it

this.#isShowValidationFeedback && this.validityMessage,
() =>
html`<span class="validity-message" data-test="validity-message"
>${unsafeHTML(this.validityMessage)}</span
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We have use cases where links are in the error message. Using unsafeHTML allows users to provide markup when required.

$0.setCustomValidity('<span>Here is a <a href="#">Link</a></span>');

I'm only aware of one use case at the moment where a link in the markup is needed - so the regular, non-HTML string path is the most likely used.

$0.setCustomValidity('validity message');

Native's setValidity() and setCustomValidity() both accept string arguments for the content. I'm trading being closer to native over using another slot and then breaking away from native APIs. I think it would be confusing. To me, the tradeoff is worth it.

We also won't do any sanitization on this content ourselves. Consumers should do it at their level rather than here.

});

it('is valid when filled in, readonly, and value exceeds `maxlength`', async () => {
it('is valid when `value` is empty and `required` is updated to `false` programmatically', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

... here!

Comment on lines +173 to +177
if (
!this.required &&
this.#internals.validity.valueMissing &&
!this.value
) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the bug fix for programmatically removing required and resetting the validity state to valid

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Radio Group has some issues with value and checked when programmatic changes happen. I'll be working on these right after this merges.

});

it('is valid when filled in, disabled, and value exceeds `maxlength`', async () => {
it('blurs the textarea and reports validity if `blur` is called', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Similar to input - diff is a lot due to the removal of tests. These are existing tests... until...

.to.be.true;
});

it('sets the validity message with `setCustomValidity()`', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

here! (much shorter)

@ynotdraw ynotdraw marked this pull request as ready for review October 25, 2024 19:10

await elementUpdated(component);

// Like native, the validity message shouldn't display until `reportValidity()` is called.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like it

type: {
summary: 'method',
detail: `
// Sets a custom validity message, similar to the native "setCustomValidity" method.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this sets a new precedent. It's different than methods like reportValidity and other native methods and properties in that it refers to native and explains how native works. Not saying that's bad or good—just that it's inconsistent with the rest of the story

Also, is message actually optional as it says below?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, good point. This mostly follows native, except of course for the optional markup it can receive. But as mentioned above, at least for the time being, this is an edge case (but a known one we need to support!). I'd be in favor of following our rule of "only document what native doesn't already do, or when we deviate from native". In that case, I can remove this documentation across all components to not set a new precedent.

I am working on a form example that I'll pass along to folks. It could also go up a layer in our Design System if we'd like.

Also, is message actually optional as it says below?

I'll double check this in the next commit 🫡

Copy link
Collaborator

Choose a reason for hiding this comment

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

In that case, I can remove this documentation across all components to not set a new precedent.

Sweet. My thought exactly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sweet, updated with 78f2edd

@ynotdraw ynotdraw merged commit 5d6a013 into main Oct 29, 2024
7 checks passed
@ynotdraw ynotdraw deleted the set-validity branch October 29, 2024 16:45
@github-actions github-actions bot mentioned this pull request Oct 29, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载