woby-wheeler
v1.1.7
Published
仿 iOS UIPickerView 的滚动选择器
Downloads
20
Readme
Woby-Wheeler
Ported from WheelPicker
仿 iOS UIPickerView 的滚动选择器
演示
Run Demo
pnpm dev
安装
NPM
npm install woby-wheeler --save
CDN
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/wheel-picker/dist/wheelpicker.min.css">
使用
import { Wheeler } from "woby-wheeler"
import { $, $$, Observable, render, useMemo, useEffect, ObservableMaybe, isObservable, type JSX } from "woby"
import data from './data.json'
import { Data } from "src/Wheel"
import './dist/output.css'
import { DateWheeler } from "./src/DateWheeler"
const v1 = () => {
const stv = [$('草莓')]
useEffect(() => console.log('Single changed', stv[0]()))
const fruits = ["西瓜,柠檬,草莓,荔枝,橘子,菠萝,香蕉,柚子,苹果,龙眼".split(",")]
const sshown = $(false)
return <>
<input class='border m-5' value={() => stv.map(v => v() + '')} onClick={() => sshown(true)} ></input>
<Wheeler
title={< h1 > 单列选择器 < button onClick={e => stv[0]('香蕉')} > 香蕉</button ></h1 >}
data={fruits}
value={stv as any}
rows={6}
hideOnBackdrop
open={sshown}
toolbar
/>
</>
}
const v2 = () => {
const fruits = "西瓜,柠檬,草莓,荔枝,橘子,菠萝,香蕉,柚子,苹果,龙眼".split(",")
const frutisEn = "watermelon,lemon,strawberry,litchi,orange,pineapple,banana,grapefruit,apple,longan".split(",")
const frutiData = $(fruits.map((name, idx) => ({
text: name,
value: frutisEn[idx]
})))
const mshown = $(false)
const vegetables = "香菜,青菜,芦笋,萝卜,水芹,黄瓜,冬瓜,番茄,茄子,土豆".split(",")
const vegetablesEn = "parsley,celery,asparagus,carrot,celery,cucumber,melon,tomato,eggplant,potato".split(",")
const vegetableData = $(vegetables.map((name, idx) => ({
text: name,
value: vegetablesEn[idx]
})))
const mtv = [$('lemon'), $('carrot')]
useEffect(() => console.log('Multiple changed', mtv[0](), mtv[1]()))
const valuer = [r => r.value, r => r.value]
return <>
<h3><label for="demo2">两列带默认值</label></h3>
<input class='border m-5' value={() => mtv.map((v, i) => $$(v))} onClick={() => mshown(true)} ></input>
<Wheeler
data={[frutiData, vegetableData]}
value={mtv}
renderer={[r => r.text, r => r.text]}
valuer={[r => r.value, r => r.value]}
open={mshown}
toolbar
/></>
}
const v3 = () => {
let defaultProv = Object.keys(data)[0]
const keys = Object.keys(data)
const cshown = $(false)
Object.keys(data).forEach(k => {
keys[k] = Object.keys(data[k])
Object.keys(data[k]).forEach(kk => {
keys[k][kk] = data[k][kk]
})
})
const dt = [
$(Object.keys(data)), //state
$(keys[defaultProv]), //Object.keys(data[defaultProv]), //city
$(keys[defaultProv][keys[defaultProv][0]])//data[defaultProv][Object.keys(data[defaultProv])[0]] //district
] as const
const value = [$<string | Data>(), $<string | Data>(), $<string | Data>()]
const empty = []
// useEffect(() => { console.log('dv', dv[0](), dv[1](), dv[2]()) })
useEffect(() => {
let l1 = keys[value[0]() + ''] ?? empty
if (!(value[1]()?.valueOf()))
value[1](l1[0])
let l2 = l1[value[1]() + ''] ?? empty
const d = dt
if (d[1]() !== l1 || d[2]() !== l2)
if (d[1]() !== l1 || d[2]() !== l2) {
d[1](l1)
if (d[2] !== l2)
d[2](l2)
const i1 = d[1]().indexOf(value[1]() + '') === -1
const i2 = d[2]().indexOf(value[2]() + '') === -1
if (i1 || i2) {
// dv[0](tempValue[0]())
if (i1)
/* dv[1]( */value[1](d[1]()[0]) //)
if (i2)
/* dv[2]( */value[2](d[2]()[0]) //)
}
}
})
return <>
<h3><label for="demo3">城市联动</label></h3>
<input class='border m-5' value={() => value.map(v => v() + '')} onClick={() => cshown(true)} ></input>
<Wheeler
data={dt as any}
value={value}
resetSelectedOnDataChanged
hideOnBackdrop
onShow={() => {
console.log("onShow")
}}
onCancel={() => {
console.log("onCancel")
}}
open={cshown}
toolbar
/></>
}
const CloseCircle = (props: JSX.SVGAttributes<SVGElement>) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" {...props}>
<path d="M48 0a48 48 0 1 0 48 48A48.051 48.051 0 0 0 48 0Zm0 84a36 36 0 1 1 36-36 36.04 36.04 0 0 1-36 36Z" class="st0" />
<path d="M64.242 31.758a5.998 5.998 0 0 0-8.484 0L48 39.516l-7.758-7.758a6 6 0 0 0-8.484 8.484L39.516 48l-7.758 7.758a6 6 0 1 0 8.484 8.484L48 56.484l7.758 7.758a6 6 0 0 0 8.484-8.484L56.484 48l7.758-7.758a5.998 5.998 0 0 0 0-8.484Z" class="st0" />
</svg>
const CheckChip = () => {
const selectAll = $(false)
const fruits = [$([
// { text: '*', value: '西瓜', checked: selectAll, readonly: $(false), valueOf: () => '*', toString: () => '*' },
{ text: '西瓜...', value: '西瓜', checked: $(false), readonly: $(true) },
{ text: '柠檬', value: '柠檬', checked: $(false), readonly: $(true) },
{ text: '...草莓', value: '草莓', checked: $(false), readonly: $(false) },
{ text: '荔枝', value: '荔枝', checked: $(false), readonly: $(false) },
{ text: '橘子..', value: '橘子', checked: $(false), readonly: $(false) },
{ text: '菠萝', value: '菠萝', checked: $(false), readonly: $(false) },
{ text: '....香蕉', value: '香蕉', checked: $(false), readonly: $(false) },
{ text: '柚子', value: '柚子', checked: $(false), readonly: $(false) },
{ text: '苹果', value: '苹果', checked: $(false), readonly: $(false) },
{ text: '龙眼....', value: '龙眼', checked: $(false), readonly: $(false) }
])]
const stv = [$(fruits[0][2])]
let suspense = false
useEffect(() => {
if (!suspense)
$$(fruits[0]).forEach((r: Data) => r.checked($$(selectAll)))
})
useEffect(() => {
if ($$(fruits[0]).some((r: Data) => !$$(r.checked) || r.text === '*')) {
suspense = true
selectAll(false)
}
})
const sshown = $(false)
return <>
<h3><label for="demo2">Checked</label></h3>
<div class='w-full border h-[30px]' onClick={() => sshown(true)}>
{() => $$(fruits[0]).map(f => $$(f.checked) ? <span className={[`bg-[#0096fb] inline-flex items-center text-[13px] leading-[19px] text-white whitespace-nowrap mr-[5px] px-2.5 py-1 rounded-[11px]`,]}>{f.text}
{() => !$$(f.readonly) ? <CloseCircle
className="icon_cancel closeIcon h-[13px] w-[13px] float-right cursor-pointer ml-[5px] fill-[white]"
onClick={e => { e.cancelBubble = true; f.checked(false) }}
/> : null}
</span> : null)}
</div>
<Wheeler
data={fruits}
value={stv as any}
renderer={[r => r.text]}
valuer={[r => r.value]}
checkboxer={[r => r.checked]}
rows={6}
hideOnBackdrop
open={sshown}
checkbox={[$(true)]}
noMask
/>
</>
}
const cshown = $(false)
const date = $(new Date())
const format = (value: Observable<Data>[]) => value.slice(0, 3).map(v => $$(v) + '').join(' ') + ' ' + value.slice(3).map(v => ($$(v) + '').padStart(2, '0')).join(':')
const cshownDateOnly = $(false)
const yearOnly = $(new Date())
const cshownYear = $(false)
const monthOnly = $(new Date())
const cshownMonth = $(false)
const yearMonth = $(new Date())
const cshownYearMonth = $(false)
const timeOnly = $(new Date()) //$([$(0), $(1), $(1)])
const cshownTime = $(false)
const hasSecond = $(true)
const TimeIcon = (props: JSX.SVGAttributes<SVGElement>) => <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" {...props}>
<path d="m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Z" />
</svg>
render(<div class='m-5'>
<h1>WheelPicker</h1>
<p>仿 iOS UIPickerView 的滚动选择器</p>
<h3>单列</h3>
{v1}
{v2}
{v3}
{CheckChip}
<h3><label for="demo4">Date</label></h3>
<div class='border m-5 w-[250px]' onClick={() => cshown(true)} >{() => $$(date).toString()} </div>
<DateWheeler
title={<div class='m-5 inline-block'>{() => $$(date).toDateString() + ' ' + $$(date).getHours() + ':' + $$(date).getMinutes() + ':' + $$(date).getSeconds()}
<div class='inline-block ml-10 px-2 border rounded-[10px] border-solid border-[lightgray] hover:bg-[#76a1aa]' onClick={() => hasSecond(!$$(hasSecond))}><input type='checkbox' checked={hasSecond} /><span class='px-2'>With Time</span>
<TimeIcon class='inline-block ml-1' /></div>
</div>}
value={date}
format={format}
shown={cshown}
hasSecond={hasSecond}
/>
<h4>Date only</h4>
<div class='border m-5 w-[250px]' onClick={() => cshownDateOnly(true)} >{() => $$(date).toString()} </div>
<DateWheeler
// hasSecond
value={date}
format={format}
shown={cshownDateOnly}
/>
<h4>Year Only</h4>
<div class='border m-5 w-[250px]' onClick={() => cshownYear(true)} >{() => Array.isArray($$(yearOnly)) ? ($$(yearOnly) as any as Observable<string>[]).map(r => $$(r) + '').join(' ') : $$(yearOnly).toString()} </div>
<DateWheeler
value={yearOnly}
format={format}
shown={cshownYear}
hasMonth={false}
hasDay={false}
/>
<h4>Month Only</h4>
<div class='border m-5 w-[250px]' onClick={() => cshownMonth(true)} >{() => Array.isArray($$(monthOnly)) ? ($$(monthOnly) as any as Observable<string>[]).map(r => $$(r) + '').join(' ') : $$(monthOnly).toString()} </div>
<DateWheeler
value={monthOnly}
format={format}
shown={cshownMonth}
hasYear={false}
hasDay={false}
/>
<h4>Year & Month</h4>
<div class='border m-5 w-[250px]' onClick={() => cshownYearMonth(true)} >{() => Array.isArray($$(yearMonth)) ? ($$(yearMonth) as any as Observable<string>[]).map(r => $$(r) + '').join(' ') : $$(yearMonth).toString()} </div>
<DateWheeler
value={yearMonth}
format={format}
shown={cshownYearMonth}
// hasYear={false}
hasDay={false}
/>
<h4>Time Only</h4>
<div class='border m-5 w-[250px]' onClick={() => cshownTime(true)} >{() => Array.isArray($$(timeOnly)) ? ($$(timeOnly) as any as Observable<string>[]).map(r => $$(r) + '').join(' ') : $$(timeOnly).toString()} </div>
<DateWheeler
value={timeOnly}
format={format}
shown={cshownTime}
hasYear={false}
hasMonth={false}
hasDay={false}
hasSecond
headers={['Hour', 'Minute', 'Second']}
/>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
</div>, document.getElementById('woby'))
选项
| 参数 | 类型 | 默认值 | 描述 | | ---------------- | -------- | ----------------------- | ---------------------------------------- | | title | string | null | 标题 | | el | element | null | 选择器对应的 input 元素 | | hideOnBackdrop | boolean | false | 点击遮罩层关闭组件(相当于点击取消按钮) | | hidden | boolean | false | 点击遮罩层关闭组件(相当于点击取消按钮) | | disabled | boolean | false | 点击遮罩层关闭组件(相当于点击取消按钮) | | data | array | [] | 每列的数据组成的数组 | | value | array | [] | 每列的默认值组成的数组 | | rows | number | 5 | 可见的行数(奇数) | | rowHeight | number | 34 | 行高 | | onShow | function | null | 显示组件时触发 | | onCancel | function | null | 点击取消时触发 |