Slider 适配微信 (#935)

* feat: slider 适配微信

* fix: 更新 demo

---------

Co-authored-by: DiamondYuan <fandi.yfd@antgroup.com>
This commit is contained in:
DiamondYuan 2023-11-17 14:28:45 +08:00 committed by GitHub
parent 3e8e10dcef
commit f3bb78bfd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1039 additions and 188 deletions

View File

@ -1,12 +1,12 @@
<container title="基本使用">
<slider
<ant-slider
defaultValue="{{ 80 }}"
onChange="onChange"
onAfterChange="onAfterChange" />
</container>
<container title="无节点双滑块">
<slider
<ant-slider
defaultValue="{{ [20, 60] }}"
range
onChange="onChange"
@ -14,7 +14,7 @@
</container>
<container title="节点单滑块">
<slider
<ant-slider
defaultValue="{{ 60 }}"
step="{{ 20 }}"
showTicks
@ -23,7 +23,7 @@
</container>
<container title="有数字节点单滑块">
<slider
<ant-slider
defaultValue="{{ 80 }}"
step="{{ 20 }}"
showTicks
@ -33,7 +33,7 @@
</container>
<container title="节点双滑块">
<slider
<ant-slider
defaultValue="{{ [10, 50] }}"
step="{{ 10 }}"
showTicks
@ -43,7 +43,7 @@
</container>
<container title="有数字节点双滑块">
<slider
<ant-slider
defaultValue="{{ [10, 50] }}"
step="{{ 10 }}"
showTicks
@ -54,7 +54,7 @@
</container>
<container title="在拖动时显示悬浮提示">
<slider
<ant-slider
defaultValue="{{ 80 }}"
showTooltip
onChange="onChange"
@ -62,12 +62,12 @@
</container>
<container title="受控模式">
<slider
<ant-slider
min="{{ 0 }}"
max="{{ 100 }}"
value="{{ value }}"
onChange="handleChange" />
<stepper
<ant-stepper
min="{{ 0 }}"
max="{{ 100 }}"
value="{{ value }}"
@ -75,7 +75,7 @@
</container>
<container title="自定义">
<slider
<ant-slider
defaultValue="{{ 80 }}"
step="{{ 20 }}"
showTooltip
@ -89,27 +89,27 @@
<text
slot="tick"
slot-scope="props"
>{{ props.value }}</text
>{{ props.value }}°C</text
>
<!-- display: inline -->
<text
slot="showTooltip"
slot="tooltip"
slot-scope="props"
>{{ props.value }}</text
>{{ props.value }}°C</text
>
<view slot="slider">
<view class="custom-slider-handler">
<icon
<ant-icon
type="SmileOutline"
style="color: #ff8f1f" />
</view>
</view>
</slider>
</ant-slider>
</container>
<container title="禁用状态">
<slider
<ant-slider
defaultValue="{{ 80 }}"
disabled
onChange="onChange" />

View File

@ -8,7 +8,7 @@ Page({
onAfterChange: function (value, e) {
console.log('当前值:', value, e);
my.showToast({
content: "\u5F53\u524D\u9009\u4E2D\u503C\u4E3A\uFF1A".concat(value)
content: 'value: ' + value,
});
},
handleChange: function (value, e) {
@ -16,5 +16,5 @@ Page({
this.setData({
value: value,
});
}
},
});

View File

@ -1,9 +1,9 @@
{
"defaultTitle": "Slider",
"usingComponents": {
"slider": "../../../src/Slider/index",
"icon": "../../../src/Icon/index",
"stepper": "../../../src/Stepper/index",
"ant-slider": "../../../src/Slider/index",
"ant-icon": "../../../src/Icon/index",
"ant-stepper": "../../../src/Stepper/index",
"container": "../../../src/Container/index"
}
}

View File

@ -1,3 +1,4 @@
import { getInstanceBoundingClientRect } from '../_util/jsapi/get-instance-bounding-client-rect';
import { ISliderProps, SliderValue } from './props';
interface Rect {
@ -33,12 +34,12 @@ export class SliderController {
constructor(private _value: SliderValue, private _props: ISliderProps) {}
handleMove(e, type: MoveType) {
handleMove(component: any, e, type: MoveType) {
if (this.props.disabled) {
return;
}
const currentId = this.getId();
this.getRect(e).then((res) => {
this.getRect(component, e).then((res) => {
const { value, moveStatus } = this.getValue(res, type);
const formatValue = this.formatValue(value);
this.fireChange(currentId, formatValue, moveStatus, type, e);
@ -116,28 +117,24 @@ export class SliderController {
return id;
}
private getRect(e): Promise<any> {
private async getRect(component: any, e: any): Promise<any> {
const elementId = e.currentTarget.id;
return new Promise((r) => {
my.createSelectorQuery()
.select(`#${elementId}`)
.boundingClientRect()
.exec((list) => {
const element = list[0];
const touch = e.changedTouches[0];
if (element) {
return r({
touch: {
pageX: touch.pageX,
},
element: {
left: element.left,
width: element.width,
},
});
}
});
});
const element = await getInstanceBoundingClientRect(
component,
`#${elementId}`
);
const touch = e.changedTouches[0];
if (element) {
return {
touch: {
pageX: touch.pageX,
},
element: {
left: element.left,
width: element.width,
},
};
}
}
fitSliderValue(
@ -146,7 +143,7 @@ export class SliderController {
max: number,
isRange: boolean
) {
if (value === undefined) {
if (value === undefined || value === null) {
if (isRange) {
return [min, min] as SliderValue;
} else {

View File

@ -19,7 +19,6 @@
</view>
</view>
</slot>
<view
slot="content"
class="ant-slider-tooltip-content">
@ -48,37 +47,36 @@
<view class="ant-slider-track-fill-background" />
<view
class="ant-slider-track-fill-front {{ activeLineClassName || '' }}"
style="width: {{ sliderWidth }}%; left: {{ sliderLeft }}%; {{ activeLineStyle || '' }}">
<view class="ant-slider-showTicks">
<block
a:for="{{ tickList }}"
a:for-index="index"
a:for-item="item">
style="width: {{ sliderWidth }}%; left: {{ sliderLeft }}%; {{ activeLineStyle || '' }}" />
<view class="ant-slider-showTicks">
<block
a:for="{{ tickList }}"
a:for-index="index"
a:for-item="item">
<view
class="ant-slider-tick ant-slider-tick-{{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) ? 'front' : 'back' }} {{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) && activeDotClassName ? activeDotClassName : '' }}"
style="left: {{ item.left }}%;{{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) && activeDotStyle ? activeDotStyle : '' }}">
<view
class="ant-slider-tick ant-slider-tick-{{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) ? 'front' : 'back' }} {{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) && activeDotClassName ? activeDotClassName : '' }}"
style="left: {{ item.left }}%;{{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) && activeDotStyle ? activeDotStyle : '' }}">
<view
a:if="{{ showNumber }}"
class="ant-slider-tick-number">
<slot
name="tick"
value="{{ item.value }}">
{{ item.value }}
</slot>
</view>
a:if="{{ showNumber }}"
class="ant-slider-tick-number">
<slot
name="tick"
value="{{ item.value }}">
{{ item.value }}
</slot>
</view>
</block>
</view>
<template
a:if="{{ range }}"
is="slider-handler"
data="{{ position: sliderLeft, icon: icon, value: mixin.value[0], showTooltip: changingStart && showTooltip }}" />
<template
is="slider-handler"
data="{{ position: sliderLeft + sliderWidth, icon: icon, value: range ? mixin.value[1] : mixin.value, showTooltip: changingEnd && showTooltip }}" />
</view>
</block>
</view>
<template
a:if="{{ range }}"
is="slider-handler"
data="{{ position: sliderLeft, icon: icon, value: mixin.value[0], showTooltip: changingStart && showTooltip }}" />
<template
is="slider-handler"
data="{{ position: sliderLeft + sliderWidth, icon: icon, value: range ? mixin.value[1] : mixin.value, showTooltip: changingEnd && showTooltip }}" />
</view>
</view>
</view>
<view />
</view>

View File

@ -1,4 +1,10 @@
import { useEvent, useMemo, useRef, useState } from 'functional-mini/component';
import {
useEvent,
useMemo,
useRef,
useState,
useComponent,
} from 'functional-mini/component';
import '../_util/assert-component2';
import { mountComponent } from '../_util/component';
import { useComponentEvent } from '../_util/hooks/useComponentEvent';
@ -15,6 +21,7 @@ const useSliderController = (props: ISliderProps) => {
};
const Slider = (props) => {
const component = useComponent();
const sliderController: SliderController = useSliderController(props);
const [value, { update, isControlled }] = useMixState(props.defaultValue, {
value: props.value,
@ -55,18 +62,18 @@ const Slider = (props) => {
useEvent(
'handleTrackTouchStart',
(e) => sliderController.handleMove(e, 'start'),
[props]
(e) => sliderController.handleMove(component, e, 'start'),
[component]
);
useEvent(
'handleTrackTouchMove',
(e) => sliderController.handleMove(e, 'move'),
[props]
(e) => sliderController.handleMove(component, e, 'move'),
[component]
);
useEvent(
'handleTrackTouchEnd',
(e) => sliderController.handleMove(e, 'end'),
[props]
(e) => sliderController.handleMove(component, e, 'end'),
[component, props]
);
const tickList = useMemo(() => {
@ -126,7 +133,18 @@ const Slider = (props) => {
};
mountComponent(Slider, {
min: 0,
value: null,
defaultValue: null,
disabled: false,
max: 100,
min: 0,
range: false,
showNumber: false,
step: 1,
showTicks: false,
showTooltip: false,
activeLineStyle: '',
activeDotStyle: '',
activeLineClassName: '',
activeDotClassName: '',
});

View File

@ -1,5 +1,6 @@
{
"pages": [
"demo/pages/Slider/index",
"demo/pages/Stepper/index",
"demo/pages/Input/index",
"demo/pages/InputCustom/index",

View File

@ -0,0 +1,22 @@
Page({
data: {
value: 80,
},
onChange: function (value, e) {
console.log('slider changed:', value, e);
},
onAfterChange: function (value, e) {
console.log('当前值:', value, e);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
wx.showToast({
title: "value: ".concat(value.detail),
});
},
handleChange: function (value, e) {
console.log('slider changed:', value, e);
this.setData({
value: value.detail,
});
},
});

View File

@ -0,0 +1,9 @@
{
"defaultTitle": "Slider",
"usingComponents": {
"ant-slider": "../../../src/Slider/index",
"ant-icon": "../../../src/Icon/index",
"ant-stepper": "../../../src/Stepper/index",
"container": "../../../src/Container/index"
}
}

View File

@ -0,0 +1,95 @@
<container title="基本使用">
<ant-slider
defaultValue="{{ 80 }}"
bind:change="onChange"
bind:afterchange="onAfterChange" />
</container>
<container title="无节点双滑块">
<ant-slider
defaultValue="{{ [20, 60] }}"
range
bind:change="onChange"
bind:afterchange="onAfterChange" />
</container>
<container title="节点单滑块">
<ant-slider
defaultValue="{{ 60 }}"
step="{{ 20 }}"
showTicks
bind:change="onChange"
bind:afterchange="onAfterChange" />
</container>
<container title="有数字节点单滑块">
<ant-slider
defaultValue="{{ 80 }}"
step="{{ 20 }}"
showTicks
showNumber
bind:change="onChange"
bind:afterchange="onAfterChange" />
</container>
<container title="节点双滑块">
<ant-slider
defaultValue="{{ [10, 50] }}"
step="{{ 10 }}"
showTicks
range
bind:change="onChange"
bind:afterchange="onAfterChange" />
</container>
<container title="有数字节点双滑块">
<ant-slider
defaultValue="{{ [10, 50] }}"
step="{{ 10 }}"
showTicks
showNumber
range
bind:change="onChange"
bind:afterchange="onAfterChange" />
</container>
<container title="在拖动时显示悬浮提示">
<ant-slider
defaultValue="{{ 80 }}"
showTooltip
bind:change="onChange"
bind:afterchange="onAfterChange" />
</container>
<container title="受控模式">
<ant-slider
min="{{ 0 }}"
max="{{ 100 }}"
value="{{ value }}"
bind:change="handleChange" />
<ant-stepper
min="{{ 0 }}"
max="{{ 100 }}"
value="{{ value }}"
bind:change="handleChange" />
</container>
<container title="自定义">
<ant-slider
defaultValue="{{ 80 }}"
step="{{ 20 }}"
showTooltip
showTicks
showNumber
activeLineStyle="background-color: #ff8f1f"
activeDotStyle="background-color: red"
bind:change="onChange"
bind:afterchange="onAfterChange"></ant-slider>
</container>
<container title="禁用状态">
<ant-slider
defaultValue="{{ 80 }}"
disabled
bind:change="onChange" />
</container>

View File

@ -0,0 +1,9 @@
.custom-slider-handler {
width: 56rpx;
height: 56rpx;
background: #FFFFFF;
box-shadow: 0 4rpx 10rpx 0 rgba(0, 0, 0, 0.12);
border-radius: 56rpx;
text-align: center;
line-height: 56rpx;
}

View File

@ -0,0 +1,251 @@
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
import { getInstanceBoundingClientRect } from '../_util/jsapi/get-instance-bounding-client-rect';
var SliderController = /** @class */ (function () {
function SliderController(_value, _props) {
this._value = _value;
this._props = _props;
this.id = 0;
this.valueId = 0;
this._callback = null;
this._moveStatus = null;
}
Object.defineProperty(SliderController.prototype, "value", {
get: function () {
return this._value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(SliderController.prototype, "props", {
get: function () {
return this._props;
},
enumerable: false,
configurable: true
});
SliderController.prototype.handleMove = function (component, e, type) {
var _this = this;
if (this.props.disabled) {
return;
}
var currentId = this.getId();
this.getRect(component, e).then(function (res) {
var _a = _this.getValue(res, type), value = _a.value, moveStatus = _a.moveStatus;
var formatValue = _this.formatValue(value);
_this.fireChange(currentId, formatValue, moveStatus, type, e);
});
};
SliderController.prototype.fireChange = function (id, value, moveStatus, type, event) {
if (id < this.valueId) {
return;
}
if (this._callback) {
var changed = !this.isSliderValueEqual(this._value, value);
var moveStatusChanged = this.isMoveStatusChanged(this._moveStatus, moveStatus);
this._value = value;
this.valueId = id;
if (changed || moveStatusChanged) {
this._callback(value, moveStatus, {
valueChange: changed,
moveStatusChange: moveStatusChanged,
type: type,
event: event,
});
}
}
};
SliderController.prototype.isMoveStatusChanged = function (value1, value2) {
if (value1 === value2) {
return false;
}
if (!value1 || !value2) {
return true;
}
if (value1.changingStart !== value2.changingStart) {
return true;
}
if (value1.changingEnd !== value2.changingEnd) {
return true;
}
return false;
};
SliderController.prototype.isSliderValueEqual = function (value1, value2) {
if (value1 === value2) {
return true;
}
if (value1 === undefined || value2 === undefined) {
return false;
}
if (typeof value1 === 'number' || typeof value2 == 'number') {
return value1 === value2;
}
if (value1[0] === value2[0] && value1[1] === value2[1]) {
return true;
}
return false;
};
SliderController.prototype.getId = function () {
var id = this.id;
this.id = this.id + 1;
return id;
};
SliderController.prototype.getRect = function (component, e) {
return __awaiter(this, void 0, void 0, function () {
var elementId, element, touch;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
elementId = e.currentTarget.id;
return [4 /*yield*/, getInstanceBoundingClientRect(component, "#".concat(elementId))];
case 1:
element = _a.sent();
touch = e.changedTouches[0];
if (element) {
return [2 /*return*/, {
touch: {
pageX: touch.pageX,
},
element: {
left: element.left,
width: element.width,
},
}];
}
return [2 /*return*/];
}
});
});
};
SliderController.prototype.fitSliderValue = function (value, min, max, isRange) {
if (value === undefined || value === null) {
if (isRange) {
return [min, min];
}
else {
return min !== null && min !== void 0 ? min : 0;
}
}
if (typeof value === 'number') {
if (value > max) {
return max;
}
if (value < min) {
return min;
}
return value;
}
var leftValue = Math.min(value[0], value[1]);
var rightValue = Math.max(value[0], value[1]);
return [Math.max(min, leftValue), Math.min(max, rightValue)];
};
SliderController.prototype.getValue = function (rect, type) {
var touchPosition = (rect.touch.pageX - rect.element.left) / rect.element.width;
var props = this.props;
var currentValue = this.value;
var value = props.min + touchPosition * (props.max - props.min);
if (!props.range) {
return {
value: value,
moveStatus: type === 'end'
? {
changingEnd: false,
changingStart: false,
}
: {
changingEnd: true,
},
};
}
else {
var leftValue = currentValue[0];
var rightValue = currentValue[1];
var leftDistance = Math.abs(leftValue - value);
var rightDistance = Math.abs(rightValue - value);
var isFarFromLeft = leftDistance > rightDistance;
var farValue = isFarFromLeft ? leftValue : rightValue;
return {
value: [value, farValue],
moveStatus: type === 'end'
? {
changingEnd: false,
changingStart: false,
}
: isFarFromLeft
? {
changingEnd: true,
}
: {
changingStart: true,
},
};
}
};
SliderController.prototype.formatValue = function (val) {
var props = this.props;
var value = this.fitSliderValue(val, props.min, props.max, props.range);
value = this.getRoundedValue(value, props.step);
return value;
};
SliderController.prototype.getRoundedValue = function (value, step) {
if (step === void 0) { step = 1; }
if (value === undefined) {
return 0;
}
if (typeof value === 'number') {
return Math.round(value / step) * step;
}
return [
Math.round(value[0] / step) * step,
Math.round(value[1] / step) * step,
];
};
SliderController.prototype.updateProps = function (props) {
this._props = props;
};
SliderController.prototype.updateValue = function (value) {
this._value = value;
};
SliderController.prototype.updateMoveStatus = function (moveStatus) {
this._moveStatus = moveStatus;
};
SliderController.prototype.onChange = function (callback) {
this._callback = callback;
};
return SliderController;
}());
export { SliderController };

View File

@ -0,0 +1,123 @@
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import { useEvent, useMemo, useRef, useState, useComponent, } from 'functional-mini/component';
import '../_util/assert-component2';
import { mountComponent } from '../_util/component';
import { useComponentEvent } from '../_util/hooks/useComponentEvent';
import { useMixState } from '../_util/hooks/useMixState';
import { SliderController } from './controller';
import { sliderDefaultProps } from './props';
var useSliderController = function (props) {
var controllerRef = useRef();
if (!controllerRef.current) {
controllerRef.current = new SliderController(0, props);
}
return controllerRef.current;
};
var Slider = function (props) {
var component = useComponent();
var sliderController = useSliderController(props);
var _a = useMixState(props.defaultValue, {
value: props.value,
postState: function (value) {
return {
valid: true,
value: sliderController.formatValue(value),
};
},
}), value = _a[0], _b = _a[1], update = _b.update, isControlled = _b.isControlled;
var _c = useState({
changingStart: false,
changingEnd: false,
}), moveStatus = _c[0], setMoveStatus = _c[1];
var triggerEvent = useComponentEvent(props).triggerEvent;
sliderController.updateProps(props);
sliderController.updateValue(value);
sliderController.updateMoveStatus(moveStatus);
sliderController.onChange(function (v, moveStatus, _a) {
var valueChange = _a.valueChange, moveStatusChange = _a.moveStatusChange, type = _a.type, event = _a.event;
if (!isControlled) {
update(v);
}
if (valueChange) {
triggerEvent('change', v);
}
if (moveStatusChange) {
setMoveStatus(function (v2) { return (__assign(__assign({}, v2), moveStatus)); });
}
if (value && type === 'end') {
triggerEvent('afterChange', v, event);
}
});
useEvent('handleTrackTouchStart', function (e) { return sliderController.handleMove(component, e, 'start'); }, [component]);
useEvent('handleTrackTouchMove', function (e) { return sliderController.handleMove(component, e, 'move'); }, [component]);
useEvent('handleTrackTouchEnd', function (e) { return sliderController.handleMove(component, e, 'end'); }, [component, props]);
var tickList = useMemo(function () {
var step = props.step, min = props.min, max = props.max, showTicks = props.showTicks;
if (!showTicks) {
return [];
}
var tickList = [];
var stepCount = (max - min) / step;
for (var i = 0; i <= stepCount; i += 1) {
tickList.push({
left: i * (100 / stepCount),
value: i * step + min,
});
}
return tickList;
}, [props]);
var _d = useMemo(function () {
var _a, _b;
var roundedValue = value;
var leftValue = 0;
var rightValue = 0;
var max = (_a = props.max) !== null && _a !== void 0 ? _a : sliderDefaultProps.max;
var min = (_b = props.min) !== null && _b !== void 0 ? _b : sliderDefaultProps.min;
if (roundedValue !== undefined) {
if (typeof roundedValue === 'number') {
leftValue = min;
rightValue = roundedValue;
}
else {
leftValue = roundedValue[0];
rightValue = roundedValue[1];
}
}
// FIX_ME when min and max is equal
var width = ((rightValue - leftValue) / (max - min)) * 100;
var left = ((leftValue - min) / (max - min)) * 100;
return {
sliderLeft: left,
sliderWidth: width,
};
}, [value]), sliderLeft = _d.sliderLeft, sliderWidth = _d.sliderWidth;
return __assign({ mixin: {
value: value,
}, tickList: tickList, sliderLeft: sliderLeft, sliderWidth: sliderWidth }, moveStatus);
};
mountComponent(Slider, {
value: null,
defaultValue: null,
disabled: false,
max: 100,
min: 0,
range: false,
showNumber: false,
step: 1,
showTicks: false,
showTooltip: false,
activeLineStyle: '',
activeDotStyle: '',
activeLineClassName: '',
activeDotClassName: '',
});

View File

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"popover": "../Popover/index"
}
}

View File

@ -0,0 +1,45 @@
---
nav:
path: /components
group:
title: 信息输入
toc: 'content'
---
# Slider 滑块
<code src="../../docs/components/compatibility.tsx" inline="true"></code>
可以通过移动滑块在某一范围内取值
## 何时使用
用于在一定范围内移动滑块获取单个或者区间数值
## 代码示例
<code src='pages/Slider/index'></code>
## API
| 属性 | 说明 | 类型 | 默认值 |
| -----|-----|-----|----- |
| activeDotClassName | 选中小圆点的类名 | string | - |
| activeLineClassName | 选中线条的样式 | string | - |
| activeDotStyle | 选中小圆点的类型 | string | - |
| activeLineStyle | 选中线条的样式 | string | - |
| className | 类名 | string | - |
| defaultValue | 初始值 | number \| [number, number] | - |
| disabled | 是否禁用 | boolean | false |
| max | 最大值 | number | 100 |
| min | 最小值 | number | 0 |
| range | 是否是双滑块 | boolean | false |
| showNumber | 是否展示刻度上的数据 | boolean | false |
| step | 步距,取值必须大于 0,并且可被 (max - min) 整除 | number | 1 |
| style | 样式 | string | - |
| showTicks | 是否显示刻度 | boolean | false |
| showTooltip | 是否在拖动时显示悬浮提示,支持使用作用域插槽自定义 | boolean | false |
| slider | 自定义滑块 | slot | - |
| tick | 自定义刻度 | slot | - |
| tooltip | 自定义拖动时显示悬浮提示 | slot | - |
| value | 当前值 | number \| [number, number] | - |
| onChange | slider 值改变时触发 | (value: number &verbar; [number, number]) => void | - |
| onAfterChange | 与 touchend 触发时机一致,把当前值作为参数传入 | (value: number &verbar; [number, number]) => void | - |

View File

@ -0,0 +1,72 @@
<wxs
src="./index.wxs"
module="sliderSjs" />
<template name="slider-handler">
<view
class="ant-slider-handler"
style="left: {{ position }}%">
<popover
placement="top"
visible="{{ showTooltip }}"
showMask="{{ false }}">
<view class="ant-slider-handler-block">
<view class="ant-slider-handler-icon-default">
<view class="ant-slider-handler-icon-default-line1" />
<view class="ant-slider-handler-icon-default-line2" />
<view class="ant-slider-handler-icon-default-line3" />
</view>
</view>
<view
slot="content"
class="ant-slider-tooltip-content">
{{ value }}
</view>
</popover>
</view>
</template>
<view
class="ant-slider {{ className ? className : '' }}"
style="opacity: {{ disabled ? '0.4' : '1' }};">
<view
class="ant-slider-track {{ showNumber ? 'ant-slider-track-number' : '' }}">
<view
class="ant-slider-track-touch-area"
id="ant-slider-id-{{ $id }}"
bind:touchstart="handleTrackTouchStart"
bind:touchend="handleTrackTouchEnd"
bind:touchmove="handleTrackTouchMove">
<view class="ant-slider-track-fill">
<view class="ant-slider-track-fill-background" />
<view
class="ant-slider-track-fill-front {{ activeLineClassName || '' }}"
style="width: {{ sliderWidth }}%; left: {{ sliderLeft }}%; {{ activeLineStyle || '' }}" />
<view class="ant-slider-showTicks">
<block
wx:for="{{ tickList }}"
wx:for-index="index"
wx:for-item="item">
<view
class="ant-slider-tick ant-slider-tick-{{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) ? 'front' : 'back' }} {{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) && activeDotClassName ? activeDotClassName : '' }}"
style="left: {{ item.left }}%;{{ sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) && activeDotStyle ? activeDotStyle : '' }}">
<view
wx:if="{{ showNumber }}"
class="ant-slider-tick-number">
{{ item.value }}
</view>
</view>
</block>
</view>
<template
wx:if="{{ range }}"
is="slider-handler"
data="{{ position: sliderLeft, icon: icon, value: mixin.value[0], showTooltip: changingStart && showTooltip }}" />
<template
is="slider-handler"
data="{{ position: sliderLeft + sliderWidth, icon: icon, value: range ? mixin.value[1] : mixin.value, showTooltip: changingEnd && showTooltip }}" />
</view>
</view>
</view>
<view />
</view>

View File

@ -0,0 +1,6 @@
var isFrontTick = function isFrontTick(item, sliderLeft, sliderWidth) {
return item.left >= sliderLeft && item.left <= sliderLeft + sliderWidth;
};
module.exports = {
isFrontTick: isFrontTick
};

View File

@ -0,0 +1,131 @@
.ant-slider {
user-select: none;
width: 100%;
}
.ant-slider-track {
position: relative;
width: 100%;
height: 28px;
background-color: #ffffff;
}
.ant-slider-track-number {
height: 48.5px;
}
.ant-slider-track-touch-area {
position: absolute;
left: 14px;
top: 0px;
right: 12.5px;
height: 100%;
}
.ant-slider-track-fill {
position: absolute;
left: 0px;
top: 12.5px;
right: 0px;
height: 3px;
}
.ant-slider-track-fill-background {
position: absolute;
background-color: #f5f5f5;
width: 100%;
height: 100%;
border-radius: 1.5px;
}
.ant-slider-track-fill-front {
position: absolute;
height: 100%;
border-radius: 1.5px;
background-color: #1677ff;
}
.ant-slider-handler {
position: absolute;
touch-action: none;
left: 0;
transform: translate(-50%, -50%);
top: 1.5px;
}
.ant-slider-handler-block {
width: 28px;
height: 28px;
background: #ffffff;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.12);
border-radius: 28px;
text-align: center;
line-height: 28px;
color: #1677ff;
}
.ant-slider-handler-icon-default {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.ant-slider-handler-icon-default-line1 {
position: absolute;
width: 2px;
height: 8px;
background-color: #1677ff;
border-radius: 1px;
top: 10px;
left: 8px;
}
.ant-slider-handler-icon-default-line2 {
position: absolute;
width: 2px;
height: 12px;
background-color: #1677ff;
border-radius: 1px;
top: 8px;
right: 13px;
}
.ant-slider-handler-icon-default-line3 {
position: absolute;
width: 2px;
height: 8px;
background-color: #1677ff;
border-radius: 1px;
top: 10px;
right: 8px;
}
.ant-slider-handler-icon-from-props {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
line-height: 100%;
}
.ant-slider-handler-icon-from-props-icon {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.ant-slider-tick {
position: absolute;
width: 8px;
height: 8px;
border-radius: 8px;
top: 1.5px;
transform: translate(-50%, -50%);
}
.ant-slider-tick-front {
background-color: #1677ff;
}
.ant-slider-tick-back {
background-color: #f5f5f5;
}
.ant-slider-tick-number {
position: absolute;
color: #333333;
font-size: 12px;
transform: translateX(-50%);
top: 22px;
left: 4px;
text-align: center;
line-height: 16.5px;
height: 16.5px;
}

View File

@ -0,0 +1,5 @@
export var sliderDefaultProps = {
min: 0,
max: 100,
step: 1,
};

View File

View File

@ -1,5 +1,6 @@
{
"pages": [
"pages/Slider",
"pages/Container",
"pages/Stepper",
"pages/Button",
@ -23,6 +24,7 @@
"src": [
"_util",
"Button",
"Slider",
"Container",
"Icon",
"Loading",

View File

@ -1,13 +1,13 @@
import Slider from '../../../src/Slider/index.axml';
import Icon from '../../../src/Icon/index.axml';
import Stepper from '../../../src/Stepper/index.axml';
import { Component, InternalData, Text, View } from 'tsxml';
import Container from '../../../src/Container/index.axml';
import { View, Component, Slot, InternalData, Text } from 'tsxml';
import AntIcon from '../../../src/Icon/index.axml';
import AntSlider from '../../../src/Slider/index.axml';
import AntStepper from '../../../src/Stepper/index.axml';
export default ({ value, props }: InternalData) => (
<Component>
<Container title="基本使用">
<Slider
<AntSlider
defaultValue={80}
onChange="onChange"
onAfterChange="onAfterChange"
@ -15,7 +15,7 @@ export default ({ value, props }: InternalData) => (
</Container>
<Container title="无节点双滑块">
<Slider
<AntSlider
defaultValue={[20, 60]}
range
onChange="onChange"
@ -24,7 +24,7 @@ export default ({ value, props }: InternalData) => (
</Container>
<Container title="节点单滑块">
<Slider
<AntSlider
defaultValue={60}
step={20}
showTicks
@ -34,7 +34,7 @@ export default ({ value, props }: InternalData) => (
</Container>
<Container title="有数字节点单滑块">
<Slider
<AntSlider
defaultValue={80}
step={20}
showTicks
@ -45,7 +45,7 @@ export default ({ value, props }: InternalData) => (
</Container>
<Container title="节点双滑块">
<Slider
<AntSlider
defaultValue={[10, 50]}
step={10}
showTicks
@ -56,7 +56,7 @@ export default ({ value, props }: InternalData) => (
</Container>
<Container title="有数字节点双滑块">
<Slider
<AntSlider
defaultValue={[10, 50]}
step={10}
showTicks
@ -68,7 +68,7 @@ export default ({ value, props }: InternalData) => (
</Container>
<Container title="在拖动时显示悬浮提示">
<Slider
<AntSlider
defaultValue={80}
showTooltip
onChange="onChange"
@ -77,12 +77,12 @@ export default ({ value, props }: InternalData) => (
</Container>
<Container title="受控模式">
<Slider min={0} max={100} value={value} onChange="handleChange" />
<Stepper min={0} max={100} value={value} onChange="handleChange" />
<AntSlider min={0} max={100} value={value} onChange="handleChange" />
<AntStepper min={0} max={100} value={value} onChange="handleChange" />
</Container>
<Container title="自定义">
<Slider
<AntSlider
defaultValue={80}
step={20}
showTooltip
@ -93,23 +93,26 @@ export default ({ value, props }: InternalData) => (
onChange="onChange"
onAfterChange="onAfterChange"
>
{/* #if ALIPAY */}
{/* 微信暂时不支持 slot */}
<Text slot="tick" slot-scope="props">
{props.value}
{props.value}°C
</Text>
<Text slot="showTooltip" slot-scope="props">
{props.value}
<Text slot="tooltip" slot-scope="props">
{props.value}°C
</Text>
<View slot="slider">
<View class="custom-slider-handler">
<Icon type="SmileOutline" style="color: #ff8f1f" />
<AntIcon type="SmileOutline" style="color: #ff8f1f" />
</View>
</View>
</Slider>
{/* #endif */}
</AntSlider>
</Container>
<Container title="禁用状态">
<Slider defaultValue={80} disabled onChange="onChange" />
<AntSlider defaultValue={80} disabled onChange="onChange" />
</Container>
</Component>
);

View File

@ -1,9 +1,9 @@
{
"defaultTitle": "Slider",
"usingComponents": {
"slider": "../../../src/Slider/index",
"icon": "../../../src/Icon/index",
"stepper": "../../../src/Stepper/index",
"ant-slider": "../../../src/Slider/index",
"ant-icon": "../../../src/Icon/index",
"ant-stepper": "../../../src/Stepper/index",
"container": "../../../src/Container/index"
}
}

View File

@ -3,18 +3,37 @@ Page({
value: 80,
},
onChange(value, e) {
console.log('slider changed:', value, e)
console.log('slider changed:', value, e);
},
onAfterChange(value, e) {
console.log('当前值:', value, e);
/// #if ALIPAY
my.showToast({
content: `当前选中值为:${value}`
content: 'value: ' + value,
});
/// #endif
/// #if WECHAT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
wx.showToast({
title: `value: ${value.detail}`,
});
/// #endif
},
handleChange(value, e) {
console.log('slider changed:', value, e)
console.log('slider changed:', value, e);
/// #if WECHAT
this.setData({
value: value.detail,
});
/// #endif
/// #if ALIPAY
this.setData({
value,
});
}
/// #endif
},
});

View File

@ -40,6 +40,11 @@ export const wechat: PlatformConfig = {
onScroll: 'bind:scroll',
},
props: {
view: {
onTouchStart: 'bind:touchstart',
onTouchMove: 'bind:touchmove',
onTouchEnd: 'bind:touchend',
},
input: {
onInput: 'bindinput',
onConfirm: 'bindconfirm',
@ -57,6 +62,9 @@ export const wechat: PlatformConfig = {
'ant-textarea': {
onChange: 'bind:change',
},
'ant-slider': {
onAfterChange: 'bind:afterchange',
},
'ant-input': {
onBlur: 'bind:blur',
onConfirm: 'bind:confirm',

View File

@ -1,3 +1,4 @@
import { getInstanceBoundingClientRect } from '../_util/jsapi/get-instance-bounding-client-rect';
import { ISliderProps, SliderValue } from './props';
interface Rect {
@ -33,12 +34,12 @@ export class SliderController {
constructor(private _value: SliderValue, private _props: ISliderProps) {}
handleMove(e, type: MoveType) {
handleMove(component: any, e, type: MoveType) {
if (this.props.disabled) {
return;
}
const currentId = this.getId();
this.getRect(e).then((res) => {
this.getRect(component, e).then((res) => {
const { value, moveStatus } = this.getValue(res, type);
const formatValue = this.formatValue(value);
this.fireChange(currentId, formatValue, moveStatus, type, e);
@ -116,28 +117,24 @@ export class SliderController {
return id;
}
private getRect(e): Promise<any> {
private async getRect(component: any, e: any): Promise<any> {
const elementId = e.currentTarget.id;
return new Promise((r) => {
my.createSelectorQuery()
.select(`#${elementId}`)
.boundingClientRect()
.exec((list) => {
const element = list[0];
const touch = e.changedTouches[0];
if (element) {
return r({
touch: {
pageX: touch.pageX,
},
element: {
left: element.left,
width: element.width,
},
});
}
});
});
const element = await getInstanceBoundingClientRect(
component,
`#${elementId}`
);
const touch = e.changedTouches[0];
if (element) {
return {
touch: {
pageX: touch.pageX,
},
element: {
left: element.left,
width: element.width,
},
};
}
}
fitSliderValue(
@ -146,7 +143,7 @@ export class SliderController {
max: number,
isRange: boolean
) {
if (value === undefined) {
if (value === undefined || value === null) {
if (isRange) {
return [min, min] as SliderValue;
} else {

View File

@ -45,11 +45,14 @@ export default (
{/* #if ALIPAY */}
</Slot>
{/* #endif */}
<View slot="content" class="ant-slider-tooltip-content">
{/* #if ALIPAY */}
<Slot name="tooltip" value={value}>
{/* #endif */}
{value}
{/* #if ALIPAY */}
</Slot>
{/* #endif */}
</View>
</Popover>
</View>
@ -78,62 +81,66 @@ export default (
style={`width: ${sliderWidth}%; left: ${sliderLeft}%; ${
activeLineStyle || ''
}`}
>
<View class="ant-slider-showTicks">
{tickList.map((item) => (
<View
class={`ant-slider-tick ant-slider-tick-${
sliderSjs.isFrontTick(item, sliderLeft, sliderWidth)
? 'front'
: 'back'
} ${
sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) &&
activeDotClassName
? activeDotClassName
: ''
}`}
style={`left: ${item.left}%;${
sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) &&
activeDotStyle
? activeDotStyle
: ''
}`}
>
{showNumber && (
<View class="ant-slider-tick-number">
<Slot name="tick" value={item.value}>
{item.value}
</Slot>
</View>
)}
</View>
))}
</View>
/>
<View class="ant-slider-showTicks">
{tickList.map((item) => (
<View
class={`ant-slider-tick ant-slider-tick-${
sliderSjs.isFrontTick(item, sliderLeft, sliderWidth)
? 'front'
: 'back'
} ${
sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) &&
activeDotClassName
? activeDotClassName
: ''
}`}
style={`left: ${item.left}%;${
sliderSjs.isFrontTick(item, sliderLeft, sliderWidth) &&
activeDotStyle
? activeDotStyle
: ''
}`}
>
{showNumber && (
<View class="ant-slider-tick-number">
{/* #if ALIPAY */}
<Slot name="tick" value={item.value}>
{/* #endif */}
{range && (
<Template
is="slider-handler"
data={{
position: sliderLeft,
icon: icon,
value: mixin.value[0],
showTooltip: changingStart && showTooltip,
}}
/>
)}
{item.value}
{/* #if ALIPAY */}
</Slot>
{/* #endif */}
</View>
)}
</View>
))}
</View>
{range && (
<Template
is="slider-handler"
data={{
position: sliderLeft + sliderWidth,
position: sliderLeft,
icon: icon,
value: range ? mixin.value[1] : mixin.value,
showTooltip: changingEnd && showTooltip,
value: mixin.value[0],
showTooltip: changingStart && showTooltip,
}}
/>
</View>
)}
<Template
is="slider-handler"
data={{
position: sliderLeft + sliderWidth,
icon: icon,
value: range ? mixin.value[1] : mixin.value,
showTooltip: changingEnd && showTooltip,
}}
/>
</View>
</View>
</View>
<View></View>
</View>
</Component>
);

View File

@ -1,4 +1,10 @@
import { useEvent, useMemo, useRef, useState } from 'functional-mini/component';
import {
useEvent,
useMemo,
useRef,
useState,
useComponent,
} from 'functional-mini/component';
import '../_util/assert-component2';
import { mountComponent } from '../_util/component';
import { useComponentEvent } from '../_util/hooks/useComponentEvent';
@ -15,6 +21,7 @@ const useSliderController = (props: ISliderProps) => {
};
const Slider = (props) => {
const component = useComponent();
const sliderController: SliderController = useSliderController(props);
const [value, { update, isControlled }] = useMixState(props.defaultValue, {
value: props.value,
@ -55,18 +62,18 @@ const Slider = (props) => {
useEvent(
'handleTrackTouchStart',
(e) => sliderController.handleMove(e, 'start'),
[props]
(e) => sliderController.handleMove(component, e, 'start'),
[component]
);
useEvent(
'handleTrackTouchMove',
(e) => sliderController.handleMove(e, 'move'),
[props]
(e) => sliderController.handleMove(component, e, 'move'),
[component]
);
useEvent(
'handleTrackTouchEnd',
(e) => sliderController.handleMove(e, 'end'),
[props]
(e) => sliderController.handleMove(component, e, 'end'),
[component, props]
);
const tickList = useMemo(() => {
@ -126,7 +133,18 @@ const Slider = (props) => {
};
mountComponent(Slider, {
min: 0,
value: null,
defaultValue: null,
disabled: false,
max: 100,
min: 0,
range: false,
showNumber: false,
step: 1,
showTicks: false,
showTooltip: false,
activeLineStyle: '',
activeDotStyle: '',
activeLineClassName: '',
activeDotClassName: '',
});

View File

@ -5,12 +5,14 @@ import os from 'os';
import path from 'path';
import shallowequal from 'shallowequal';
import vm from 'vm';
import { SelectorQuery } from './selector-query';
interface Instance {
$id: number;
props: Record<string, any>;
data: Record<string, any>;
methods: Record<string, (this: Instance, ...args: any) => void>;
createSelectorQuery: () => SelectorQuery;
setData: (
data: Record<string, any>,
callback?: (this: Instance) => void
@ -63,6 +65,7 @@ function createInstance(config: Instance, props: Record<string, any>, my: any) {
methods: {
...config.methods,
},
createSelectorQuery: my.createSelectorQuery,
setData(data: Record<string, any>, callback: (this: Instance) => void) {
if (shallowequal(data, instance.data)) {
return;

View File

@ -17,6 +17,12 @@
"tsxml": ["./src/tsxml/index.tsx"]
}
},
"include": ["src/**/*", "typings/**/*", "demo/**/*", "tests/**/*"],
"include": [
"src/**/*",
"./typings/**/*",
"demo/**/*",
"tests/**/*",
"demo/wechat.d.ts"
],
"exclude": ["node_modules"]
}