foundation.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. /* eslint-disable no-param-reassign */
  2. /* eslint-disable max-len */
  3. /* eslint-disable no-nested-ternary */
  4. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  5. import touchEventPolyfill from '../utils/touchPolyfill';
  6. import warning from '../utils/warning';
  7. import { handlePrevent } from '../utils/a11y';
  8. export interface Marks{
  9. [key: number]: string;
  10. }
  11. export type tipFormatterBasicType = string | number | boolean | null;
  12. export interface SliderProps{
  13. defaultValue?: number | number[];
  14. disabled?: boolean;
  15. included?: boolean; // Whether to juxtapose. Allow dragging
  16. marks?: Marks; // Scale
  17. max?: number;
  18. min?: number;
  19. range?: boolean; // Whether both sides
  20. step?: number;
  21. tipFormatter?: (value: tipFormatterBasicType | tipFormatterBasicType[]) => any;
  22. value?: number | number[];
  23. vertical?: boolean;
  24. onAfterChange?: (value: SliderProps['value']) => void; // triggered when mouse up and clicked
  25. onChange?: (value: SliderProps['value']) => void;
  26. tooltipVisible?: boolean;
  27. style?: Record<string, any>;
  28. className?: string;
  29. showBoundary?: boolean;
  30. railStyle?: Record<string, any>;
  31. verticalReverse?: boolean;
  32. 'aria-label'?: string;
  33. 'aria-labelledby'?: string;
  34. 'aria-valuetext'?: string;
  35. getAriaValueText?: (value: number, index?: number) => string;
  36. }
  37. export interface SliderState {
  38. currentValue: number | number[];
  39. min: number;
  40. max: number;
  41. focusPos: 'min' | 'max' | '';
  42. onChange: (value: SliderProps['value']) => void;
  43. disabled: SliderProps['disabled'];
  44. chooseMovePos: 'min' | 'max' | '';
  45. isDrag: boolean;
  46. clickValue: 0;
  47. showBoundary: boolean;
  48. isInRenderTree: boolean;
  49. firstDotFocusVisible: boolean;
  50. secondDotFocusVisible: boolean;
  51. }
  52. export interface SliderLengths{
  53. sliderX: number;
  54. sliderY: number;
  55. sliderWidth: number;
  56. sliderHeight: number;
  57. }
  58. export interface ScrollParentVal{
  59. scrollTop: number;
  60. scrollLeft: number;
  61. }
  62. export interface OverallVars{
  63. dragging: boolean[];
  64. }
  65. export interface SliderAdapter extends DefaultAdapter<SliderProps, SliderState>{
  66. getSliderLengths: () => SliderLengths;
  67. getParentRect: () => DOMRect | void;
  68. getScrollParentVal: () => ScrollParentVal;
  69. isEventFromHandle: (e: any) => boolean;
  70. getOverallVars: () => OverallVars;
  71. updateDisabled: (disabled: SliderState['disabled']) => void;
  72. transNewPropsToState: <K extends keyof SliderState>(stateObj: Pick<SliderState, K>, callback?: () => void) => void;
  73. notifyChange: (callbackValue: number | number[]) => void;
  74. setDragging: (value: boolean[]) => void;
  75. updateCurrentValue: (value: SliderState['currentValue']) => void;
  76. setOverallVars: (key: string, value: any) => void;
  77. getMinHandleEl: () => { current: HTMLElement };
  78. getMaxHandleEl: () => { current: HTMLElement };
  79. onHandleDown: (e: any) => any;
  80. onHandleMove: (mousePos: number, isMin: boolean, stateChangeCallback?: () => void, clickTrack?: boolean, outPutValue?: number | number[]) => boolean | void;
  81. setEventDefault: (e: any) => void;
  82. setStateVal: (state: keyof SliderState, value: any) => void;
  83. onHandleEnter: (position: SliderState['focusPos']) => void;
  84. onHandleLeave: () => void;
  85. onHandleUpBefore: (e: any) => void;
  86. onHandleUpAfter: () => void;
  87. unSubscribeEventListener: () => void;
  88. checkAndUpdateIsInRenderTreeState: () => boolean;
  89. }
  90. export default class SliderFoundation extends BaseFoundation<SliderAdapter> {
  91. private _dragOffset: number;
  92. constructor(adapter: SliderAdapter) {
  93. super({ ...SliderFoundation.defaultAdapter, ...adapter });
  94. }
  95. init() {
  96. this._checkCurrentValue();
  97. this._dragOffset = 0;
  98. }
  99. _checkCurrentValue() {
  100. const { currentValue, min, max } = this.getStates();
  101. let checked;
  102. if (Array.isArray(currentValue)) {
  103. checked = [];
  104. checked[0] = this._checkValidity(currentValue[0], min, max);
  105. checked[1] = this._checkValidity(currentValue[1], min, max);
  106. } else {
  107. checked = this._checkValidity(currentValue, min, max);
  108. }
  109. this._adapter.updateCurrentValue(checked);
  110. }
  111. /**
  112. * Untie event
  113. * @memberof SliderFoundation
  114. */
  115. destroy() {
  116. // debugger
  117. this._adapter.unSubscribeEventListener();
  118. }
  119. /**
  120. * Calculate the percentage corresponding to the current value for style calculation
  121. * @{}
  122. *
  123. * @memberof SliderFoundation
  124. */
  125. getMinAndMaxPercent = (value: number | number[]) => {
  126. // debugger
  127. const { range, min, max } = this._adapter.getProps();
  128. const minPercent = range ? (value[0] - min) / (max - min) : (value as number - min) / (max - min);
  129. const maxPercent = range ? (value[1] - min) / (max - min) : 1;
  130. return { min: this._checkValidity(minPercent), max: this._checkValidity(maxPercent) };
  131. };
  132. /**
  133. * Check if value is out of range
  134. * @memberof SliderFoundation
  135. */
  136. _checkValidity = (value: number, min = 0, max = 1) => {
  137. const checked = value > max ?
  138. max :
  139. value < min ?
  140. min :
  141. value;
  142. return checked;
  143. };
  144. /**
  145. * When render handle, the display and content of the tooltip are calculated according to the conditions
  146. * @visible: props passed in by the component
  147. * @formatter: tooltip content formatting function
  148. * @memberof SliderFoundation
  149. */
  150. computeHandleVisibleVal = (visible: SliderProps['tooltipVisible'], formatter: SliderProps['tipFormatter'], range: SliderProps['range']) => {
  151. // debugger;
  152. const { focusPos, currentValue } = this._adapter.getStates();
  153. const tipVisible = { min: false, max: false };
  154. let tipChildren;
  155. if (formatter) {
  156. tipChildren = {
  157. min: range ?
  158. formatter(this.outPutValue(currentValue[0])) :
  159. formatter(this.outPutValue(currentValue)),
  160. max: range ? formatter(this.outPutValue(currentValue[1])) : null,
  161. };
  162. } else {
  163. tipChildren = {
  164. min: range ? this.outPutValue(currentValue[0]) : this.outPutValue(currentValue),
  165. max: range ? this.outPutValue(currentValue[1]) : null,
  166. };
  167. }
  168. if (visible) {
  169. tipVisible.min = true;
  170. tipVisible.max = true;
  171. } else if (typeof visible === 'undefined' && formatter) {
  172. if (focusPos === 'min') {
  173. tipVisible.min = true;
  174. } else if (focusPos === 'max') {
  175. tipVisible.max = true;
  176. }
  177. }
  178. const result = {
  179. tipVisible,
  180. tipChildren,
  181. };
  182. return result;
  183. };
  184. /**
  185. * Calculate whether the value passed in is valid
  186. *
  187. * @memberof SliderFoundation
  188. */
  189. valueFormatIsCorrect = (value: SliderProps['value']) => {
  190. if (Array.isArray(value)) {
  191. return typeof value[0] === 'number' && typeof value[0] === 'number';
  192. } else {
  193. return typeof value === 'number';
  194. }
  195. };
  196. /**
  197. * Fix the mouse position to position the parent container relative to the position
  198. *
  199. * @memberof SliderFoundation
  200. */
  201. handleMousePos = (pageX: number, pageY: number) => {
  202. const parentRect = this._adapter.getParentRect();
  203. const scrollParent = this._adapter.getScrollParentVal();
  204. const parentX = parentRect ? parentRect.left : 0;
  205. const parentY = parentRect ? parentRect.top : 0;
  206. return { x: pageX - parentX + scrollParent.scrollLeft, y: pageY - parentY + scrollParent.scrollTop };
  207. };
  208. /**
  209. * Provides the nearest scrollable parent node of the current node, which is used to calculate the scrollTop and scrollLeft attributes
  210. *
  211. * @memberof SliderFoundation
  212. */
  213. getScrollParent = (element: HTMLElement) => {
  214. // TODO: move window document out of foundation.
  215. const el = element;
  216. const regex = /(auto|scroll)/;
  217. const style = (node: Element, prop: string) => window.getComputedStyle(node, null).getPropertyValue(prop);
  218. const scroll = (node: Element) => regex.test(style(node, 'overflow') + style(node, 'overflow-y') + style(node, 'overflow-x'));
  219. const scrollParent = (node: Element): Element => (
  220. !node || (node === document.body || !(node instanceof Element)) ? document.body : scroll(node) ? node : scrollParent(node.parentNode as Element)
  221. );
  222. return scrollParent(el);
  223. };
  224. /**
  225. * Fixed the event location, beyond the maximum, minimum, left and right, etc. directly modified to the effective location
  226. *
  227. * @memberof SliderFoundation
  228. */
  229. checkMeetMinMax = (position: number) => {
  230. // Returns the length of the distance to the left
  231. const { vertical, verticalReverse, range } = this._adapter.getProps();
  232. const value = this._adapter.getState('currentValue');
  233. const currentPos = this.transValueToPos(value);
  234. const { sliderX, sliderY, sliderWidth, sliderHeight } = this._adapter.getSliderLengths();
  235. const { chooseMovePos, isDrag } = this._adapter.getStates();
  236. const len = vertical ? sliderHeight : sliderWidth;
  237. let startPos;
  238. if (vertical && verticalReverse) {
  239. startPos = sliderY + len;
  240. } else {
  241. startPos = vertical ? sliderY : sliderX;
  242. }
  243. startPos = chooseMovePos === 'max' && isDrag ? currentPos[0] : startPos;
  244. // eslint-disable-next-line one-var
  245. let endPos;
  246. if (vertical && verticalReverse) {
  247. endPos = sliderY;
  248. } else {
  249. endPos = vertical ? sliderY + sliderHeight : sliderX + sliderWidth;
  250. }
  251. endPos = chooseMovePos === 'min' && isDrag && range ? currentPos[1] : endPos;
  252. if (vertical && verticalReverse) {
  253. if (position >= startPos) {
  254. position = startPos;
  255. } else if (position <= endPos) {
  256. position = endPos;
  257. }
  258. } else {
  259. if (position <= startPos) {
  260. position = startPos;
  261. } else if (position >= endPos) {
  262. position = endPos;
  263. }
  264. }
  265. return position;
  266. };
  267. /**
  268. * Converting location information to value requires processing if step is not 1 (invalid move returns false)
  269. *
  270. * @memberof SliderFoundation
  271. */
  272. transPosToValue = (mousePos: number, isMin: boolean) => {
  273. const pos = this.checkMeetMinMax(mousePos);
  274. const { min, max, currentValue } = this._adapter.getStates();
  275. const { range, vertical, step, verticalReverse } = this._adapter.getProps();
  276. const { sliderX, sliderY, sliderWidth, sliderHeight } = this._adapter.getSliderLengths();
  277. const startPos = vertical ? sliderY : sliderX;
  278. const len = vertical ? sliderHeight : sliderWidth;
  279. let stepValue;
  280. if (vertical && verticalReverse) {
  281. isMin = !isMin;
  282. stepValue = ((startPos + len - pos) / len) * (max - min) + min;
  283. } else {
  284. stepValue = ((pos - startPos) / len) * (max - min) + min;
  285. }
  286. // debugger
  287. // eslint-disable-next-line one-var
  288. let compareValue;
  289. if (range) {
  290. compareValue = isMin ? currentValue[0] : currentValue[1];
  291. } else {
  292. compareValue = currentValue;
  293. }
  294. if (step !== 1) { // Find nearest step point
  295. stepValue = Math.round(stepValue / step) * step;
  296. }
  297. if (range && stepValue !== compareValue) {
  298. if (vertical && verticalReverse) {
  299. return (isMin ? [currentValue[0], stepValue] : [stepValue, currentValue[1]]);
  300. } else {
  301. return isMin ? [stepValue, currentValue[1]] : [currentValue[0], stepValue];
  302. }
  303. } else if (!range && stepValue !== compareValue) {
  304. return (stepValue);
  305. } else {
  306. return false;
  307. }
  308. };
  309. /**
  310. * Convert value values into location information
  311. *
  312. * @memberof SliderFoundation
  313. */
  314. transValueToPos = (value: SliderProps['value']) => {
  315. const { min, max } = this._adapter.getStates();
  316. const { vertical, range, verticalReverse } = this._adapter.getProps();
  317. const { sliderX, sliderY, sliderWidth, sliderHeight } = this._adapter.getSliderLengths();
  318. const startPos = vertical ? sliderY : sliderX;
  319. const len = vertical ? sliderHeight : sliderWidth;
  320. if (range) {
  321. if (vertical && verticalReverse) {
  322. return [startPos + len - ((value[0] - min) * len) / (max - min), startPos + len - ((value[1] - min) * len) / (max - min)];
  323. } else {
  324. return [((value[0] - min) * len) / (max - min) + startPos, ((value[1] - min) * len) / (max - min) + startPos];
  325. }
  326. } else {
  327. return ((value as number - min) * len) / (max - min) + startPos;
  328. }
  329. };
  330. /**
  331. * Determine whether the mark should be highlighted: valid interval and include = false
  332. *
  333. * @memberof SliderFoundation
  334. */
  335. isMarkActive = (mark: number) => {
  336. const { min, max, range, included } = this._adapter.getProps();
  337. const currentValue = this._adapter.getState('currentValue');
  338. if (typeof (mark / 1) === 'number' && mark >= min && mark <= max) {
  339. if (range) {
  340. return (mark > currentValue[1] || mark < currentValue[0]) && included ? 'unActive' : 'active';
  341. } else {
  342. return mark <= currentValue && included ? 'active' : 'unActive';
  343. }
  344. } else {
  345. return false;
  346. }
  347. };
  348. /**
  349. * onchange output conversion, default rounding without decimal, step less than 1 has decimal
  350. *
  351. * @memberof SliderFoundation
  352. */
  353. outPutValue = (inputValue: SliderProps['value']) => {
  354. const step = this._adapter.getProp('step');
  355. let transWay = Math.round;
  356. if (step < 1 && step >= 0.1) {
  357. transWay = value => Math.round(value * 10) / 10;
  358. } else if (step < 0.1 && step >= 0.01) {
  359. transWay = value => Math.round(value * 100) / 100;
  360. } else if (step < 0.01 && step >= 0.001) {
  361. transWay = value => Math.round(value * 1000) / 1000;
  362. }
  363. if (Array.isArray(inputValue)) {
  364. return [transWay(inputValue[0]), transWay(inputValue[1])];
  365. } else {
  366. return transWay(inputValue);
  367. }
  368. };
  369. handleDisabledChange = (disabled: SliderState['disabled']) => {
  370. this._adapter.updateDisabled(disabled);
  371. };
  372. checkAndUpdateIsInRenderTreeState = () => this._adapter.checkAndUpdateIsInRenderTreeState();
  373. calculateOutputValue = (position: number, isMin: boolean): undefined | number | number[] => {
  374. const moveValue = this.transPosToValue(position, isMin);
  375. if (moveValue === false) {
  376. return undefined;
  377. }
  378. return this.outPutValue(moveValue);
  379. }
  380. /**
  381. *
  382. *
  383. * @memberof SliderFoundation
  384. */
  385. handleValueChange = (prevValue: SliderProps['value'], nextValue: SliderProps['value']) => {
  386. const { min, max } = this._adapter.getStates();
  387. let resultState = null;
  388. const disableState = {};
  389. if (this.valueFormatIsCorrect(nextValue)) {
  390. if (Array.isArray(prevValue) && Array.isArray(nextValue)) {
  391. nextValue = [
  392. nextValue[0] < min ? min : nextValue[0], // Math.round(nextValue[0]),
  393. nextValue[1] > max ? max : nextValue[1], // Math.round(nextValue[1])
  394. ];
  395. // this._adapter.notifyChange(this.outPutValue(nextValue));
  396. resultState = Object.assign(disableState, {
  397. currentValue: nextValue,
  398. });
  399. }
  400. if (typeof prevValue === 'number' && typeof nextValue === 'number') {
  401. if (nextValue > max) {
  402. nextValue = max;
  403. } else {
  404. nextValue = nextValue < min ? min : nextValue; // Math.round(nextValue);
  405. }
  406. // this._adapter.notifyChange(this.outPutValue(nextValue));
  407. resultState = Object.assign(disableState, {
  408. currentValue: nextValue,
  409. });
  410. }
  411. } else {
  412. resultState = disableState;
  413. }
  414. if (resultState) {
  415. this._adapter.transNewPropsToState(resultState);
  416. }
  417. };
  418. onHandleDown = (e: any, handler: any) => {
  419. this._adapter.onHandleDown(e);
  420. const disabled = this._adapter.getState('disabled');
  421. const { vertical } = this._adapter.getProps();
  422. const { dragging } = this._adapter.getOverallVars();
  423. if (disabled) {
  424. return false;
  425. }
  426. this._adapter.setStateVal('isDrag', true);
  427. this._adapter.setStateVal('chooseMovePos', handler);
  428. if (handler === 'min') {
  429. this._adapter.setDragging([true, dragging[1]]);
  430. } else {
  431. this._adapter.setDragging([dragging[0], true]);
  432. }
  433. const mousePos = this.handleMousePos(e.pageX, e.pageY);
  434. let pos = vertical ? mousePos.y : mousePos.x;
  435. if (!this._adapter.isEventFromHandle(e)) {
  436. this._dragOffset = 0;
  437. } else {
  438. const handlePosition = this._getHandleCenterPosition(vertical, e.target);
  439. this._dragOffset = vertical ? pos - handlePosition : pos - handlePosition;
  440. pos = handlePosition;
  441. }
  442. return true;
  443. };
  444. onHandleMove = (e: any) => {
  445. this._adapter.setEventDefault(e);
  446. const { disabled, chooseMovePos } = this._adapter.getStates();
  447. const { vertical } = this._adapter.getProps();
  448. const { dragging } = this._adapter.getOverallVars();
  449. if (disabled) {
  450. return false;
  451. }
  452. this.onHandleEnter(chooseMovePos);
  453. const mousePos = this.handleMousePos(e.pageX, e.pageY);
  454. let pagePos = vertical ? mousePos.y : mousePos.x;
  455. pagePos = pagePos - this._dragOffset;
  456. if ((chooseMovePos === 'min' && dragging[0]) || (chooseMovePos === 'max' && dragging[1])) {
  457. const outPutValue = this.calculateOutputValue(pagePos, chooseMovePos === 'min' );
  458. if (outPutValue === undefined) {
  459. return false;
  460. }
  461. this._adapter.notifyChange(outPutValue);
  462. // allow drag for controlled component, so no _isControlledComponent check
  463. this._adapter.onHandleMove(pagePos, chooseMovePos === 'min', undefined, false, outPutValue);
  464. }
  465. return true;
  466. };
  467. // run when user touch left or right handle.
  468. onHandleTouchStart = (e: any, handler: 'min' | 'max') => {
  469. const handleMinDom = this._adapter.getMinHandleEl().current;
  470. const handleMaxDom = this._adapter.getMaxHandleEl().current;
  471. if (e.target === handleMinDom || e.target === handleMaxDom) {
  472. handlePrevent(e);
  473. const touch = touchEventPolyfill(e.touches[0], e);
  474. this.onHandleDown(touch, handler);
  475. }
  476. };
  477. onHandleTouchMove = (e: any) => {
  478. const handleMinDom = this._adapter.getMinHandleEl().current;
  479. const handleMaxDom = this._adapter.getMaxHandleEl().current;
  480. if (e.target === handleMinDom || e.target === handleMaxDom) {
  481. const touch = touchEventPolyfill(e.touches[0], e);
  482. this.onHandleMove(touch);
  483. }
  484. };
  485. onHandleEnter = (pos: SliderState['focusPos']) => {
  486. // debugger;
  487. // this._adapter.setEventDefault(e);
  488. const { disabled, focusPos } = this._adapter.getStates();
  489. if (!disabled) {
  490. if (!focusPos && pos !== focusPos) {
  491. this._adapter.onHandleEnter(pos);
  492. }
  493. }
  494. };
  495. onHandleLeave = () => {
  496. // this._adapter.setEventDefault(e);
  497. const disabled = this._adapter.getState('disabled');
  498. if (!disabled) {
  499. this._adapter.onHandleLeave();
  500. }
  501. };
  502. onHandleUp = (e: any) => {
  503. this._adapter.onHandleUpBefore(e);
  504. // const value = this._adapter.getProp('value');
  505. const { disabled, chooseMovePos } = this._adapter.getStates();
  506. const { dragging } = this._adapter.getOverallVars();
  507. if (disabled) {
  508. return false;
  509. }
  510. if (chooseMovePos === 'min') {
  511. this._adapter.setDragging([false, dragging[1]]);
  512. } else {
  513. this._adapter.setDragging([dragging[0], false]);
  514. }
  515. this._adapter.setStateVal('isDrag', false);
  516. this._adapter.onHandleLeave();
  517. this._adapter.onHandleUpAfter();
  518. return true;
  519. };
  520. _handleValueDecreaseWithKeyBoard = (step: number, handler: 'min'| 'max') => {
  521. const { min, currentValue } = this.getStates();
  522. const { range } = this.getProps();
  523. if (handler === 'min') {
  524. if (range) {
  525. let newMinValue = currentValue[0] - step;
  526. newMinValue = newMinValue < min ? min : newMinValue;
  527. return [newMinValue, currentValue[1]];
  528. } else {
  529. let newMinValue = currentValue - step;
  530. newMinValue = newMinValue < min ? min : newMinValue;
  531. return newMinValue;
  532. }
  533. } else {
  534. let newMaxValue = currentValue[1] - step;
  535. newMaxValue = newMaxValue < currentValue[0] ? currentValue[0] : newMaxValue;
  536. return [currentValue[0], newMaxValue];
  537. }
  538. }
  539. _handleValueIncreaseWithKeyBoard = (step: number, handler: 'min'| 'max') => {
  540. const { max, currentValue } = this.getStates();
  541. const { range } = this.getProps();
  542. if (handler === 'min') {
  543. if (range) {
  544. let newMinValue = currentValue[0] + step;
  545. newMinValue = newMinValue > currentValue[1] ? currentValue[1] : newMinValue;
  546. return [newMinValue, currentValue[1]];
  547. } else {
  548. let newMinValue = currentValue + step;
  549. newMinValue = newMinValue > max ? max : newMinValue;
  550. return newMinValue;
  551. }
  552. } else {
  553. let newMaxValue = currentValue[1] + step;
  554. newMaxValue = newMaxValue > max ? max : newMaxValue;
  555. return [currentValue[0], newMaxValue];
  556. }
  557. }
  558. _handleHomeKey = (handler: 'min'| 'max') => {
  559. const { min, currentValue } = this.getStates();
  560. const { range } = this.getProps();
  561. if (handler === 'min') {
  562. if (range) {
  563. return [min, currentValue[1]];
  564. } else {
  565. return min;
  566. }
  567. } else {
  568. return [currentValue[0], currentValue[0]];
  569. }
  570. }
  571. _handleEndKey = (handler: 'min'| 'max') => {
  572. const { max, currentValue } = this.getStates();
  573. const { range } = this.getProps();
  574. if (handler === 'min') {
  575. if (range) {
  576. return [currentValue[1], currentValue[1]];
  577. } else {
  578. return max;
  579. }
  580. } else {
  581. return [currentValue[0], max];
  582. }
  583. }
  584. handleKeyDown = (event: any, handler: 'min'| 'max') => {
  585. const { min, max, currentValue } = this.getStates();
  586. const { step, range } = this.getProps();
  587. let outputValue;
  588. switch (event.key) {
  589. case "ArrowLeft":
  590. case "ArrowDown":
  591. outputValue = this._handleValueDecreaseWithKeyBoard(step, handler);
  592. break;
  593. case "ArrowRight":
  594. case "ArrowUp":
  595. outputValue = this._handleValueIncreaseWithKeyBoard(step, handler);
  596. break;
  597. case "PageUp":
  598. outputValue = this._handleValueIncreaseWithKeyBoard(10 * step, handler);
  599. break;
  600. case "PageDown":
  601. outputValue = this._handleValueDecreaseWithKeyBoard(10 * step, handler);
  602. break;
  603. case "Home":
  604. outputValue = this._handleHomeKey(handler);
  605. break;
  606. case "End":
  607. outputValue = this._handleEndKey(handler);
  608. break;
  609. case 'default':
  610. break;
  611. }
  612. if (["ArrowLeft", "ArrowDown", "ArrowRight", "ArrowUp", "PageUp", "PageDown", "Home", "End"].includes(event.key)) {
  613. let update = true;
  614. if (Array.isArray(currentValue)) {
  615. update = !(currentValue[0] === outputValue[0] && currentValue[1] === outputValue[1]);
  616. } else {
  617. update = currentValue !== outputValue;
  618. }
  619. if (update) {
  620. this._adapter.updateCurrentValue(outputValue);
  621. this._adapter.notifyChange(outputValue);
  622. }
  623. handlePrevent(event);
  624. }
  625. }
  626. // eslint-disable-next-line @typescript-eslint/no-empty-function
  627. onFocus = (e: any, handler: 'min'| 'max') => {
  628. handlePrevent(e);
  629. const { target } = e;
  630. try {
  631. if (target.matches(':focus-visible')) {
  632. if (handler === 'min') {
  633. this._adapter.setStateVal('firstDotFocusVisible', true);
  634. } else {
  635. this._adapter.setStateVal('secondDotFocusVisible', true);
  636. }
  637. }
  638. } catch (error) {
  639. warning(true, 'Warning: [Semi Slider] The current browser does not support the focus-visible');
  640. }
  641. }
  642. onBlur = (e: any, handler: 'min'| 'max') => {
  643. const { firstDotFocusVisible, secondDotFocusVisible } = this.getStates();
  644. if (handler === 'min') {
  645. firstDotFocusVisible && this._adapter.setStateVal('firstDotFocusVisible', false);
  646. } else {
  647. secondDotFocusVisible && this._adapter.setStateVal('secondDotFocusVisible', false);
  648. }
  649. }
  650. handleWrapClick = (e: any) => {
  651. const { disabled, isDrag } = this._adapter.getStates();
  652. if (isDrag || disabled || this._adapter.isEventFromHandle(e)) {
  653. return;
  654. }
  655. const { vertical } = this.getProps();
  656. const mousePos = this.handleMousePos(e.pageX, e.pageY);
  657. const position = vertical ? mousePos.y : mousePos.x;
  658. const isMin = this.checkWhichHandle(position);
  659. const outPutValue = this.calculateOutputValue(position, isMin);
  660. if (outPutValue === undefined) {
  661. return;
  662. }
  663. this._adapter.notifyChange(outPutValue);
  664. // check if is controlled component
  665. if (this._isControlledComponent()) {
  666. // only perform callback ops, skip UI update
  667. return;
  668. }
  669. // trigger UI state update
  670. this.setHandlePos(position, isMin, true, outPutValue);
  671. };
  672. /**
  673. * Move the slider to the current click position
  674. *
  675. * @memberof SliderFoundation
  676. */
  677. setHandlePos = (position: number, isMin: boolean, clickTrack = false, outPutValue: number | number[]) => {
  678. this._adapter.onHandleMove(position, isMin, () => this._adapter.onHandleUpAfter(), clickTrack, outPutValue);
  679. };
  680. /**
  681. * Determine which slider should be moved currently
  682. *
  683. * @memberof SliderFoundation
  684. */
  685. checkWhichHandle = (pagePos: number) => {
  686. const { vertical, verticalReverse } = this.getProps();
  687. const { currentValue } = this._adapter.getStates();
  688. const currentPos = this.transValueToPos(currentValue);
  689. let isMin = true;
  690. if (Array.isArray(currentPos)) {
  691. // Slide on both sides
  692. if (
  693. pagePos > currentPos[1] ||
  694. Math.abs(pagePos - currentPos[0]) > Math.abs(pagePos - currentPos[1])
  695. ) {
  696. isMin = false;
  697. }
  698. }
  699. if (vertical && verticalReverse) {
  700. isMin = !isMin;
  701. }
  702. return isMin;
  703. };
  704. handleWrapperEnter = () => {
  705. this._adapter.setStateVal('showBoundary', true);
  706. };
  707. handleWrapperLeave = () => {
  708. this._adapter.setStateVal('showBoundary', false);
  709. };
  710. private _getHandleCenterPosition(vertical: boolean, handle: HTMLElement) {
  711. const pos = handle.getBoundingClientRect();
  712. const { x, y } = this.handleMousePos(pos.left + (pos.width * 0.5), pos.top + (pos.height * 0.5));
  713. return vertical ? y : x;
  714. }
  715. }