foundation.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  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: () => HTMLSpanElement;
  78. getMaxHandleEl: () => HTMLSpanElement;
  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 checkHowManyDecimals = (num:number)=>{
  355. const reg = /^\d+(\.\d+)?$/;
  356. if (reg.test(String(num))){
  357. return num.toString().split('.')[1]?.length ?? 0;
  358. }
  359. return 0;
  360. };
  361. const step = this._adapter.getProp('step');
  362. const transWay = (()=>{
  363. const decimals = checkHowManyDecimals(step);
  364. const multipler = Math.pow(10, decimals);
  365. return (value: number)=>{
  366. return Math.round(value * multipler) / multipler;
  367. };
  368. })();
  369. if (Array.isArray(inputValue)) {
  370. return [transWay(inputValue[0]), transWay(inputValue[1])];
  371. } else {
  372. return transWay(inputValue);
  373. }
  374. };
  375. handleDisabledChange = (disabled: SliderState['disabled']) => {
  376. this._adapter.updateDisabled(disabled);
  377. };
  378. checkAndUpdateIsInRenderTreeState = () => this._adapter.checkAndUpdateIsInRenderTreeState();
  379. calculateOutputValue = (position: number, isMin: boolean): undefined | number | number[] => {
  380. const moveValue = this.transPosToValue(position, isMin);
  381. if (moveValue === false) {
  382. return undefined;
  383. }
  384. return this.outPutValue(moveValue);
  385. }
  386. /**
  387. *
  388. *
  389. * @memberof SliderFoundation
  390. */
  391. handleValueChange = (prevValue: SliderProps['value'], nextValue: SliderProps['value']) => {
  392. const { min, max } = this._adapter.getStates();
  393. let resultState = null;
  394. const disableState = {};
  395. if (this.valueFormatIsCorrect(nextValue)) {
  396. if (Array.isArray(prevValue) && Array.isArray(nextValue)) {
  397. nextValue = [
  398. nextValue[0] < min ? min : nextValue[0], // Math.round(nextValue[0]),
  399. nextValue[1] > max ? max : nextValue[1], // Math.round(nextValue[1])
  400. ];
  401. // this._adapter.notifyChange(this.outPutValue(nextValue));
  402. resultState = Object.assign(disableState, {
  403. currentValue: nextValue,
  404. });
  405. }
  406. if (typeof prevValue === 'number' && typeof nextValue === 'number') {
  407. if (nextValue > max) {
  408. nextValue = max;
  409. } else {
  410. nextValue = nextValue < min ? min : nextValue; // Math.round(nextValue);
  411. }
  412. // this._adapter.notifyChange(this.outPutValue(nextValue));
  413. resultState = Object.assign(disableState, {
  414. currentValue: nextValue,
  415. });
  416. }
  417. } else {
  418. resultState = disableState;
  419. }
  420. if (resultState) {
  421. this._adapter.transNewPropsToState(resultState);
  422. }
  423. };
  424. onHandleDown = (e: any, handler: any) => {
  425. this._adapter.onHandleDown(e);
  426. const disabled = this._adapter.getState('disabled');
  427. const { vertical } = this._adapter.getProps();
  428. const { dragging } = this._adapter.getOverallVars();
  429. if (disabled) {
  430. return false;
  431. }
  432. this._adapter.setStateVal('isDrag', true);
  433. this._adapter.setStateVal('chooseMovePos', handler);
  434. if (handler === 'min') {
  435. this._adapter.setDragging([true, dragging[1]]);
  436. } else {
  437. this._adapter.setDragging([dragging[0], true]);
  438. }
  439. const mousePos = this.handleMousePos(e.pageX, e.pageY);
  440. let pos = vertical ? mousePos.y : mousePos.x;
  441. if (!this._adapter.isEventFromHandle(e)) {
  442. this._dragOffset = 0;
  443. } else {
  444. const handlePosition = this._getHandleCenterPosition(vertical, e.target);
  445. this._dragOffset = vertical ? pos - handlePosition : pos - handlePosition;
  446. pos = handlePosition;
  447. }
  448. return true;
  449. };
  450. onHandleMove = (e: any) => {
  451. this._adapter.setEventDefault(e);
  452. const { disabled, chooseMovePos } = this._adapter.getStates();
  453. const { vertical } = this._adapter.getProps();
  454. const { dragging } = this._adapter.getOverallVars();
  455. if (disabled) {
  456. return false;
  457. }
  458. this.onHandleEnter(chooseMovePos);
  459. const mousePos = this.handleMousePos(e.pageX, e.pageY);
  460. let pagePos = vertical ? mousePos.y : mousePos.x;
  461. pagePos = pagePos - this._dragOffset;
  462. if ((chooseMovePos === 'min' && dragging[0]) || (chooseMovePos === 'max' && dragging[1])) {
  463. const outPutValue = this.calculateOutputValue(pagePos, chooseMovePos === 'min' );
  464. if (outPutValue === undefined) {
  465. return false;
  466. }
  467. this._adapter.notifyChange(outPutValue);
  468. // allow drag for controlled component, so no _isControlledComponent check
  469. this._adapter.onHandleMove(pagePos, chooseMovePos === 'min', undefined, false, outPutValue);
  470. }
  471. return true;
  472. };
  473. // run when user touch left or right handle.
  474. onHandleTouchStart = (e: any, handler: 'min' | 'max') => {
  475. const handleMinDom = this._adapter.getMinHandleEl();
  476. const handleMaxDom = this._adapter.getMaxHandleEl();
  477. if (e.target === handleMinDom || e.target === handleMaxDom) {
  478. handlePrevent(e);
  479. const touch = touchEventPolyfill(e.touches[0], e);
  480. this.onHandleDown(touch, handler);
  481. }
  482. };
  483. onHandleTouchMove = (e: any) => {
  484. const handleMinDom = this._adapter.getMinHandleEl();
  485. const handleMaxDom = this._adapter.getMaxHandleEl();
  486. if (e.target === handleMinDom || e.target === handleMaxDom) {
  487. const touch = touchEventPolyfill(e.touches[0], e);
  488. this.onHandleMove(touch);
  489. }
  490. };
  491. onHandleEnter = (pos: SliderState['focusPos']) => {
  492. // debugger;
  493. // this._adapter.setEventDefault(e);
  494. const { disabled, focusPos } = this._adapter.getStates();
  495. if (!disabled) {
  496. if (!focusPos && pos !== focusPos) {
  497. this._adapter.onHandleEnter(pos);
  498. }
  499. }
  500. };
  501. onHandleLeave = () => {
  502. // this._adapter.setEventDefault(e);
  503. const disabled = this._adapter.getState('disabled');
  504. if (!disabled) {
  505. this._adapter.onHandleLeave();
  506. }
  507. };
  508. onHandleUp = (e: any) => {
  509. this._adapter.onHandleUpBefore(e);
  510. // const value = this._adapter.getProp('value');
  511. const { disabled, chooseMovePos } = this._adapter.getStates();
  512. const { dragging } = this._adapter.getOverallVars();
  513. if (disabled) {
  514. return false;
  515. }
  516. if (chooseMovePos === 'min') {
  517. this._adapter.setDragging([false, dragging[1]]);
  518. } else {
  519. this._adapter.setDragging([dragging[0], false]);
  520. }
  521. this._adapter.setStateVal('isDrag', false);
  522. this._adapter.onHandleLeave();
  523. this._adapter.onHandleUpAfter();
  524. return true;
  525. };
  526. _handleValueDecreaseWithKeyBoard = (step: number, handler: 'min'| 'max') => {
  527. const { min, currentValue } = this.getStates();
  528. const { range } = this.getProps();
  529. if (handler === 'min') {
  530. if (range) {
  531. let newMinValue = currentValue[0] - step;
  532. newMinValue = newMinValue < min ? min : newMinValue;
  533. return [newMinValue, currentValue[1]];
  534. } else {
  535. let newMinValue = currentValue - step;
  536. newMinValue = newMinValue < min ? min : newMinValue;
  537. return newMinValue;
  538. }
  539. } else {
  540. let newMaxValue = currentValue[1] - step;
  541. newMaxValue = newMaxValue < currentValue[0] ? currentValue[0] : newMaxValue;
  542. return [currentValue[0], newMaxValue];
  543. }
  544. }
  545. _handleValueIncreaseWithKeyBoard = (step: number, handler: 'min'| 'max') => {
  546. const { max, currentValue } = this.getStates();
  547. const { range } = this.getProps();
  548. if (handler === 'min') {
  549. if (range) {
  550. let newMinValue = currentValue[0] + step;
  551. newMinValue = newMinValue > currentValue[1] ? currentValue[1] : newMinValue;
  552. return [newMinValue, currentValue[1]];
  553. } else {
  554. let newMinValue = currentValue + step;
  555. newMinValue = newMinValue > max ? max : newMinValue;
  556. return newMinValue;
  557. }
  558. } else {
  559. let newMaxValue = currentValue[1] + step;
  560. newMaxValue = newMaxValue > max ? max : newMaxValue;
  561. return [currentValue[0], newMaxValue];
  562. }
  563. }
  564. _handleHomeKey = (handler: 'min'| 'max') => {
  565. const { min, currentValue } = this.getStates();
  566. const { range } = this.getProps();
  567. if (handler === 'min') {
  568. if (range) {
  569. return [min, currentValue[1]];
  570. } else {
  571. return min;
  572. }
  573. } else {
  574. return [currentValue[0], currentValue[0]];
  575. }
  576. }
  577. _handleEndKey = (handler: 'min'| 'max') => {
  578. const { max, currentValue } = this.getStates();
  579. const { range } = this.getProps();
  580. if (handler === 'min') {
  581. if (range) {
  582. return [currentValue[1], currentValue[1]];
  583. } else {
  584. return max;
  585. }
  586. } else {
  587. return [currentValue[0], max];
  588. }
  589. }
  590. handleKeyDown = (event: any, handler: 'min'| 'max') => {
  591. const { min, max, currentValue } = this.getStates();
  592. const { step, range } = this.getProps();
  593. let outputValue;
  594. switch (event.key) {
  595. case "ArrowLeft":
  596. case "ArrowDown":
  597. outputValue = this._handleValueDecreaseWithKeyBoard(step, handler);
  598. break;
  599. case "ArrowRight":
  600. case "ArrowUp":
  601. outputValue = this._handleValueIncreaseWithKeyBoard(step, handler);
  602. break;
  603. case "PageUp":
  604. outputValue = this._handleValueIncreaseWithKeyBoard(10 * step, handler);
  605. break;
  606. case "PageDown":
  607. outputValue = this._handleValueDecreaseWithKeyBoard(10 * step, handler);
  608. break;
  609. case "Home":
  610. outputValue = this._handleHomeKey(handler);
  611. break;
  612. case "End":
  613. outputValue = this._handleEndKey(handler);
  614. break;
  615. case 'default':
  616. break;
  617. }
  618. if (["ArrowLeft", "ArrowDown", "ArrowRight", "ArrowUp", "PageUp", "PageDown", "Home", "End"].includes(event.key)) {
  619. let update = true;
  620. if (Array.isArray(currentValue)) {
  621. update = !(currentValue[0] === outputValue[0] && currentValue[1] === outputValue[1]);
  622. } else {
  623. update = currentValue !== outputValue;
  624. }
  625. if (update) {
  626. this._adapter.updateCurrentValue(outputValue);
  627. this._adapter.notifyChange(outputValue);
  628. }
  629. handlePrevent(event);
  630. }
  631. }
  632. // eslint-disable-next-line @typescript-eslint/no-empty-function
  633. onFocus = (e: any, handler: 'min'| 'max') => {
  634. handlePrevent(e);
  635. const { target } = e;
  636. try {
  637. if (target.matches(':focus-visible')) {
  638. if (handler === 'min') {
  639. this._adapter.setStateVal('firstDotFocusVisible', true);
  640. } else {
  641. this._adapter.setStateVal('secondDotFocusVisible', true);
  642. }
  643. }
  644. } catch (error) {
  645. warning(true, 'Warning: [Semi Slider] The current browser does not support the focus-visible');
  646. }
  647. }
  648. onBlur = (e: any, handler: 'min'| 'max') => {
  649. const { firstDotFocusVisible, secondDotFocusVisible } = this.getStates();
  650. if (handler === 'min') {
  651. firstDotFocusVisible && this._adapter.setStateVal('firstDotFocusVisible', false);
  652. } else {
  653. secondDotFocusVisible && this._adapter.setStateVal('secondDotFocusVisible', false);
  654. }
  655. }
  656. handleWrapClick = (e: any) => {
  657. const { disabled, isDrag } = this._adapter.getStates();
  658. if (isDrag || disabled || this._adapter.isEventFromHandle(e)) {
  659. return;
  660. }
  661. const { vertical } = this.getProps();
  662. const mousePos = this.handleMousePos(e.pageX, e.pageY);
  663. const position = vertical ? mousePos.y : mousePos.x;
  664. const isMin = this.checkWhichHandle(position);
  665. const outPutValue = this.calculateOutputValue(position, isMin);
  666. if (outPutValue === undefined) {
  667. return;
  668. }
  669. this._adapter.notifyChange(outPutValue);
  670. // check if is controlled component
  671. if (this._isControlledComponent()) {
  672. // only perform callback ops, skip UI update
  673. return;
  674. }
  675. // trigger UI state update
  676. this.setHandlePos(position, isMin, true, outPutValue);
  677. };
  678. /**
  679. * Move the slider to the current click position
  680. *
  681. * @memberof SliderFoundation
  682. */
  683. setHandlePos = (position: number, isMin: boolean, clickTrack = false, outPutValue: number | number[]) => {
  684. this._adapter.onHandleMove(position, isMin, () => this._adapter.onHandleUpAfter(), clickTrack, outPutValue);
  685. };
  686. /**
  687. * Determine which slider should be moved currently
  688. *
  689. * @memberof SliderFoundation
  690. */
  691. checkWhichHandle = (pagePos: number) => {
  692. const { vertical, verticalReverse } = this.getProps();
  693. const { currentValue } = this._adapter.getStates();
  694. const currentPos = this.transValueToPos(currentValue);
  695. let isMin = true;
  696. if (Array.isArray(currentPos)) {
  697. // Slide on both sides
  698. if (
  699. pagePos > currentPos[1] ||
  700. Math.abs(pagePos - currentPos[0]) > Math.abs(pagePos - currentPos[1])
  701. ) {
  702. isMin = false;
  703. }
  704. }
  705. if (vertical && verticalReverse) {
  706. isMin = !isMin;
  707. }
  708. return isMin;
  709. };
  710. handleWrapperEnter = () => {
  711. this._adapter.setStateVal('showBoundary', true);
  712. };
  713. handleWrapperLeave = () => {
  714. this._adapter.setStateVal('showBoundary', false);
  715. };
  716. private _getHandleCenterPosition(vertical: boolean, handle: HTMLElement) {
  717. const pos = handle.getBoundingClientRect();
  718. const { x, y } = this.handleMousePos(pos.left + (pos.width * 0.5), pos.top + (pos.height * 0.5));
  719. return vertical ? y : x;
  720. }
  721. }