UNPKG

woby-wheeler

Version:
368 lines (300 loc) 13.9 kB
# Woby-Wheeler Ported from [WheelPicker](https://cople.github.io/WheelPicker/) 仿 iOS UIPickerView 的滚动选择器 ## 演示 [DEMO](https://github.com/wongchichong/woby-wheeler) ## Run Demo ``` pnpm dev ``` ## 安装 ### NPM ```sh npm install woby-wheeler --save ``` ### CDN ```html <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/wheel-picker/dist/wheelpicker.min.css"> ``` ## 使用 ```ts 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 class={[`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 class="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 | 点击取消时触发 | ## License [MIT](http://opensource.org/licenses/MIT)