Skip to content
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

Add milliseconds #32

Open
Angelk90 opened this issue May 14, 2021 · 0 comments
Open

Add milliseconds #32

Angelk90 opened this issue May 14, 2021 · 0 comments

Comments

@Angelk90
Copy link

Angelk90 commented May 14, 2021

Hi @antonfisher ,

It would also be possible to add milliseconds according to the specifications used in the subtitles field, and use the dot as a placeholder.
Esempio:
00:01:00.000 -> hh:mm:ss:milliseconds -> milliseconds range [000, 999]

Link: codesandbox

import React, { ChangeEvent, CSSProperties, ReactElement } from "react";

const DEFAULT_COLON = ":";
const DEFAULT_COLON_FULL = ".";
const DEFAULT_VALUE_SHORT = `00${DEFAULT_COLON}00`;
const DEFAULT_VALUE_SECOND = `00${DEFAULT_COLON}00${DEFAULT_COLON}00`;
const DEFAULT_VALUE_FULL = `00${DEFAULT_COLON}00${DEFAULT_COLON}00${DEFAULT_COLON_FULL}000`;

export function isNumber<T>(value: T): boolean {
  const number = Number(value);
  return !isNaN(number) && String(value) === String(number);
}

export function formatTimeItem(value?: string | number): string {
  return `${value || ""}00`.substr(0, 2);
}

export function formatTimeFullItem(value?: string | number): string {
  return `${value || ""}000`.substr(0, 3);
}

export function validateTimeAndCursor(
  showSeconds = false,
  showFull = false,
  value = "",
  defaultValue = "",
  colon = DEFAULT_COLON,
  cursorPosition = 0
): [string, number] {
  let newCursorPosition = Number(cursorPosition);

  let [oldH, oldM, oldTempS] = defaultValue.split(colon);
  let [oldS, oldF] = oldTempS.split(DEFAULT_COLON_FULL);

  let [newH, newM, newTempS] = String(value).split(colon);
  let [newS, newF] = newTempS.split(DEFAULT_COLON_FULL);

  //console.log(oldH, oldM, oldS, oldF, oldTempS);
  //console.log(newH, newM, newS, newF, newTempS);

  newH = formatTimeItem(newH);
  if (Number(newH[0]) > 2) {
    newH = oldH;
    newCursorPosition -= 1;
  } else if (Number(newH[0]) === 2) {
    if (Number(oldH[0]) === 2 && Number(newH[1]) > 3) {
      newH = `2${oldH[1]}`;
      newCursorPosition -= 2;
    } else if (Number(newH[1]) > 3) {
      newH = "23";
    }
  }

  newM = formatTimeItem(newM);
  if (Number(newM[0]) > 5) {
    newM = oldM;
    newCursorPosition -= 1;
  }

  if (showFull || showSeconds) {
    newS = formatTimeItem(newS);
    if (Number(newS[0]) > 5) {
      newS = oldS;
      newCursorPosition -= 1;
    }
  }

  if (showFull) newF = formatTimeFullItem(newF);

  const validatedValue = showFull
    ? `${newH}${colon}${newM}${colon}${newS}${DEFAULT_COLON_FULL}${newF}`
    : showSeconds
    ? `${newH}${colon}${newM}${colon}${newS}`
    : `${newH}${colon}${newM}`;

  return [validatedValue, newCursorPosition];
}

type onChangeType = (
  event: ChangeEvent<HTMLInputElement>,
  value: string
) => void;

interface Props {
  value?: string;
  onChange?: onChangeType;
  showSeconds?: boolean;
  showFull?: boolean;
  input: ReactElement | null;
  inputRef?: () => HTMLInputElement | null;
  colon?: string;
  style?: CSSProperties | {};
}

interface State {
  value: string;
  _colon: string;
  _defaultValue: string;
  _showSeconds: boolean;
  _showFull: boolean;
  _maxLength: number;
}

export default class TimeField extends React.Component<Props, State> {
  static defaultProps: Props = {
    showSeconds: false,
    showFull: false,
    input: null,
    style: {},
    colon: DEFAULT_COLON
  };

  constructor(props: Props) {
    super(props);

    const _showSeconds = Boolean(props.showSeconds);
    const _showFull = Boolean(props.showFull);
    const _defaultValue = _showFull
      ? DEFAULT_VALUE_FULL
      : _showSeconds
      ? DEFAULT_VALUE_SECOND
      : DEFAULT_VALUE_SHORT;
    const _colon =
      props.colon && props.colon.length === 1 ? props.colon : DEFAULT_COLON;
    const [validatedTime] = validateTimeAndCursor(
      _showSeconds,
      _showFull,
      this.props.value,
      _defaultValue,
      _colon
    );

    this.state = {
      value: validatedTime,
      _colon,
      _showSeconds,
      _showFull,
      _defaultValue,
      _maxLength: _defaultValue.length
    };

    this.onInputChange = this.onInputChange.bind(this);
  }

  componentDidUpdate(prevProps: Props): void {
    if (this.props.value !== prevProps.value) {
      const [validatedTime] = validateTimeAndCursor(
        this.state._showSeconds,
        this.state._showFull,
        this.props.value,
        this.state._defaultValue,
        this.state._colon
      );
      this.setState({
        value: validatedTime
      });
    }
  }

  onInputChange(
    event: ChangeEvent<HTMLInputElement>,
    callback: onChangeType
  ): void {
    const oldValue = this.state.value;
    const inputEl = event.target;
    const inputValue = inputEl.value;
    const position = inputEl.selectionEnd || 0;
    const isTyped = inputValue.length > oldValue.length;
    const cursorCharacter = inputValue[position - 1];
    const addedCharacter = isTyped ? cursorCharacter : null;
    const removedCharacter = isTyped ? null : oldValue[position];
    const replacedSingleCharacter =
      inputValue.length === oldValue.length ? oldValue[position - 1] : null;
    const colon = this.state._colon;

    let newValue = oldValue;
    let newPosition = position;

    console.log(
      newValue,
      newPosition,
      addedCharacter,
      position,
      this.state._maxLength,
      replacedSingleCharacter
    );

    if (addedCharacter !== null) {
      if (position > this.state._maxLength) {
        newPosition = this.state._maxLength;
      } else if (
        (position === 3 || position === 6) &&
        addedCharacter === colon
      ) {
        newValue = `${inputValue.substr(
          0,
          position - 1
        )}${colon}${inputValue.substr(position + 1)}`;
      } else if (position === 9 && addedCharacter === DEFAULT_COLON_FULL) {
        newValue = `${inputValue.substr(
          0,
          position - 1
        )}${DEFAULT_COLON_FULL}${inputValue.substr(position + 1)}`;
      } else if (
        (position === 3 || position === 6) &&
        isNumber(addedCharacter)
      ) {
        newValue = `${inputValue.substr(
          0,
          position - 1
        )}${colon}${addedCharacter}${inputValue.substr(position + 2)}`;
        newPosition = position + 1;
      } else if (position === 9 && isNumber(addedCharacter)) {
        newValue = `${inputValue.substr(
          0,
          position - 1
        )}${DEFAULT_COLON_FULL}${addedCharacter}${inputValue.substr(
          position + 2
        )}`;
        newPosition = position + 1;
      } else if (isNumber(addedCharacter)) {
        // user typed a number
        newValue =
          inputValue.substr(0, position - 1) +
          addedCharacter +
          inputValue.substr(position + 1);
        if (position === 2 || position === 5 || position === 8) {
          newPosition = position + 1;
        }
      } else {
        // if user typed NOT a number, then keep old value & position
        newPosition = position - 1;
      }
    } else if (replacedSingleCharacter !== null) {
      // user replaced only a single character
      if (isNumber(cursorCharacter)) {
        if (position - 1 === 2 || position - 1 === 5) {
          newValue = `${inputValue.substr(
            0,
            position - 1
          )}${colon}${inputValue.substr(position)}`;
        } else if (position - 1 === 8) {
          newValue = `${inputValue.substr(
            0,
            position - 1
          )}${DEFAULT_COLON_FULL}${inputValue.substr(position)}`;
        } else {
          newValue = inputValue;
        }
      } else {
        // user replaced a number on some non-number character
        newValue = oldValue;
        newPosition = position - 1;
      }
    } else if (
      typeof cursorCharacter !== "undefined" &&
      cursorCharacter !== colon &&
      !isNumber(cursorCharacter)
    ) {
      // set of characters replaced by non-number
      newValue = oldValue;
      newPosition = position - 1;
    } else if (removedCharacter !== null) {
      if ((position === 2 || position === 5) && removedCharacter === colon) {
        newValue = `${inputValue.substr(
          0,
          position - 1
        )}0${colon}${inputValue.substr(position)}`;
        newPosition = position - 1;
      } else if (position === 8 && removedCharacter === DEFAULT_COLON_FULL) {
        newValue = `${inputValue.substr(
          0,
          position - 1
        )}0${DEFAULT_COLON_FULL}${inputValue.substr(position)}`;
        newPosition = position - 1;
      } else {
        // user removed a number
        newValue = `${inputValue.substr(0, position)}0${inputValue.substr(
          position
        )}`;
      }
    }

    const [validatedTime, validatedCursorPosition] = validateTimeAndCursor(
      this.state._showSeconds,
      this.state._showFull,
      newValue,
      oldValue,
      colon,
      newPosition
    );

    this.setState({ value: validatedTime }, () => {
      inputEl.selectionStart = validatedCursorPosition;
      inputEl.selectionEnd = validatedCursorPosition;
      callback(event, validatedTime);
    });

    event.persist();
  }

  render(): ReactElement {
    const { value } = this.state;
    const {
      onChange,
      style,
      showSeconds,
      showFull,
      input,
      inputRef,
      colon,
      ...props
    } = this.props; //eslint-disable-line no-unused-vars
    const onChangeHandler = (event: ChangeEvent<HTMLInputElement>) =>
      this.onInputChange(
        event,
        (e: ChangeEvent<HTMLInputElement>, v: string) =>
          onChange && onChange(e, v)
      );

    if (input) {
      return React.cloneElement(input, {
        ...props,
        value,
        style,
        onChange: onChangeHandler
      });
    }

    return (
      <input
        type="text"
        {...props}
        ref={inputRef}
        value={value}
        onChange={onChangeHandler}
        style={{ width: showFull ? 250 : showSeconds ? 54 : 35, ...style }}
      />
    );
  }
}
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

No branches or pull requests

1 participant