提交 77f943f0 编写于 作者: D DCloud_LXH

feat(h5): label、checkbox、checkbox-group

上级 2b71a962
import { defineComponent, inject, provide, ref } from 'vue'
import type { Ref, ExtractPropTypes, ComputedRef } from 'vue'
import { PolySymbol } from '@dcloudio/uni-core'
import { UniFormCtx, uniFormKey } from '../form'
import { CustomEventTrigger, useCustomEvent } from '../../helpers/useEvent'
export const uniCheckGroupKey = PolySymbol(__DEV__ ? 'uniCheckGroup' : 'ucg')
type UniCheckGroupFieldCtx = ComputedRef<{
checkboxChecked: boolean
value: string
}>
export interface UniCheckGroupCtx {
addField: (field: UniCheckGroupFieldCtx) => void
removeField: (field: UniCheckGroupFieldCtx) => void
checkboxChange: ($event: Event) => void
}
const props = {
name: {
type: String,
default: '',
},
}
type CheckBoxGroupProps = ExtractPropTypes<typeof props>
export default /*#__PURE__*/ defineComponent({
name: 'CheckboxGroup',
props,
// emits: ['change'],
setup(props, { emit, slots }) {
const rootRef: Ref<HTMLElement | null> = ref(null)
const trigger = useCustomEvent(rootRef, emit)
useProvideCheckGroup(props, trigger)
return () => {
return (
<uni-checkbox-group ref={rootRef}>
{slots.default && slots.default()}
</uni-checkbox-group>
)
}
},
})
function useProvideCheckGroup(
props: CheckBoxGroupProps,
trigger: CustomEventTrigger
) {
const fields: UniCheckGroupFieldCtx[] = []
const getFieldsValue = () =>
fields.reduce((res, field) => {
if (field.value.checkboxChecked) {
res.push(field.value.value)
}
return res
}, new Array())
provide<UniCheckGroupCtx>(uniCheckGroupKey, {
addField(field: UniCheckGroupFieldCtx) {
fields.push(field)
},
removeField(field: UniCheckGroupFieldCtx) {
fields.splice(fields.indexOf(field), 1)
},
checkboxChange($event) {
trigger('change', $event, {
value: getFieldsValue(),
})
},
})
const uniForm = inject<UniFormCtx>(
uniFormKey,
(false as unknown) as UniFormCtx
)
if (uniForm) {
uniForm.addField({
submit: () => {
let data: [string, any] = ['', null]
if (props.name !== '') {
data.push(props.name, getFieldsValue())
}
return data
},
})
}
return getFieldsValue
}
<template>
<uni-checkbox-group v-bind="$attrs">
<slot />
</uni-checkbox-group>
</template>
<script>
import {
emitter,
listeners
} from '../../mixins'
export default {
name: 'CheckboxGroup',
mixins: [emitter, listeners],
props: {
name: {
type: String,
default: ''
}
},
data () {
return {
checkboxList: []
}
},
listeners: {
'@checkbox-change': '_changeHandler',
'@checkbox-group-update': '_checkboxGroupUpdateHandler'
},
created () {
this.$dispatch('Form', 'uni-form-group-update', {
type: 'add',
vm: this
})
},
beforeDestroy () {
this.$dispatch('Form', 'uni-form-group-update', {
type: 'remove',
vm: this
})
},
methods: {
_changeHandler ($event) {
const value = []
this.checkboxList.forEach(vm => {
if (vm.checkboxChecked) {
value.push(vm.value)
}
})
this.$trigger('change', $event, {
value: value
})
},
_checkboxGroupUpdateHandler ($event) {
if ($event.type === 'add') {
this.checkboxList.push($event.vm)
} else {
const index = this.checkboxList.indexOf($event.vm)
this.checkboxList.splice(index, 1)
}
},
_getFormData () {
const data = {}
if (this.name !== '') {
const value = []
this.checkboxList.forEach(vm => {
if (vm.checkboxChecked) {
value.push(vm.value)
}
})
data.value = value
data.key = this.name
}
return data
}
}
}
</script>
\ No newline at end of file
import {
defineComponent,
onBeforeUnmount,
watch,
inject,
ref,
computed,
} from 'vue'
import type { Ref } from 'vue'
import { useListeners } from '../../helpers/useListeners'
import { UniCheckGroupCtx, uniCheckGroupKey } from '../checkbox-group'
import { UniFormCtx, uniFormKey } from '../form'
import { uniLabelKey, UniLabelCtx } from '../label'
import {
createSvgIconVNode,
ICON_PATH_SUCCESS_NO_CIRCLE,
} from '@dcloudio/uni-core'
const props = {
checked: {
type: [Boolean, String],
default: false,
},
id: {
type: String,
default: '',
},
disabled: {
type: [Boolean, String],
default: false,
},
color: {
type: String,
default: '#007aff',
},
value: {
type: String,
default: '',
},
}
export default /*#__PURE__*/ defineComponent({
name: 'Checkbox',
props,
setup(props, { slots }) {
const checkboxChecked = ref(props.checked)
const checkboxValue = ref(props.value)
watch(
[() => props.checked, () => props.value],
([newChecked, newModelValue]) => {
checkboxChecked.value = newChecked
checkboxValue.value = newModelValue
}
)
const reset = () => {
checkboxChecked.value = false
}
const { uniCheckGroup, uniLabel } = useCheckboxInject(
checkboxChecked,
checkboxValue,
reset
)
const _onClick = ($event: Event) => {
if (props.disabled) {
return
}
checkboxChecked.value = !checkboxChecked.value
uniCheckGroup && uniCheckGroup.checkboxChange($event)
}
uniLabel.addHandler(_onClick)
onBeforeUnmount(() => {
uniLabel.removeHandler(_onClick)
})
useListeners(props, { 'label-click': _onClick })
return () => {
const { disabled, color } = props
return (
<uni-checkbox disabled={disabled} onClick={_onClick}>
<div class="uni-checkbox-wrapper">
<div class="uni-checkbox-input">
{checkboxChecked.value
? createSvgIconVNode(ICON_PATH_SUCCESS_NO_CIRCLE, color, 16)
: ''}
</div>
{slots.default && slots.default()}
</div>
</uni-checkbox>
)
}
},
})
function useCheckboxInject(
checkboxChecked: Ref<string | boolean>,
checkboxValue: Ref<string>,
reset: () => void
) {
const filed = computed(() => ({
checkboxChecked: Boolean(checkboxChecked.value),
value: checkboxValue.value,
}))
const formField = { reset }
const uniCheckGroup = inject<UniCheckGroupCtx>(
uniCheckGroupKey,
(false as unknown) as UniCheckGroupCtx
)
if (!!uniCheckGroup) {
uniCheckGroup.addField(filed)
}
const uniForm = inject<UniFormCtx>(
uniFormKey,
(false as unknown) as UniFormCtx
)
if (!!uniForm) {
uniForm.addField(formField)
}
const uniLabel = inject<UniLabelCtx>(
uniLabelKey,
(false as unknown) as UniLabelCtx
)
onBeforeUnmount(() => {
uniCheckGroup && uniCheckGroup.removeField(filed)
uniForm && uniForm.addField(formField)
})
return {
uniCheckGroup,
uniForm,
uniLabel,
}
}
<template>
<uni-checkbox
:disabled="disabled"
v-bind="$attrs"
@click="_onClick"
>
<div class="uni-checkbox-wrapper">
<div
:class="[checkboxChecked ? 'uni-checkbox-input-checked' : '']"
:style="{color:color}"
class="uni-checkbox-input"
/>
<slot />
</div>
</uni-checkbox>
</template>
<script>
import {
emitter,
listeners
} from '../../mixins'
export default {
name: 'Checkbox',
mixins: [emitter, listeners],
props: {
checked: {
type: [Boolean, String],
default: false
},
id: {
type: String,
default: ''
},
disabled: {
type: [Boolean, String],
default: false
},
color: {
type: String,
default: '#007aff'
},
value: {
type: String,
default: ''
}
},
data () {
return {
checkboxChecked: this.checked,
checkboxValue: this.value
}
},
watch: {
checked (val) {
this.checkboxChecked = val
},
value (val) {
this.checkboxValue = val
}
},
listeners: {
'label-click': '_onClick',
'@label-click': '_onClick'
},
created () {
this.$dispatch('CheckboxGroup', 'uni-checkbox-group-update', {
type: 'add',
vm: this
})
this.$dispatch('Form', 'uni-form-group-update', {
type: 'add',
vm: this
})
},
beforeDestroy () {
this.$dispatch('CheckboxGroup', 'uni-checkbox-group-update', {
type: 'remove',
vm: this
})
this.$dispatch('Form', 'uni-form-group-update', {
type: 'remove',
vm: this
})
},
methods: {
_onClick ($event) {
if (this.disabled) {
return
}
this.checkboxChecked = !this.checkboxChecked
this.$dispatch('CheckboxGroup', 'uni-checkbox-change', $event)
},
_resetFormData () {
this.checkboxChecked = false
}
}
}
</script>
\ No newline at end of file
......@@ -11,8 +11,8 @@ export interface UniFormCtx {
}
interface UniFormFieldCtx {
submit: () => [string, any]
reset: () => void
submit?: () => [string, any]
reset?: () => void
}
export default /*#__PURE__*/ defineComponent({
......@@ -40,15 +40,17 @@ function provideForm(emit: SetupContext['emit']) {
emit('submit', {
detail: {
value: fields.reduce((res, field) => {
const [name, value] = field.submit()
name && (res[name] = value)
if (field.submit) {
const [name, value] = field.submit()
name && (res[name] = value)
}
return res
}, Object.create(null)),
},
})
},
reset() {
fields.forEach((field) => field.reset())
fields.forEach((field) => field.reset && field.reset())
emit('reset')
},
})
......
import Audio from './audio/index.vue'
import Button from './button/index'
import Canvas from './canvas/index.vue'
import Checkbox from './checkbox/index.vue'
import CheckboxGroup from './checkbox-group/index.vue'
import Checkbox from './checkbox/index'
import CheckboxGroup from './checkbox-group/index'
import Editor from './editor/index'
import Form from './form/index'
import Icon from './icon/index'
import Image from './image/index'
import Input from './input/index.vue'
import Label from './label/index.vue'
import Label from './label/index'
// import MovableArea from './movable-area/index.vue'
import MovableView from './movable-view/index.vue'
import Navigator from './navigator/index.vue'
......
import { defineComponent, provide, getCurrentInstance, computed } from 'vue'
import { PolySymbol } from '@dcloudio/uni-core'
export const uniLabelKey = PolySymbol(__DEV__ ? 'uniLabel' : 'ul')
const props = {
for: {
type: String,
default: '',
},
}
export default /*#__PURE__*/ defineComponent({
name: 'Label',
props,
setup(props, { emit, slots }) {
const instance = getCurrentInstance()!
const vm = instance.proxy!
const pageId = vm.$root!.$page.id
const handlers = useProvideLabel()
const pointer = computed(
() => props.for || (slots.default && slots.default.length)
)
const _onClick = ($event: Event) => {
const EventTarget = $event.target as HTMLElement
let stopPropagation = /^uni-(checkbox|radio|switch)-/.test(
EventTarget.className
)
if (!stopPropagation) {
stopPropagation = /^uni-(checkbox|radio|switch|button|svg)$/i.test(
EventTarget.tagName
)
}
// 现在checkbox图标已经改为svg实现,svg和path都跳过
if (!stopPropagation) {
stopPropagation = /^(svg|path)$/i.test(EventTarget.tagName)
}
if (stopPropagation) {
return
}
if (props.for) {
UniViewJSBridge.emit(
'uni-label-click-' + pageId + '-' + props.for,
$event,
true
)
} else {
handlers.forEach((handler) => {
handler($event, true)
})
}
}
return () => (
<uni-label class={{ 'uni-label-pointer': pointer }} onClick={_onClick}>
{slots.default && slots.default()}
</uni-label>
)
},
})
export interface UniLabelCtx {
addHandler: (handler: UniLabelHandlerCtx) => void
removeHandler: (handler: UniLabelHandlerCtx) => void
}
type UniLabelHandlerCtx = ($event: Event, b: boolean) => void
function useProvideLabel() {
const handlers: UniLabelHandlerCtx[] = []
provide<UniLabelCtx>(uniLabelKey, {
addHandler(handler: UniLabelHandlerCtx) {
handlers.push(handler)
},
removeHandler(handler: UniLabelHandlerCtx) {
handlers.splice(handlers.indexOf(handler), 1)
},
})
return handlers
}
<template>
<uni-label
:class="{'uni-label-pointer':pointer}"
v-bind="$attrs"
@click="_onClick"
>
<slot />
</uni-label>
</template>
<script>
import {
emitter
} from '../../mixins'
export default {
name: 'Label',
mixins: [emitter],
props: {
for: {
type: String,
default: ''
}
},
computed: {
pointer () {
return this.for || (this.$slots.default && this.$slots.default.length)
}
},
methods: {
_onClick ($event) {
let stopPropagation = /^uni-(checkbox|radio|switch)-/.test($event.target.className)
if (!stopPropagation) {
stopPropagation = /^uni-(checkbox|radio|switch|button)$/i.test($event.target.tagName)
}
if (stopPropagation) {
return
}
if (this.for) {
UniViewJSBridge.emit('uni-label-click-' + this.$page.id + '-' + this.for, $event, true)
} else {
this.$broadcast(['Checkbox', 'Radio', 'Switch', 'Button'], 'uni-label-click', $event, true)
}
}
}
}
</script>
\ No newline at end of file
import { isPlainObject } from '@vue/shared'
import { watch, onUnmounted, getCurrentInstance } from 'vue'
export function /*#__PURE__*/ useListeners(
props: { id: string },
listeners: Record<string, Function>
) {
_addListeners(props.id, listeners)
watch(
() => props.id,
(newId, oldId) => {
_removeListeners(oldId, listeners, true)
_addListeners(newId, listeners, true)
}
)
onUnmounted(() => {
_removeListeners(props.id, listeners)
})
}
function _addListeners(
id: string,
listeners: Record<string, Function>,
watch?: boolean
) {
const $page = getCurrentInstance()!.proxy?.$page
if (watch && !id) {
// id被置空
return
}
if (!isPlainObject(listeners)) {
return
}
Object.keys(listeners).forEach((name) => {
if (watch) {
// watch id
if (name.indexOf('@') !== 0 && name.indexOf('uni-') !== 0) {
UniViewJSBridge.on(`uni-${name}-${$page!.id}-${id}`, listeners[name])
}
} else {
if (name.indexOf('uni-') === 0) {
// 完全限定
UniViewJSBridge.on(name, listeners[name])
} else if (id) {
// scoped
UniViewJSBridge.on(`uni-${name}-${$page!.id}-${id}`, listeners[name])
}
}
})
}
function _removeListeners(
id: string,
listeners: Record<string, Function>,
watch?: boolean
) {
const $page = getCurrentInstance()!.proxy?.$page
if (watch && !id) {
// id之前不存在
return
}
if (!isPlainObject(listeners)) {
return
}
Object.keys(listeners).forEach((name) => {
if (watch) {
// watch id
if (name.indexOf('@') !== 0 && name.indexOf('uni-') !== 0) {
UniViewJSBridge.off(`uni-${name}-${$page!.id}-${id}`, listeners[name])
}
} else {
if (name.indexOf('uni-') === 0) {
// 完全限定
UniViewJSBridge.off(name, listeners[name])
} else if (id) {
// scoped
UniViewJSBridge.off(`uni-${name}-${$page!.id}-${id}`, listeners[name])
}
}
})
}
......@@ -27,27 +27,16 @@ uni-checkbox[disabled] {
border-radius: 3px;
width: 22px;
height: 22px;
line-height: 25px;
text-align: center;
position: relative;
}
uni-checkbox[disabled='false'] .uni-checkbox-input:hover,
uni-checkbox:not([disabled]) .uni-checkbox-input:hover {
border-color: #007aff;
}
.uni-checkbox-input.uni-checkbox-input-checked {
color: #007aff;
}
.uni-checkbox-input.uni-checkbox-input-checked:before {
font: normal normal normal 14px/1 'uni';
content: '\EA08';
font-size: 22px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -48%) scale(0.73);
}
.uni-checkbox-input.uni-checkbox-input-disabled {
background-color: #e1e1e1;
}
......
......@@ -2,6 +2,8 @@ import { ComponentPublicInstance } from 'vue'
import { normalizeTarget } from '@dcloudio/uni-shared'
import { getWindowOffset } from '../../helpers'
// TODO 临时跳过内置组件事件处理
const TempSkipComponents = ['UNI-CHECKBOX', 'UNI-LABEL']
const isClickEvent = (val: Event): val is MouseEvent => val.type === 'click'
const isMouseEvent = (val: Event): val is MouseEvent =>
val.type.indexOf('mouse') === 0
......@@ -13,7 +15,11 @@ export function $nne(this: ComponentPublicInstance, evt: Event) {
return evt
}
const { tagName } = currentTarget
if (tagName.indexOf('UNI-') !== 0 || tagName === 'UNI-PAGE-WRAPPER') {
if (
tagName.indexOf('UNI-') !== 0 ||
tagName === 'UNI-PAGE-WRAPPER' ||
TempSkipComponents.indexOf(tagName) !== -1
) {
// TODO 下拉刷新事件返回原始event,目前硬编码,后续换其他方案解决
return evt
}
......
此差异已折叠。
......@@ -103,7 +103,7 @@ export const hideLoading = defineAsyncApi<API_TYPE_HIDE_LOADING>(
}
)
const hidePopup = (type: 'onHideToast' | 'onHideLoading') => {
const hidePopup = (type: 'onHideToast' | 'onHideLoading' | 'onHidePopup') => {
const { t } = useI18n()
if (!showType) {
return
......@@ -123,3 +123,7 @@ const hidePopup = (type: 'onHideToast' | 'onHideLoading') => {
showToastState.visible = false
}, 10)
}
setTimeout(() => {
UniServiceJSBridge.on('onHidePopup', () => hidePopup('onHidePopup'))
}, 0)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册