lodash-es#clamp TypeScript Examples

The following examples show how to use lodash-es#clamp. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: usePagination.ts    From UUI with MIT License 5 votes vote down vote up
export function usePagination(
  value: IPagination,
  onChange: (pagination: IPagination) => void,
) {
  const prevOffset = useMemo(() => value.offset - value.limit, [value.offset, value.limit])
  const nextOffset = useMemo(() => value.offset + value.limit, [value.offset, value.limit])

  const hasPrevious = useMemo(() => prevOffset >= 0, [prevOffset])
  const hasNext = useMemo(() => nextOffset < value.count, [nextOffset, value.count])
  const currentPage = useMemo(
    () => {
      if (value.offset === 0) return 1
      return value.offset && value.limit ? Math.floor(value.offset / value.limit) + 1 : 0
    },
    [value.offset, value.limit],
  )
  const totalPage = useMemo(() => value.limit ? Math.ceil(value.count / value.limit) : 0, [value.count, value.limit])

  const toNextPage = () => {
    onChange({ offset: nextOffset, limit: value.limit, count: value.count })
  }
  const toPrevPage = () => {
    onChange({ offset: Math.max(0, prevOffset), limit: value.limit, count: value.count })
  }
  const toNthPage = (n: number) => {
    onChange({ offset: clamp((n - 1) * value.limit, 0, value.count), limit: value.limit, count: value.count })
  }
  const changePageSize = (size: number) => {
    onChange({ ...value, limit: Math.max(size, 0) })
  }

  return {
    ...value,
    currentPage,
    totalPage,
    toNextPage,
    toPrevPage,
    toNthPage,
    changePageSize,
    hasPrevious,
    hasNext,
  }
}
Example #2
Source File: numberHelper.ts    From UUI with MIT License 5 votes vote down vote up
export function limitRange(value: number, min?: number, max?: number) {
  return clamp(value, min === undefined ? Number.NEGATIVE_INFINITY : min, max === undefined ? Number.POSITIVE_INFINITY : max)
}
Example #3
Source File: numberHelper.ts    From UUI with MIT License 5 votes vote down vote up
export function numberAbbr(value: number, unit: NumberAbbrUnit, maxPrecision = 2) {
  const precisionNumber = Math.pow(10, Math.round(clamp(maxPrecision, 0, Number.MAX_SAFE_INTEGER)))
  return Math.round(value / NumberAbbrUnitValue[unit] * precisionNumber) / precisionNumber
}
Example #4
Source File: workspaceReducer.ts    From diagram-maker with Apache License 2.0 5 votes vote down vote up
clampPosition = (position: Position, size: Size, zoom: number, containerSize: Size): Position => {
  const x = clamp(position.x, -1 * Math.max(0, size.width * zoom - containerSize.width), 0);
  const y = clamp(position.y, -1 * Math.max(0, size.height * zoom - containerSize.height), 0);

  return { x, y };
}
Example #5
Source File: ProgressBar.tsx    From UUI with MIT License 4 votes vote down vote up
ProgressBar = UUIFunctionComponent(
  {
    name: "ProgressBar",
    nodes: {
      Root: "div",
      Container: "div",
      BarFill: "div",
      CircularWrapper: "div",
      CircularBackground: "div",
      CircularLeftWrapper: "div",
      CircularRightWrapper: "div",
      CircularLeft: "div",
      CircularRight: "div",
    },
    propTypes: ProgressBarPropTypes,
  },
  (props: ProgressBarFeatureProps, { nodes, NodeDataProps }) => {
    const {
      Root,
      Container,
      BarFill,
      CircularWrapper,
      CircularBackground,
      CircularLeftWrapper,
      CircularRightWrapper,
      CircularLeft,
      CircularRight,
    } = nodes;

    const finalProps = {
      value: props.value === undefined ? 0 : props.value
    }

    /**
     * Calculate the position and size of thumbs, remarks and lines.
     */
    const styles = useMemo(() => {
      const value = Math.max(0, Math.min(1, finalProps.value));

      switch (props.circular) {
        case false:
        case undefined:
          return {
            BarFill: {
              width: toPercentage(value),
            },
          };
        case true:
          return {
            CircularLeft: {
              ...(!props.indeterminate
                ? {
                    opacity: finalProps.value < 0.5 ? 0 : 1,
                    transform: value >= 0.5 ? `rotate(-${(1 - (value - 0.5) / 0.5) * 180}deg)` : "none",
                  }
                : null),
            },
            CircularRight: {
              transform: !props.indeterminate && value <= 0.5 ? `rotate(-${(1 - value / 0.5) * 180}deg)` : undefined,
            },
          };
      }
    }, [finalProps.value, props.circular, props.indeterminate]);

    return (
      <Root
        {...NodeDataProps({
          'disabled': !!props.disabled,
          'circular': !!props.circular,
          'indeterminate': !!props.indeterminate,
        })}
        role="progressbar"
        aria-valuemin={0}
        aria-valuemax={100}
        aria-valuenow={clamp(Math.round(finalProps.value * 100), 0, 100)}
        aria-valuetext={toPercentage(finalProps.value)}
      >
        <Container>
          {!props.circular ? (
            <BarFill style={{ ...styles.BarFill }} />
          ) : (
            <CircularWrapper>
              <CircularBackground />
              <CircularLeftWrapper>
                <CircularLeft style={{ ...styles.CircularLeft }} />
              </CircularLeftWrapper>
              <CircularRightWrapper>
                <CircularRight style={{ ...styles.CircularRight }} />
              </CircularRightWrapper>
            </CircularWrapper>
          )}
        </Container>
      </Root>
    );
  }
)
Example #6
Source File: Slider.tsx    From UUI with MIT License 4 votes vote down vote up
Slider = UUIFunctionComponent({
  name: 'Slider',
  nodes: {
    Root: 'div',
    Container: 'div',
    ActiveLine: 'div',
    InactiveLine: 'div',
    Thumb: 'div',
    Remark: 'div',
    RemarkLabel: 'div',
  },
  propTypes: SliderPropTypes,
}, (props: SliderFeatureProps, { nodes, NodeDataProps }) => {
  const { Root, Container, ActiveLine, InactiveLine, Thumb, Remark, RemarkLabel } = nodes

  const finalProps = {
    remarks: props.remarks || [],
  }

  /**
   * Due to Slider supports the selection of a value or a range of values,
   * a unified interface is needed to handle what type of data the component should return.
   */
  const finalValue = useMemo(() => {
    return [
      typeof props.value === 'number' ? props.min : props.value[0],
      typeof props.value === 'number' ? props.value : props.value[1],
    ] as const
  }, [props.min, props.value])
  const onFinalChange = useCallback((value: [number, number]) => {
    const newValue: [number, number] = [Number(value[0].toFixed(8)), Number(value[1].toFixed(8))]
    if (typeof props.value === 'number') {
      props.onChange.call(undefined, newValue[1])
    } else {
      props.onChange.call(undefined, newValue)
    }
  }, [props.value, props.onChange])

  /**
   * Handle thumbs position
   */
  const [thumbDragging, setThumbDragging] = useState<0 | 1 | null>(null)
  const [finalPosition, setFinalPosition] = useState<[number, number]>([
    (finalValue[0]-props.min) / (props.max-props.min),
    (finalValue[1]-props.min) / (props.max-props.min),
  ])
  useEffect(() => {
    setFinalPosition([
      (finalValue[0]-props.min) / (props.max-props.min),
      (finalValue[1]-props.min) / (props.max-props.min),
    ])
  }, [finalValue, props.min, props.max])
  const containerRef = useRef<any>()
  const getPositionFromEvent = (event: MouseEvent | TouchEvent) => {
    if (!containerRef.current) return null
    const containerRect = containerRef.current.getBoundingClientRect()
    const leadingPosition = props.vertical ? containerRect.bottom : containerRect.left
    const trailingPosition = props.vertical ? containerRect.top : containerRect.right

    const currentPosition = (() => {
      switch (event.type) {
        case 'mousedown':
        case 'mousemove':
        case 'click':
            return props.vertical ? (event as MouseEvent).clientY : (event as MouseEvent).clientX
        case 'touchstart':
        case 'touchmove':
          return props.vertical ? (event as TouchEvent).touches[0].clientY :(event as TouchEvent).touches[0].clientX
        default:
          return null
      }
    })();
    if (!currentPosition) return null
    let newPosition = (currentPosition - leadingPosition) / (trailingPosition - leadingPosition)
    newPosition = clamp(newPosition, 0.00, 1.00)
    return newPosition
  }

  const onEventPositionChange = (position: number, thumbIndex: 0 | 1) => {
    const newValue = Math.round((props.max-props.min) / props.step * position) * props.step + props.min
    setFinalPosition((value) => {
      value[thumbIndex] = position
      return value
    })
    if (newValue !== props.value) {
      const newFinalValue: [number, number] = [finalValue[0], finalValue[1]]
      newFinalValue[thumbIndex] = newValue
      onFinalChange(newFinalValue)
    }
  }

  const onMouseDownOrTouchStart = (event: React.MouseEvent<HTMLDivElement, MouseEvent> | React.TouchEvent<HTMLDivElement>) => {
    if (props.disabled) return;
    const newPosition = getPositionFromEvent(event as any)
    if (!newPosition) return;

    const targetIndex = isArray(props.value)
      ? (Math.abs(finalPosition[0] - newPosition) < Math.abs(finalPosition[1] - newPosition) ? 0 : 1)
      : 1;
    !props.disabled && setThumbDragging(targetIndex)

    onEventPositionChange(newPosition, targetIndex)
  }
  const onMouseUpOrTouchEnd = () => { !props.disabled && setThumbDragging(null) }
  const onMouseOrTouchMove = (event: MouseEvent | TouchEvent) => {
    if (props.disabled) return;
    if (thumbDragging === null) return;
    const newPosition = getPositionFromEvent(event)
    if (newPosition === null) return;

    onEventPositionChange(newPosition, thumbDragging)

  }
  useEvent('mousemove', onMouseOrTouchMove as any, window, { capture: !props.disabled && !!thumbDragging })
  useEvent('touchmove', onMouseOrTouchMove as any, window, { capture: !props.disabled && !!thumbDragging })
  useEvent('mouseup', onMouseUpOrTouchEnd as any, window, { capture: !props.disabled && !!thumbDragging })
  useEvent('touchend', onMouseUpOrTouchEnd as any, window, { capture: !props.disabled && !!thumbDragging })

  /**
   * Calculate the position and size of thumbs, remarks and lines.
   */
  const styles = useMemo(() => {
    const sortPosition = clone(finalPosition).sort()
    switch (props.vertical) {
      case false:
      case undefined:
        return {
          LeadingInactiveLine: {
            width: toPercentage(sortPosition[0]),
            display: typeof props.value === 'number' ? 'none' : undefined,
          },
          ActiveLine: {
            width: toPercentage(sortPosition[1] - sortPosition[0]),
          },
          TrailingInactiveLine: {
            width: toPercentage(1 - sortPosition[1]),
          },
          LeadingThumb: {
            left: toPercentage(finalPosition[0]),
            display: typeof props.value === 'number' ? 'none' : undefined,
            transform: 'translateX(-50%)',
          },
          TrailingThumb: {
            left: toPercentage(finalPosition[1]),
            transform: 'translateX(-50%)',
          },
          Remark: finalProps.remarks.map((remark) => {
            const position = (remark.value - props.min) / (props.max - props.min)
            return {
              left: toPercentage(position),
              transform: 'translateX(-50%)',
            }
          })
        }
      case true:
        return {
          TrailingInactiveLine: {
            height: toPercentage(sortPosition[0]),
            display: typeof props.value === 'number' ? 'none' : undefined,
          },
          ActiveLine: {
            height: toPercentage(sortPosition[1] - sortPosition[0]),
          },
          LeadingInactiveLine: {
            height: toPercentage(1 - sortPosition[1]),
          },
          LeadingThumb: {
            bottom: toPercentage(finalPosition[0]),
            display: typeof props.value === 'number' ? 'none' : undefined,
            transform: 'translateY(50%)',
          },
          TrailingThumb: {
            bottom: toPercentage(finalPosition[1]),
            transform: 'translateY(50%)',
          },
          Remark: finalProps.remarks.map((remark) => {
            const position = (remark.value - props.min) / (props.max - props.min)
            return {
              bottom: toPercentage(position),
              transform: 'translateY(50%)',
            }
          })
        }
    }
  }, [finalPosition, props.value, props.max, props.min, props.vertical, finalProps.remarks])

  const [focusThumbIndex, setFocusThumbIndex] = useState<number | null>(null)

  return (
    <Root
      {...NodeDataProps({
        'disabled': !!props.disabled,
        'vertical': !!props.vertical,
      })}
      onMouseDown={onMouseDownOrTouchStart}
      onTouchStart={onMouseDownOrTouchStart}
      onKeyDown={(event) => {
        switch (event.keyCode) {
          case KeyCode.ArrowLeft:
          case KeyCode.ArrowDown: {
            if (focusThumbIndex !== null) {
              const newValue = Array.from(finalValue) as [number, number];
              newValue[focusThumbIndex] = clamp(newValue[focusThumbIndex] - props.step, props.min, props.max);
              onFinalChange(newValue)
            }
            break
          }
          case KeyCode.ArrowRight:
          case KeyCode.ArrowUp: {
            if (focusThumbIndex !== null) {
              const newValue = Array.from(finalValue) as [number, number];
              newValue[focusThumbIndex] = clamp(newValue[focusThumbIndex] + props.step, props.min, props.max);
              onFinalChange(newValue)
            }
            break
          }
          case KeyCode.PageDown: {
            if (focusThumbIndex !== null) {
              const newValue = Array.from(finalValue) as [number, number];
              newValue[focusThumbIndex] = clamp(newValue[focusThumbIndex] - props.step * 10, props.min, props.max);
              onFinalChange(newValue)
            }
            break
          }
          case KeyCode.PageUp: {
            if (focusThumbIndex !== null) {
              const newValue = Array.from(finalValue) as [number, number];
              newValue[focusThumbIndex] = clamp(newValue[focusThumbIndex] + props.step * 10, props.min, props.max);
              onFinalChange(newValue)
            }
            break
          }
          case KeyCode.Home: {
            if (focusThumbIndex !== null) {
              const newValue = Array.from(finalValue) as [number, number];
              newValue[focusThumbIndex] = props.max;
              onFinalChange(newValue)
            }
            break
          }
          case KeyCode.End: {
            if (focusThumbIndex !== null) {
              const newValue = Array.from(finalValue) as [number, number];
              newValue[focusThumbIndex] = props.min;
              onFinalChange(newValue)
            }
            break
          }
          default:
            // do nothing
        }
      }}
      onFocus={props.onFocus}
      onBlur={props.onBlur}
    >
      <Container ref={containerRef}>
        <InactiveLine style={{ ...styles.LeadingInactiveLine }} />
        <ActiveLine style={{ ...styles.ActiveLine }} />
        <InactiveLine style={{ ...styles.TrailingInactiveLine }} />
        {finalProps.remarks.map((remark, index) => {
          const isActive = inRange(remark.value, finalValue[0], finalValue[1])
          return (
            <Remark
              key={index}
              {...NodeDataProps({
                'active': !!isActive,
              })}
              style={{ ...styles.Remark[index] }}
            >
              <RemarkLabel>{remark.label}</RemarkLabel>
            </Remark>
          )
        })}
        <Thumb
          role="slider"
          aria-orientation={props.vertical ? "vertical" : "horizontal"}
          aria-valuemin={props.min}
          aria-valuemax={props.max}
          aria-valuenow={finalValue[0]}
          aria-valuetext={`${finalValue[0]}`}
          tabIndex={props.disabled ? -1 : 0}
          style={{ ...styles.LeadingThumb }}
          onFocus={() => { setFocusThumbIndex(0) }}
          onBlur={() => { setFocusThumbIndex(null)}}
        />
        <Thumb
          role="slider"
          aria-orientation={props.vertical ? "vertical" : "horizontal"}
          aria-valuemin={props.min}
          aria-valuemax={props.max}
          aria-valuenow={finalValue[1]}
          aria-valuetext={`${finalValue[1]}`}
          tabIndex={props.disabled ? -1 : 0}
          style={{ ...styles.TrailingThumb }}
          onFocus={() => { setFocusThumbIndex(1) }}
          onBlur={() => { setFocusThumbIndex(null)}}
        />

      </Container>
    </Root>
  )
})
Example #7
Source File: CustomTimeline.tsx    From atlas with GNU General Public License v3.0 4 votes vote down vote up
CustomTimeline: React.FC<CustomTimelineProps> = ({
  player,
  isFullScreen,
  playerState,
  playVideo,
  pauseVideo,
  setPlayerState,
}) => {
  const playProgressThumbRef = useRef<HTMLButtonElement>(null)
  const playProgressRef = useRef<HTMLDivElement>(null)
  const seekBarRef = useRef<HTMLDivElement>(null)
  const mouseDisplayTooltipRef = useRef<HTMLDivElement>(null)

  const [playProgressWidth, setPlayProgressWidth] = useState(0)
  const [loadProgressWidth, setLoadProgressWidth] = useState(0)
  const [mouseDisplayWidth, setMouseDisplayWidth] = useState(0)
  const [mouseDisplayTooltipTime, setMouseDisplayTooltipTime] = useState('0:00')
  const [mouseDisplayTooltipWidth, setMouseDisplayTooltipWidth] = useState(0)
  const [playProgressThumbWidth, setPlayProgressThumbWidth] = useState(0)
  const [isScrubbing, setIsScrubbing] = useState(false)
  const [playedBefore, setPlayedBefore] = useState(false)

  useEffect(() => {
    if (!player) {
      return
    }
    const handler = (event: Event) => {
      if (event.type === 'seeking' && isScrubbing && !player.paused()) {
        setPlayedBefore(true)
        pauseVideo(player)
      }
      if (event.type === 'seeked' && !isScrubbing) {
        if (playedBefore) {
          playVideo(player)
          setPlayedBefore(false)
        }
      }
    }
    player.on(['seeking', 'seeked'], handler)
    return () => {
      player.off(['seeking', 'seeked'], handler)
    }
  }, [isScrubbing, player, playedBefore, pauseVideo, playVideo])

  useEffect(() => {
    const playProgress = playProgressRef.current
    const playProgressThumb = playProgressThumbRef.current
    if (
      !player ||
      !playerState ||
      playerState === 'ended' ||
      playerState === 'error' ||
      !playProgress ||
      isScrubbing ||
      !playProgressThumb
    ) {
      return
    }

    const interval = window.setInterval(() => {
      const duration = player.duration()
      const currentTime = player.currentTime()
      const buffered = player.buffered()

      // set playProgress

      const progressPercentage = round((currentTime / duration) * 100, 2)
      setPlayProgressWidth(progressPercentage)
      setPlayProgressThumbWidth(playProgressThumb.clientWidth)

      if (progressPercentage === 100) {
        setPlayerState('ended')
      }

      // get all buffered time ranges
      const bufferedTimeRanges = Array.from({ length: buffered.length }).map((_, idx) => ({
        bufferedStart: buffered.start(idx),
        bufferedEnd: buffered.end(idx),
      }))

      const currentBufferedTimeRange = bufferedTimeRanges.find(
        (el) => el.bufferedEnd > currentTime && el.bufferedStart < currentTime
      )

      if (currentBufferedTimeRange) {
        const loadProgressPercentage = round((currentBufferedTimeRange.bufferedEnd / duration) * 100, 2)
        setLoadProgressWidth(loadProgressPercentage)
      } else {
        setLoadProgressWidth(0)
      }
    }, UPDATE_INTERVAL)
    return () => {
      clearInterval(interval)
    }
  }, [isScrubbing, player, playerState, setPlayerState])

  const handleMouseAndTouchMove = (e: React.MouseEvent | React.TouchEvent) => {
    const seekBar = seekBarRef.current
    const mouseDisplayTooltip = mouseDisplayTooltipRef.current
    if (!seekBar || !mouseDisplayTooltip || !player) {
      return
    }
    // this will prevent hiding controls when scrubbing on mobile
    player.enableTouchActivity()

    const duration = player.duration()

    // position of seekBar
    const { x: seekBarPosition, width: seekBarWidth } = seekBar.getBoundingClientRect()
    const position = 'clientX' in e ? e.clientX - seekBarPosition : e.touches[0].clientX - seekBarPosition
    const percentage = clamp(round((position / seekBarWidth) * 100, 2), 0, 100)
    setMouseDisplayWidth(percentage)
    setMouseDisplayTooltipWidth(mouseDisplayTooltip.clientWidth)

    // tooltip text
    if (duration) {
      setMouseDisplayTooltipTime(formatDurationShort(round((percentage / 100) * duration)))
    }
    if (isScrubbing) {
      setPlayProgressWidth(percentage)
    }
  }

  const handleJumpToTime = (e: React.MouseEvent | React.TouchEvent) => {
    const seekBar = seekBarRef.current
    if (!seekBar || (e.type === 'mouseleave' && !isScrubbing) || !player) {
      return
    }

    const { x: seekBarPosition, width: seekBarWidth } = seekBar.getBoundingClientRect()
    const mouseOrTouchPosition =
      'clientX' in e ? e.clientX - seekBarPosition : e.changedTouches[0].clientX - seekBarPosition

    const percentage = clamp(round(mouseOrTouchPosition / seekBarWidth, 4), 0, 100)
    const newTime = percentage * (player?.duration() || 0)
    player.currentTime(newTime)
    setIsScrubbing(false)
  }

  return (
    <ProgressControl
      onClick={(event) => event.stopPropagation()}
      isScrubbing={isScrubbing}
      isFullScreen={isFullScreen}
      onMouseMove={handleMouseAndTouchMove}
      onTouchMove={handleMouseAndTouchMove}
      onMouseLeave={handleJumpToTime}
      onMouseDown={() => setIsScrubbing(true)}
      onTouchStart={() => setIsScrubbing(true)}
      onMouseUp={handleJumpToTime}
      onTouchEnd={handleJumpToTime}
    >
      <SeekBar ref={seekBarRef}>
        <LoadProgress style={{ width: loadProgressWidth + '%' }} />
        <MouseDisplayWrapper>
          <MouseDisplay style={{ width: mouseDisplayWidth + '%' }} />
          <MouseDisplayTooltip
            ref={mouseDisplayTooltipRef}
            style={{
              left: `clamp(0px, calc(${mouseDisplayWidth}% - ${
                mouseDisplayTooltipWidth / 2
              }px), calc(100% - ${mouseDisplayTooltipWidth}px))`,
            }}
            isFullScreen={isFullScreen}
          >
            <StyledTooltipText variant="t200">{mouseDisplayTooltipTime}</StyledTooltipText>
          </MouseDisplayTooltip>
        </MouseDisplayWrapper>
        <PlayProgressWrapper>
          <PlayProgress style={{ width: playProgressWidth + '%' }} ref={playProgressRef} />
          <PlayProgressThumb
            ref={playProgressThumbRef}
            style={{
              left: `clamp(0px, calc(${playProgressWidth}% - ${
                playProgressThumbWidth / 2
              }px), calc(100% - ${playProgressThumbWidth}px))`,
            }}
          />
        </PlayProgressWrapper>
      </SeekBar>
    </ProgressControl>
  )
}