UNPKG

woby-wheeler

Version:
669 lines (668 loc) 24 kB
import { jsx, jsxs, Fragment } from "woby/jsx-runtime"; import { $, useMemo, useEffect, $$, isObservable, batch } from "woby"; import { nanoid } from "nanoid"; import { useViewportSize } from "use-woby"; const prefixed = (prop) => { let style = document.createElement("div").style; let vendors = ["Webkit", "Moz", "ms", "O"]; let name; if (prop in style) return prop; for (var i = 0, len = vendors.length; i < len; i++) { name = vendors[i] + prop.charAt(0).toUpperCase() + prop.substring(1); if (name in style) return name; } return null; }; const getStyle = (el, prop) => { prop = prop.replace(/([A-Z])/g, "-$1"); prop = prop.toLowerCase(); return window.getComputedStyle(el, null).getPropertyValue(prop); }; const isTouch = (e) => "touches" in e; const Wheel = (props) => { const { data, rowHeight = 34, adjustTime = 400, bounceTime = 600, momentumThresholdTime = 300, momentumThresholdDistance = 10, value, resetSelectedOnDataChanged = false, width, checkbox, valuer, renderer = (r) => r, checkboxer = (r) => null, disabler = (r) => null } = props; let { rows = 5 } = props; const _items = $([]); const list = $([]); const y = $(0); const selectedIndex = $(0); const isTransition = $(false); const isTouching = $(false); const easings = $({ scroll: "cubic-bezier(0.23, 1, 0.32, 1)", // easeOutQuint scrollBounce: "cubic-bezier(0.25, 0.46, 0.45, 0.94)", // easeOutQuard bounce: "cubic-bezier(0.165, 0.84, 0.44, 1)" // easeOutQuart }); const transformName = $(prefixed("transform")); const transitionName = $(prefixed("transition")); const wheel = $(); const scroller = $(); const wheelHeight = useMemo(() => { var _a; return (_a = wheel()) == null ? void 0 : _a.offsetHeight; }); const maxScrollY = $(); const startY = $(); const lastY = $(); const startTime = $(); useEffect(() => { $$(data); if ($$(resetSelectedOnDataChanged)) selectedIndex(0); }); useEffect(() => { if ($$(rows) % 2 === 0) isObservable(rows) ? rows((r) => ++r) : rows++; }); const _momentum = (current, start, time, lowerMargin, wheelSize, deceleration, rowHeight2) => { let distance = current - start; let speed = Math.abs(distance) / time; let destination; let duration; deceleration = deceleration === void 0 ? 6e-4 : deceleration; destination = current + speed * speed / (2 * deceleration) * (distance < 0 ? -1 : 1); duration = speed / deceleration; destination = Math.round(destination / rowHeight2) * rowHeight2; if (destination < lowerMargin) { destination = wheelSize ? lowerMargin - wheelSize / 2.5 * (speed / 8) : lowerMargin; distance = Math.abs(destination - current); duration = distance / speed; } else if (destination > 0) { destination = wheelSize ? wheelSize / 2.5 * (speed / 8) : 0; distance = Math.abs(current) + destination; duration = distance / speed; } return { destination: Math.round(destination), duration }; }; const _resetPosition = (duration) => { let yy = y(); duration = duration || 0; if (yy > 0) yy = 0; if (yy < maxScrollY()) yy = maxScrollY(); if (yy === y()) return false; _scrollTo(yy, duration, easings().bounce); return true; }; const _getClosestSelectablePosition = (y2) => { let index = Math.abs(Math.round(y2 / $$(rowHeight))); const items = _items(); if (!$$(disabler(items[index]))) return y2; let max = Math.max(index, items.length - index); for (let i = 1; i <= max; i++) { if (!$$(disabler(items[index + i]))) { index += i; break; } if (!$$(disabler(items[index - i]))) { index -= i; break; } } return index * -$$(rowHeight); }; const _scrollTo = (yy, duration, easing) => { if (y() === yy) { _scrollFinish(); return false; } y(_getClosestSelectablePosition(yy)); if (duration && duration > 0) { isTransition(true); scroller().style[transitionName()] = duration + "ms " + easing; } else { _scrollFinish(); } }; const _scrollFinish = () => { let newIndex = Math.abs(y() / $$(rowHeight)); if (selectedIndex() != newIndex) { selectedIndex(newIndex); const v = _items()[selectedIndex()]; value(valuer && v ? valuer(v) : v); } }; useEffect(() => { const v = $$(value); const i = $$(data).findIndex((vv, i2) => (valuer ? valuer(vv) : vv) === v); selectedIndex(i); }); const _getCurrentY = () => { const matrixValues = getStyle(scroller(), transformName()).match(/-?\d+(\.\d+)?/g); return parseInt(matrixValues[matrixValues.length - 1]); }; const _start = (event) => { event.preventDefault(); const items = _items(); if (!items.length) return; if (isTransition()) { isTransition(false); y(_getCurrentY()); scroller().style[transitionName()] = ""; } startY(y()); lastY(isTouch(event) ? event.touches[0].pageY : event.pageY); startTime(Date.now()); isTouching(true); }; const _move = (event) => { if (!isTouching()) return false; let yy = isTouch(event) ? event.changedTouches[0].pageY : event.pageY; let deltaY = yy - lastY(); let targetY = y() + deltaY; let now = Date.now(); lastY(yy); if (targetY > 0 || targetY < maxScrollY()) { targetY = y() + deltaY / 3; } y(Math.round(targetY)); if (now - startTime() > $$(momentumThresholdTime)) { startTime(now); startY(y()); } return false; }; const _end = (event) => { var _a, _b, _c, _d, _e, _f, _g; if (!isTouching()) return false; const deltaTime = Date.now() - startTime(); let duration = $$(adjustTime); let easing = easings().scroll; const distanceY = Math.abs(y() - startY()); let momentumVals; let yy; isTouching(false); if (deltaTime < $$(momentumThresholdTime) && distanceY <= 10 && ((_a = event.target) == null ? void 0 : _a.classList.contains("wheelpicker-item"))) { const aid = +(((_b = event.target) == null ? void 0 : _b.getAttribute("_wsidx")) ?? ((_d = (_c = event.target) == null ? void 0 : _c.parentElement) == null ? void 0 : _d.getAttribute("_wsidx")) ?? ((_g = (_f = (_e = event.target) == null ? void 0 : _e.parentElement) == null ? void 0 : _f.parentElement) == null ? void 0 : _g.getAttribute("_wsidx"))); _scrollTo(aid * -$$(rowHeight), duration, easing); return false; } if (_resetPosition($$(bounceTime))) return; if (deltaTime < $$(momentumThresholdTime) && distanceY > $$(momentumThresholdDistance)) { momentumVals = _momentum(y(), startY(), deltaTime, maxScrollY(), wheelHeight(), 7e-4, $$(rowHeight)); yy = momentumVals.destination; duration = momentumVals.duration; } else { yy = Math.round(y() / $$(rowHeight)) * $$(rowHeight); } if (yy > 0 || yy < maxScrollY()) { easing = easings().scrollBounce; } _scrollTo(yy, duration, easing); }; const _transitionEnd = () => { isTransition(false); scroller().style[transitionName()] = ""; if (!_resetPosition($$(bounceTime))) _scrollFinish(); }; useEffect(() => { const dt = $$(data); const lis = []; const items = []; items.push(...dt.map((item, idx) => { const id = nanoid(); let li = () => /* @__PURE__ */ jsx( "li", { class: [ "wheelpicker-item", `cursor-pointer h-[34px] leading-[34px] overflow-hidden text-center text-ellipsis whitespace-nowrap`, () => $$(disabler(item)) ? "wheelpicker-item-disabled cursor-not-allowed pointer-events-none opacity-50" : "", () => idx === selectedIndex() ? "wheelpicker-item-selected cursor-default" : "" ], _wsidx: idx, children: () => $$(checkbox) ? /* @__PURE__ */ jsxs( "div", { class: "w-[100px] mx-0 my-auto inline-block cursor-pointer", onClick: () => { var _a; return (_a = checkboxer(item)) == null ? void 0 : _a(!$$(checkboxer(item))); }, children: [ /* @__PURE__ */ jsx("input", { id, class: "float-left translate-y-[90%] cursor-pointer", type: "checkbox", checked: checkboxer(item), indeterminate: () => typeof $$(checkboxer(item)) === "undefined" }), /* @__PURE__ */ jsx("label", { for: id, onClick: () => { var _a; return (_a = checkboxer(item)) == null ? void 0 : _a(!$$(checkboxer(item))); }, children: /* @__PURE__ */ jsx("span", { class: "ml-4 cursor-pointer", children: renderer(item) }) }) ] } ) : renderer(item) } ); lis.push(li); return item; })); list(lis); _items(items); y(selectedIndex() * -$$(rowHeight)); maxScrollY(-$$(rowHeight) * (dt.length - 1)); value(valuer && items[selectedIndex()] ? valuer(items[selectedIndex()]) : items[selectedIndex()]); }); const _wheel = (event) => { var _a, _b, _c, _d, _e, _f, _g, _h, _i; event.preventDefault(); let pid; let pwid; let duration = $$(adjustTime); let easing = easings().scroll; if (!event.target) return; const aid = +(((_a = event.target) == null ? void 0 : _a.getAttribute("_wsidx")) ?? ((_b = event.target.parentElement) == null ? void 0 : _b.getAttribute("_wsidx")) ?? ((_e = (_d = (_c = event.target) == null ? void 0 : _c.parentElement) == null ? void 0 : _d.parentElement) == null ? void 0 : _e.getAttribute("_wsidx")) ?? ((_i = (_h = (_g = (_f = event.target) == null ? void 0 : _f.parentElement) == null ? void 0 : _g.parentElement) == null ? void 0 : _h.parentElement) == null ? void 0 : _i.getAttribute("_wsidx"))); _scrollTo((pid = (aid === pwid ? pid : aid) + Math.sign(event.deltaY)) * -$$(rowHeight), duration, easing); pwid = aid; }; return /* @__PURE__ */ jsx( "div", { ref: wheel, class: "wheelpicker-wheel flex-[1_auto] relative overflow-hidden", style: { height: $$(rowHeight) * $$(rows) + "px", width }, onPointerDown: _start, onPointerMove: _move, onPointerUp: _end, onPointerCancel: _end, onWheel: _wheel, children: /* @__PURE__ */ jsx( "ul", { ref: scroller, class: "wheelpicker-wheel-scroller overflow-hidden list-none m-0 p-0", style: { transform: () => "translate3d(0," + y() + "px,0)", marginTop: $$(rowHeight) * Math.floor($$(rows) / 2) + "px" }, onTransitionEnd: _transitionEnd, children: list } ) } ); }; function Wheeler(props) { const { data, rows = 5, rowHeight = 34, onCancel, onOk, disabled, open = $(true), value: oValue, title, hideOnBlur, commitOnBlur, resetSelectedOnDataChanged, ok = "OK", cancel = "Cancel", //"确定" headers, toolbar, noMask, valuer, renderer, checkboxer, checkbox, commitOnOk = $(false), //@ts-ignore disabler } = props; const value = !$$(commitOnOk) ? oValue : oValue.map((v) => $($$(v))); const closed = $(true); const container = $(); const restore = $(); const cancelled = $(); const oriValue = value.map((v) => $$(v)); const pdata = isObservable(data) ? data : $(data); useEffect(() => { if (!Array.isArray($$(value))) console.error("value must be array."); const d = $$(data); if ($$(pdata) === d) return; d.forEach((dd, i) => { if (!value[i]) value[i] = $($$(d[i])[0]); if (!$$(value[i])) value[i]($$(d[i])[0]); }); }); (() => { const d = $$(data); d.forEach((dd, i) => { var _a; if (!value[i]) value[i] = $($$(d[i])[0]); if (!$$(value[i])) value[i]((_a = $$(d[i])) == null ? void 0 : _a[0]); }); })(); $$(data).forEach((_, i) => { oriValue[i] = $$(value[i]); }); const _backdropTransEnd = () => { if (!$$(open)) { container().style.display = "none"; closed(true); } }; const _set = (silent) => { cancelled(false); batch(() => { for (let i = 0; i < value.length; i++) oriValue[i] = $$(value[i]); if ($$(commitOnOk)) for (let i = 0; i < value.length; i++) oValue[i]($$(value[i])); }); onOk == null ? void 0 : onOk(); open(false); }; useEffect(() => { if (restore()) batch(() => { value.forEach((v, i) => v(oriValue[i])); }); }); const _cancel = () => { cancelled(restore(true)); onCancel == null ? void 0 : onCancel(); open(false); }; useEffect(() => { if ($$(open)) { if ($$(disabled) || !closed() || !$$(container)) return; let cont = $$(container); closed(restore(false)); cont.style.display = "block"; } }); const width = useMemo(() => 100 / $$(data).filter((f) => !!$$(f)).length + "%"); const ws = useMemo(() => $$(data).map((v, i) => /* @__PURE__ */ jsx( Wheel, { rows, rowHeight, width, data: v, resetSelectedOnDataChanged, value: value[i], valuer: valuer == null ? void 0 : valuer[i], renderer: (renderer == null ? void 0 : renderer[i]) ?? ((r) => r), checkboxer: (checkboxer == null ? void 0 : checkboxer[i]) ?? ((r) => null), checkbox: checkbox == null ? void 0 : checkbox[i], disabler: (disabler == null ? void 0 : disabler[i]) ?? ((r) => null) } ))); const height = $$(rowHeight) * Math.floor($$(rows) / 2) - 1 + "px"; const { width: w, height: h, offsetTop, offsetLeft } = useViewportSize(); const s = useMemo(() => { return { height: $$(h), width: $$(w), top: $$(offsetTop), left: $$(offsetLeft) }; }); useEffect(() => console.log("headers", $$(headers))); return () => !$$(open) ? null : /* @__PURE__ */ jsxs("div", { ref: container, class: "wheelpicker fixed w-full h-full hidden z-[77] left-0 top-0", style: s, onDblClick: () => _set(), children: [ /* @__PURE__ */ jsx("div", { class: ["wheelpicker-backdrop duration-[0.4s] h-full bg-[rgba(0,0,0,0.5)] opacity-0 [transform:translateZ(0)]", () => $$(open) ? "opacity-100" : ""], onTransitionEnd: _backdropTransEnd, onClick: () => $$(hideOnBlur) ? $$(commitOnBlur) ? _set() : _cancel() : null }), /* @__PURE__ */ jsxs( "div", { class: [ "wheelpicker-panel duration-[0.4s] absolute w-full bg-[#F7F7F7] text-base text-black select-none left-0 bottom-0 ", () => $$(open) ? "[transform:none]" : "[transform:translateY(100%)]", "absolute w-full z-[1000] p-2.5 bottom-0" ], style: { // transform: () => $$(shown) ? 'transform:none' : 'translate3d(0,100%,0)' }, children: [ () => $$(toolbar) ? /* @__PURE__ */ jsxs("div", { class: "wheelpicker-actions overflow-hidden border-b-[#C6C6C6] border-b border-solid", children: [ /* @__PURE__ */ jsx("button", { type: "button", class: "btn-cancel text-sm h-11 px-[1em] py-0 [border:none] [background:none] float-left font-bold", onClick: _cancel, children: cancel }), /* @__PURE__ */ jsx("button", { type: "button", class: "btn-set text-sm h-11 px-[1em] py-0 [border:none] [background:none] float-right text-[#1078FC] font-bold", onClick: () => _set(), children: ok }), /* @__PURE__ */ jsx("h4", { class: "wheelpicker-title text-center text-[1em] m-0", children: title }) ] }) : null, () => !$$(headers) ? null : $$(headers).map((h2) => /* @__PURE__ */ jsx("div", { class: "inline-block text-center font-bold", style: { width }, children: h2 })), /* @__PURE__ */ jsxs("div", { class: "wheelpicker-main relative bg-white", children: [ /* @__PURE__ */ jsx("div", { class: "wheelpicker-wheels flex justify-center", children: ws }), () => $$(noMask) ? null : /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx("div", { class: "wheelpicker-mask absolute w-full pointer-events-none left-0 [transform:translateZ(0)] wheelpicker-mask-top h-3/6 top-0 [background:linear-gradient(to_bottom,#FFF,rgba(255,255,255,0.5)75%)]", style: { height } }), /* @__PURE__ */ jsx("div", { class: "wheelpicker-mask absolute w-full pointer-events-none left-0 [transform:translateZ(0)] wheelpicker-mask-current h-[34px] mt-[-18px] border-y-[#C6C6C6] border-t border-solid border-b top-2/4" }), /* @__PURE__ */ jsx("div", { class: "wheelpicker-mask absolute w-full pointer-events-none left-0 [transform:translateZ(0)] wheelpicker-mask-btm h-3/6 bottom-0 [background:linear-gradient(to_top,#FFF,rgba(255,255,255,0.5)75%)]", style: { height } }) ] }) ] }) ] } ) ] }); } const currentYear = new Date(Date.now()).getFullYear(); const get = (d) => { const year = d.getFullYear(); const month = d.getMonth(); const day = d.getDate(); const hour = d.getHours(); const minute = d.getMinutes(); const second = d.getSeconds(); return { year, month, day, hour, minute, second }; }; const DateWheeler = (props) => { const { max = 2101, min = 1900, hasYear = $(true), hasMonth = $(true), hasDay = $(true), hasHour, hasMinute, hasSecond, // format = (value: Observable<number | string>[]) => value.slice(0, 3).map(v => $$(v) + '').join(' ') + ' ' + value.slice(3).map(v => ($$(v) + '').padStart(2, '0')).join(':'), headers = ["Year", "Month", "Day", "Hour", "Minute", "Second"], shown = $(false), title = (value2) => /* @__PURE__ */ jsxs("div", { class: "font-bold", children: [ " ", () => $$(value2).toLocaleDateString() ] }) } = props ?? {}; const years = Array.from({ length: $$(max) - $$(min) }, (_, i) => $$(min) + i); const Months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; function days(year, month) { const lastDayOfMonth = new Date(year, month + 1, 0); const daysInMonth = lastDayOfMonth.getDate(); return Array.from({ length: daysInMonth }, (_, i) => i + 1); } const oDate = isObservable(props.value) ? props.value : $(props.value ?? /* @__PURE__ */ new Date()); const date = $($$(oDate)); useEffect(() => { date($$(oDate)); }); useEffect(() => { (!$$(date) || isNaN(+$$(date))) && date(/* @__PURE__ */ new Date()); }); (!$$(date) || isNaN(+$$(date))) && date(/* @__PURE__ */ new Date()); const dt = useMemo( () => [ $$(hasYear) ? $(years) : void 0, //state $$(hasMonth) ? $(Months) : void 0, //Object.keys(data[defaultProv]), //city $$(hasDay) ? $(days($$(date).getFullYear(), $$(date).getMonth())) : void 0, //$(days(+$$(dv[0]), months.indexOf(($$(dv[1]) ?? 'January') + '')))//data[defaultProv][Object.keys(data[defaultProv])[0]] //district $$(hasHour) || $$(hasMinute) || $$(hasSecond) ? $(Array.from({ length: 24 - 0 }, (_, i) => 0 + i)) : void 0, $$(hasMinute) || $$(hasSecond) ? $(Array.from({ length: 60 - 0 }, (_, i) => 0 + i)) : void 0, $$(hasSecond) ? $(Array.from({ length: 60 - 0 }, (_, i) => 0 + i)) : void 0 ].filter((n) => !!n) ); const value = (() => { const { year, month, day, hour, minute, second } = get($$(date)); const [hY, hM, hD, hH, hm, hS] = [$$(hasYear), $$(hasMonth), $$(hasDay), $$(hasHour), $$(hasMinute), $$(hasSecond)]; return [ $(hY ? year : void 0), $(hM ? Months[month] : void 0), $(hD ? day : void 0), $(hH || hm || hS ? hour : void 0), $(hm || hS ? minute : void 0), $(hS ? second : void 0) ]; })(); useEffect(() => { const { year, month, day, hour, minute, second } = get($$(oDate)); [$$(hasYear), $$(hasMonth), $$(hasDay), $$(hasHour), $$(hasMinute), $$(hasSecond)]; value[0](year); value[1](Months[month]); value[2](day); value[3](hour); value[4](minute); value[5](second); }); const d = useMemo(() => { const year = $$(value[0]); const month = $$(value[1]); const day = $$(value[2]); const hour = $$(value[3]); const minute = $$(value[4]); const second = $$(value[5]); if ($$(hasDay)) { if ($$(hasMonth) && $$(hasYear)) { const ds = days(+year, Months.indexOf(month + "")); if ($$($$(dt)[2]).length !== ds.length) $$(dt)[2](ds); const l3 = day; if (+l3 > ds.length) value[2](ds[ds.length - 1]); } } let y, M, d2, h, m, s; const [hY, hM, hD, hH, hm, hS] = [$$(hasYear), $$(hasMonth), $$(hasDay), $$(hasHour), $$(hasMinute), $$(hasSecond)]; if (hY) y = year; if (hM) M = month; if (hD) d2 = day; if (hH || hm || hS) h = hour; if (hm || hS) m = minute; if (hS) s = second; return new Date(y ?? currentYear, M ? Months.indexOf($$(value[1]) + "") : 0, d2 ?? 1, h ?? 0, m ?? 0, s ?? 0); }); useEffect(() => { if (+$$(date) !== +$$(d)) date($$(d)); }); const hd = useMemo(() => { const [hY, hM, hD, hH, hm, hS] = [$$(hasYear), $$(hasMonth), $$(hasDay), $$(hasHour), $$(hasMinute), $$(hasSecond)]; return [hY ? headers[0] : void 0, hM ? headers[1] : void 0, hD ? headers[2] : void 0, hH || hm || hS ? headers[3] : void 0, hm || hS ? headers[4] : void 0, hS ? headers[5] : void 0].filter((n) => !!n); }); return () => $$(shown) ? /* @__PURE__ */ jsx( Wheeler, { data: dt, value, title: title(d), headers: hd, open: shown, toolbar: true, onOk: () => oDate($$(date)) } ) : null; }; const useRecordWheeler = (d, options) => { const keys = Object.keys(d); const data = [$(keys.map((key) => ({ text: key, value: key, checked: d[key] })))]; const checked = keys.map((key) => d[key]); return { data, checked, value: [$()], renderer: [(r) => r.text], valuer: [(r) => r.value], checkboxer: [(r) => r.checked], checkbox: [$(true)], noMask: true, hideOnBackdrop: true, rows: Math.min(6, keys.length), open: $(false), ...options ?? {} }; }; const useArrayWheeler = (data, options) => { const { all } = options ?? {}; const checked = all ? $$(data).map((f) => $(false)) : void 0; if (all) { useEffect(() => { if (typeof $$(checked[0]) === "undefined") return; if ($$(checked[0])) checked.forEach((c, i) => i === 0 ? null : checked[i](true)); else checked.forEach((c, i) => i === 0 ? null : checked[i](false)); }); useEffect(() => { const c = checked.slice(1); const at = c.every((f) => $$(f)); const af = c.every((f) => !$$(f)); if (at) checked[0](true); if (af) checked[0](false); if (!at && !af) checked[0](void 0); }); } return { data: [data], checked, value: [$()], renderer: [(r) => r], valuer: [(r) => r], checkboxer: all ? [(r) => checked[$$(data).indexOf(r)]] : null, checkbox: [$(true)], noMask: true, hideOnBackdrop: true, rows: Math.min(6, data.length), open: $(false), ...options ?? {} }; }; const useEnumData = (o) => { const no = {}; const data = []; Object.keys(o).filter((k) => isNaN(+k)).forEach((k) => { no[k] = o[k]; data.push({ key: k, value: o[k] }); }); const renderer = (r) => r == null ? void 0 : r.key; const valuer = (r) => r; const rows = $(Math.min(data.length, 7)); return { obj: no, data, renderer, valuer, rows }; }; const useEnum = ({ data, valuer, renderer, rows }) => ({ data: [data], valuer: [valuer], renderer: [renderer], value: [$()], open: $(false), rows }); export { DateWheeler, Wheel, Wheeler, useArrayWheeler, useEnum, useEnumData, useRecordWheeler }; //# sourceMappingURL=index.es.js.map