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

text-field: allow floating label to be outside of notched-outline markup #5326

Closed
devversion opened this issue Dec 12, 2019 · 14 comments
Closed
Assignees

Comments

@devversion
Copy link
Contributor

Currently the floating label always needs to be wrapped inside of the notched-outline structure. e.g.

<div class="mdc-notched-outline">
  <div class="mdc-notched-outline__leading"></div>
  <div class="mdc-notched-outline__notch">
    <!-- Needs label here! -->
  </div>
  <div class="mdc-notched-outline__trailing"></div>
</div>

The use case of allowing that the notched-outline is decoupled from the floating label structure-wise is that prefixes and suffixes can have flexible dimensions. To make the outline work properly with flexible prefixes and suffixes, the following things are necessary:

Untitled drawing (2)

  1. The floating label needs to be inside of a container that is matches the boundaries of the input. This ensures that the floating label "looks" like a placeholder in the docked state, and in the floated state it won't be in the prefix container.
  2. The notched-outline needs to overlap the whole text-field. i.e. including prefix and suffix container.

Doing these things allows for flexible prefix and suffix containers (which is a requirement for our MDC-based implementation of the Angular Material form-field).

The problem currently is that we can only do (1) while (2) is not possible when (1) is solved. This is because the floating label cannot be a child of the notched-outline as it would mean that the label is no longer relative to the input container. Both actions/requirements are mutual exclusive at the time of writing.

To make this work the best would be if the notched-outline does not need the label as children, but rather can just accept it through a foundation/adapter method.

@asyncLiz
Copy link
Contributor

Thanks for the issue!

I'm not entirely sure I understand why the label's position would be related to prefix or suffix. No matter how wide a prefix is, the label's position does not change. Prefix and suffix are considered "part of" the input. In your diagram, #prefix and #suffix should be inside #input.

Screen Shot 2019-12-12 at 11 49 58 AM

Outlined Prefix/Suffix https://material.io/components/text-fields/#outlined-text-field

The only time a label's horizontal position changes is if there's a leading icon.

We are actively working on prefix/suffix in #1892, the DOM structure will look something similar to this:

<span class="mdc-text-field__prefix">$</span>
<input class="mdc-text-field__input" value="42">
<span class="mdc-text-field__suffix">.00</span>

Would it be possible to incorporate our prefix/suffix work into mdc-form-field?

@devversion
Copy link
Contributor Author

devversion commented Dec 13, 2019

@asyncLiz Thanks for the quick reply!

I think my graphic was most likely confusing. The actual text-field is the whole container surrounded by the red border. The red border should actually be the outline of the text-field. So we actually consider prefix and suffix as part of the input, but just in a perspective of how it will be composed in the DOM, the native <input> element is in between two containers for prefix and suffix (demonstrated in the drawing)

In our implementation of the Angular Material form-field, we use the prefix and suffix containers for both icons and text and consider a leading icon as prefix. That's probably wrong though in terms of the spec since leading icons and prefixes have different semantics (i.e. label needs to shift horizontally for icons as you explained).

So let's just talk about this issue in in terms of leading icons where the label needs to be shifted (as you said). In that case, consider that the leading icons have an unknown width. How would one be able to ensure that the floating label does not overlap the icon in the non-floating state? I see that MDC achieves this by just adding more horizontal padding to the input, and by shifting the floating label by x pixels. (enforcing that icons have a certain width). This won't work for us since we do not know the width of the icon / nor enforce a certain icon width.

So to make this work, we want the label to be a children of #infixContainer (blue-border box). The MDC floating label uses absolute positioning and is usually positioned relatively to the text-field box (red in the graphic). We take advantage of this, and just make it so that the label is positioned relatively to the #infixContainer. That way the label will never exceed the boundaries of the infix container and always align properly next to the leading icon with unknown width (in the graphic the icon is in #prefix).

Untitled drawing (3)

Does this somehow make sense to you? If it's still somewhat unclear please let me know 😄 Thanks!

@asyncLiz
Copy link
Contributor

I would definitely recommend treating prefix/suffix and icons differently instead of as the same container. I think you'll run into quite a few problems. Off the top of my head, prefix/suffix padding between them and the input is different than icons, and changes if the text is end vs start aligned.

How close is your implementation following the spec? I can talk with design and see if there's capability for icons to be wider than 24px.

I think the biggest problem we'll run into is that the notched outline requires the label to be a child, so that it can querySelector it. I'll do some experiments this morning and see if I can think of a way that would be possible.

@devversion
Copy link
Contributor Author

devversion commented Dec 13, 2019

We think of the prefix and suffix as simple containers where people can put in arbitrary elements. I think we have a ambiguous naming for this though since it's technically not necessarily matching the prefix/suffix semantics explained in the Material Design specification.

For things like icons we'll have some additional default styling inside prefix/suffix containers to ensure there is proper spacing. I totally realize that this might have downsides but we mostly need to do that for backwards compatibility with our old implementation of the form-field and I think in general it makes sense to give users the flexibility to put in anything they want (e.g. some people put in icons wrapped by buttons). In any case though, for icons the goal is to be visually the same as the current leading icon support available in MDC text-field (just with providing the flexibility and backwards compatibility)

I think the biggest problem we'll run into is that the notched outline requires the label to be a > child, so that it can querySelector it. I'll do some experiments this morning and see if I can think of a way that would be possible.

Indeed. That is the problem I ran into. If the label would be decoupled from the notched-outline, then this issue would be solved for us. It should be possible to just keep the current behavior, but provide a possibility to pass in a label element. (It might be necessary to modify how the necessary styles are applied to the label though; could be through a class that is added to the label element)

Also thanks for looking if this is possible!

devversion added a commit to devversion/material2 that referenced this issue Jan 13, 2020
…stom controls better

* Fixes the baseline of form-field controls and their inputs. Previously
the baseline was incorrect due to flex alignment.
* Improves support for custom form-field controls by ensuring that
spacing applied to MDC inputs, also applies to custom controls.
   * The same will be needed for the outline appearance, but
   unfortunately we cannot apply any spacing to the infix until we find
   a solution for: material-components/material-components-web#5326
devversion added a commit to devversion/material2 that referenced this issue Jan 13, 2020
…stom controls better

* Fixes the baseline of form-field controls and their inputs. Previously
the baseline was incorrect due to flex alignment.
* Improves support for custom form-field controls by ensuring that
spacing applied to MDC inputs, also applies to custom controls.
   * The same will be needed for the outline appearance, but
   unfortunately we cannot apply any spacing to the infix until we find
   a solution for: material-components/material-components-web#5326
devversion added a commit to devversion/material2 that referenced this issue Jan 13, 2020
…stom controls better

* Fixes the baseline of form-field controls and their inputs. Previously
the baseline was incorrect due to flex alignment.
* Improves support for custom form-field controls by ensuring that
spacing applied to MDC inputs, also applies to custom controls.
   * The same will be needed for the outline appearance, but
   unfortunately we cannot apply any spacing to the infix until we find
   a solution for: material-components/material-components-web#5326
andrewseguin pushed a commit to angular/components that referenced this issue Jan 15, 2020
…stom controls better (#18161)

* Fixes the baseline of form-field controls and their inputs. Previously
the baseline was incorrect due to flex alignment.
* Improves support for custom form-field controls by ensuring that
spacing applied to MDC inputs, also applies to custom controls.
   * The same will be needed for the outline appearance, but
   unfortunately we cannot apply any spacing to the infix until we find
   a solution for: material-components/material-components-web#5326
@asyncLiz
Copy link
Contributor

Hi @devversion, I'd like to take another look at this issue now that I understand what Angular is doing with MDC a little better.

The tl;dr is that we probably won't support using the label outside of the notched outline anytime soon. It breaks a few critical things, such as the default width gap in the notch of the outline (which is important for SSR to avoid a FOUC).

However, I'm not entirely unconvinced that what you're wanting cannot be achieved. You need the label to be relative the infix container for the filled text field variant, but I do not believe you need it for the outlined variant.

Take a look at how the spec describes leading icons for the two:

Screen Shot 2020-01-27 at 9 05 55 AM
Screen Shot 2020-01-27 at 9 06 00 AM

The label is displaced in the filled variant, but not the outlined variant.
Screen Shot 2020-01-27 at 9 06 04 AM

I would argue that any arbitrary leading content in an outlined text field would also not displace the label. With that assumption, it's no longer necessary to have a notched label inside the infix container.

The downside is that you'll need one more *ngIf statement to hide the label in the infix container when using the outlined variant, but I think that'd be a good compromise.

What do you think?

yifange pushed a commit to yifange/components that referenced this issue Jan 30, 2020
…stom controls better (angular#18161)

* Fixes the baseline of form-field controls and their inputs. Previously
the baseline was incorrect due to flex alignment.
* Improves support for custom form-field controls by ensuring that
spacing applied to MDC inputs, also applies to custom controls.
   * The same will be needed for the outline appearance, but
   unfortunately we cannot apply any spacing to the infix until we find
   a solution for: material-components/material-components-web#5326
@devversion
Copy link
Contributor Author

@asyncLiz Thanks for looking into this. I think that you are right in saying that for the outline appearance, the label doesn't need to be displaced if the floating label is active.

Though, what happens if there is no content inside the text-field and the text-field is not focused? In those cases, the label still needs to be relative to the infix container, as otherwise the label would overlap the leading icon/prefix. MDC doesn't have this issue since they known that the prefix/icon has a fixed width. Due to this limitation, my proposal was to always have the label relative to the infix container, while the outline wraps the prefix, input and suffix elements.

image

I think the issue we are facing here is somewhat special. Ideally, we'd not allow arbitrary prefix/suffix content, but just have fixed sizes as in MDC. This isn't an option for us right now due to backwards compatibility. In the future, we should definitely get rid of this.

Regarding SSR: why is that? Couldn't the default width gap be still determined? The reference to the label still exists. Just the CSS selectors which should apply to the floating label need to be adjusted. I will experiment more with this. In general, it feels more like something we need to address on our side since it's an issue specific to our old implementation of the text-field.

@devversion
Copy link
Contributor Author

@asyncLiz I experimented more with this and found a reasonable solution. We completely follow what MDC does, but instead of assuming a fixed width for the prefix container, we determine the prefix width programmatically and update the floating label horizontal position.

So basically we are doing the same thing MDC does, but just use a dynamic width for the prefix. Does that sound reasonable to you?

@asyncLiz
Copy link
Contributor

asyncLiz commented Feb 4, 2020

Ideally we'd want to not have to do that calculation programmatically, but I think I agree that it is a decent workaround with arbitrary unknown-width prefix content. Especially since we agree that it isn't the long-term solution we want.

We can also place the burden on end users. If they don't want a FOUC while waiting for JS to run, they could set some sort of attribute or property that defines the width of their prefix content.

Do you have any use cases or examples of dynamic prefix content? It would be good to get an idea of what people are using it for so we can bring it back to design for guidance.

@devversion
Copy link
Contributor Author

devversion commented Feb 4, 2020

Yeah, it definitely sounds ideal to avoid the programmatic calculation at all. Do you know how it works currently for the notched-outline? i.e. how can it determine the label width on the server?

I don't know of any good examples of dynamic prefix content. I just know that we need to support it for backwards compatibility, and that the prefix container width is not guaranteed to be always the same. e.g. someone could provide prefixes for a phone number, different icons, or a prefix for a serial number.

@asyncLiz
Copy link
Contributor

asyncLiz commented Feb 4, 2020

It works because the label element is a child of the mdc-notched-outline__notch element. The notch gets its width from its child element: the label. The label gets its length from the width of the text.

Screen Shot 2020-02-04 at 11 07 02 AM

As the width of the label increases, the width of its parent notch increases.

Screen Shot 2020-02-04 at 11 08 42 AM

I do think one important distinction needs to be made: arbitrary leading content vs true prefixes. A true prefix (for example, "+1" in front of a phone number, or "$" in front of a monetary input) is text only and should be part of the #infixContainer in your diagram, not the #prefix container.

Prefixes are aligned with the floating label for filled text fields. They do not change the floating label's horizontal position.

Screen Shot 2020-02-04 at 11 11 50 AM

Arbitrary leading content would include things such as leading icons. They do move the horizontal position of a filled variant's label and the outlined variant's unfocused label. Those should be part of the #prefix container in your diagram.

I think it's critical that this distinction be made in the API instead of putting prefix and leading content together in the same container.

This example, taken from https://material.angular.io/components/form-field/overview, would be an incorrect way of using a prefix and suffix.

Screen Shot 2020-02-04 at 11 17 42 AM

The prefix is not aligned under the floating label, and both the prefix and suffix are not using the correct secondary color to distinguish them from the input's color.

It may be necessary to introduce a new ng-content selector for this distinction. For example:

<mat-form-field>
  <i matPrefix class="custom-icon"></i>
  <span matPrefixText>$</span>
  <input matInput placeholder="Amount" type="number">
  <span matSuffixText>.00</span>
</mat-form-field>

You could keep [matPrefix] for leading content to avoid a breaking change, but encourage [matPrefixText] and [matSuffixText] for Material-themed prefixes and suffixes.

@devversion
Copy link
Contributor Author

devversion commented Feb 5, 2020

@asyncLiz I see. The only thing that confuses me is that the notched-outline requires us to pass an explicit width for the notch. Obviously it can just rely on the content of the notch (i.e. the label).

This mostly unrelated to this whole issue, but do you know what the reason for this is?

I do think one important distinction needs to be made: arbitrary leading content vs true prefixes. A true prefix (for example, "+1" in front of a phone number, or "$" in front of a monetary input) is text only and should be part of the #infixContainer in your diagram, not the #prefix container.

Yes. I totally agree. We chatted about this in our team meeting, and it sounded like we can made this distinction once MDC provides support for this (based on the design doc that you wrote). Using selectors like matPrefixText and matSuffixText matches exactly what we thought of. Eventually, we can get rid of the arbitrary prefix and suffix containers (by also having projection for leading/trailing icons).

If they don't want a FOUC while waiting for JS to run, they could set some sort of attribute or property that defines the width of their prefix content.

Yeah it looks like we'd need to do something like that to make our "unknown" prefix width work with SSR. It's unfortunate that we need to do the measurements, but I don't think there is any other way.

Edit: I got it working in SSR by temporarily rendering the label inside the infix on the server if it's docked. This makes it seem as if the prefix width has been properly measured.

@asyncLiz
Copy link
Contributor

asyncLiz commented Feb 5, 2020

You shouldn't have to provide an explicit width for the notch. It should use the width of its content by default, and when it's upgraded with JS it'll calculate a more precise spec-compliant width and set that in a style attribute.

I was doing a lot of thinking about this last night and trying to come up with ways that it could work. Your idea of rendering it inside #infix before JS runs, then calculating the width of leading content and moving it from #infix into the notched outline is the same idea I had.

Before JS

<label class="mdc-text-field">
  <i class="material-icons mdc-text-field__icon mdc-text-field__icon--leading">event</i>
  <div class="mdc-text-field__infix">
    <input class="mdc-text-field__input" aria-labelledby="label1">
    <span class="mdc-floating-label" id="label1">Label</span>
  </div>
  <div class="mdc-notched-outline">
    <div class="mdc-notched-outline__leading"></div>
    <div class="mdc-notched-outline__notch">
      <span class="mdc-floating-label" aria-hidden="true" style="opacity: 0" id="label2">Label</span>
    </div>
    <div class="mdc-notched-outline__trailing"></div>
  </div>
</label>

After JS

<label class="mdc-text-field">
  <i class="material-icons mdc-text-field__icon mdc-text-field__icon--leading">event</i>
  <div class="mdc-text-field__infix">
    <input class="mdc-text-field__input" aria-labelledby="label2">
    <span class="mdc-floating-label" id="label1" aria-hidden="true" style="opacity: 0">Label</span>
  </div>
  <div class="mdc-notched-outline">
    <div class="mdc-notched-outline__leading"></div>
    <div class="mdc-notched-outline__notch">
      <span class="mdc-floating-label" style="left: 36px;" id="label2">Label</span>
    </div>
    <div class="mdc-notched-outline__trailing"></div>
  </div>
</label>

It's a bit messier because we need a duplicate DOM element. I'll bring it up with my team today to get their opinions.

@devversion
Copy link
Contributor Author

You shouldn't have to provide an explicit width for the notch. It should use the width of its content by default, and when it's upgraded with JS it'll calculate a more precise spec-compliant width and set that in a style attribute.

I guess my question is not necessarily related to this issue, but why is the JS measurement helping with being more spec-compliant? I'm trying to reason about it since it requires us to update the notch whenever the label text changes.

I was doing a lot of thinking about this last night and trying to come up with ways that it could work. Your idea of rendering it inside #infix before JS runs, then calculating the width of leading content and moving it from #infix into the notched outline is the same idea I had.

Thanks for helping with that. Glad that you came up with the same solution. In our case, we just render the infix label on the server, and in the browser we render the non-infix one. In Angular, this doesn't really result in a lot of code mess (fortunately), and also shouldn't have any performance implications.

@asyncLiz
Copy link
Contributor

I'm going to close this issue for now. I think we've come up with a valid workaround offline and using the suggestions above.

It's unlikely that the workaround will be implemented into MDC since we do not support arbitrary prefix content, but it should work for Angular.

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

No branches or pull requests

2 participants