-
-
Notifications
You must be signed in to change notification settings - Fork 338
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
Form builder #915
Form builder #915
Conversation
- Separate styling from the components - Export a base style file for the entire component library
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High-level thoughts
- Overall this looks great!
- I like the
FormConfiguration
structure. - I think we'll probably want to move all the form-builder components out of the component library before we release the library. I don't think the form-builder is nearly as general-purpose as the component library. Even for someone who wants to build a form from JSON, I suspect that most of the time their needs will differ from ours in subtle ways that make this code tough to re-use. I'd lean towards keeping all this code strictly within the Mathesar codebase. I say leave it where it is for now, but keep this in mind when the time comes to clean up the component library for release.
All independent discussion topics are enumerated with (1), (2), etc.
Questions
(1) DynamicInput
props type
vs inputType
-
Why does
DynamicInput
have separate props fortype
andinputType
? I find this design to be confusing, but I don't fully understand the rationale for keeping these props separate, so I'm open to the possibility that they need to remain separate. Can I make a<DynamicInput type=float inputType=checkbox />
or a<DynamicInput type=time inputType=textarea />
? If not, how do I determine the valid combinations oftype
andinputType
? -
My inclination is to merge these two props into one prop that would contain a union type having all the valid combinations of both props.
-
If we're going to keep the props separate, I'd like to:
-
Use better names for the props and types.
Currently we have:
export let type: DynamicInputType; export let inputType: DynamicInputElementType;
That's especially confusing because if I'm holding those four names in my head, I'm likely to equate the
inputType
prop with theDynamicInputType
type. Matching the types with the props requires more memorization that should be necessary.Ideally:
export let fooType: DynamicInputFooType; export let barType: DynamicInputBarType;
Where "foo" and "bar" are more meaningful words. I'm not sure what those words should be, but I can offer suggestions once I gain a better understanding of the rationale for keeping these props separate.
-
Add some code comments explaining: why the two props are separate; and explaining how to ues them with some examples that illustrate the need for separate props.
-
Fix in this PR
(2) Fix uncommitted changes to package-lock.json
I'm seeing local changes to package-lock.json
after restarting the Docker container.
git diff HEAD --shortstat
shows 1 file changed, 53 insertions(+), 56 deletions(-)
package-lock.json
has md5 d36574608c2dd23cfed5347aedc3baf5
Are you seeing the same thing after restarting your container? If not, we should troubleshoot. If so, I think we should commit those changes.
(3) Fix NumberInput
setting value
to string
The two-way binding for value
should reliably produce a type of number | undefined
.
Case 1
- Instantiate a
NumberInput
component with a boundvalue
equal to7
. - In the browser focus on the input, placing the cursor at the end.
- Enter
8
. - Expect
value
to be78
(number). - Observe
value
to be"78"
(string).
Case 2
- Instantiate a
NumberInput
component with a boundvalue
equal to7
. - In the browser focus on the input, placing the cursor at the end.
- Press Backspace once, clearing the contents of the input.
- Expect
value
to beundefined
. - Observe
value
to be""
(empty string).
Furthermore (and on a related note), I'm a bit confused about what's happening with interface HTMLNumberInputElement
, and my hunch is that we should remove it.
My understanding is that the value
property of an <input>
will always be a string, even when setting type='number'
. That means that writing e.target as HTMLNumberInputElement
(in NumberInput.svelte
) is incorrectly providing type assurance that value
will be a number.
I would expect NumberInput
to assume that the underlying DOM element will have a string value, and to be converting that string to a number (i.e. with Number(inputElement.value)
) to produce a binding to the value
prop that satisfies number | undefined
.
(4) DRY out code-level docs for DynamicInput
- I've mentioned this before, but I don't like code comments unless they answer a "why" question. I don't like that we have lots of comments on props, just for the sake of Storybook. If it were my codebase I'd remove all of these. However, this is not my codebase, so I've acquiesced to comments on props just for the sake of Storybook.
- The comment on the
type
prop ofDynamicInput
goes a step further though, which I think is worth addressing, because it actually duplicates code into the comment. If I'm refactoring theDynamicInputType
I'll have no way of knowing I need to update a code comment in a different file. Can we either remove this code comment or change it to something more generic that doesn't have code duplicated in the comment?
(5) In interface FormLayout
, make type
property required
- Since
FormLayout
is used in the discriminated union forFormElement
, it's discriminating property,type
should be required.
(6) Improve naming of some form builder type properties
- In
ConditionalSwitchElement
, renameswitch
tovariable
- In
ConditionalIfElement
, renameif
tovariable
Fix in this PR — or track by opening a new ticket
I wrote these comments with an eye towards copy-pasting them into new tickets if needed.
(7) More gracefully handle some edge cases in NumberInput
Independent sub-issues, with steps to reproduce.
-
Support pasting multi-character values
- Begin with a value of
1
- Into your clipboard, copy
22
- Focus on the input and select all its contents
- Paste
- Expect
22
- Observe
1
Adding a
+
after the group invalidKeyRegex
appear to fix this. - Begin with a value of
-
Support entering
-0.1
- Begin with an empty input
- Enter
-0
and observe-0
which is good - Type
.
- Expect
-0.
- Observe
0
-
Allow empty integer
- Begin with
1
andisInteger
- Place cursor at end
- Press
Backspace
once - Expect empty input
- Observe
0
- Begin with
-
Don't wipe contents when entering
7..
- Begin with an empty input
- Enter
7.
and observe7.
which is good - Type
.
- Expect
7.
- Observe an empty input
-
Retain cursor position
- Begin with
78
- Move cursor between
7
and8
- Enter
a
- Expect
78
with cursor between7
and8
- Observe
78
with cursor after8
- Begin with
-
Don't auto-delete decimal
- Begin with
1.2
- Delete the
2
- Expect
1.
- Observe
1
- Begin with
-
Don't auto-delete minus sign
- Begin with
-8
- Delete the
8
- Expect
-
- Observe an empty input
- Begin with
-
Don't truncate integer value when entering decimal midway through number
- Begin with
23
andisInteger
- Place cursor between
2
and3
- Enter
.
- Expect
23
- Observe
2
- Begin with
-
Remove support for numbers like
8e9
, or allow users to enter them. I can enter89
, then place my cursor between the two digits and typee
. But I can't enter8e9
directly. I don't think we need support entering these kinds of numbers into the input though.
Additional thoughts
- I'd also like to stress test this a bit more after the string type issue is fixed.
- It might be worth using the
beforeinput
event instead of theinput
event.
(8) Add tests for NumberInput
This is the kind of component that really benefits from having some tests in my opinion.
We could pretty easily write tests for the scenarios I've laid out above, as well as other ones that already work correctly.
(9) Remove type='number'
on NumberInput
I hate the browser native number input, and I think most people do too.
- I don't like the tiny up/down buttons. They look bad and don't add much value.
- The value of the input changes in response to scroll events, which can be really annoying sometimes.
I'd prefer that we remove
Considerations:
- We'll need to set inputmode to either
decimal
ornumeric
, potentially with some logic based on the props. - We'll need to adjust some of the logic within the
onInput
function to account for the different behavior ofinputElement.value
when the user enters a non-numeric string likex
into the input.
(10) Remove $$restProps
on BaseInput
component instances
There are 6 occurrences of <BaseInput {...$$restProps}
. I'd rather changes this to <BaseInput {labelController}
(and add a labelController
prop to the component which consumes BaseInput
).
Rationale:
- Props serve as documentation to help developers use the components. The fact that
TextInput
now accepts alabelController
prop is quite opaque. Further, we lose type safety when passinglabelController
values intoTextInput
because the type checker also doesn't understand thatTextInput
accepts aLabelController
in itslabelController
prop. - I've used
{...$$restProps}
in exactly this way before (to reduce code duplication) and I've ended up regretting it later. I'd rather sacrifice some DRY principles for the sake of being more explicit with our props. - I'm okay with
{...$$restProps}
as its used inButton.svelte
because it's placed on a DOM element. In that case, it's clear to the developer that theButton
component can accept arbitrary DOM attributes likedisabled
. I'm also okay with it inDynamicInput.svelte
because it's harder to avoid in that situation. But I'd like to avoid using{...$$restProps}
on custom components if we can easily do so.
(11) Improve focus indicator of InputGroup
component
I really like the changes that simplify TextInput
to a single DOM element, and I want to make sure to keep those changes. There's also clearly value in keeping the "grouped input" functionality. But the changes in this PR lead to a minor stylistic regressions in my opinion.
Examples:
I'd prefer that the focus indicator encompass the entire group, like it did before.
I'd like to keep the changes that add a grey background to the group items as it helps to distinguish the label from the input.
In Storybook, I see that InputGroup
is also intended to handle the use case of multiple inputs grouped together, in which case the focus indicators would need to be separate. But this use-case seems quite esoteric to me. I can't recall ever having seen a UI like that. I'd suggest we abandon any attempts to support it.
(12) Remove !important
in component library CSS
The InputGroup
component introduces CSS which uses !important
. I'm fine using !important
in Mathesar CSS, but I think we should strictly avoid it within the component library since we are aiming for components which can be re-styled.
I haven't yet thought through a strategy for eliminating !important
in this case, but I can help figure it out if needed.
(1) DynamicInput props
|
(2) Fix uncommitted changes to
|
(3) (7) (8) (9) Everything related to
|
(10) Remove
|
(11) (12)
|
Discussion topic statuschecked = resolved
(1)
|
(5) In interface
|
Reflections on patterns for documenting component propsI've given my reasons on the commenting props for (4) in #915 (comment). I am all in for providing documentation but (10) on the other hand is a whole other problem.
|
(1) DynamicInput props type vs inputType
(2) Fix uncommitted changes to package-lock.json
(4) DRY out code-level docs for DynamicInput
(5) In interface FormLayout, make type property required
(10) Remove $$restProps on BaseInput component instances
That's my bad. I would like it to be exported. I'm still adjusting to the pattern of using
My intention with the design of the In <ul>
<li><Label {controller}>First label</Label></li>
<li><Checkbox labelController={controller} /></li>
<li><Label {controller}>Second label</Label></li>
</ul> This story exemplifies a scenario with two properties that both require manually passing the controller to the input:
While the need for those situations will be uncommon, I think we should handle them since we want the component library to be general purpose. We could manually pass an
The problem with relying on the documentation within If you're swayed by the above arguments we can handle the changes in a new ticket. I'd like to add |
I've set this to merge on pass. Excited to be getting this in! Fantastic work on this one, @pavish 🎉 I can tell you put a lot of thought into designing the JSON stuff and I think it's going to be awesome! |
@seancolsen Thanks for setting this to merge on pass but you forgot to approve the PR. :) I'd rather have us reach a consensus that both of us are atleast partially satisfied on, for:
It would be better to create separate issues for both of these and have them as agenda items on our next UI sync call. |
🤣 Oh dear Yeah let's chat about those remaining two topic on our next sync call. |
This PR contains the following:
Sample json config:
The variables property purely represent data related config and would be extended to contain associated validations, formatting etc., in the future.
The layout property instructs our component on how the layout is supposed to appear.
Demos can be viewed by running storybook.
Checklist
Update index.md
).master
branch of the repositoryvisible errors.
Developer Certificate of Origin
Developer Certificate of Origin