• MDC – Text field


    前言

    Angular Material 只有 Form field, 但 Material Design 有份 Text field 和 Form field, Form field 是给 checkbox 和 radio 用的, Text field 则是给 input, select 用的.

    它就是一个框, 里头包含了 label 和 accessor (e.g. input, select, textarea 等)

    封装了 floating label, error color, helper text 等等功能.

    Input Text

    先来一个最简单的 input text 的 field

    Filled and Outlined

    Material 3 中, 有 2 种 text field 设计, 一个是 filled 一个是 outlined, 下面以 outlined 作为例子.

    HTML

    <label class="mdc-text-field mdc-text-field--outlined">
      <span class="mdc-notched-outline">
        <span class="mdc-notched-outline__leading"></span>
        <span class="mdc-notched-outline__notch">
          <span class="mdc-floating-label">Your Name</span>
        </span>
        <span class="mdc-notched-outline__trailing"></span>
      </span>
      <input type="text" name="firstName" class="mdc-text-field__input"/>
    </label>

    它包含了 3 给组件.

    1. text-field

    2. notched-outline (notch 是 缺口, 槽口, 凹槽的意思)

    3. floating-label

    Yarn add

    有 3 个组件所以需要安装 3 给 module. MDC 把组件分的非常细.

    yarn add @material/textfield
    yarn add @material/notched-outline
    yarn add @material/floating-label

    Scss

    @use '@material/floating-label/mdc-floating-label';
    @use '@material/notched-outline/mdc-notched-outline';
    @use '@material/textfield';
    @include textfield.core-styles;

    也是三剑客一起上

    TypeScript

    import { MDCTextField } from '@material/textfield';
    document.querySelectorAll(
    '.mdc-text-field').forEach(element => { new MDCTextField(element); });

    记得, 绝大部分 MDC 的组件用法都是 HTML, Scss, TypeScript 三边都需要的 (除非没有交互才不需要 TypeScript)

    效果

    Error Design

    Material Design 的体验是 inline error, 就是说尽可能快的让用户知道它 invalid 了.

    比如上面这个 input 是 required 的, 当用户 blur 以后马上就会变红色.

    required

    MDC 对游览器原生的 validation 都有做处理. 比如 required

    <input type="text" class="mdc-text-field__input" required />

    当 input 有 required 属性时, label 会自动加上星号 *, 当用户没有填写内容 unblur 后框框会变红色.

    以下是相关源码, 来自 MDCTextFieldFoundation class

    从第三段可以看出, MDC 是通过游览器 native validation 实现验证的. MDC 本身并没有任何验证逻辑.

    Helper Text

    参考: Text field helper text

    Helper text 就是框框下面的一行提示. 可以用来提示用户如何填写资料 (比如格式, 例子等等).

    它是 text field 下的另一个 module 负责

    HTML

    <label class="mdc-text-field mdc-text-field--outlined">
      <span class="mdc-notched-outline">
        <span class="mdc-notched-outline__leading"></span>
        <span class="mdc-notched-outline__notch">
          <span class="mdc-floating-label">Your Name</span>
        </span>
        <span class="mdc-notched-outline__trailing"></span>
      </span>
      <input type="text" class="mdc-text-field__input" />
    </label>
    <div class="mdc-text-field-helper-line">
      <div class="mdc-text-field-helper-text">helper text</div>
    </div>

    helper-line element 需要放到 text field 的 next sibling, 位置很重要哦

    text field 是通过 nextElementSibling 去找到它对应的 helper text 的

    提醒: helper text 是 sibling, 很容易就会破坏 Flex / Grid 布局, 最好是把它们 wrap 起来, wrap 起来后 lable 是 inline, input 有 default width (also depend on on font-size), 所以要把 label set 成 block element 或者 width 100% 哦

    Scss

    @use '@material/textfield/helper-text';
    @include helper-text.helper-text-core-styles;

    TypeScript

    import { MDCTextFieldHelperText } from '@material/textfield/helper-text';
    
    document.querySelectorAll('.mdc-text-field-helper-text').forEach(element => {
      new MDCTextFieldHelperText(element);
    });

    persistent

    细看你会发现它默认的体验是: 当 focused, helper text 会出现, 当 blur 以后 helper text 会消失.

    如果希望它一直出现, 可以加上 class "mdc-text-field-helper-text--persistent"

    <div class="mdc-text-field-helper-text mdc-text-field-helper-text--persistent">helper text</div>

    或用 TypeScript set

    document.querySelectorAll('.mdc-text-field-helper-text').forEach(element => {
      const helperText = new MDCTextFieldHelperText(element);
      helperText.foundationForTextField.setPersistent(true);
    });

    效果

    as error message

    在 Material Design 手册中有说到, error message 和 helper text 是 share 同一个位置的.

    也就是说当 error message 出现的时候 helper text 就必须被替换掉. MDC 没有直接提供这样的体验支持, 但是可以用 TS 动态 setup.

    先看看没有 helper text 但是有 error message 的情况怎么写

    <div class="mdc-text-field-helper-text mdc-text-field-helper-text--validation-msg">
      Name is required
    </div>

    加上 class "mdc-text-field-helper-text--validation-msg"

    或者 TypeScript set

    helperText.foundationForTextField.setValidation(true);

    效果

    如果想同时处理 error message 和 helper text 需要直接操作 helperText 对象

    document.querySelectorAll('.mdc-text-field-helper-text').forEach(element => {
      const helperText = new MDCTextFieldHelperText(element);
      helperText.foundationForTextField.setContent('helper text');
      helperText.foundationForTextField.setPersistent(true);
      // 监听 form-field attr 出现 invalid 的 class 就切换到 error message
      helperText.foundationForTextField.setContent('Name is required');
      helperText.foundationForTextField.setValidation(true);
    });

    关键就是监听到 invalid 时切换成 error message. 流程大概是

    1. 找到对应的 form field (它们是 sibling 关系, 所以找的到)

    2. 用 mutation 监听 class 的变化, invalid 时 text field 会有 class "mdc-text-field--invalid" (依赖这个比较稳定)

    3. 操作 helperText 对象, 却换成 error message 或者切换回来.

    如果有 2 个验证, 比如 required + email 想针对不同 validation 时输出正确的 error message 他也没有 build-in 的. 需要用 TS 去换 error message.

    另外我也发现了, Angular Material 虽然用了 MDC 的 text field 但它没有用 helper text. 反而是自己实现了一套.

    character counter & max length

    helper text 还有一个常用的场景就是 max length 提示

    它是搭配 input maxlength 一起使用的

    helper text 不需要写任何内容, MDC 会自动填上.

    另外, character counter 和 helper text 是不冲突的哦, 因为一个在左边一个在右边

    <div class="mdc-text-field-helper-line">
      <div class="mdc-text-field-helper-text">helper text</div>
      <div class="mdc-text-field-character-counter"></div>
    </div>

    Textarea

    text field 里面也可以放 textarea

    <label class="mdc-text-field mdc-text-field--outlined mdc-text-field--textarea">
      <span class="mdc-notched-outline">
        <span class="mdc-notched-outline__leading"></span>
        <span class="mdc-notched-outline__notch">
          <span class="mdc-floating-label">Your Name</span>
        </span>
        <span class="mdc-notched-outline__trailing"></span>
      </span>
      <span class="mdc-text-field__resizer">
        <textarea class="mdc-text-field__input" rows="4" cols="40"></textarea>
      </span>
    </label>
    View Code

    和 input 的结构一样, 只是 text field 需要添加 class "mdc-text-field--textarea" 和里面使用一个 span wrap 着 textarea

    这个 span wrapper 是为了让它支持 resize, 如果不需要 resize 可以省略掉这个 span

    效果

    有一个点需要注意, 它的 resize 是 apply 在 span 上, 而不是 textarea 哦

     

    而且是双边 resize: both, 没有接口可以设置单边 resize, 只能通过 override 它的 style="resize: vertical"

    Input Date

    MDC 没有 datepicker, MWC 也没有 datepicker, 因为 Material Design 团队一直画不出他们满意的 datepicker 所以干脆就不画了.

    一直等到 Material Design 3 才终于画出来了, 但是从稿到开发成型, 以 Material Design 团队的实力, 最少也需要 1 – 2 年的时间. 所以短期是肯定用不了的.

    目前最好的方案是用原生的 input date 替代.

    但是它也有许多问题哦. 我们来看看吧.

    label missing

    直接把 type 改成 date 就可以了

    <input type="date" class="mdc-text-field__input" />

    效果

    第一个是正常的表现, 第二个的 label 不见了, 之所以会这样是因为

    width = 0px 了, 相关代码如下

    有点复杂, 我觉得大概率就是一个 bug 而已 (因为 Firefox 没有这个问题), 所以不用去理会它.

    它应该是太早尝试获取 width 值了, 可能游览器还没有 render 好, 做一个 delay 是可以解决了

    document.querySelectorAll('.mdc-text-field').forEach(element => {
      const textField = new MDCTextField(element);
      requestAnimationFrame(() => {
        textField.layout();
      });
    });

    icon mission

    上面是原生 input date 的样子, Chrome 是有一个 icon 的. 因为它和 Firefox 的体验不同.

    Chrome 只有点击 icon 才能召唤出 picker (在 laptop), Firefox 点击任何一个地方都会召唤出 picker.

    也就是说使用 Chrome, 没有 icon = 没有 picker.

    那为什么 MDC 没有 icon 呢? 

    因为这个 icon 会破坏 Material Design, 而且不容易用 CSS 去调整它, 加上它不属于 W3C 规范, 所以 MDC 果断把它给关了. stackoverflow – Hide the calendar icon in Google Chrome

    所以, laptop 没有 picker 用, 体验就掉了. 怎么办呢?

    stackoverflow – Method to show native datepicker in Chrome

    可以通过原生 API, input.showPicker() 召唤出 picker. 但需要很新的 browser 才支持 Can I use – showPicker

    Chrome 99 (2022 年 3 月发布的)

    trailing icons

    参考: Text field icon

    有了这个 API, 那么解决思路就是搞一个 calendar trailing icons, 点击以后调用 show.picker()

    HTML

    <input type="date" class="mdc-text-field__input" />
    <i
      class="material-icons mdc-text-field__icon mdc-text-field__icon--leading"
      tabindex="0"
      role="button"
      >event</i
    >

    放到 input 的后面, 注意: tabindex="0" 一定要放哦, 不然是无法点击的.

    Scss

    @use "@material/textfield/icon";
    
    @include icon.icon-core-styles;

    TypeScript

    const textFieldIcon = new MDCTextFieldIcon(document.querySelector('.mdc-text-field__icon')!);
    textFieldIcon.listen('click', () => {
      (
        textFieldIcon.root.previousElementSibling! as unknown as { showPicker: () => void }
      ).showPicker();
    });

    效果

    当发现游览器不支持

    if ('showPicker' in HTMLInputElement.prototype) {
      // showPicker() is supported.
    }

    可以直接把 icon hide 起来或者 pointer-event: none. 不然点击率没有反应很尴尬.

    Select

    参考: Github – Select Menus

    安装 module

    yarn add @material/list
    yarn add @material/menu-surface
    yarn add @material/menu
    yarn add @material/select

    HTML

    <div class="mdc-select mdc-select--outlined demo-width-class my-demo-select">
      <div class="mdc-select__anchor" aria-labelledby="outlined-select-label">
        <span class="mdc-notched-outline">
          <span class="mdc-notched-outline__leading"></span>
          <span class="mdc-notched-outline__notch">
            <span id="outlined-select-label" class="mdc-floating-label">Pick a Food Group</span>
          </span>
          <span class="mdc-notched-outline__trailing"></span>
        </span>
        <span class="mdc-select__selected-text-container">
          <span id="demo-selected-text" class="mdc-select__selected-text"></span>
        </span>
        <span class="mdc-select__dropdown-icon">
          <svg class="mdc-select__dropdown-icon-graphic" viewBox="7 10 10 5" focusable="false">
            <polygon
              class="mdc-select__dropdown-icon-inactive"
              stroke="none"
              fill-rule="evenodd"
              points="7 10 12 15 17 10"
            ></polygon>
            <polygon
              class="mdc-select__dropdown-icon-active"
              stroke="none"
              fill-rule="evenodd"
              points="7 15 12 10 17 15"
            ></polygon>
          </svg>
        </span>
      </div>
    
      <div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
        <ul class="mdc-deprecated-list" role="listbox" aria-label="Food picker listbox">
          <li
            class="mdc-deprecated-list-item mdc-deprecated-list-item--selected"
            aria-selected="true"
            data-value=""
            role="option"
          >
            <span class="mdc-deprecated-list-item__ripple"></span>
          </li>
          <li
            class="mdc-deprecated-list-item"
            aria-selected="false"
            data-value="grains"
            role="option"
          >
            <span class="mdc-deprecated-list-item__ripple"></span>
            <span class="mdc-deprecated-list-item__text"> Bread, Cereal, Rice, and Pasta </span>
          </li>
          <li
            class="mdc-deprecated-list-item mdc-deprecated-list-item--disabled"
            aria-selected="false"
            data-value="vegetables"
            aria-disabled="true"
            role="option"
          >
            <span class="mdc-deprecated-list-item__ripple"></span>
            <span class="mdc-deprecated-list-item__text"> Vegetables </span>
          </li>
          <li
            class="mdc-deprecated-list-item"
            aria-selected="false"
            data-value="fruit"
            role="option"
          >
            <span class="mdc-deprecated-list-item__ripple"></span>
            <span class="mdc-deprecated-list-item__text"> Fruit </span>
          </li>
        </ul>
      </div>
    </div>
    View Code

    注意: 用 mdc-deprecated-list 是因为当前 MDC 正在升级中...

    Scss

    @use '@material/list/mdc-list';
    @use '@material/menu-surface/mdc-menu-surface';
    @use '@material/menu/mdc-menu';
    @use '@material/select/styles';
    @use '@material/select';
    
    .demo-width-class {
      width: 400px;
    }
    .my-demo-select {
      @include select.outlined-density(-2); /* 注: 我用的是 outlined */
    }

    docs 有说, 需要 set width

    TypeScript

    import {MDCSelect} from '@material/select';
    
    const select = new MDCSelect(document.querySelector('.mdc-select'));
    
    select.listen('MDCSelect:change', () => {
      alert(`Selected option at index ${select.selectedIndex} with value "${select.value}"`);
    });

    效果

    icon 没有旋转 animation

    下面这个是官网 demo 的样子

    右边的 icon 会有一个 180° 旋转, 但是我们做出来的却没有.

    我看了一下它的 HTML 发现它俩实现根本就不一样. 我们的 icon 有 2 个, 一个上, 一个下. 切换.

    demo 的只有一个 icon, 不是切换是旋转. 也不知道哪个是正确的.

    Use in Form

    要在 form 使用 select 的话, 需要加上一个 input hidden 作为 submit value, MDC 会同步它

    <div class="mdc-select mdc-select--filled demo-width-class">
      <input type="hidden" name="demo-input">
      <div class="mdc-select__anchor">
        <!-- Rest of component omitted for brevity -->
      </div>
    </div>

    Default Value

    在 list 加上 selected

    同时在 display value 加上相同的值

    The Validation Problem

    虽然 select 支持 required validation

    但毕竟不是原生 validation, 所以无法真的搭配 form 来使用, 比如 submit 的时候, by right 有 invalid 是不能成功的, 但是 select 的 invalid 是无法通知 form 的, 所以 form 能 submit 成功.

    native form 没有类似 Angular Form 那样支持扩展 accessor, 所以要实现的话非常麻烦.

    思路: 

    拦截 form submit 

    检查 select 是否 valid

    invalid 就 prevent default

    focus to select anchor element (这个是 tabindex 0)

    set invalid (为了让它变红色和出 error message)

    太麻烦了, 所以建议用 native select 来实现.

    Native Select

    no support native select

    以前 MDC 是支持 native select 的, 但后来由于 maintain 不来, 所以 remove 掉了.

    参考: Github Issue – Remove support for native select from MDC Select

    虽然没有 build-in 的, 但是自己做也不会太难

    HTML

    <label class="mdc-text-field mdc-text-field--outlined">
      <span class="mdc-notched-outline">
        <span class="mdc-notched-outline__leading"></span>
        <span class="mdc-notched-outline__notch">
          <span class="mdc-floating-label">Full Name</span>
        </span>
        <span class="mdc-notched-outline__trailing"></span>
      </span>
      <select name="fullName" class="mdc-text-field__input" required>
        <option value=""></option>
        <option value="Dog">Dog</option>
        <option value="Cat">Cat</option>
        <option value="Banana">Banana</option>
      </select>
    </label>
    View Code

    用 text field 把 input 换成 select 就可以了 (注意: 虽然是 select, 但 class 依然要放 mdc-text-field__input 哦)

    Scss 和 TypeScript

    和 input 一样, 我就不写了

    箭头的问题

    MDC 把箭头 hide 起来了

    可以通过 override 的方式叫它出来

    select {
      appearance: revert !important;
    }

    但它有缺陷, 颜色不美

    不管什么情况下都是黑色, 如果体验要求不高的话, 可以算了.

    我试过用 trailing icons 做 (类似上面做 datepicker 那样), 但 select 没有 showPicker 这个功能, 所以这个方向是错的.

    正确的做法是 appearance: none, 然后用 background image 做箭头. 

    参考: stackoverflow – Select arrow style change

    Theme

    参考: Theming Guide

    text field 框的颜色是依据 them 的 primary color

    而 Material Design 默认的颜色是紫色

    想换颜色最好是把 primary 换了.

    @use '@material/theme' with (
      $primary: MediumBlue
    );

    效果

  • 相关阅读:
    Leetcode 15. 3Sum
    本周学习小结(01/07
    面试总结之Data Science
    学习笔记之MongoDB
    本周学习小结(13/05
    Django知识点总结
    Django【进阶篇 】
    Django【基础篇】
    如何拿到半数面试公司Offer——我的Python求职之路(转载)
    Django框架(三)
  • 原文地址:https://www.cnblogs.com/keatkeat/p/16285349.html
Copyright © 2020-2023  润新知