This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+173
View File
@@ -0,0 +1,173 @@
# rc-slider
Slider UI component for React
[![NPM version][npm-image]][npm-url]
[![npm download][download-image]][download-url]
[![build status][github-actions-image]][github-actions-url]
[![Codecov][codecov-image]][codecov-url]
[![bundle size][bundlephobia-image]][bundlephobia-url]
[![dumi][dumi-image]][dumi-url]
[npm-image]: http://img.shields.io/npm/v/rc-slider.svg?style=flat-square
[npm-url]: http://npmjs.org/package/rc-slider
[travis-image]: https://img.shields.io/travis/react-component/slider/master?style=flat-square
[travis-url]: https://travis-ci.com/react-component/slider
[github-actions-image]: https://github.com/react-component/slider/workflows/CI/badge.svg
[github-actions-url]: https://github.com/react-component/slider/actions
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/slider/master.svg?style=flat-square
[codecov-url]: https://app.codecov.io/gh/react-component/slider
[david-url]: https://david-dm.org/react-component/slider
[david-image]: https://david-dm.org/react-component/slider/status.svg?style=flat-square
[david-dev-url]: https://david-dm.org/react-component/slider?type=dev
[david-dev-image]: https://david-dm.org/react-component/slider/dev-status.svg?style=flat-square
[download-image]: https://img.shields.io/npm/dm/rc-slider.svg?style=flat-square
[download-url]: https://npmjs.org/package/rc-slider
[bundlephobia-url]: https://bundlephobia.com/package/rc-slider
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-slider
[dumi-url]: https://github.com/umijs/dumi
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
## Install
[![rc-slider](https://nodei.co/npm/rc-slider.png)](https://npmjs.org/package/rc-slider)
## Example
`npm start` and then go to http://localhost:8000
Online examples: https://slider.react-component.now.sh/
## Usage
## Slider
```js
import Slider from '@rc-component/slider';
import '@rc-component/slider/assets/index.css';
export default () => (
<>
<Slider />
</>
);
```
## Range
Please refer to [#825](https://github.com/react-component/slider/issues/825) for information regarding usage of `Range`.
An example:
```js
import Slider, { Range } from '@rc-component/slider';
import '@rc-component/slider/assets/index.css';
export default () => (
<>
<Slider range />
</>
);
```
## Compatibility
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/electron/electron_48x48.png" alt="Electron" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br>Electron |
| --- | --- | --- | --- | --- |
| IE11, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## API
### createSliderWithTooltip(Slider | Range) => React.Component
An extension to make Slider or Range support Tooltip on handle.
```js
const Slider = require('@rc-component/slider');
const createSliderWithTooltip = Slider.createSliderWithTooltip;
const Range = createSliderWithTooltip(Slider.Range);
```
[Online demo](http://react-component.github.io/slider/?path=/story/rc-slider--handle)
After Range or Slider was wrapped by createSliderWithTooltip, it will have the following props:
| Name | Type | Default | Description |
| ------------ | ------- | ------- | ----------- |
| tipFormatter | (value: number): React.ReactNode | `value => value` | A function to format tooltip's overlay |
| tipProps | Object | `{` <br>`placement: 'top',` <br> ` prefixCls: 'rc-slider-tooltip',` <br> `overlay: tipFormatter(value)` <br> `}` | A function to format tooltip's overlay |
### Common API
The following APIs are shared by Slider and Range.
| Name | Type | Default | Description |
| ------------ | ------- | ------- | ----------- |
| className | string | `''` | Additional CSS class for the root DOM node |
| min | number | `0` | The minimum value of the slider |
| max | number | `100` | The maximum value of the slider |
| id | string | `''` | Unique identifier for the component, used for accessibility |
| marks | `{number: ReactNode}` or`{number: { style, label }}` | `{}` | Marks on the slider. The key determines the position, and the value determines what will show. If you want to set the style of a specific mark point, the value should be an object which contains `style` and `label` properties. |
| step | number or `null` | `1` | Value to be added or subtracted on each step the slider makes. Must be greater than zero, and `max` - `min` should be evenly divisible by the step value. <br /> When `marks` is not an empty object, `step` can be set to `null`, to make `marks` as steps. |
| vertical | boolean | `false` | If vertical is `true`, the slider will be vertical. |
| handle | (props) => React.ReactNode | | A handle generator which could be used to customized handle. |
| included | boolean | `true` | If the value is `true`, it means a continuous value interval, otherwise, it is a independent value. |
| reverse | boolean | `false` | If the value is `true`, it means the component is rendered reverse. |
| disabled | boolean | `false` | If `true`, handles can't be moved. |
| keyboard | boolean | `true` | Support using keyboard to move handlers. |
| dots | boolean | `false` | When the `step` value is greater than 1, you can set the `dots` to `true` if you want to render the slider with dots. |
| onBeforeChange | Function | NOOP | `onBeforeChange` will be triggered when `ontouchstart` or `onmousedown` is triggered. |
| onChange | Function | NOOP | `onChange` will be triggered while the value of Slider changing. |
| onChangeComplete | Function | NOOP | `onChangeComplete` will be triggered when `ontouchend` or `onmouseup` is triggered. |
| minimumTrackStyle | Object | | please use `trackStyle` instead. (`only used for slider, just for compatibility , will be deprecate at rc-slider@9.x `) |
| maximumTrackStyle | Object | | please use `railStyle` instead (`only used for slider, just for compatibility , will be deprecate at rc-slider@9.x`) |
| handleStyle | Array[Object] \| Object | `[{}]` | The style used for handle. (`both for slider(`Object`) and range(`Array of Object`), the array will be used for multi handle following element order`) |
| trackStyle | Array[Object] \| Object | `[{}]` | The style used for track. (`both for slider(`Object`) and range(`Array of Object`), the array will be used for multi track following element order`)|
| railStyle | Object | `{}` | The style used for the track base color. |
| dotStyle | Object \| (dotValue) => Object | `{}` | The style used for the dots. |
| activeDotStyle | Object \| (dotValue) => Object | `{}` | The style used for the active dots. |
### Slider
| Name | Type | Default | Description |
| ------------ | ------- | ------- | ----------- |
| defaultValue | number | `0` | Set initial value of slider. |
| value | number | - | Set current value of slider. |
| startPoint | number | `undefined` | Track starts from this value. If `undefined`, `min` is used. |
| tabIndex | number | `0` | Set the tabIndex of the slider handle. |
| ariaLabelForHandle | string | - | Set the `aria-label` attribute on the slider handle. |
| ariaLabelledByForHandle | string | - | Set the `aria-labelledby` attribute on the slider handle. |
| ariaRequired | boolean | - | Set the `aria-required` attribute on the slider handle. |
| ariaValueTextFormatterForHandle | (value) => string | - | A function to set the `aria-valuetext` attribute on the slider handle. It receives the current value of the slider and returns a formatted string describing the value. See [WAI-ARIA Authoring Practices 1.1](https://www.w3.org/TR/wai-aria-practices-1.1/#slider) for more information. |
### Range
| Name | Type | Default | Description |
| ------------ | ------- | ------- | ----------- |
| defaultValue | `number[]` | `[0, 0]` | Set initial positions of handles. |
| value | `number[]` | | Set current positions of handles. |
| tabIndex | number[] | `[0, 0]` | Set the tabIndex of each handle. |
| ariaLabelGroupForHandles | Array[string] | - | Set the `aria-label` attribute on each handle. |
| ariaLabelledByGroupForHandles | Array[string] | - | Set the `aria-labelledby` attribute on each handle. |
| ariaValueTextFormatterGroupForHandles | Array[(value) => string] | - | A function to set the `aria-valuetext` attribute on each handle. It receives the current value of the slider and returns a formatted string describing the value. See [WAI-ARIA Authoring Practices 1.1](https://www.w3.org/TR/wai-aria-practices-1.1/#slider) for more information. |
| count | number | `1` | Determine how many ranges to render, and multiple handles will be rendered (number + 1). |
| allowCross | boolean | `true` | `allowCross` could be set as `true` to allow those handles to cross. |
| pushable | boolean or number | `false` | `pushable` could be set as `true` to allow pushing of surrounding handles when moving a handle. When set to a number, the number will be the minimum ensured distance between handles. Example: ![](http://i.giphy.com/l46Cs36c9HrHMExoc.gif) |
| draggableTrack | boolean | `false` | Open the track drag. open after click on the track will be invalid. |
### SliderTooltip
The Tooltip Component that keep following with content.
## Development
```
npm install
npm start
```
## Test Case
`npm run test`
## Coverage
`npm run coverage`
## License
`rc-slider` is released under the MIT license.
+275
View File
@@ -0,0 +1,275 @@
.rc-slider {
position: relative;
width: 100%;
height: 14px;
padding: 5px 0;
border-radius: 6px;
touch-action: none;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider * {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-rail {
position: absolute;
width: 100%;
height: 4px;
background-color: #e9e9e9;
border-radius: 6px;
}
.rc-slider-track,
.rc-slider-tracks {
position: absolute;
height: 4px;
background-color: #abe2fb;
border-radius: 6px;
}
.rc-slider-track-draggable {
z-index: 1;
box-sizing: content-box;
background-clip: content-box;
border-top: 5px solid rgba(0, 0, 0, 0);
border-bottom: 5px solid rgba(0, 0, 0, 0);
transform: translateY(-5px);
}
.rc-slider-handle {
position: absolute;
z-index: 1;
width: 14px;
height: 14px;
margin-top: -5px;
background-color: #fff;
border: solid 2px #96dbfa;
border-radius: 50%;
cursor: pointer;
cursor: -webkit-grab;
cursor: grab;
opacity: 0.8;
user-select: none;
touch-action: pan-x;
}
.rc-slider-handle-dragging.rc-slider-handle-dragging.rc-slider-handle-dragging {
border-color: #57c5f7;
box-shadow: 0 0 0 5px #96dbfa;
}
.rc-slider-handle-dragging.rc-slider-handle-dragging.rc-slider-handle-dragging-delete {
opacity: 0;
}
.rc-slider-handle:focus {
outline: none;
box-shadow: none;
}
.rc-slider-handle:focus-visible {
border-color: #2db7f5;
box-shadow: 0 0 0 3px #96dbfa;
}
.rc-slider-handle-click-focused:focus {
border-color: #96dbfa;
box-shadow: unset;
}
.rc-slider-handle:hover {
border-color: #57c5f7;
}
.rc-slider-handle:active {
border-color: #57c5f7;
box-shadow: 0 0 5px #57c5f7;
cursor: -webkit-grabbing;
cursor: grabbing;
}
.rc-slider-mark {
position: absolute;
top: 18px;
left: 0;
width: 100%;
font-size: 12px;
}
.rc-slider-mark-text {
position: absolute;
display: inline-block;
color: #999;
text-align: center;
vertical-align: middle;
cursor: pointer;
}
.rc-slider-mark-text-active {
color: #666;
}
.rc-slider-step {
position: absolute;
width: 100%;
height: 4px;
background: transparent;
}
.rc-slider-dot {
position: absolute;
bottom: -2px;
width: 8px;
height: 8px;
vertical-align: middle;
background-color: #fff;
border: 2px solid #e9e9e9;
border-radius: 50%;
cursor: pointer;
}
.rc-slider-dot-active {
border-color: #96dbfa;
}
.rc-slider-dot-reverse {
margin-right: -4px;
}
.rc-slider-disabled {
background-color: #e9e9e9;
}
.rc-slider-disabled .rc-slider-track {
background-color: #ccc;
}
.rc-slider-disabled .rc-slider-handle,
.rc-slider-disabled .rc-slider-dot {
background-color: #fff;
border-color: #ccc;
box-shadow: none;
cursor: not-allowed;
}
.rc-slider-disabled .rc-slider-mark-text,
.rc-slider-disabled .rc-slider-dot {
cursor: not-allowed !important;
}
.rc-slider-vertical {
width: 14px;
height: 100%;
padding: 0 5px;
}
.rc-slider-vertical .rc-slider-rail {
width: 4px;
height: 100%;
}
.rc-slider-vertical .rc-slider-track {
bottom: 0;
left: 5px;
width: 4px;
}
.rc-slider-vertical .rc-slider-track-draggable {
border-top: 0;
border-right: 5px solid rgba(0, 0, 0, 0);
border-bottom: 0;
border-left: 5px solid rgba(0, 0, 0, 0);
transform: translateX(-5px);
}
.rc-slider-vertical .rc-slider-handle {
position: absolute;
z-index: 1;
margin-top: 0;
margin-left: -5px;
touch-action: pan-y;
}
.rc-slider-vertical .rc-slider-mark {
top: 0;
left: 18px;
height: 100%;
}
.rc-slider-vertical .rc-slider-step {
width: 4px;
height: 100%;
}
.rc-slider-vertical .rc-slider-dot {
margin-left: -2px;
}
.rc-slider-tooltip-zoom-down-enter,
.rc-slider-tooltip-zoom-down-appear {
display: block !important;
animation-duration: 0.3s;
animation-fill-mode: both;
animation-play-state: paused;
}
.rc-slider-tooltip-zoom-down-leave {
display: block !important;
animation-duration: 0.3s;
animation-fill-mode: both;
animation-play-state: paused;
}
.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active,
.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active {
animation-name: rcSliderTooltipZoomDownIn;
animation-play-state: running;
}
.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active {
animation-name: rcSliderTooltipZoomDownOut;
animation-play-state: running;
}
.rc-slider-tooltip-zoom-down-enter,
.rc-slider-tooltip-zoom-down-appear {
transform: scale(0, 0);
animation-timing-function: cubic-bezier(0.23, 1, 0.32, 1);
}
.rc-slider-tooltip-zoom-down-leave {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
}
@keyframes rcSliderTooltipZoomDownIn {
0% {
transform: scale(0, 0);
transform-origin: 50% 100%;
opacity: 0;
}
100% {
transform: scale(1, 1);
transform-origin: 50% 100%;
}
}
@keyframes rcSliderTooltipZoomDownOut {
0% {
transform: scale(1, 1);
transform-origin: 50% 100%;
}
100% {
transform: scale(0, 0);
transform-origin: 50% 100%;
opacity: 0;
}
}
.rc-slider-tooltip {
position: absolute;
top: -9999px;
left: -9999px;
visibility: visible;
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-tooltip * {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.rc-slider-tooltip-hidden {
display: none;
}
.rc-slider-tooltip-placement-top {
padding: 4px 0 8px 0;
}
.rc-slider-tooltip-inner {
min-width: 24px;
height: 24px;
padding: 6px 2px;
color: #fff;
font-size: 12px;
line-height: 1;
text-align: center;
text-decoration: none;
background-color: #6c6c6c;
border-radius: 6px;
box-shadow: 0 0 4px #d9d9d9;
}
.rc-slider-tooltip-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow {
bottom: 4px;
left: 50%;
margin-left: -4px;
border-width: 4px 4px 0;
border-top-color: #6c6c6c;
}
@@ -0,0 +1,27 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
interface RenderProps {
index: number;
prefixCls: string;
value: number;
dragging: boolean;
draggingDelete: boolean;
}
export interface HandleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onFocus' | 'onMouseEnter'> {
prefixCls: string;
style?: React.CSSProperties;
value: number;
valueIndex: number;
dragging: boolean;
draggingDelete: boolean;
onStartMove: OnStartMove;
onDelete?: (index: number) => void;
onOffsetChange: (value: number | 'min' | 'max', valueIndex: number) => void;
onFocus: (e: React.FocusEvent<HTMLDivElement>, index: number) => void;
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>, index: number) => void;
render?: (origin: React.ReactElement<React.HTMLAttributes<HTMLDivElement>>, props: RenderProps) => React.ReactElement;
onChangeComplete?: () => void;
mock?: boolean;
}
declare const Handle: React.ForwardRefExoticComponent<HandleProps & React.RefAttributes<HTMLDivElement>>;
export default Handle;
+170
View File
@@ -0,0 +1,170 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import { clsx } from 'clsx';
import KeyCode from "@rc-component/util/es/KeyCode";
import * as React from 'react';
import SliderContext from "../context";
import { getDirectionStyle, getIndex } from "../util";
const Handle = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
value,
valueIndex,
onStartMove,
onDelete,
style,
render,
dragging,
draggingDelete,
onOffsetChange,
onChangeComplete,
onFocus,
onMouseEnter,
...restProps
} = props;
const {
min,
max,
direction,
disabled,
keyboard,
range,
tabIndex,
ariaLabelForHandle,
ariaLabelledByForHandle,
ariaRequired,
ariaValueTextFormatterForHandle,
styles,
classNames
} = React.useContext(SliderContext);
const handlePrefixCls = `${prefixCls}-handle`;
// ============================ Events ============================
const onInternalStartMove = e => {
if (!disabled) {
onStartMove(e, valueIndex);
}
};
const onInternalFocus = e => {
onFocus?.(e, valueIndex);
};
const onInternalMouseEnter = e => {
onMouseEnter(e, valueIndex);
};
// =========================== Keyboard ===========================
const onKeyDown = e => {
if (!disabled && keyboard) {
let offset = null;
// Change the value
switch (e.which || e.keyCode) {
case KeyCode.LEFT:
offset = direction === 'ltr' || direction === 'btt' ? -1 : 1;
break;
case KeyCode.RIGHT:
offset = direction === 'ltr' || direction === 'btt' ? 1 : -1;
break;
// Up is plus
case KeyCode.UP:
offset = direction !== 'ttb' ? 1 : -1;
break;
// Down is minus
case KeyCode.DOWN:
offset = direction !== 'ttb' ? -1 : 1;
break;
case KeyCode.HOME:
offset = 'min';
break;
case KeyCode.END:
offset = 'max';
break;
case KeyCode.PAGE_UP:
offset = 2;
break;
case KeyCode.PAGE_DOWN:
offset = -2;
break;
case KeyCode.BACKSPACE:
case KeyCode.DELETE:
onDelete?.(valueIndex);
break;
}
if (offset !== null) {
e.preventDefault();
onOffsetChange(offset, valueIndex);
}
}
};
const handleKeyUp = e => {
switch (e.which || e.keyCode) {
case KeyCode.LEFT:
case KeyCode.RIGHT:
case KeyCode.UP:
case KeyCode.DOWN:
case KeyCode.HOME:
case KeyCode.END:
case KeyCode.PAGE_UP:
case KeyCode.PAGE_DOWN:
onChangeComplete?.();
break;
}
};
// ============================ Offset ============================
const positionStyle = getDirectionStyle(direction, value, min, max);
// ============================ Render ============================
let divProps = {};
if (valueIndex !== null) {
divProps = {
tabIndex: disabled ? null : getIndex(tabIndex, valueIndex),
role: 'slider',
'aria-valuemin': min,
'aria-valuemax': max,
'aria-valuenow': value,
'aria-disabled': disabled,
'aria-label': getIndex(ariaLabelForHandle, valueIndex),
'aria-labelledby': getIndex(ariaLabelledByForHandle, valueIndex),
'aria-required': getIndex(ariaRequired, valueIndex),
'aria-valuetext': getIndex(ariaValueTextFormatterForHandle, valueIndex)?.(value),
'aria-orientation': direction === 'ltr' || direction === 'rtl' ? 'horizontal' : 'vertical',
onMouseDown: onInternalStartMove,
onTouchStart: onInternalStartMove,
onFocus: onInternalFocus,
onMouseEnter: onInternalMouseEnter,
onKeyDown,
onKeyUp: handleKeyUp
};
}
let handleNode = /*#__PURE__*/React.createElement("div", _extends({
ref: ref,
className: clsx(handlePrefixCls, {
[`${handlePrefixCls}-${valueIndex + 1}`]: valueIndex !== null && range,
[`${handlePrefixCls}-dragging`]: dragging,
[`${handlePrefixCls}-dragging-delete`]: draggingDelete
}, classNames.handle),
style: {
...positionStyle,
...style,
...styles.handle
}
}, divProps, restProps));
// Customize
if (render) {
handleNode = render(handleNode, {
index: valueIndex,
prefixCls,
value,
dragging,
draggingDelete
});
}
return handleNode;
});
if (process.env.NODE_ENV !== 'production') {
Handle.displayName = 'Handle';
}
export default Handle;
@@ -0,0 +1,29 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
import type { HandleProps } from './Handle';
export interface HandlesProps {
prefixCls: string;
style?: React.CSSProperties | React.CSSProperties[];
values: number[];
onStartMove: OnStartMove;
onOffsetChange: (value: number | 'min' | 'max', valueIndex: number) => void;
onFocus?: (e: React.FocusEvent<HTMLDivElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLDivElement>) => void;
onDelete?: (index: number) => void;
handleRender?: HandleProps['render'];
/**
* When config `activeHandleRender`,
* it will render another hidden handle for active usage.
* This is useful for accessibility or tooltip usage.
*/
activeHandleRender?: HandleProps['render'];
draggingIndex: number;
draggingDelete: boolean;
onChangeComplete?: () => void;
}
export interface HandlesRef {
focus: (index: number) => void;
hideHelp: VoidFunction;
}
declare const Handles: React.ForwardRefExoticComponent<HandlesProps & React.RefAttributes<HandlesRef>>;
export default Handles;
+95
View File
@@ -0,0 +1,95 @@
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
import * as React from 'react';
import { flushSync } from 'react-dom';
import { getIndex } from "../util";
import Handle from "./Handle";
const Handles = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
style,
onStartMove,
onOffsetChange,
values,
handleRender,
activeHandleRender,
draggingIndex,
draggingDelete,
onFocus,
...restProps
} = props;
const handlesRef = React.useRef({});
// =========================== Active ===========================
const [activeVisible, setActiveVisible] = React.useState(false);
const [activeIndex, setActiveIndex] = React.useState(-1);
const onActive = index => {
setActiveIndex(index);
setActiveVisible(true);
};
const onHandleFocus = (e, index) => {
onActive(index);
onFocus?.(e);
};
const onHandleMouseEnter = (e, index) => {
onActive(index);
};
// =========================== Render ===========================
React.useImperativeHandle(ref, () => ({
focus: index => {
handlesRef.current[index]?.focus();
},
hideHelp: () => {
flushSync(() => {
setActiveVisible(false);
});
}
}));
// =========================== Render ===========================
// Handle Props
const handleProps = {
prefixCls,
onStartMove,
onOffsetChange,
render: handleRender,
onFocus: onHandleFocus,
onMouseEnter: onHandleMouseEnter,
...restProps
};
return /*#__PURE__*/React.createElement(React.Fragment, null, values.map((value, index) => {
const dragging = draggingIndex === index;
return /*#__PURE__*/React.createElement(Handle, _extends({
ref: node => {
if (!node) {
delete handlesRef.current[index];
} else {
handlesRef.current[index] = node;
}
},
dragging: dragging,
draggingDelete: dragging && draggingDelete,
style: getIndex(style, index),
key: index,
value: value,
valueIndex: index
}, handleProps));
}), activeHandleRender && activeVisible && /*#__PURE__*/React.createElement(Handle, _extends({
key: "a11y"
}, handleProps, {
value: values[activeIndex],
valueIndex: null,
dragging: draggingIndex !== -1,
draggingDelete: draggingDelete,
render: activeHandleRender,
style: {
pointerEvents: 'none'
},
tabIndex: null,
"aria-hidden": true
})));
});
if (process.env.NODE_ENV !== 'production') {
Handles.displayName = 'Handles';
}
export default Handles;
+10
View File
@@ -0,0 +1,10 @@
import * as React from 'react';
export interface MarkProps {
prefixCls: string;
children?: React.ReactNode;
style?: React.CSSProperties;
value: number;
onClick: (value: number) => void;
}
declare const Mark: React.FC<MarkProps>;
export default Mark;
+41
View File
@@ -0,0 +1,41 @@
import { clsx } from 'clsx';
import * as React from 'react';
import SliderContext from "../context";
import { getDirectionStyle } from "../util";
const Mark = props => {
const {
prefixCls,
style,
children,
value,
onClick
} = props;
const {
min,
max,
direction,
includedStart,
includedEnd,
included
} = React.useContext(SliderContext);
const textCls = `${prefixCls}-text`;
// ============================ Offset ============================
const positionStyle = getDirectionStyle(direction, value, min, max);
return /*#__PURE__*/React.createElement("span", {
className: clsx(textCls, {
[`${textCls}-active`]: included && includedStart <= value && value <= includedEnd
}),
style: {
...positionStyle,
...style
},
onMouseDown: e => {
e.stopPropagation();
},
onClick: () => {
onClick(value);
}
}, children);
};
export default Mark;
+15
View File
@@ -0,0 +1,15 @@
import * as React from 'react';
export interface MarkObj {
style?: React.CSSProperties;
label?: React.ReactNode;
}
export interface InternalMarkObj extends MarkObj {
value: number;
}
export interface MarksProps {
prefixCls: string;
marks?: InternalMarkObj[];
onClick: (value: number) => void;
}
declare const Marks: React.FC<MarksProps>;
export default Marks;
+29
View File
@@ -0,0 +1,29 @@
import * as React from 'react';
import Mark from "./Mark";
const Marks = props => {
const {
prefixCls,
marks,
onClick
} = props;
const markPrefixCls = `${prefixCls}-mark`;
// Not render mark if empty
if (!marks.length) {
return null;
}
return /*#__PURE__*/React.createElement("div", {
className: markPrefixCls
}, marks.map(({
value,
style,
label
}) => /*#__PURE__*/React.createElement(Mark, {
key: value,
prefixCls: markPrefixCls,
style: style,
value: value,
onClick: onClick
}, label)));
};
export default Marks;
+82
View File
@@ -0,0 +1,82 @@
import * as React from 'react';
import type { HandlesProps } from './Handles';
import type { MarkObj } from './Marks';
import type { AriaValueFormat, SliderClassNames, SliderStyles } from './interface';
/**
* New:
* - click mark to update range value
* - handleRender
* - Fix handle with count not correct
* - Fix pushable not work in some case
* - No more FindDOMNode
* - Move all position related style into inline style
* - Key: up is plus, down is minus
* - fix Key with step = null not align with marks
* - Change range should not trigger onChange
* - keyboard support pushable
*/
export type RangeConfig = {
editable?: boolean;
draggableTrack?: boolean;
/** Set min count when `editable` */
minCount?: number;
/** Set max count when `editable` */
maxCount?: number;
};
export interface SliderProps<ValueType = number | number[]> {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
classNames?: SliderClassNames;
styles?: SliderStyles;
id?: string;
disabled?: boolean;
keyboard?: boolean;
autoFocus?: boolean;
onFocus?: (e: React.FocusEvent<HTMLDivElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLDivElement>) => void;
range?: boolean | RangeConfig;
/** @deprecated Use `range.minCount` or `range.maxCount` to handle this */
count?: number;
min?: number;
max?: number;
step?: number | null;
value?: ValueType;
defaultValue?: ValueType;
onChange?: (value: ValueType) => void;
/** @deprecated It's always better to use `onChange` instead */
onBeforeChange?: (value: ValueType) => void;
/** @deprecated Use `onChangeComplete` instead */
onAfterChange?: (value: ValueType) => void;
onChangeComplete?: (value: ValueType) => void;
allowCross?: boolean;
pushable?: boolean | number;
reverse?: boolean;
vertical?: boolean;
included?: boolean;
startPoint?: number;
/** @deprecated Please use `styles.track` instead */
trackStyle?: React.CSSProperties | React.CSSProperties[];
/** @deprecated Please use `styles.handle` instead */
handleStyle?: React.CSSProperties | React.CSSProperties[];
/** @deprecated Please use `styles.rail` instead */
railStyle?: React.CSSProperties;
dotStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
activeDotStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
marks?: Record<string | number, React.ReactNode | MarkObj>;
dots?: boolean;
handleRender?: HandlesProps['handleRender'];
activeHandleRender?: HandlesProps['handleRender'];
track?: boolean;
tabIndex?: number | number[];
ariaLabelForHandle?: string | string[];
ariaLabelledByForHandle?: string | string[];
ariaRequired?: boolean;
ariaValueTextFormatterForHandle?: AriaValueFormat | AriaValueFormat[];
}
export interface SliderRef {
focus: () => void;
blur: () => void;
}
declare const Slider: React.ForwardRefExoticComponent<SliderProps<number | number[]> & React.RefAttributes<SliderRef>>;
export default Slider;
+437
View File
@@ -0,0 +1,437 @@
import useControlledState from "@rc-component/util/es/hooks/useControlledState";
import useEvent from "@rc-component/util/es/hooks/useEvent";
import isEqual from "@rc-component/util/es/isEqual";
import warning from "@rc-component/util/es/warning";
import { clsx } from 'clsx';
import * as React from 'react';
import Handles from "./Handles";
import Marks from "./Marks";
import Steps from "./Steps";
import Tracks from "./Tracks";
import SliderContext from "./context";
import useDrag from "./hooks/useDrag";
import useOffset from "./hooks/useOffset";
import useRange from "./hooks/useRange";
/**
* New:
* - click mark to update range value
* - handleRender
* - Fix handle with count not correct
* - Fix pushable not work in some case
* - No more FindDOMNode
* - Move all position related style into inline style
* - Key: up is plus, down is minus
* - fix Key with step = null not align with marks
* - Change range should not trigger onChange
* - keyboard support pushable
*/
const Slider = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls = 'rc-slider',
className,
style,
classNames,
styles,
id,
// Status
disabled = false,
keyboard = true,
autoFocus,
onFocus,
onBlur,
// Value
min = 0,
max = 100,
step = 1,
value,
defaultValue,
range,
count,
onChange,
onBeforeChange,
onAfterChange,
onChangeComplete,
// Cross
allowCross = true,
pushable = false,
// Direction
reverse,
vertical,
// Style
included = true,
startPoint,
trackStyle,
handleStyle,
railStyle,
dotStyle,
activeDotStyle,
// Decorations
marks,
dots,
// Components
handleRender,
activeHandleRender,
track,
// Accessibility
tabIndex = 0,
ariaLabelForHandle,
ariaLabelledByForHandle,
ariaRequired,
ariaValueTextFormatterForHandle
} = props;
const handlesRef = React.useRef(null);
const containerRef = React.useRef(null);
const direction = React.useMemo(() => {
if (vertical) {
return reverse ? 'ttb' : 'btt';
}
return reverse ? 'rtl' : 'ltr';
}, [reverse, vertical]);
// ============================ Range =============================
const [rangeEnabled, rangeEditable, rangeDraggableTrack, minCount, maxCount] = useRange(range);
const mergedMin = React.useMemo(() => isFinite(min) ? min : 0, [min]);
const mergedMax = React.useMemo(() => isFinite(max) ? max : 100, [max]);
// ============================= Step =============================
const mergedStep = React.useMemo(() => step !== null && step <= 0 ? 1 : step, [step]);
// ============================= Push =============================
const mergedPush = React.useMemo(() => {
if (typeof pushable === 'boolean') {
return pushable ? mergedStep : false;
}
return pushable >= 0 ? pushable : false;
}, [pushable, mergedStep]);
// ============================ Marks =============================
const markList = React.useMemo(() => {
return Object.keys(marks || {}).map(key => {
const mark = marks[key];
const markObj = {
value: Number(key)
};
if (mark && typeof mark === 'object' && ! /*#__PURE__*/React.isValidElement(mark) && ('label' in mark || 'style' in mark)) {
markObj.style = mark.style;
markObj.label = mark.label;
} else {
markObj.label = mark;
}
return markObj;
}).filter(({
label
}) => label || typeof label === 'number').sort((a, b) => a.value - b.value);
}, [marks]);
// ============================ Format ============================
const [formatValue, offsetValues] = useOffset(mergedMin, mergedMax, mergedStep, markList, allowCross, mergedPush);
// ============================ Values ============================
const [mergedValue, setValue] = useControlledState(defaultValue, value);
const rawValues = React.useMemo(() => {
const valueList = mergedValue === null || mergedValue === undefined ? [] : Array.isArray(mergedValue) ? mergedValue : [mergedValue];
const [val0 = mergedMin] = valueList;
let returnValues = mergedValue === null ? [] : [val0];
// Format as range
if (rangeEnabled) {
returnValues = [...valueList];
// When count provided or value is `undefined`, we fill values
if (count || mergedValue === undefined) {
const pointCount = count >= 0 ? count + 1 : 2;
returnValues = returnValues.slice(0, pointCount);
// Fill with count
while (returnValues.length < pointCount) {
returnValues.push(returnValues[returnValues.length - 1] ?? mergedMin);
}
}
returnValues.sort((a, b) => a - b);
}
// Align in range
returnValues.forEach((val, index) => {
returnValues[index] = formatValue(val);
});
return returnValues;
}, [mergedValue, rangeEnabled, mergedMin, count, formatValue]);
// =========================== onChange ===========================
const getTriggerValue = triggerValues => rangeEnabled ? triggerValues : triggerValues[0];
const triggerChange = useEvent(nextValues => {
// Order first
const cloneNextValues = [...nextValues].sort((a, b) => a - b);
// Trigger event if needed
if (onChange && !isEqual(cloneNextValues, rawValues, true)) {
onChange(getTriggerValue(cloneNextValues));
}
// We set this later since it will re-render component immediately
setValue(cloneNextValues);
});
const finishChange = useEvent(draggingDelete => {
// Trigger from `useDrag` will tell if it's a delete action
if (draggingDelete) {
handlesRef.current.hideHelp();
}
const finishValue = getTriggerValue(rawValues);
onAfterChange?.(finishValue);
warning(!onAfterChange, '[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.');
onChangeComplete?.(finishValue);
});
const onDelete = index => {
if (disabled || !rangeEditable || rawValues.length <= minCount) {
return;
}
const cloneNextValues = [...rawValues];
cloneNextValues.splice(index, 1);
onBeforeChange?.(getTriggerValue(cloneNextValues));
triggerChange(cloneNextValues);
const nextFocusIndex = Math.max(0, index - 1);
handlesRef.current.hideHelp();
handlesRef.current.focus(nextFocusIndex);
};
const [draggingIndex, draggingValue, draggingDelete, cacheValues, onStartDrag] = useDrag(containerRef, direction, rawValues, mergedMin, mergedMax, formatValue, triggerChange, finishChange, offsetValues, rangeEditable, minCount);
/**
* When `rangeEditable` will insert a new value in the values array.
* Else it will replace the value in the values array.
*/
const changeToCloseValue = (newValue, e) => {
if (!disabled) {
// Create new values
const cloneNextValues = [...rawValues];
let valueIndex = 0;
let valueBeforeIndex = 0; // Record the index which value < newValue
let valueDist = mergedMax - mergedMin;
rawValues.forEach((val, index) => {
const dist = Math.abs(newValue - val);
if (dist <= valueDist) {
valueDist = dist;
valueIndex = index;
}
if (val < newValue) {
valueBeforeIndex = index;
}
});
let focusIndex = valueIndex;
if (rangeEditable && valueDist !== 0 && (!maxCount || rawValues.length < maxCount)) {
cloneNextValues.splice(valueBeforeIndex + 1, 0, newValue);
focusIndex = valueBeforeIndex + 1;
} else {
cloneNextValues[valueIndex] = newValue;
}
// Fill value to match default 2 (only when `rawValues` is empty)
if (rangeEnabled && !rawValues.length && count === undefined) {
cloneNextValues.push(newValue);
}
const nextValue = getTriggerValue(cloneNextValues);
onBeforeChange?.(nextValue);
triggerChange(cloneNextValues);
if (e) {
document.activeElement?.blur?.();
handlesRef.current.focus(focusIndex);
onStartDrag(e, focusIndex, cloneNextValues);
} else {
// https://github.com/ant-design/ant-design/issues/49997
onAfterChange?.(nextValue);
warning(!onAfterChange, '[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.');
onChangeComplete?.(nextValue);
}
}
};
// ============================ Click =============================
const onSliderMouseDown = e => {
e.preventDefault();
const {
width,
height,
left,
top,
bottom,
right
} = containerRef.current.getBoundingClientRect();
const {
clientX,
clientY
} = e;
let percent;
switch (direction) {
case 'btt':
percent = (bottom - clientY) / height;
break;
case 'ttb':
percent = (clientY - top) / height;
break;
case 'rtl':
percent = (right - clientX) / width;
break;
default:
percent = (clientX - left) / width;
}
const nextValue = mergedMin + percent * (mergedMax - mergedMin);
changeToCloseValue(formatValue(nextValue), e);
};
// =========================== Keyboard ===========================
const [keyboardValue, setKeyboardValue] = React.useState(null);
const onHandleOffsetChange = (offset, valueIndex) => {
if (!disabled) {
const next = offsetValues(rawValues, offset, valueIndex);
onBeforeChange?.(getTriggerValue(rawValues));
triggerChange(next.values);
setKeyboardValue(next.value);
}
};
React.useEffect(() => {
if (keyboardValue !== null) {
const valueIndex = rawValues.indexOf(keyboardValue);
if (valueIndex >= 0) {
handlesRef.current.focus(valueIndex);
}
}
setKeyboardValue(null);
}, [keyboardValue]);
// ============================= Drag =============================
const mergedDraggableTrack = React.useMemo(() => {
if (rangeDraggableTrack && mergedStep === null) {
if (process.env.NODE_ENV !== 'production') {
warning(false, '`draggableTrack` is not supported when `step` is `null`.');
}
return false;
}
return rangeDraggableTrack;
}, [rangeDraggableTrack, mergedStep]);
const onStartMove = useEvent((e, valueIndex) => {
onStartDrag(e, valueIndex);
onBeforeChange?.(getTriggerValue(rawValues));
});
// Auto focus for updated handle
const dragging = draggingIndex !== -1;
React.useEffect(() => {
if (!dragging) {
const valueIndex = rawValues.lastIndexOf(draggingValue);
handlesRef.current.focus(valueIndex);
}
}, [dragging]);
// =========================== Included ===========================
const sortedCacheValues = React.useMemo(() => [...cacheValues].sort((a, b) => a - b), [cacheValues]);
// Provide a range values with included [min, max]
// Used for Track, Mark & Dot
const [includedStart, includedEnd] = React.useMemo(() => {
if (!rangeEnabled) {
return [mergedMin, sortedCacheValues[0]];
}
return [sortedCacheValues[0], sortedCacheValues[sortedCacheValues.length - 1]];
}, [sortedCacheValues, rangeEnabled, mergedMin]);
// ============================= Refs =============================
React.useImperativeHandle(ref, () => ({
focus: () => {
handlesRef.current.focus(0);
},
blur: () => {
const {
activeElement
} = document;
if (containerRef.current?.contains(activeElement)) {
activeElement?.blur();
}
}
}));
// ========================== Auto Focus ==========================
React.useEffect(() => {
if (autoFocus) {
handlesRef.current.focus(0);
}
}, []);
// =========================== Context ============================
const context = React.useMemo(() => ({
min: mergedMin,
max: mergedMax,
direction,
disabled,
keyboard,
step: mergedStep,
included,
includedStart,
includedEnd,
range: rangeEnabled,
tabIndex,
ariaLabelForHandle,
ariaLabelledByForHandle,
ariaRequired,
ariaValueTextFormatterForHandle,
styles: styles || {},
classNames: classNames || {}
}), [mergedMin, mergedMax, direction, disabled, keyboard, mergedStep, included, includedStart, includedEnd, rangeEnabled, tabIndex, ariaLabelForHandle, ariaLabelledByForHandle, ariaRequired, ariaValueTextFormatterForHandle, styles, classNames]);
// ============================ Render ============================
return /*#__PURE__*/React.createElement(SliderContext.Provider, {
value: context
}, /*#__PURE__*/React.createElement("div", {
ref: containerRef,
className: clsx(prefixCls, className, {
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-vertical`]: vertical,
[`${prefixCls}-horizontal`]: !vertical,
[`${prefixCls}-with-marks`]: markList.length
}),
style: style,
onMouseDown: onSliderMouseDown,
id: id
}, /*#__PURE__*/React.createElement("div", {
className: clsx(`${prefixCls}-rail`, classNames?.rail),
style: {
...railStyle,
...styles?.rail
}
}), track !== false && /*#__PURE__*/React.createElement(Tracks, {
prefixCls: prefixCls,
style: trackStyle,
values: rawValues,
startPoint: startPoint,
onStartMove: mergedDraggableTrack ? onStartMove : undefined
}), /*#__PURE__*/React.createElement(Steps, {
prefixCls: prefixCls,
marks: markList,
dots: dots,
style: dotStyle,
activeStyle: activeDotStyle
}), /*#__PURE__*/React.createElement(Handles, {
ref: handlesRef,
prefixCls: prefixCls,
style: handleStyle,
values: cacheValues,
draggingIndex: draggingIndex,
draggingDelete: draggingDelete,
onStartMove: onStartMove,
onOffsetChange: onHandleOffsetChange,
onFocus: onFocus,
onBlur: onBlur,
handleRender: handleRender,
activeHandleRender: activeHandleRender,
onChangeComplete: finishChange,
onDelete: rangeEditable ? onDelete : undefined
}), /*#__PURE__*/React.createElement(Marks, {
prefixCls: prefixCls,
marks: markList,
onClick: changeToCloseValue
})));
});
if (process.env.NODE_ENV !== 'production') {
Slider.displayName = 'Slider';
}
export default Slider;
+9
View File
@@ -0,0 +1,9 @@
import * as React from 'react';
export interface DotProps {
prefixCls: string;
value: number;
style?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
activeStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
}
declare const Dot: React.FC<DotProps>;
export default Dot;
+41
View File
@@ -0,0 +1,41 @@
import { clsx } from 'clsx';
import * as React from 'react';
import SliderContext from "../context";
import { getDirectionStyle } from "../util";
const Dot = props => {
const {
prefixCls,
value,
style,
activeStyle
} = props;
const {
min,
max,
direction,
included,
includedStart,
includedEnd
} = React.useContext(SliderContext);
const dotClassName = `${prefixCls}-dot`;
const active = included && includedStart <= value && value <= includedEnd;
// ============================ Offset ============================
let mergedStyle = {
...getDirectionStyle(direction, value, min, max),
...(typeof style === 'function' ? style(value) : style)
};
if (active) {
mergedStyle = {
...mergedStyle,
...(typeof activeStyle === 'function' ? activeStyle(value) : activeStyle)
};
}
return /*#__PURE__*/React.createElement("span", {
className: clsx(dotClassName, {
[`${dotClassName}-active`]: active
}),
style: mergedStyle
});
};
export default Dot;
+11
View File
@@ -0,0 +1,11 @@
import * as React from 'react';
import type { InternalMarkObj } from '../Marks';
export interface StepsProps {
prefixCls: string;
marks: InternalMarkObj[];
dots?: boolean;
style?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
activeStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
}
declare const Steps: React.FC<StepsProps>;
export default Steps;
+45
View File
@@ -0,0 +1,45 @@
import * as React from 'react';
import SliderContext from "../context";
import Dot from "./Dot";
const Steps = props => {
const {
prefixCls,
marks,
dots,
style,
activeStyle
} = props;
const {
min,
max,
step
} = React.useContext(SliderContext);
const stepDots = React.useMemo(() => {
const dotSet = new Set();
// Add marks
marks.forEach(mark => {
dotSet.add(mark.value);
});
// Fill dots
if (dots && step !== null) {
let current = min;
while (current <= max) {
dotSet.add(current);
current += step;
}
}
return Array.from(dotSet);
}, [min, max, step, dots, marks]);
return /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-step`
}, stepDots.map(dotValue => /*#__PURE__*/React.createElement(Dot, {
prefixCls: prefixCls,
key: dotValue,
value: dotValue,
style: style,
activeStyle: activeStyle
})));
};
export default Steps;
+14
View File
@@ -0,0 +1,14 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
export interface TrackProps {
prefixCls: string;
style?: React.CSSProperties;
/** Replace with origin prefix concat className */
replaceCls?: string;
start: number;
end: number;
index: number;
onStartMove?: OnStartMove;
}
declare const Track: React.FC<TrackProps>;
export default Track;
+67
View File
@@ -0,0 +1,67 @@
import { clsx } from 'clsx';
import * as React from 'react';
import SliderContext from "../context";
import { getOffset } from "../util";
const Track = props => {
const {
prefixCls,
style,
start,
end,
index,
onStartMove,
replaceCls
} = props;
const {
direction,
min,
max,
disabled,
range,
classNames
} = React.useContext(SliderContext);
const trackPrefixCls = `${prefixCls}-track`;
const offsetStart = getOffset(start, min, max);
const offsetEnd = getOffset(end, min, max);
// ============================ Events ============================
const onInternalStartMove = e => {
if (!disabled && onStartMove) {
onStartMove(e, -1);
}
};
// ============================ Render ============================
const positionStyle = {};
switch (direction) {
case 'rtl':
positionStyle.right = `${offsetStart * 100}%`;
positionStyle.width = `${offsetEnd * 100 - offsetStart * 100}%`;
break;
case 'btt':
positionStyle.bottom = `${offsetStart * 100}%`;
positionStyle.height = `${offsetEnd * 100 - offsetStart * 100}%`;
break;
case 'ttb':
positionStyle.top = `${offsetStart * 100}%`;
positionStyle.height = `${offsetEnd * 100 - offsetStart * 100}%`;
break;
default:
positionStyle.left = `${offsetStart * 100}%`;
positionStyle.width = `${offsetEnd * 100 - offsetStart * 100}%`;
}
const className = replaceCls || clsx(trackPrefixCls, {
[`${trackPrefixCls}-${index + 1}`]: index !== null && range,
[`${prefixCls}-track-draggable`]: onStartMove
}, classNames.track);
return /*#__PURE__*/React.createElement("div", {
className: className,
style: {
...positionStyle,
...style
},
onMouseDown: onInternalStartMove,
onTouchStart: onInternalStartMove
});
};
export default Track;
+11
View File
@@ -0,0 +1,11 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
export interface TrackProps {
prefixCls: string;
style?: React.CSSProperties | React.CSSProperties[];
values: number[];
onStartMove?: OnStartMove;
startPoint?: number;
}
declare const Tracks: React.FC<TrackProps>;
export default Tracks;
+76
View File
@@ -0,0 +1,76 @@
import { clsx } from 'clsx';
import * as React from 'react';
import SliderContext from "../context";
import { getIndex } from "../util";
import Track from "./Track";
const Tracks = props => {
const {
prefixCls,
style,
values,
startPoint,
onStartMove
} = props;
const {
included,
range,
min,
styles,
classNames
} = React.useContext(SliderContext);
// =========================== List ===========================
const trackList = React.useMemo(() => {
if (!range) {
// null value do not have track
if (values.length === 0) {
return [];
}
const startValue = startPoint ?? min;
const endValue = values[0];
return [{
start: Math.min(startValue, endValue),
end: Math.max(startValue, endValue)
}];
}
// Multiple
const list = [];
for (let i = 0; i < values.length - 1; i += 1) {
list.push({
start: values[i],
end: values[i + 1]
});
}
return list;
}, [values, range, startPoint, min]);
if (!included) {
return null;
}
// ========================== Render ==========================
const tracksNode = trackList?.length && (classNames.tracks || styles.tracks) ? /*#__PURE__*/React.createElement(Track, {
index: null,
prefixCls: prefixCls,
start: trackList[0].start,
end: trackList[trackList.length - 1].end,
replaceCls: clsx(classNames.tracks, `${prefixCls}-tracks`),
style: styles.tracks
}) : null;
return /*#__PURE__*/React.createElement(React.Fragment, null, tracksNode, trackList.map(({
start,
end
}, index) => /*#__PURE__*/React.createElement(Track, {
index: index,
prefixCls: prefixCls,
style: {
...getIndex(style, index),
...styles.track
},
start: start,
end: end,
key: index,
onStartMove: onStartMove
})));
};
export default Tracks;
+38
View File
@@ -0,0 +1,38 @@
import * as React from 'react';
import type { AriaValueFormat, Direction, SliderClassNames, SliderStyles } from './interface';
export interface SliderContextProps {
min: number;
max: number;
includedStart: number;
includedEnd: number;
direction: Direction;
disabled?: boolean;
keyboard?: boolean;
included?: boolean;
step: number | null;
range?: boolean;
tabIndex: number | number[];
ariaLabelForHandle?: string | string[];
ariaLabelledByForHandle?: string | string[];
ariaRequired?: boolean;
ariaValueTextFormatterForHandle?: AriaValueFormat | AriaValueFormat[];
classNames: SliderClassNames;
styles: SliderStyles;
}
declare const SliderContext: React.Context<SliderContextProps>;
export default SliderContext;
export interface UnstableContextProps {
onDragStart?: (info: {
rawValues: number[];
draggingIndex: number;
draggingValue: number;
}) => void;
onDragChange?: (info: {
rawValues: number[];
deleteIndex: number;
draggingIndex: number;
draggingValue: number;
}) => void;
}
/** @private NOT PROMISE AVAILABLE. DO NOT USE IN PRODUCTION. */
export declare const UnstableContext: React.Context<UnstableContextProps>;
+16
View File
@@ -0,0 +1,16 @@
import * as React from 'react';
const SliderContext = /*#__PURE__*/React.createContext({
min: 0,
max: 0,
direction: 'ltr',
step: 1,
includedStart: 0,
includedEnd: 0,
tabIndex: 0,
keyboard: true,
styles: {},
classNames: {}
});
export default SliderContext;
/** @private NOT PROMISE AVAILABLE. DO NOT USE IN PRODUCTION. */
export const UnstableContext = /*#__PURE__*/React.createContext({});
@@ -0,0 +1,11 @@
import * as React from 'react';
import type { Direction, OnStartMove } from '../interface';
import type { OffsetValues } from './useOffset';
declare function useDrag(containerRef: React.RefObject<HTMLDivElement>, direction: Direction, rawValues: number[], min: number, max: number, formatValue: (value: number) => number, triggerChange: (values: number[]) => void, finishChange: (draggingDelete: boolean) => void, offsetValues: OffsetValues, editable: boolean, minCount: number): [
draggingIndex: number,
draggingValue: number,
draggingDelete: boolean,
returnValues: number[],
onStartMove: OnStartMove
];
export default useDrag;
+200
View File
@@ -0,0 +1,200 @@
import * as React from 'react';
import useEvent from "@rc-component/util/es/hooks/useEvent";
import useLayoutEffect from "@rc-component/util/es/hooks/useLayoutEffect";
import { UnstableContext } from "../context";
/** Drag to delete offset. It's a user experience number for dragging out */
const REMOVE_DIST = 130;
function getPosition(e) {
const obj = 'targetTouches' in e ? e.targetTouches[0] : e;
return {
pageX: obj.pageX,
pageY: obj.pageY
};
}
function useDrag(containerRef, direction, rawValues, min, max, formatValue, triggerChange, finishChange, offsetValues, editable, minCount) {
const [draggingValue, setDraggingValue] = React.useState(null);
const [draggingIndex, setDraggingIndex] = React.useState(-1);
const [draggingDelete, setDraggingDelete] = React.useState(false);
const [cacheValues, setCacheValues] = React.useState(rawValues);
const [originValues, setOriginValues] = React.useState(rawValues);
const mouseMoveEventRef = React.useRef(null);
const mouseUpEventRef = React.useRef(null);
const touchEventTargetRef = React.useRef(null);
const {
onDragStart,
onDragChange
} = React.useContext(UnstableContext);
useLayoutEffect(() => {
if (draggingIndex === -1) {
setCacheValues(rawValues);
}
}, [rawValues, draggingIndex]);
// Clean up event
React.useEffect(() => () => {
document.removeEventListener('mousemove', mouseMoveEventRef.current);
document.removeEventListener('mouseup', mouseUpEventRef.current);
if (touchEventTargetRef.current) {
touchEventTargetRef.current.removeEventListener('touchmove', mouseMoveEventRef.current);
touchEventTargetRef.current.removeEventListener('touchend', mouseUpEventRef.current);
}
}, []);
const flushValues = (nextValues, nextValue, deleteMark) => {
// Perf: Only update state when value changed
if (nextValue !== undefined) {
setDraggingValue(nextValue);
}
setCacheValues(nextValues);
let changeValues = nextValues;
if (deleteMark) {
changeValues = nextValues.filter((_, i) => i !== draggingIndex);
}
triggerChange(changeValues);
if (onDragChange) {
onDragChange({
rawValues: nextValues,
deleteIndex: deleteMark ? draggingIndex : -1,
draggingIndex,
draggingValue: nextValue
});
}
};
const updateCacheValue = useEvent((valueIndex, offsetPercent, deleteMark) => {
if (valueIndex === -1) {
// >>>> Dragging on the track
const startValue = originValues[0];
const endValue = originValues[originValues.length - 1];
const maxStartOffset = min - startValue;
const maxEndOffset = max - endValue;
// Get valid offset
let offset = offsetPercent * (max - min);
offset = Math.max(offset, maxStartOffset);
offset = Math.min(offset, maxEndOffset);
// Use first value to revert back of valid offset (like steps marks)
const formatStartValue = formatValue(startValue + offset);
offset = formatStartValue - startValue;
const cloneCacheValues = originValues.map(val => val + offset);
flushValues(cloneCacheValues);
} else {
// >>>> Dragging on the handle
const offsetDist = (max - min) * offsetPercent;
// Always start with the valueIndex origin value
const cloneValues = [...cacheValues];
cloneValues[valueIndex] = originValues[valueIndex];
const next = offsetValues(cloneValues, offsetDist, valueIndex, 'dist');
flushValues(next.values, next.value, deleteMark);
}
});
const onStartMove = (e, valueIndex, startValues) => {
e.stopPropagation();
// 如果是点击 track 触发的,需要传入变化后的初始值,而不能直接用 rawValues
const initialValues = startValues || rawValues;
const originValue = initialValues[valueIndex];
setDraggingIndex(valueIndex);
setDraggingValue(originValue);
setOriginValues(initialValues);
setCacheValues(initialValues);
setDraggingDelete(false);
const {
pageX: startX,
pageY: startY
} = getPosition(e);
// We declare it here since closure can't get outer latest value
let deleteMark = false;
// Internal trigger event
if (onDragStart) {
onDragStart({
rawValues: initialValues,
draggingIndex: valueIndex,
draggingValue: originValue
});
}
// Moving
const onMouseMove = event => {
event.preventDefault();
const {
pageX: moveX,
pageY: moveY
} = getPosition(event);
const offsetX = moveX - startX;
const offsetY = moveY - startY;
const {
width,
height
} = containerRef.current.getBoundingClientRect();
let offSetPercent;
let removeDist;
switch (direction) {
case 'btt':
offSetPercent = -offsetY / height;
removeDist = offsetX;
break;
case 'ttb':
offSetPercent = offsetY / height;
removeDist = offsetX;
break;
case 'rtl':
offSetPercent = -offsetX / width;
removeDist = offsetY;
break;
default:
offSetPercent = offsetX / width;
removeDist = offsetY;
}
// Check if need mark remove
deleteMark = editable ? Math.abs(removeDist) > REMOVE_DIST && minCount < cacheValues.length : false;
setDraggingDelete(deleteMark);
updateCacheValue(valueIndex, offSetPercent, deleteMark);
};
// End
const onMouseUp = event => {
event.preventDefault();
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('mousemove', onMouseMove);
if (touchEventTargetRef.current) {
touchEventTargetRef.current.removeEventListener('touchmove', mouseMoveEventRef.current);
touchEventTargetRef.current.removeEventListener('touchend', mouseUpEventRef.current);
}
mouseMoveEventRef.current = null;
mouseUpEventRef.current = null;
touchEventTargetRef.current = null;
finishChange(deleteMark);
setDraggingIndex(-1);
setDraggingDelete(false);
};
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('mousemove', onMouseMove);
e.currentTarget.addEventListener('touchend', onMouseUp);
e.currentTarget.addEventListener('touchmove', onMouseMove);
mouseMoveEventRef.current = onMouseMove;
mouseUpEventRef.current = onMouseUp;
touchEventTargetRef.current = e.currentTarget;
};
// Only return cache value when it mapping with rawValues
const returnValues = React.useMemo(() => {
const sourceValues = [...rawValues].sort((a, b) => a - b);
const targetValues = [...cacheValues].sort((a, b) => a - b);
const counts = {};
targetValues.forEach(val => {
counts[val] = (counts[val] || 0) + 1;
});
sourceValues.forEach(val => {
counts[val] = (counts[val] || 0) - 1;
});
const maxDiffCount = editable ? 1 : 0;
const diffCount = Object.values(counts).reduce((prev, next) => prev + Math.abs(next), 0);
return diffCount <= maxDiffCount ? cacheValues : rawValues;
}, [rawValues, cacheValues, editable]);
return [draggingIndex, draggingValue, draggingDelete, returnValues, onStartMove];
}
export default useDrag;
@@ -0,0 +1,10 @@
import type { InternalMarkObj } from '../Marks';
/** Format value align with step & marks */
type FormatValue = (value: number) => number;
type OffsetMode = 'unit' | 'dist';
export type OffsetValues = (values: number[], offset: number | 'min' | 'max', valueIndex: number, mode?: OffsetMode) => {
value: number;
values: number[];
};
export default function useOffset(min: number, max: number, step: number, markList: InternalMarkObj[], allowCross: boolean, pushable: false | number): [FormatValue, OffsetValues];
export {};
+207
View File
@@ -0,0 +1,207 @@
import * as React from 'react';
/** Format the value in the range of [min, max] */
/** Format value align with step */
/** Format value align with step & marks */
export default function useOffset(min, max, step, markList, allowCross, pushable) {
const formatRangeValue = React.useCallback(val => Math.max(min, Math.min(max, val)), [min, max]);
const formatStepValue = React.useCallback(val => {
if (step !== null) {
const stepValue = min + Math.round((formatRangeValue(val) - min) / step) * step;
// Cut number in case to be like 0.30000000000000004
const getDecimal = num => (String(num).split('.')[1] || '').length;
const maxDecimal = Math.max(getDecimal(step), getDecimal(max), getDecimal(min));
const fixedValue = Number(stepValue.toFixed(maxDecimal));
return min <= fixedValue && fixedValue <= max ? fixedValue : null;
}
return null;
}, [step, min, max, formatRangeValue]);
const formatValue = React.useCallback(val => {
const formatNextValue = formatRangeValue(val);
// List align values
const alignValues = markList.map(mark => mark.value);
if (step !== null) {
alignValues.push(formatStepValue(val));
}
// min & max
alignValues.push(min, max);
// Align with marks
let closeValue = alignValues[0];
let closeDist = max - min;
alignValues.forEach(alignValue => {
const dist = Math.abs(formatNextValue - alignValue);
if (dist <= closeDist) {
closeValue = alignValue;
closeDist = dist;
}
});
return closeValue;
}, [min, max, markList, step, formatRangeValue, formatStepValue]);
// ========================== Offset ==========================
// Single Value
const offsetValue = (values, offset, valueIndex, mode = 'unit') => {
if (typeof offset === 'number') {
let nextValue;
const originValue = values[valueIndex];
// Only used for `dist` mode
const targetDistValue = originValue + offset;
// Compare next step value & mark value which is best match
let potentialValues = [];
markList.forEach(mark => {
potentialValues.push(mark.value);
});
// Min & Max
potentialValues.push(min, max);
// In case origin value is align with mark but not with step
potentialValues.push(formatStepValue(originValue));
// Put offset step value also
const sign = offset > 0 ? 1 : -1;
if (mode === 'unit') {
potentialValues.push(formatStepValue(originValue + sign * step));
} else {
potentialValues.push(formatStepValue(targetDistValue));
}
// Find close one
potentialValues = potentialValues.filter(val => val !== null)
// Remove reverse value
.filter(val => offset < 0 ? val <= originValue : val >= originValue);
if (mode === 'unit') {
// `unit` mode can not contain itself
potentialValues = potentialValues.filter(val => val !== originValue);
}
const compareValue = mode === 'unit' ? originValue : targetDistValue;
nextValue = potentialValues[0];
let valueDist = Math.abs(nextValue - compareValue);
potentialValues.forEach(potentialValue => {
const dist = Math.abs(potentialValue - compareValue);
if (dist < valueDist) {
nextValue = potentialValue;
valueDist = dist;
}
});
// Out of range will back to range
if (nextValue === undefined) {
return offset < 0 ? min : max;
}
// `dist` mode
if (mode === 'dist') {
return nextValue;
}
// `unit` mode may need another round
if (Math.abs(offset) > 1) {
const cloneValues = [...values];
cloneValues[valueIndex] = nextValue;
return offsetValue(cloneValues, offset - sign, valueIndex, mode);
}
return nextValue;
} else if (offset === 'min') {
return min;
} else if (offset === 'max') {
return max;
}
};
/** Same as `offsetValue` but return `changed` mark to tell value changed */
const offsetChangedValue = (values, offset, valueIndex, mode = 'unit') => {
const originValue = values[valueIndex];
const nextValue = offsetValue(values, offset, valueIndex, mode);
return {
value: nextValue,
changed: nextValue !== originValue
};
};
const needPush = dist => {
return pushable === null && dist === 0 || typeof pushable === 'number' && dist < pushable;
};
// Values
const offsetValues = (values, offset, valueIndex, mode = 'unit') => {
const nextValues = values.map(formatValue);
const originValue = nextValues[valueIndex];
const nextValue = offsetValue(nextValues, offset, valueIndex, mode);
nextValues[valueIndex] = nextValue;
if (allowCross === false) {
// >>>>> Allow Cross
const pushNum = pushable || 0;
// ============ AllowCross ===============
if (valueIndex > 0 && nextValues[valueIndex - 1] !== originValue) {
nextValues[valueIndex] = Math.max(nextValues[valueIndex], nextValues[valueIndex - 1] + pushNum);
}
if (valueIndex < nextValues.length - 1 && nextValues[valueIndex + 1] !== originValue) {
nextValues[valueIndex] = Math.min(nextValues[valueIndex], nextValues[valueIndex + 1] - pushNum);
}
} else if (typeof pushable === 'number' || pushable === null) {
// >>>>> Pushable
// =============== Push ==================
// >>>>>> Basic push
// End values
for (let i = valueIndex + 1; i < nextValues.length; i += 1) {
let changed = true;
while (needPush(nextValues[i] - nextValues[i - 1]) && changed) {
({
value: nextValues[i],
changed
} = offsetChangedValue(nextValues, 1, i));
}
}
// Start values
for (let i = valueIndex; i > 0; i -= 1) {
let changed = true;
while (needPush(nextValues[i] - nextValues[i - 1]) && changed) {
({
value: nextValues[i - 1],
changed
} = offsetChangedValue(nextValues, -1, i - 1));
}
}
// >>>>> Revert back to safe push range
// End to Start
for (let i = nextValues.length - 1; i > 0; i -= 1) {
let changed = true;
while (needPush(nextValues[i] - nextValues[i - 1]) && changed) {
({
value: nextValues[i - 1],
changed
} = offsetChangedValue(nextValues, -1, i - 1));
}
}
// Start to End
for (let i = 0; i < nextValues.length - 1; i += 1) {
let changed = true;
while (needPush(nextValues[i + 1] - nextValues[i]) && changed) {
({
value: nextValues[i + 1],
changed
} = offsetChangedValue(nextValues, 1, i + 1));
}
}
}
return {
value: nextValues[valueIndex],
values: nextValues
};
};
return [formatValue, offsetValues];
}
@@ -0,0 +1,8 @@
import type { SliderProps } from '../Slider';
export default function useRange(range?: SliderProps['range']): [
range: boolean,
rangeEditable: boolean,
rangeDraggableTrack: boolean,
minCount: number,
maxCount?: number
];
+19
View File
@@ -0,0 +1,19 @@
import { warning } from "@rc-component/util/es/warning";
import { useMemo } from 'react';
export default function useRange(range) {
return useMemo(() => {
if (range === true || !range) {
return [!!range, false, false, 0];
}
const {
editable,
draggableTrack,
minCount,
maxCount
} = range;
if (process.env.NODE_ENV !== 'production') {
warning(!editable || !draggableTrack, '`editable` can not work with `draggableTrack`.');
}
return [true, editable, !editable && draggableTrack, minCount || 0, maxCount];
}, [range]);
}
+5
View File
@@ -0,0 +1,5 @@
import type { SliderProps, SliderRef } from './Slider';
import Slider from './Slider';
export { UnstableContext } from './context';
export type { SliderProps, SliderRef };
export default Slider;
+3
View File
@@ -0,0 +1,3 @@
import Slider from "./Slider";
export { UnstableContext } from "./context";
export default Slider;
+7
View File
@@ -0,0 +1,7 @@
import type React from 'react';
export type Direction = 'rtl' | 'ltr' | 'ttb' | 'btt';
export type OnStartMove = (e: React.MouseEvent | React.TouchEvent, valueIndex: number, startValues?: number[]) => void;
export type AriaValueFormat = (value: number) => string;
export type SemanticName = 'tracks' | 'track' | 'rail' | 'handle';
export type SliderClassNames = Partial<Record<SemanticName, string>>;
export type SliderStyles = Partial<Record<SemanticName, React.CSSProperties>>;
+1
View File
@@ -0,0 +1 @@
export {};
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="react" />
import type { Direction } from './interface';
export declare function getOffset(value: number, min: number, max: number): number;
export declare function getDirectionStyle(direction: Direction, value: number, min: number, max: number): import("react").CSSProperties;
/** Return index value if is list or return value directly */
export declare function getIndex<T>(value: T | T[], index: number): T;
+31
View File
@@ -0,0 +1,31 @@
export function getOffset(value, min, max) {
return (value - min) / (max - min);
}
export function getDirectionStyle(direction, value, min, max) {
const offset = getOffset(value, min, max);
const positionStyle = {};
switch (direction) {
case 'rtl':
positionStyle.right = `${offset * 100}%`;
positionStyle.transform = 'translateX(50%)';
break;
case 'btt':
positionStyle.bottom = `${offset * 100}%`;
positionStyle.transform = 'translateY(50%)';
break;
case 'ttb':
positionStyle.top = `${offset * 100}%`;
positionStyle.transform = 'translateY(-50%)';
break;
default:
positionStyle.left = `${offset * 100}%`;
positionStyle.transform = 'translateX(-50%)';
break;
}
return positionStyle;
}
/** Return index value if is list or return value directly */
export function getIndex(value, index) {
return Array.isArray(value) ? value[index] : value;
}
@@ -0,0 +1,27 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
interface RenderProps {
index: number;
prefixCls: string;
value: number;
dragging: boolean;
draggingDelete: boolean;
}
export interface HandleProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onFocus' | 'onMouseEnter'> {
prefixCls: string;
style?: React.CSSProperties;
value: number;
valueIndex: number;
dragging: boolean;
draggingDelete: boolean;
onStartMove: OnStartMove;
onDelete?: (index: number) => void;
onOffsetChange: (value: number | 'min' | 'max', valueIndex: number) => void;
onFocus: (e: React.FocusEvent<HTMLDivElement>, index: number) => void;
onMouseEnter: (e: React.MouseEvent<HTMLDivElement>, index: number) => void;
render?: (origin: React.ReactElement<React.HTMLAttributes<HTMLDivElement>>, props: RenderProps) => React.ReactElement;
onChangeComplete?: () => void;
mock?: boolean;
}
declare const Handle: React.ForwardRefExoticComponent<HandleProps & React.RefAttributes<HTMLDivElement>>;
export default Handle;
+179
View File
@@ -0,0 +1,179 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var _KeyCode = _interopRequireDefault(require("@rc-component/util/lib/KeyCode"));
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("../context"));
var _util = require("../util");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const Handle = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
value,
valueIndex,
onStartMove,
onDelete,
style,
render,
dragging,
draggingDelete,
onOffsetChange,
onChangeComplete,
onFocus,
onMouseEnter,
...restProps
} = props;
const {
min,
max,
direction,
disabled,
keyboard,
range,
tabIndex,
ariaLabelForHandle,
ariaLabelledByForHandle,
ariaRequired,
ariaValueTextFormatterForHandle,
styles,
classNames
} = React.useContext(_context.default);
const handlePrefixCls = `${prefixCls}-handle`;
// ============================ Events ============================
const onInternalStartMove = e => {
if (!disabled) {
onStartMove(e, valueIndex);
}
};
const onInternalFocus = e => {
onFocus?.(e, valueIndex);
};
const onInternalMouseEnter = e => {
onMouseEnter(e, valueIndex);
};
// =========================== Keyboard ===========================
const onKeyDown = e => {
if (!disabled && keyboard) {
let offset = null;
// Change the value
switch (e.which || e.keyCode) {
case _KeyCode.default.LEFT:
offset = direction === 'ltr' || direction === 'btt' ? -1 : 1;
break;
case _KeyCode.default.RIGHT:
offset = direction === 'ltr' || direction === 'btt' ? 1 : -1;
break;
// Up is plus
case _KeyCode.default.UP:
offset = direction !== 'ttb' ? 1 : -1;
break;
// Down is minus
case _KeyCode.default.DOWN:
offset = direction !== 'ttb' ? -1 : 1;
break;
case _KeyCode.default.HOME:
offset = 'min';
break;
case _KeyCode.default.END:
offset = 'max';
break;
case _KeyCode.default.PAGE_UP:
offset = 2;
break;
case _KeyCode.default.PAGE_DOWN:
offset = -2;
break;
case _KeyCode.default.BACKSPACE:
case _KeyCode.default.DELETE:
onDelete?.(valueIndex);
break;
}
if (offset !== null) {
e.preventDefault();
onOffsetChange(offset, valueIndex);
}
}
};
const handleKeyUp = e => {
switch (e.which || e.keyCode) {
case _KeyCode.default.LEFT:
case _KeyCode.default.RIGHT:
case _KeyCode.default.UP:
case _KeyCode.default.DOWN:
case _KeyCode.default.HOME:
case _KeyCode.default.END:
case _KeyCode.default.PAGE_UP:
case _KeyCode.default.PAGE_DOWN:
onChangeComplete?.();
break;
}
};
// ============================ Offset ============================
const positionStyle = (0, _util.getDirectionStyle)(direction, value, min, max);
// ============================ Render ============================
let divProps = {};
if (valueIndex !== null) {
divProps = {
tabIndex: disabled ? null : (0, _util.getIndex)(tabIndex, valueIndex),
role: 'slider',
'aria-valuemin': min,
'aria-valuemax': max,
'aria-valuenow': value,
'aria-disabled': disabled,
'aria-label': (0, _util.getIndex)(ariaLabelForHandle, valueIndex),
'aria-labelledby': (0, _util.getIndex)(ariaLabelledByForHandle, valueIndex),
'aria-required': (0, _util.getIndex)(ariaRequired, valueIndex),
'aria-valuetext': (0, _util.getIndex)(ariaValueTextFormatterForHandle, valueIndex)?.(value),
'aria-orientation': direction === 'ltr' || direction === 'rtl' ? 'horizontal' : 'vertical',
onMouseDown: onInternalStartMove,
onTouchStart: onInternalStartMove,
onFocus: onInternalFocus,
onMouseEnter: onInternalMouseEnter,
onKeyDown,
onKeyUp: handleKeyUp
};
}
let handleNode = /*#__PURE__*/React.createElement("div", _extends({
ref: ref,
className: (0, _clsx.clsx)(handlePrefixCls, {
[`${handlePrefixCls}-${valueIndex + 1}`]: valueIndex !== null && range,
[`${handlePrefixCls}-dragging`]: dragging,
[`${handlePrefixCls}-dragging-delete`]: draggingDelete
}, classNames.handle),
style: {
...positionStyle,
...style,
...styles.handle
}
}, divProps, restProps));
// Customize
if (render) {
handleNode = render(handleNode, {
index: valueIndex,
prefixCls,
value,
dragging,
draggingDelete
});
}
return handleNode;
});
if (process.env.NODE_ENV !== 'production') {
Handle.displayName = 'Handle';
}
var _default = exports.default = Handle;
@@ -0,0 +1,29 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
import type { HandleProps } from './Handle';
export interface HandlesProps {
prefixCls: string;
style?: React.CSSProperties | React.CSSProperties[];
values: number[];
onStartMove: OnStartMove;
onOffsetChange: (value: number | 'min' | 'max', valueIndex: number) => void;
onFocus?: (e: React.FocusEvent<HTMLDivElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLDivElement>) => void;
onDelete?: (index: number) => void;
handleRender?: HandleProps['render'];
/**
* When config `activeHandleRender`,
* it will render another hidden handle for active usage.
* This is useful for accessibility or tooltip usage.
*/
activeHandleRender?: HandleProps['render'];
draggingIndex: number;
draggingDelete: boolean;
onChangeComplete?: () => void;
}
export interface HandlesRef {
focus: (index: number) => void;
hideHelp: VoidFunction;
}
declare const Handles: React.ForwardRefExoticComponent<HandlesProps & React.RefAttributes<HandlesRef>>;
export default Handles;
+104
View File
@@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _reactDom = require("react-dom");
var _util = require("../util");
var _Handle = _interopRequireDefault(require("./Handle"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
const Handles = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls,
style,
onStartMove,
onOffsetChange,
values,
handleRender,
activeHandleRender,
draggingIndex,
draggingDelete,
onFocus,
...restProps
} = props;
const handlesRef = React.useRef({});
// =========================== Active ===========================
const [activeVisible, setActiveVisible] = React.useState(false);
const [activeIndex, setActiveIndex] = React.useState(-1);
const onActive = index => {
setActiveIndex(index);
setActiveVisible(true);
};
const onHandleFocus = (e, index) => {
onActive(index);
onFocus?.(e);
};
const onHandleMouseEnter = (e, index) => {
onActive(index);
};
// =========================== Render ===========================
React.useImperativeHandle(ref, () => ({
focus: index => {
handlesRef.current[index]?.focus();
},
hideHelp: () => {
(0, _reactDom.flushSync)(() => {
setActiveVisible(false);
});
}
}));
// =========================== Render ===========================
// Handle Props
const handleProps = {
prefixCls,
onStartMove,
onOffsetChange,
render: handleRender,
onFocus: onHandleFocus,
onMouseEnter: onHandleMouseEnter,
...restProps
};
return /*#__PURE__*/React.createElement(React.Fragment, null, values.map((value, index) => {
const dragging = draggingIndex === index;
return /*#__PURE__*/React.createElement(_Handle.default, _extends({
ref: node => {
if (!node) {
delete handlesRef.current[index];
} else {
handlesRef.current[index] = node;
}
},
dragging: dragging,
draggingDelete: dragging && draggingDelete,
style: (0, _util.getIndex)(style, index),
key: index,
value: value,
valueIndex: index
}, handleProps));
}), activeHandleRender && activeVisible && /*#__PURE__*/React.createElement(_Handle.default, _extends({
key: "a11y"
}, handleProps, {
value: values[activeIndex],
valueIndex: null,
dragging: draggingIndex !== -1,
draggingDelete: draggingDelete,
render: activeHandleRender,
style: {
pointerEvents: 'none'
},
tabIndex: null,
"aria-hidden": true
})));
});
if (process.env.NODE_ENV !== 'production') {
Handles.displayName = 'Handles';
}
var _default = exports.default = Handles;
+10
View File
@@ -0,0 +1,10 @@
import * as React from 'react';
export interface MarkProps {
prefixCls: string;
children?: React.ReactNode;
style?: React.CSSProperties;
value: number;
onClick: (value: number) => void;
}
declare const Mark: React.FC<MarkProps>;
export default Mark;
+50
View File
@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("../context"));
var _util = require("../util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Mark = props => {
const {
prefixCls,
style,
children,
value,
onClick
} = props;
const {
min,
max,
direction,
includedStart,
includedEnd,
included
} = React.useContext(_context.default);
const textCls = `${prefixCls}-text`;
// ============================ Offset ============================
const positionStyle = (0, _util.getDirectionStyle)(direction, value, min, max);
return /*#__PURE__*/React.createElement("span", {
className: (0, _clsx.clsx)(textCls, {
[`${textCls}-active`]: included && includedStart <= value && value <= includedEnd
}),
style: {
...positionStyle,
...style
},
onMouseDown: e => {
e.stopPropagation();
},
onClick: () => {
onClick(value);
}
}, children);
};
var _default = exports.default = Mark;
+15
View File
@@ -0,0 +1,15 @@
import * as React from 'react';
export interface MarkObj {
style?: React.CSSProperties;
label?: React.ReactNode;
}
export interface InternalMarkObj extends MarkObj {
value: number;
}
export interface MarksProps {
prefixCls: string;
marks?: InternalMarkObj[];
onClick: (value: number) => void;
}
declare const Marks: React.FC<MarksProps>;
export default Marks;
+38
View File
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _Mark = _interopRequireDefault(require("./Mark"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Marks = props => {
const {
prefixCls,
marks,
onClick
} = props;
const markPrefixCls = `${prefixCls}-mark`;
// Not render mark if empty
if (!marks.length) {
return null;
}
return /*#__PURE__*/React.createElement("div", {
className: markPrefixCls
}, marks.map(({
value,
style,
label
}) => /*#__PURE__*/React.createElement(_Mark.default, {
key: value,
prefixCls: markPrefixCls,
style: style,
value: value,
onClick: onClick
}, label)));
};
var _default = exports.default = Marks;
+82
View File
@@ -0,0 +1,82 @@
import * as React from 'react';
import type { HandlesProps } from './Handles';
import type { MarkObj } from './Marks';
import type { AriaValueFormat, SliderClassNames, SliderStyles } from './interface';
/**
* New:
* - click mark to update range value
* - handleRender
* - Fix handle with count not correct
* - Fix pushable not work in some case
* - No more FindDOMNode
* - Move all position related style into inline style
* - Key: up is plus, down is minus
* - fix Key with step = null not align with marks
* - Change range should not trigger onChange
* - keyboard support pushable
*/
export type RangeConfig = {
editable?: boolean;
draggableTrack?: boolean;
/** Set min count when `editable` */
minCount?: number;
/** Set max count when `editable` */
maxCount?: number;
};
export interface SliderProps<ValueType = number | number[]> {
prefixCls?: string;
className?: string;
style?: React.CSSProperties;
classNames?: SliderClassNames;
styles?: SliderStyles;
id?: string;
disabled?: boolean;
keyboard?: boolean;
autoFocus?: boolean;
onFocus?: (e: React.FocusEvent<HTMLDivElement>) => void;
onBlur?: (e: React.FocusEvent<HTMLDivElement>) => void;
range?: boolean | RangeConfig;
/** @deprecated Use `range.minCount` or `range.maxCount` to handle this */
count?: number;
min?: number;
max?: number;
step?: number | null;
value?: ValueType;
defaultValue?: ValueType;
onChange?: (value: ValueType) => void;
/** @deprecated It's always better to use `onChange` instead */
onBeforeChange?: (value: ValueType) => void;
/** @deprecated Use `onChangeComplete` instead */
onAfterChange?: (value: ValueType) => void;
onChangeComplete?: (value: ValueType) => void;
allowCross?: boolean;
pushable?: boolean | number;
reverse?: boolean;
vertical?: boolean;
included?: boolean;
startPoint?: number;
/** @deprecated Please use `styles.track` instead */
trackStyle?: React.CSSProperties | React.CSSProperties[];
/** @deprecated Please use `styles.handle` instead */
handleStyle?: React.CSSProperties | React.CSSProperties[];
/** @deprecated Please use `styles.rail` instead */
railStyle?: React.CSSProperties;
dotStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
activeDotStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
marks?: Record<string | number, React.ReactNode | MarkObj>;
dots?: boolean;
handleRender?: HandlesProps['handleRender'];
activeHandleRender?: HandlesProps['handleRender'];
track?: boolean;
tabIndex?: number | number[];
ariaLabelForHandle?: string | string[];
ariaLabelledByForHandle?: string | string[];
ariaRequired?: boolean;
ariaValueTextFormatterForHandle?: AriaValueFormat | AriaValueFormat[];
}
export interface SliderRef {
focus: () => void;
blur: () => void;
}
declare const Slider: React.ForwardRefExoticComponent<SliderProps<number | number[]> & React.RefAttributes<SliderRef>>;
export default Slider;
+445
View File
@@ -0,0 +1,445 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState"));
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
var _isEqual = _interopRequireDefault(require("@rc-component/util/lib/isEqual"));
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _Handles = _interopRequireDefault(require("./Handles"));
var _Marks = _interopRequireDefault(require("./Marks"));
var _Steps = _interopRequireDefault(require("./Steps"));
var _Tracks = _interopRequireDefault(require("./Tracks"));
var _context = _interopRequireDefault(require("./context"));
var _useDrag = _interopRequireDefault(require("./hooks/useDrag"));
var _useOffset = _interopRequireDefault(require("./hooks/useOffset"));
var _useRange = _interopRequireDefault(require("./hooks/useRange"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* New:
* - click mark to update range value
* - handleRender
* - Fix handle with count not correct
* - Fix pushable not work in some case
* - No more FindDOMNode
* - Move all position related style into inline style
* - Key: up is plus, down is minus
* - fix Key with step = null not align with marks
* - Change range should not trigger onChange
* - keyboard support pushable
*/
const Slider = /*#__PURE__*/React.forwardRef((props, ref) => {
const {
prefixCls = 'rc-slider',
className,
style,
classNames,
styles,
id,
// Status
disabled = false,
keyboard = true,
autoFocus,
onFocus,
onBlur,
// Value
min = 0,
max = 100,
step = 1,
value,
defaultValue,
range,
count,
onChange,
onBeforeChange,
onAfterChange,
onChangeComplete,
// Cross
allowCross = true,
pushable = false,
// Direction
reverse,
vertical,
// Style
included = true,
startPoint,
trackStyle,
handleStyle,
railStyle,
dotStyle,
activeDotStyle,
// Decorations
marks,
dots,
// Components
handleRender,
activeHandleRender,
track,
// Accessibility
tabIndex = 0,
ariaLabelForHandle,
ariaLabelledByForHandle,
ariaRequired,
ariaValueTextFormatterForHandle
} = props;
const handlesRef = React.useRef(null);
const containerRef = React.useRef(null);
const direction = React.useMemo(() => {
if (vertical) {
return reverse ? 'ttb' : 'btt';
}
return reverse ? 'rtl' : 'ltr';
}, [reverse, vertical]);
// ============================ Range =============================
const [rangeEnabled, rangeEditable, rangeDraggableTrack, minCount, maxCount] = (0, _useRange.default)(range);
const mergedMin = React.useMemo(() => isFinite(min) ? min : 0, [min]);
const mergedMax = React.useMemo(() => isFinite(max) ? max : 100, [max]);
// ============================= Step =============================
const mergedStep = React.useMemo(() => step !== null && step <= 0 ? 1 : step, [step]);
// ============================= Push =============================
const mergedPush = React.useMemo(() => {
if (typeof pushable === 'boolean') {
return pushable ? mergedStep : false;
}
return pushable >= 0 ? pushable : false;
}, [pushable, mergedStep]);
// ============================ Marks =============================
const markList = React.useMemo(() => {
return Object.keys(marks || {}).map(key => {
const mark = marks[key];
const markObj = {
value: Number(key)
};
if (mark && typeof mark === 'object' && ! /*#__PURE__*/React.isValidElement(mark) && ('label' in mark || 'style' in mark)) {
markObj.style = mark.style;
markObj.label = mark.label;
} else {
markObj.label = mark;
}
return markObj;
}).filter(({
label
}) => label || typeof label === 'number').sort((a, b) => a.value - b.value);
}, [marks]);
// ============================ Format ============================
const [formatValue, offsetValues] = (0, _useOffset.default)(mergedMin, mergedMax, mergedStep, markList, allowCross, mergedPush);
// ============================ Values ============================
const [mergedValue, setValue] = (0, _useControlledState.default)(defaultValue, value);
const rawValues = React.useMemo(() => {
const valueList = mergedValue === null || mergedValue === undefined ? [] : Array.isArray(mergedValue) ? mergedValue : [mergedValue];
const [val0 = mergedMin] = valueList;
let returnValues = mergedValue === null ? [] : [val0];
// Format as range
if (rangeEnabled) {
returnValues = [...valueList];
// When count provided or value is `undefined`, we fill values
if (count || mergedValue === undefined) {
const pointCount = count >= 0 ? count + 1 : 2;
returnValues = returnValues.slice(0, pointCount);
// Fill with count
while (returnValues.length < pointCount) {
returnValues.push(returnValues[returnValues.length - 1] ?? mergedMin);
}
}
returnValues.sort((a, b) => a - b);
}
// Align in range
returnValues.forEach((val, index) => {
returnValues[index] = formatValue(val);
});
return returnValues;
}, [mergedValue, rangeEnabled, mergedMin, count, formatValue]);
// =========================== onChange ===========================
const getTriggerValue = triggerValues => rangeEnabled ? triggerValues : triggerValues[0];
const triggerChange = (0, _useEvent.default)(nextValues => {
// Order first
const cloneNextValues = [...nextValues].sort((a, b) => a - b);
// Trigger event if needed
if (onChange && !(0, _isEqual.default)(cloneNextValues, rawValues, true)) {
onChange(getTriggerValue(cloneNextValues));
}
// We set this later since it will re-render component immediately
setValue(cloneNextValues);
});
const finishChange = (0, _useEvent.default)(draggingDelete => {
// Trigger from `useDrag` will tell if it's a delete action
if (draggingDelete) {
handlesRef.current.hideHelp();
}
const finishValue = getTriggerValue(rawValues);
onAfterChange?.(finishValue);
(0, _warning.default)(!onAfterChange, '[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.');
onChangeComplete?.(finishValue);
});
const onDelete = index => {
if (disabled || !rangeEditable || rawValues.length <= minCount) {
return;
}
const cloneNextValues = [...rawValues];
cloneNextValues.splice(index, 1);
onBeforeChange?.(getTriggerValue(cloneNextValues));
triggerChange(cloneNextValues);
const nextFocusIndex = Math.max(0, index - 1);
handlesRef.current.hideHelp();
handlesRef.current.focus(nextFocusIndex);
};
const [draggingIndex, draggingValue, draggingDelete, cacheValues, onStartDrag] = (0, _useDrag.default)(containerRef, direction, rawValues, mergedMin, mergedMax, formatValue, triggerChange, finishChange, offsetValues, rangeEditable, minCount);
/**
* When `rangeEditable` will insert a new value in the values array.
* Else it will replace the value in the values array.
*/
const changeToCloseValue = (newValue, e) => {
if (!disabled) {
// Create new values
const cloneNextValues = [...rawValues];
let valueIndex = 0;
let valueBeforeIndex = 0; // Record the index which value < newValue
let valueDist = mergedMax - mergedMin;
rawValues.forEach((val, index) => {
const dist = Math.abs(newValue - val);
if (dist <= valueDist) {
valueDist = dist;
valueIndex = index;
}
if (val < newValue) {
valueBeforeIndex = index;
}
});
let focusIndex = valueIndex;
if (rangeEditable && valueDist !== 0 && (!maxCount || rawValues.length < maxCount)) {
cloneNextValues.splice(valueBeforeIndex + 1, 0, newValue);
focusIndex = valueBeforeIndex + 1;
} else {
cloneNextValues[valueIndex] = newValue;
}
// Fill value to match default 2 (only when `rawValues` is empty)
if (rangeEnabled && !rawValues.length && count === undefined) {
cloneNextValues.push(newValue);
}
const nextValue = getTriggerValue(cloneNextValues);
onBeforeChange?.(nextValue);
triggerChange(cloneNextValues);
if (e) {
document.activeElement?.blur?.();
handlesRef.current.focus(focusIndex);
onStartDrag(e, focusIndex, cloneNextValues);
} else {
// https://github.com/ant-design/ant-design/issues/49997
onAfterChange?.(nextValue);
(0, _warning.default)(!onAfterChange, '[rc-slider] `onAfterChange` is deprecated. Please use `onChangeComplete` instead.');
onChangeComplete?.(nextValue);
}
}
};
// ============================ Click =============================
const onSliderMouseDown = e => {
e.preventDefault();
const {
width,
height,
left,
top,
bottom,
right
} = containerRef.current.getBoundingClientRect();
const {
clientX,
clientY
} = e;
let percent;
switch (direction) {
case 'btt':
percent = (bottom - clientY) / height;
break;
case 'ttb':
percent = (clientY - top) / height;
break;
case 'rtl':
percent = (right - clientX) / width;
break;
default:
percent = (clientX - left) / width;
}
const nextValue = mergedMin + percent * (mergedMax - mergedMin);
changeToCloseValue(formatValue(nextValue), e);
};
// =========================== Keyboard ===========================
const [keyboardValue, setKeyboardValue] = React.useState(null);
const onHandleOffsetChange = (offset, valueIndex) => {
if (!disabled) {
const next = offsetValues(rawValues, offset, valueIndex);
onBeforeChange?.(getTriggerValue(rawValues));
triggerChange(next.values);
setKeyboardValue(next.value);
}
};
React.useEffect(() => {
if (keyboardValue !== null) {
const valueIndex = rawValues.indexOf(keyboardValue);
if (valueIndex >= 0) {
handlesRef.current.focus(valueIndex);
}
}
setKeyboardValue(null);
}, [keyboardValue]);
// ============================= Drag =============================
const mergedDraggableTrack = React.useMemo(() => {
if (rangeDraggableTrack && mergedStep === null) {
if (process.env.NODE_ENV !== 'production') {
(0, _warning.default)(false, '`draggableTrack` is not supported when `step` is `null`.');
}
return false;
}
return rangeDraggableTrack;
}, [rangeDraggableTrack, mergedStep]);
const onStartMove = (0, _useEvent.default)((e, valueIndex) => {
onStartDrag(e, valueIndex);
onBeforeChange?.(getTriggerValue(rawValues));
});
// Auto focus for updated handle
const dragging = draggingIndex !== -1;
React.useEffect(() => {
if (!dragging) {
const valueIndex = rawValues.lastIndexOf(draggingValue);
handlesRef.current.focus(valueIndex);
}
}, [dragging]);
// =========================== Included ===========================
const sortedCacheValues = React.useMemo(() => [...cacheValues].sort((a, b) => a - b), [cacheValues]);
// Provide a range values with included [min, max]
// Used for Track, Mark & Dot
const [includedStart, includedEnd] = React.useMemo(() => {
if (!rangeEnabled) {
return [mergedMin, sortedCacheValues[0]];
}
return [sortedCacheValues[0], sortedCacheValues[sortedCacheValues.length - 1]];
}, [sortedCacheValues, rangeEnabled, mergedMin]);
// ============================= Refs =============================
React.useImperativeHandle(ref, () => ({
focus: () => {
handlesRef.current.focus(0);
},
blur: () => {
const {
activeElement
} = document;
if (containerRef.current?.contains(activeElement)) {
activeElement?.blur();
}
}
}));
// ========================== Auto Focus ==========================
React.useEffect(() => {
if (autoFocus) {
handlesRef.current.focus(0);
}
}, []);
// =========================== Context ============================
const context = React.useMemo(() => ({
min: mergedMin,
max: mergedMax,
direction,
disabled,
keyboard,
step: mergedStep,
included,
includedStart,
includedEnd,
range: rangeEnabled,
tabIndex,
ariaLabelForHandle,
ariaLabelledByForHandle,
ariaRequired,
ariaValueTextFormatterForHandle,
styles: styles || {},
classNames: classNames || {}
}), [mergedMin, mergedMax, direction, disabled, keyboard, mergedStep, included, includedStart, includedEnd, rangeEnabled, tabIndex, ariaLabelForHandle, ariaLabelledByForHandle, ariaRequired, ariaValueTextFormatterForHandle, styles, classNames]);
// ============================ Render ============================
return /*#__PURE__*/React.createElement(_context.default.Provider, {
value: context
}, /*#__PURE__*/React.createElement("div", {
ref: containerRef,
className: (0, _clsx.clsx)(prefixCls, className, {
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-vertical`]: vertical,
[`${prefixCls}-horizontal`]: !vertical,
[`${prefixCls}-with-marks`]: markList.length
}),
style: style,
onMouseDown: onSliderMouseDown,
id: id
}, /*#__PURE__*/React.createElement("div", {
className: (0, _clsx.clsx)(`${prefixCls}-rail`, classNames?.rail),
style: {
...railStyle,
...styles?.rail
}
}), track !== false && /*#__PURE__*/React.createElement(_Tracks.default, {
prefixCls: prefixCls,
style: trackStyle,
values: rawValues,
startPoint: startPoint,
onStartMove: mergedDraggableTrack ? onStartMove : undefined
}), /*#__PURE__*/React.createElement(_Steps.default, {
prefixCls: prefixCls,
marks: markList,
dots: dots,
style: dotStyle,
activeStyle: activeDotStyle
}), /*#__PURE__*/React.createElement(_Handles.default, {
ref: handlesRef,
prefixCls: prefixCls,
style: handleStyle,
values: cacheValues,
draggingIndex: draggingIndex,
draggingDelete: draggingDelete,
onStartMove: onStartMove,
onOffsetChange: onHandleOffsetChange,
onFocus: onFocus,
onBlur: onBlur,
handleRender: handleRender,
activeHandleRender: activeHandleRender,
onChangeComplete: finishChange,
onDelete: rangeEditable ? onDelete : undefined
}), /*#__PURE__*/React.createElement(_Marks.default, {
prefixCls: prefixCls,
marks: markList,
onClick: changeToCloseValue
})));
});
if (process.env.NODE_ENV !== 'production') {
Slider.displayName = 'Slider';
}
var _default = exports.default = Slider;
+9
View File
@@ -0,0 +1,9 @@
import * as React from 'react';
export interface DotProps {
prefixCls: string;
value: number;
style?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
activeStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
}
declare const Dot: React.FC<DotProps>;
export default Dot;
+50
View File
@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("../context"));
var _util = require("../util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Dot = props => {
const {
prefixCls,
value,
style,
activeStyle
} = props;
const {
min,
max,
direction,
included,
includedStart,
includedEnd
} = React.useContext(_context.default);
const dotClassName = `${prefixCls}-dot`;
const active = included && includedStart <= value && value <= includedEnd;
// ============================ Offset ============================
let mergedStyle = {
...(0, _util.getDirectionStyle)(direction, value, min, max),
...(typeof style === 'function' ? style(value) : style)
};
if (active) {
mergedStyle = {
...mergedStyle,
...(typeof activeStyle === 'function' ? activeStyle(value) : activeStyle)
};
}
return /*#__PURE__*/React.createElement("span", {
className: (0, _clsx.clsx)(dotClassName, {
[`${dotClassName}-active`]: active
}),
style: mergedStyle
});
};
var _default = exports.default = Dot;
+11
View File
@@ -0,0 +1,11 @@
import * as React from 'react';
import type { InternalMarkObj } from '../Marks';
export interface StepsProps {
prefixCls: string;
marks: InternalMarkObj[];
dots?: boolean;
style?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
activeStyle?: React.CSSProperties | ((dotValue: number) => React.CSSProperties);
}
declare const Steps: React.FC<StepsProps>;
export default Steps;
+54
View File
@@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("../context"));
var _Dot = _interopRequireDefault(require("./Dot"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Steps = props => {
const {
prefixCls,
marks,
dots,
style,
activeStyle
} = props;
const {
min,
max,
step
} = React.useContext(_context.default);
const stepDots = React.useMemo(() => {
const dotSet = new Set();
// Add marks
marks.forEach(mark => {
dotSet.add(mark.value);
});
// Fill dots
if (dots && step !== null) {
let current = min;
while (current <= max) {
dotSet.add(current);
current += step;
}
}
return Array.from(dotSet);
}, [min, max, step, dots, marks]);
return /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-step`
}, stepDots.map(dotValue => /*#__PURE__*/React.createElement(_Dot.default, {
prefixCls: prefixCls,
key: dotValue,
value: dotValue,
style: style,
activeStyle: activeStyle
})));
};
var _default = exports.default = Steps;
@@ -0,0 +1,14 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
export interface TrackProps {
prefixCls: string;
style?: React.CSSProperties;
/** Replace with origin prefix concat className */
replaceCls?: string;
start: number;
end: number;
index: number;
onStartMove?: OnStartMove;
}
declare const Track: React.FC<TrackProps>;
export default Track;
+76
View File
@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("../context"));
var _util = require("../util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Track = props => {
const {
prefixCls,
style,
start,
end,
index,
onStartMove,
replaceCls
} = props;
const {
direction,
min,
max,
disabled,
range,
classNames
} = React.useContext(_context.default);
const trackPrefixCls = `${prefixCls}-track`;
const offsetStart = (0, _util.getOffset)(start, min, max);
const offsetEnd = (0, _util.getOffset)(end, min, max);
// ============================ Events ============================
const onInternalStartMove = e => {
if (!disabled && onStartMove) {
onStartMove(e, -1);
}
};
// ============================ Render ============================
const positionStyle = {};
switch (direction) {
case 'rtl':
positionStyle.right = `${offsetStart * 100}%`;
positionStyle.width = `${offsetEnd * 100 - offsetStart * 100}%`;
break;
case 'btt':
positionStyle.bottom = `${offsetStart * 100}%`;
positionStyle.height = `${offsetEnd * 100 - offsetStart * 100}%`;
break;
case 'ttb':
positionStyle.top = `${offsetStart * 100}%`;
positionStyle.height = `${offsetEnd * 100 - offsetStart * 100}%`;
break;
default:
positionStyle.left = `${offsetStart * 100}%`;
positionStyle.width = `${offsetEnd * 100 - offsetStart * 100}%`;
}
const className = replaceCls || (0, _clsx.clsx)(trackPrefixCls, {
[`${trackPrefixCls}-${index + 1}`]: index !== null && range,
[`${prefixCls}-track-draggable`]: onStartMove
}, classNames.track);
return /*#__PURE__*/React.createElement("div", {
className: className,
style: {
...positionStyle,
...style
},
onMouseDown: onInternalStartMove,
onTouchStart: onInternalStartMove
});
};
var _default = exports.default = Track;
@@ -0,0 +1,11 @@
import * as React from 'react';
import type { OnStartMove } from '../interface';
export interface TrackProps {
prefixCls: string;
style?: React.CSSProperties | React.CSSProperties[];
values: number[];
onStartMove?: OnStartMove;
startPoint?: number;
}
declare const Tracks: React.FC<TrackProps>;
export default Tracks;
+85
View File
@@ -0,0 +1,85 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _clsx = require("clsx");
var React = _interopRequireWildcard(require("react"));
var _context = _interopRequireDefault(require("../context"));
var _util = require("../util");
var _Track = _interopRequireDefault(require("./Track"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const Tracks = props => {
const {
prefixCls,
style,
values,
startPoint,
onStartMove
} = props;
const {
included,
range,
min,
styles,
classNames
} = React.useContext(_context.default);
// =========================== List ===========================
const trackList = React.useMemo(() => {
if (!range) {
// null value do not have track
if (values.length === 0) {
return [];
}
const startValue = startPoint ?? min;
const endValue = values[0];
return [{
start: Math.min(startValue, endValue),
end: Math.max(startValue, endValue)
}];
}
// Multiple
const list = [];
for (let i = 0; i < values.length - 1; i += 1) {
list.push({
start: values[i],
end: values[i + 1]
});
}
return list;
}, [values, range, startPoint, min]);
if (!included) {
return null;
}
// ========================== Render ==========================
const tracksNode = trackList?.length && (classNames.tracks || styles.tracks) ? /*#__PURE__*/React.createElement(_Track.default, {
index: null,
prefixCls: prefixCls,
start: trackList[0].start,
end: trackList[trackList.length - 1].end,
replaceCls: (0, _clsx.clsx)(classNames.tracks, `${prefixCls}-tracks`),
style: styles.tracks
}) : null;
return /*#__PURE__*/React.createElement(React.Fragment, null, tracksNode, trackList.map(({
start,
end
}, index) => /*#__PURE__*/React.createElement(_Track.default, {
index: index,
prefixCls: prefixCls,
style: {
...(0, _util.getIndex)(style, index),
...styles.track
},
start: start,
end: end,
key: index,
onStartMove: onStartMove
})));
};
var _default = exports.default = Tracks;
+38
View File
@@ -0,0 +1,38 @@
import * as React from 'react';
import type { AriaValueFormat, Direction, SliderClassNames, SliderStyles } from './interface';
export interface SliderContextProps {
min: number;
max: number;
includedStart: number;
includedEnd: number;
direction: Direction;
disabled?: boolean;
keyboard?: boolean;
included?: boolean;
step: number | null;
range?: boolean;
tabIndex: number | number[];
ariaLabelForHandle?: string | string[];
ariaLabelledByForHandle?: string | string[];
ariaRequired?: boolean;
ariaValueTextFormatterForHandle?: AriaValueFormat | AriaValueFormat[];
classNames: SliderClassNames;
styles: SliderStyles;
}
declare const SliderContext: React.Context<SliderContextProps>;
export default SliderContext;
export interface UnstableContextProps {
onDragStart?: (info: {
rawValues: number[];
draggingIndex: number;
draggingValue: number;
}) => void;
onDragChange?: (info: {
rawValues: number[];
deleteIndex: number;
draggingIndex: number;
draggingValue: number;
}) => void;
}
/** @private NOT PROMISE AVAILABLE. DO NOT USE IN PRODUCTION. */
export declare const UnstableContext: React.Context<UnstableContextProps>;
+24
View File
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.UnstableContext = void 0;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const SliderContext = /*#__PURE__*/React.createContext({
min: 0,
max: 0,
direction: 'ltr',
step: 1,
includedStart: 0,
includedEnd: 0,
tabIndex: 0,
keyboard: true,
styles: {},
classNames: {}
});
var _default = exports.default = SliderContext;
/** @private NOT PROMISE AVAILABLE. DO NOT USE IN PRODUCTION. */
const UnstableContext = exports.UnstableContext = /*#__PURE__*/React.createContext({});
@@ -0,0 +1,11 @@
import * as React from 'react';
import type { Direction, OnStartMove } from '../interface';
import type { OffsetValues } from './useOffset';
declare function useDrag(containerRef: React.RefObject<HTMLDivElement>, direction: Direction, rawValues: number[], min: number, max: number, formatValue: (value: number) => number, triggerChange: (values: number[]) => void, finishChange: (draggingDelete: boolean) => void, offsetValues: OffsetValues, editable: boolean, minCount: number): [
draggingIndex: number,
draggingValue: number,
draggingDelete: boolean,
returnValues: number[],
onStartMove: OnStartMove
];
export default useDrag;
+209
View File
@@ -0,0 +1,209 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _useEvent = _interopRequireDefault(require("@rc-component/util/lib/hooks/useEvent"));
var _useLayoutEffect = _interopRequireDefault(require("@rc-component/util/lib/hooks/useLayoutEffect"));
var _context = require("../context");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/** Drag to delete offset. It's a user experience number for dragging out */
const REMOVE_DIST = 130;
function getPosition(e) {
const obj = 'targetTouches' in e ? e.targetTouches[0] : e;
return {
pageX: obj.pageX,
pageY: obj.pageY
};
}
function useDrag(containerRef, direction, rawValues, min, max, formatValue, triggerChange, finishChange, offsetValues, editable, minCount) {
const [draggingValue, setDraggingValue] = React.useState(null);
const [draggingIndex, setDraggingIndex] = React.useState(-1);
const [draggingDelete, setDraggingDelete] = React.useState(false);
const [cacheValues, setCacheValues] = React.useState(rawValues);
const [originValues, setOriginValues] = React.useState(rawValues);
const mouseMoveEventRef = React.useRef(null);
const mouseUpEventRef = React.useRef(null);
const touchEventTargetRef = React.useRef(null);
const {
onDragStart,
onDragChange
} = React.useContext(_context.UnstableContext);
(0, _useLayoutEffect.default)(() => {
if (draggingIndex === -1) {
setCacheValues(rawValues);
}
}, [rawValues, draggingIndex]);
// Clean up event
React.useEffect(() => () => {
document.removeEventListener('mousemove', mouseMoveEventRef.current);
document.removeEventListener('mouseup', mouseUpEventRef.current);
if (touchEventTargetRef.current) {
touchEventTargetRef.current.removeEventListener('touchmove', mouseMoveEventRef.current);
touchEventTargetRef.current.removeEventListener('touchend', mouseUpEventRef.current);
}
}, []);
const flushValues = (nextValues, nextValue, deleteMark) => {
// Perf: Only update state when value changed
if (nextValue !== undefined) {
setDraggingValue(nextValue);
}
setCacheValues(nextValues);
let changeValues = nextValues;
if (deleteMark) {
changeValues = nextValues.filter((_, i) => i !== draggingIndex);
}
triggerChange(changeValues);
if (onDragChange) {
onDragChange({
rawValues: nextValues,
deleteIndex: deleteMark ? draggingIndex : -1,
draggingIndex,
draggingValue: nextValue
});
}
};
const updateCacheValue = (0, _useEvent.default)((valueIndex, offsetPercent, deleteMark) => {
if (valueIndex === -1) {
// >>>> Dragging on the track
const startValue = originValues[0];
const endValue = originValues[originValues.length - 1];
const maxStartOffset = min - startValue;
const maxEndOffset = max - endValue;
// Get valid offset
let offset = offsetPercent * (max - min);
offset = Math.max(offset, maxStartOffset);
offset = Math.min(offset, maxEndOffset);
// Use first value to revert back of valid offset (like steps marks)
const formatStartValue = formatValue(startValue + offset);
offset = formatStartValue - startValue;
const cloneCacheValues = originValues.map(val => val + offset);
flushValues(cloneCacheValues);
} else {
// >>>> Dragging on the handle
const offsetDist = (max - min) * offsetPercent;
// Always start with the valueIndex origin value
const cloneValues = [...cacheValues];
cloneValues[valueIndex] = originValues[valueIndex];
const next = offsetValues(cloneValues, offsetDist, valueIndex, 'dist');
flushValues(next.values, next.value, deleteMark);
}
});
const onStartMove = (e, valueIndex, startValues) => {
e.stopPropagation();
// 如果是点击 track 触发的,需要传入变化后的初始值,而不能直接用 rawValues
const initialValues = startValues || rawValues;
const originValue = initialValues[valueIndex];
setDraggingIndex(valueIndex);
setDraggingValue(originValue);
setOriginValues(initialValues);
setCacheValues(initialValues);
setDraggingDelete(false);
const {
pageX: startX,
pageY: startY
} = getPosition(e);
// We declare it here since closure can't get outer latest value
let deleteMark = false;
// Internal trigger event
if (onDragStart) {
onDragStart({
rawValues: initialValues,
draggingIndex: valueIndex,
draggingValue: originValue
});
}
// Moving
const onMouseMove = event => {
event.preventDefault();
const {
pageX: moveX,
pageY: moveY
} = getPosition(event);
const offsetX = moveX - startX;
const offsetY = moveY - startY;
const {
width,
height
} = containerRef.current.getBoundingClientRect();
let offSetPercent;
let removeDist;
switch (direction) {
case 'btt':
offSetPercent = -offsetY / height;
removeDist = offsetX;
break;
case 'ttb':
offSetPercent = offsetY / height;
removeDist = offsetX;
break;
case 'rtl':
offSetPercent = -offsetX / width;
removeDist = offsetY;
break;
default:
offSetPercent = offsetX / width;
removeDist = offsetY;
}
// Check if need mark remove
deleteMark = editable ? Math.abs(removeDist) > REMOVE_DIST && minCount < cacheValues.length : false;
setDraggingDelete(deleteMark);
updateCacheValue(valueIndex, offSetPercent, deleteMark);
};
// End
const onMouseUp = event => {
event.preventDefault();
document.removeEventListener('mouseup', onMouseUp);
document.removeEventListener('mousemove', onMouseMove);
if (touchEventTargetRef.current) {
touchEventTargetRef.current.removeEventListener('touchmove', mouseMoveEventRef.current);
touchEventTargetRef.current.removeEventListener('touchend', mouseUpEventRef.current);
}
mouseMoveEventRef.current = null;
mouseUpEventRef.current = null;
touchEventTargetRef.current = null;
finishChange(deleteMark);
setDraggingIndex(-1);
setDraggingDelete(false);
};
document.addEventListener('mouseup', onMouseUp);
document.addEventListener('mousemove', onMouseMove);
e.currentTarget.addEventListener('touchend', onMouseUp);
e.currentTarget.addEventListener('touchmove', onMouseMove);
mouseMoveEventRef.current = onMouseMove;
mouseUpEventRef.current = onMouseUp;
touchEventTargetRef.current = e.currentTarget;
};
// Only return cache value when it mapping with rawValues
const returnValues = React.useMemo(() => {
const sourceValues = [...rawValues].sort((a, b) => a - b);
const targetValues = [...cacheValues].sort((a, b) => a - b);
const counts = {};
targetValues.forEach(val => {
counts[val] = (counts[val] || 0) + 1;
});
sourceValues.forEach(val => {
counts[val] = (counts[val] || 0) - 1;
});
const maxDiffCount = editable ? 1 : 0;
const diffCount = Object.values(counts).reduce((prev, next) => prev + Math.abs(next), 0);
return diffCount <= maxDiffCount ? cacheValues : rawValues;
}, [rawValues, cacheValues, editable]);
return [draggingIndex, draggingValue, draggingDelete, returnValues, onStartMove];
}
var _default = exports.default = useDrag;
@@ -0,0 +1,10 @@
import type { InternalMarkObj } from '../Marks';
/** Format value align with step & marks */
type FormatValue = (value: number) => number;
type OffsetMode = 'unit' | 'dist';
export type OffsetValues = (values: number[], offset: number | 'min' | 'max', valueIndex: number, mode?: OffsetMode) => {
value: number;
values: number[];
};
export default function useOffset(min: number, max: number, step: number, markList: InternalMarkObj[], allowCross: boolean, pushable: false | number): [FormatValue, OffsetValues];
export {};
@@ -0,0 +1,214 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useOffset;
var React = _interopRequireWildcard(require("react"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/** Format the value in the range of [min, max] */
/** Format value align with step */
/** Format value align with step & marks */
function useOffset(min, max, step, markList, allowCross, pushable) {
const formatRangeValue = React.useCallback(val => Math.max(min, Math.min(max, val)), [min, max]);
const formatStepValue = React.useCallback(val => {
if (step !== null) {
const stepValue = min + Math.round((formatRangeValue(val) - min) / step) * step;
// Cut number in case to be like 0.30000000000000004
const getDecimal = num => (String(num).split('.')[1] || '').length;
const maxDecimal = Math.max(getDecimal(step), getDecimal(max), getDecimal(min));
const fixedValue = Number(stepValue.toFixed(maxDecimal));
return min <= fixedValue && fixedValue <= max ? fixedValue : null;
}
return null;
}, [step, min, max, formatRangeValue]);
const formatValue = React.useCallback(val => {
const formatNextValue = formatRangeValue(val);
// List align values
const alignValues = markList.map(mark => mark.value);
if (step !== null) {
alignValues.push(formatStepValue(val));
}
// min & max
alignValues.push(min, max);
// Align with marks
let closeValue = alignValues[0];
let closeDist = max - min;
alignValues.forEach(alignValue => {
const dist = Math.abs(formatNextValue - alignValue);
if (dist <= closeDist) {
closeValue = alignValue;
closeDist = dist;
}
});
return closeValue;
}, [min, max, markList, step, formatRangeValue, formatStepValue]);
// ========================== Offset ==========================
// Single Value
const offsetValue = (values, offset, valueIndex, mode = 'unit') => {
if (typeof offset === 'number') {
let nextValue;
const originValue = values[valueIndex];
// Only used for `dist` mode
const targetDistValue = originValue + offset;
// Compare next step value & mark value which is best match
let potentialValues = [];
markList.forEach(mark => {
potentialValues.push(mark.value);
});
// Min & Max
potentialValues.push(min, max);
// In case origin value is align with mark but not with step
potentialValues.push(formatStepValue(originValue));
// Put offset step value also
const sign = offset > 0 ? 1 : -1;
if (mode === 'unit') {
potentialValues.push(formatStepValue(originValue + sign * step));
} else {
potentialValues.push(formatStepValue(targetDistValue));
}
// Find close one
potentialValues = potentialValues.filter(val => val !== null)
// Remove reverse value
.filter(val => offset < 0 ? val <= originValue : val >= originValue);
if (mode === 'unit') {
// `unit` mode can not contain itself
potentialValues = potentialValues.filter(val => val !== originValue);
}
const compareValue = mode === 'unit' ? originValue : targetDistValue;
nextValue = potentialValues[0];
let valueDist = Math.abs(nextValue - compareValue);
potentialValues.forEach(potentialValue => {
const dist = Math.abs(potentialValue - compareValue);
if (dist < valueDist) {
nextValue = potentialValue;
valueDist = dist;
}
});
// Out of range will back to range
if (nextValue === undefined) {
return offset < 0 ? min : max;
}
// `dist` mode
if (mode === 'dist') {
return nextValue;
}
// `unit` mode may need another round
if (Math.abs(offset) > 1) {
const cloneValues = [...values];
cloneValues[valueIndex] = nextValue;
return offsetValue(cloneValues, offset - sign, valueIndex, mode);
}
return nextValue;
} else if (offset === 'min') {
return min;
} else if (offset === 'max') {
return max;
}
};
/** Same as `offsetValue` but return `changed` mark to tell value changed */
const offsetChangedValue = (values, offset, valueIndex, mode = 'unit') => {
const originValue = values[valueIndex];
const nextValue = offsetValue(values, offset, valueIndex, mode);
return {
value: nextValue,
changed: nextValue !== originValue
};
};
const needPush = dist => {
return pushable === null && dist === 0 || typeof pushable === 'number' && dist < pushable;
};
// Values
const offsetValues = (values, offset, valueIndex, mode = 'unit') => {
const nextValues = values.map(formatValue);
const originValue = nextValues[valueIndex];
const nextValue = offsetValue(nextValues, offset, valueIndex, mode);
nextValues[valueIndex] = nextValue;
if (allowCross === false) {
// >>>>> Allow Cross
const pushNum = pushable || 0;
// ============ AllowCross ===============
if (valueIndex > 0 && nextValues[valueIndex - 1] !== originValue) {
nextValues[valueIndex] = Math.max(nextValues[valueIndex], nextValues[valueIndex - 1] + pushNum);
}
if (valueIndex < nextValues.length - 1 && nextValues[valueIndex + 1] !== originValue) {
nextValues[valueIndex] = Math.min(nextValues[valueIndex], nextValues[valueIndex + 1] - pushNum);
}
} else if (typeof pushable === 'number' || pushable === null) {
// >>>>> Pushable
// =============== Push ==================
// >>>>>> Basic push
// End values
for (let i = valueIndex + 1; i < nextValues.length; i += 1) {
let changed = true;
while (needPush(nextValues[i] - nextValues[i - 1]) && changed) {
({
value: nextValues[i],
changed
} = offsetChangedValue(nextValues, 1, i));
}
}
// Start values
for (let i = valueIndex; i > 0; i -= 1) {
let changed = true;
while (needPush(nextValues[i] - nextValues[i - 1]) && changed) {
({
value: nextValues[i - 1],
changed
} = offsetChangedValue(nextValues, -1, i - 1));
}
}
// >>>>> Revert back to safe push range
// End to Start
for (let i = nextValues.length - 1; i > 0; i -= 1) {
let changed = true;
while (needPush(nextValues[i] - nextValues[i - 1]) && changed) {
({
value: nextValues[i - 1],
changed
} = offsetChangedValue(nextValues, -1, i - 1));
}
}
// Start to End
for (let i = 0; i < nextValues.length - 1; i += 1) {
let changed = true;
while (needPush(nextValues[i + 1] - nextValues[i]) && changed) {
({
value: nextValues[i + 1],
changed
} = offsetChangedValue(nextValues, 1, i + 1));
}
}
}
return {
value: nextValues[valueIndex],
values: nextValues
};
};
return [formatValue, offsetValues];
}
@@ -0,0 +1,8 @@
import type { SliderProps } from '../Slider';
export default function useRange(range?: SliderProps['range']): [
range: boolean,
rangeEditable: boolean,
rangeDraggableTrack: boolean,
minCount: number,
maxCount?: number
];
@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = useRange;
var _warning = require("@rc-component/util/lib/warning");
var _react = require("react");
function useRange(range) {
return (0, _react.useMemo)(() => {
if (range === true || !range) {
return [!!range, false, false, 0];
}
const {
editable,
draggableTrack,
minCount,
maxCount
} = range;
if (process.env.NODE_ENV !== 'production') {
(0, _warning.warning)(!editable || !draggableTrack, '`editable` can not work with `draggableTrack`.');
}
return [true, editable, !editable && draggableTrack, minCount || 0, maxCount];
}, [range]);
}
+5
View File
@@ -0,0 +1,5 @@
import type { SliderProps, SliderRef } from './Slider';
import Slider from './Slider';
export { UnstableContext } from './context';
export type { SliderProps, SliderRef };
export default Slider;
+16
View File
@@ -0,0 +1,16 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "UnstableContext", {
enumerable: true,
get: function () {
return _context.UnstableContext;
}
});
exports.default = void 0;
var _Slider = _interopRequireDefault(require("./Slider"));
var _context = require("./context");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = exports.default = _Slider.default;
+7
View File
@@ -0,0 +1,7 @@
import type React from 'react';
export type Direction = 'rtl' | 'ltr' | 'ttb' | 'btt';
export type OnStartMove = (e: React.MouseEvent | React.TouchEvent, valueIndex: number, startValues?: number[]) => void;
export type AriaValueFormat = (value: number) => string;
export type SemanticName = 'tracks' | 'track' | 'rail' | 'handle';
export type SliderClassNames = Partial<Record<SemanticName, string>>;
export type SliderStyles = Partial<Record<SemanticName, React.CSSProperties>>;
+5
View File
@@ -0,0 +1,5 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="react" />
import type { Direction } from './interface';
export declare function getOffset(value: number, min: number, max: number): number;
export declare function getDirectionStyle(direction: Direction, value: number, min: number, max: number): import("react").CSSProperties;
/** Return index value if is list or return value directly */
export declare function getIndex<T>(value: T | T[], index: number): T;
+39
View File
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getDirectionStyle = getDirectionStyle;
exports.getIndex = getIndex;
exports.getOffset = getOffset;
function getOffset(value, min, max) {
return (value - min) / (max - min);
}
function getDirectionStyle(direction, value, min, max) {
const offset = getOffset(value, min, max);
const positionStyle = {};
switch (direction) {
case 'rtl':
positionStyle.right = `${offset * 100}%`;
positionStyle.transform = 'translateX(50%)';
break;
case 'btt':
positionStyle.bottom = `${offset * 100}%`;
positionStyle.transform = 'translateY(50%)';
break;
case 'ttb':
positionStyle.top = `${offset * 100}%`;
positionStyle.transform = 'translateY(-50%)';
break;
default:
positionStyle.left = `${offset * 100}%`;
positionStyle.transform = 'translateX(-50%)';
break;
}
return positionStyle;
}
/** Return index value if is list or return value directly */
function getIndex(value, index) {
return Array.isArray(value) ? value[index] : value;
}
+83
View File
@@ -0,0 +1,83 @@
{
"name": "@rc-component/slider",
"version": "1.0.1",
"description": "Slider UI component for React",
"keywords": [
"react",
"react-component",
"react-slider",
"slider",
"input",
"range"
],
"homepage": "https://react-component.github.io/slider",
"bugs": {
"url": "http://github.com/react-component/slider/issues"
},
"repository": {
"type": "git",
"url": "git@github.com:react-component/slider.git"
},
"license": "MIT",
"main": "./lib/index",
"module": "./es/index",
"types": "./lib/index.d.ts",
"style": "./assets/index.css",
"files": [
"assets/*.css",
"lib",
"es"
],
"scripts": {
"compile": "father build && lessc assets/index.less assets/index.css",
"coverage": "rc-test --coverage",
"docs:build": "dumi build",
"docs:deploy": "gh-pages -d .doc",
"lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md",
"now-build": "npm run docs:build",
"prepublishOnly": "npm run compile && rc-np",
"prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"start": "dumi dev",
"test": "rc-test",
"tsc": "bunx tsc --noEmit"
},
"dependencies": {
"@rc-component/util": "^1.3.0",
"clsx": "^2.1.1"
},
"devDependencies": {
"@rc-component/father-plugin": "^2.1.3",
"@rc-component/np": "^1.0.0",
"@rc-component/tooltip": "^1.2.1",
"@rc-component/trigger": "^3.5.2",
"@testing-library/jest-dom": "^6.1.5",
"@testing-library/react": "^12.1.3",
"@types/jest": "^29.5.1",
"@types/node": "^22.5.0",
"@types/react": "^18.2.42",
"@types/react-dom": "^18.0.11",
"@umijs/fabric": "^4.0.1",
"cross-env": "^7.0.0",
"dumi": "^2.2.10",
"eslint": "^8.54.0",
"eslint-plugin-jest": "^28.2.0",
"eslint-plugin-unicorn": "^54.0.0",
"father": "^4.3.5",
"father-build": "^1.18.6",
"gh-pages": "^6.1.0",
"glob": "^7.1.6",
"less": "^4.1.3",
"rc-test": "^7.0.15",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"regenerator-runtime": "^0.14.0",
"typescript": "^5.1.6"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0"
},
"engines": {
"node": ">=8.x"
}
}