UNPKG

@onesy/ui-react

Version:
465 lines (453 loc) 17.5 kB
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; const _excluded = ["ref", "tonal", "color", "size", "value", "valueDefault", "onChange", "selecting", "selectingDefault", "onChangeSelecting", "format", "dayTime", "hour", "minute", "second", "autoNext", "min", "max", "validate", "readOnly", "disabled", "valid", "renderValue", "onDoneSelecting", "onClick", "className", "BackgroundProps"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import React from 'react'; import { classNames, style, useOnesyTheme } from '@onesy/style-react'; import { clamp, getLeadingZerosNumber, is, isEnvironment, unique } from '@onesy/utils'; import { OnesyDate, is as isOnesyDate, set } from '@onesy/date'; import RoundMeterElement from '../RoundMeter'; import PathElement from '../Path'; import { staticClassName } from '../utils'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const useStyle = style(theme => ({ root: { userSelect: 'none', touchAction: 'none', '& .onesy-RoundMeter-children, & .onesy-RoundMeter-labels': { pointerEvents: 'none' }, '& svg > *': { cursor: 'grab' } }, mouseDown: { '& svg > *': { cursor: 'grabbing' } } }), { name: 'onesy-Clock' }); const Clock = props__ => { const theme = useOnesyTheme(); const props = _objectSpread(_objectSpread(_objectSpread({}, theme?.ui?.elements?.all?.props?.default), theme?.ui?.elements?.onesyClock?.props?.default), props__); const RoundMeter = theme?.elements?.RoundMeter || RoundMeterElement; const Path = theme?.elements?.Path || PathElement; const { ref, tonal = true, color = 'primary', size = 'regular', value: value_, valueDefault, onChange, selecting: selecting_, selectingDefault, onChangeSelecting, format = '12', dayTime = 'am', hour = true, minute = true, second = false, autoNext, min, max, validate, readOnly, disabled, valid: valid_, renderValue, onDoneSelecting, onClick: onClick_, className, BackgroundProps } = props, other = _objectWithoutProperties(props, _excluded); const { classes } = useStyle(); const [value, setValue] = React.useState((valueDefault !== undefined ? valueDefault : value_) || new OnesyDate()); const [selecting, setSelecting] = React.useState((selectingDefault !== undefined ? selectingDefault : selecting_) || 'hour'); const [mouseDown, setMouseDown] = React.useState(false); const refs = { root: React.useRef(null), middle: React.useRef(null), mouseDown: React.useRef(null), value: React.useRef(null), selecting: React.useRef(null), autoNext: React.useRef(null), hour: React.useRef(null), minute: React.useRef(null), second: React.useRef(null), format: React.useRef(null), dayTime: React.useRef(null), previous: React.useRef(selecting) }; refs.mouseDown.current = mouseDown; refs.value.current = value; refs.hour.current = hour; refs.minute.current = minute; refs.second.current = second; refs.selecting.current = selecting; refs.autoNext.current = autoNext; refs.format.current = format; refs.dayTime.current = dayTime; const resolve = (valueNew = refs.value.current, dayTimeValue = refs.dayTime.current) => { // Resolve the range value const valueHour = valueNew.hour; if (format === '12') { if (dayTimeValue === 'am' && valueHour > 12) return set(valueHour - 12, 'hour', valueNew); if (dayTimeValue === 'pm' && valueHour < 12) return set(valueHour + 12, 'hour', valueNew); } return valueNew; }; const inputToValue = (valueNew_0, unit = refs.selecting.current) => { let onesyDate = new OnesyDate(refs.value.current); let valueTime = valueNew_0; if (is('string', valueTime) && valueTime.startsWith('0')) valueTime = valueTime.slice(1); valueTime = +valueTime; if (unit === 'hour') onesyDate = set(format === '12' && dayTime === 'pm' ? valueTime + 12 : valueTime, 'hour', onesyDate);else onesyDate = set(valueTime, unit, onesyDate); return resolve(onesyDate); }; const onMove = (x_, y_) => { const rectMiddle = refs.middle.current.getBoundingClientRect(); const x = x_ - rectMiddle.x; const y = y_ - rectMiddle.y; const radians = Math.atan2(x, y); const degrees = radians * 180 / Math.PI; const angle = 180 - degrees; // Make array of values // for hours, minutes and seconds // with +- 50% around the value // Find item in that array that this angle fits within let valuesAll = []; if (refs.selecting.current === 'hour') { const part = 360 / 12; valuesAll = Array.from({ length: 12 }).map((item, index_) => [part * index_ - part / 2, part * index_ + part / 2]); let index = valuesAll.findIndex(item_0 => angle >= item_0[0] && angle <= item_0[1]); if (index === -1 || index === 0) index = refs.format.current === '24' ? 0 : 12; if (refs.format.current === '24') { let within = false; const labelElements = refs.root.current.querySelectorAll('.onesy-RoundMeter-labels'); const elements = { outer: labelElements[0], inner: labelElements[1] }; const rects = { outer: elements.outer.getBoundingClientRect(), inner: elements.inner.getBoundingClientRect() }; const part_ = Math.abs(Math.abs(rects.outer.x) - Math.abs(rects.inner.x)); const valueMoved = Math.sqrt(x ** 2 + y ** 2); const middleInner = Math.abs(Math.abs(rectMiddle.x) - Math.abs(rects.inner.x)); if (valueMoved <= middleInner + part_ / 2) within = true; if (within) index += 12; index = clamp(index, 0, 23); } // Validate if (!valid(inputToValue(index, 'hour'), 'hour')) return; // Update values onUpdate(inputToValue(index, 'hour')); } else if (['minute', 'second'].includes(refs.selecting.current)) { const part_0 = 360 / 60; valuesAll = Array.from({ length: 60 }).map((item_1, index__0) => [part_0 * index__0 - part_0 / 2, part_0 * index__0 + part_0 / 2]); let index_0 = valuesAll.findIndex(item_2 => angle >= item_2[0] && angle <= item_2[1]); if (index_0 === -1 || index_0 === 0) index_0 = 0; // Validate if (!valid(inputToValue(index_0), refs.selecting.current)) return; // Update values onUpdate(inputToValue(index_0)); } }; React.useEffect(() => { const onMouseUp = () => { if (refs.mouseDown.current) { setMouseDown(false); // Auto next if (refs.autoNext.current) { if (['hour', 'minute', 'second'].includes(refs.selecting.current)) { let valueSelecting; if (refs.selecting.current === 'second') valueSelecting = 'hour'; if (refs.selecting.current === 'minute') valueSelecting = refs.second.current ? 'second' : 'hour'; if (refs.selecting.current === 'hour' && refs.minute.current) valueSelecting = 'minute'; onUpdateSelecting(valueSelecting); } } if (is('function', onDoneSelecting)) onDoneSelecting(refs.value.current, refs.selecting.current); } }; // Mouse move const onMouseMove = event => { if (refs.mouseDown.current) { const { clientY, clientX } = event; onMove(clientX, clientY); } }; // Touch move const onTouchMove = event_0 => { if (refs.mouseDown.current) { const { clientY: clientY_0, clientX: clientX_0 } = event_0.touches[0]; onMove(clientX_0, clientY_0); } }; const rootDocument = isEnvironment('browser') ? refs.root.current?.ownerDocument || window.document : undefined; rootDocument.addEventListener('mouseup', onMouseUp); rootDocument.addEventListener('mousemove', onMouseMove); rootDocument.addEventListener('touchend', onMouseUp); rootDocument.addEventListener('touchmove', onTouchMove, { passive: true }); return () => { rootDocument.removeEventListener('mousemove', onMouseMove); rootDocument.removeEventListener('mouseup', onMouseUp); rootDocument.removeEventListener('touchmove', onTouchMove); rootDocument.removeEventListener('touchend', onMouseUp); }; }, []); React.useEffect(() => { if (value_ !== undefined && value_ !== value) setValue(value_); }, [value_]); const updateTransitions = () => { // Add momentary transition to the OnesyRoundMeter-children > * // if selecting value updates if (refs.root.current) { let elementChildren = refs.root.current.getElementsByClassName('onesy-RoundMeter-children')[0]; let elementLabels = refs.root.current.getElementsByClassName('onesy-RoundMeter-labels')[0]; if (elementChildren && elementLabels) { elementChildren = Array.from(elementChildren.children); elementLabels = Array.from(elementLabels.children); elementChildren.forEach(item_3 => item_3.style.transition = 'transform .3s'); elementLabels.forEach(item_4 => item_4.style.transition = 'fill .3s'); setTimeout(() => { [...elementChildren, ...elementLabels].forEach(item_5 => item_5.style.removeProperty('transition')); }, 300); } } }; React.useEffect(() => { if (selecting_ !== undefined && selecting_ !== selecting) { setSelecting(selecting_); refs.previous.current = selecting_; updateTransitions(); } }, [selecting_]); React.useEffect(() => { if (selecting !== refs.previous.current) { refs.previous.current = selecting; updateTransitions(); } }, [selecting]); const onUpdate = valueNew_1 => { const newValue = valueNew_1?.milliseconds || null; const previousValue = refs.value.current.milliseconds; if (newValue === previousValue) return; if (!(readOnly || disabled)) { // Inner controlled value if (!props.hasOwnProperty('value')) setValue(valueNew_1); if (is('function', onChange)) onChange(valueNew_1); } }; const onUpdateSelecting = valueNew_2 => { if (!(readOnly || disabled)) { // Inner controlled selecting if (!props.hasOwnProperty('selecting')) setSelecting(valueNew_2); if (is('function', onChangeSelecting)) onChangeSelecting(valueNew_2); } }; const valid = (...args) => { if (is('function', valid_)) return valid_(...args); const onesyDate_0 = args[0]; if (min || max || validate) { let response = true; if (is('function', validate)) response = validate(onesyDate_0); if (min !== undefined) response = response && isOnesyDate(onesyDate_0, 'after or same', min); if (max !== undefined) response = response && isOnesyDate(onesyDate_0, 'before or same', max); return response; } return true; }; const onMouseDown = () => { setMouseDown(true); }; const onClick = event_1 => { const { clientX: x_0, clientY: y_0 } = event_1; onMove(x_0, y_0); if (is('function', onClick_)) onClick_(event_1); }; const palette = React.useMemo(() => { if (['inherit', 'default'].includes(color)) return theme.methods.color(theme.palette.text.default.primary); if (color === 'themed') return theme.methods.color(theme.palette.text.default.secondary); if (color === 'inverted') return theme.methods.color(theme.palette.background.default.primary); return theme.methods.color(theme.palette.color[color]?.main || color); }, [color, theme]); let valueClock = ''; let valueClock24 = 0; let valuePosition; let labels = []; let lowerPointer = false; const colors = { regular: 'currentColor', inverse: theme.methods.palette.color.value(undefined, 90, true, palette) }; if (selecting === 'hour') { // Value valueClock = valueClock24 = value?.hour; if (format === '24' && valueClock > 11) lowerPointer = true; if (valueClock > 12) valueClock -= 12; valuePosition = 100 / 12 * valueClock; // Labels if (format === '12') labels = unique([ // 12 hours ...Array.from({ length: 12 }).map((item_6, index_1) => ({ value: index_1 === 0 ? 12 : index_1, padding: theme.methods.space.value(2.5, 'px'), style: { fontSize: 14, opacity: valid(inputToValue(index_1 === 0 ? 12 : index_1, 'hour'), 'hour') ? 1 : 0.27, fill: valueClock === 12 && index_1 === 0 || valueClock === index_1 ? colors.inverse : colors.regular }, position: index_1 * (100 / 12) }))], 'position');else { labels = [unique([ // 0-11 hours ...Array.from({ length: 12 }).map((item_7, index_2) => ({ value: index_2 === 0 ? '00' : index_2, padding: theme.methods.space.value(2.5, 'px'), style: { fontSize: 14, opacity: valid(inputToValue(index_2 === 0 ? 0 : index_2, 'hour'), 'hour') ? 1 : 0.27, fill: valueClock24 === index_2 ? colors.inverse : colors.regular }, position: index_2 * (100 / 12) }))], 'position'), unique([ // 12-23 hours ...Array.from({ length: 12 }).map((item_8, index_3) => ({ value: 12 + index_3, padding: theme.methods.space.value(6, 'px'), style: { fontSize: 14, opacity: valid(inputToValue(12 + index_3, 'hour'), 'hour') ? 1 : 0.27, fill: valueClock24 === 12 + index_3 ? colors.inverse : colors.regular }, position: index_3 * (100 / 12) }))], 'position')]; } } if (selecting === 'minute') { // Value valueClock = value?.minute; valuePosition = 100 / 60 * valueClock; // Labels labels = unique([ // 59 minutes ...Array.from({ length: 12 }).map((item_9, index_4) => ({ value: index_4 === 0 ? '00' : getLeadingZerosNumber(60 / 12 * index_4), padding: theme.methods.space.value(2.5, 'px'), style: { fontSize: 14, opacity: valid(inputToValue(index_4 === 0 ? 0 : 60 / 12 * index_4), 'minute') ? 1 : 0.27, fill: valueClock === 60 / 12 * index_4 ? colors.inverse : colors.regular }, position: index_4 * (100 / 12) }))], 'position'); } if (selecting === 'second') { // Value valueClock = value?.second; valuePosition = 100 / 60 * valueClock; // Labels labels = unique([ // 59 seconds ...Array.from({ length: 12 }).map((item_10, index_5) => ({ value: index_5 === 0 ? '00' : getLeadingZerosNumber(60 / 12 * index_5), padding: theme.methods.space.value(2.5, 'px'), style: { fontSize: 14, opacity: valid(inputToValue(index_5 === 0 ? 0 : 60 / 12 * index_5, 'second'), 'second') ? 1 : 0.27, fill: valueClock === 60 / 12 * index_5 ? colors.inverse : colors.regular }, position: index_5 * (100 / 12) }))], 'position'); } return /*#__PURE__*/_jsxs(RoundMeter, _objectSpread(_objectSpread({ ref: item_11 => { if (ref) { if (is('function', ref)) ref(item_11);else ref.current = item_11; } refs.root.current = item_11; }, tonal: tonal, color: color, size: size, labels: labels, arcsVisible: false, childrenPosition: "pre-marks", onClick: onClick, background: true, BackgroundProps: { fill: theme.methods.palette.color.value(undefined, 70, true, palette), onMouseDown: onMouseDown, onTouchStart: onMouseDown }, renderLabel: is('function', renderValue) ? (x_1, y_1, valueItem, otherProps) => renderValue(value, selecting, x_1, y_1, valueItem, otherProps) : undefined }, other), {}, { className: classNames([staticClassName('Clock', theme) && ['onesy-Clock-round-meter'], className, classes.root, mouseDown && classes.mouseDown]), children: [/*#__PURE__*/_jsx(Path, { ref: refs.middle, Component: "circle", r: "4", cx: "120", cy: "120", style: { stroke: 'none', fill: palette[40] } }), /*#__PURE__*/_jsx(Path, { d: "M 120 119 L 195 119 A 1 1 0 0 1 195 121 L 120 121 A 1 1 0 0 1 121 119", value: valuePosition, style: { transformOrigin: '50% 50%', fill: palette[40], stroke: 'none' } }), /*#__PURE__*/_jsx(Path, { Component: "circle", r: "24", cx: lowerPointer ? 182 : 212.5, cy: "120", value: valuePosition, style: { transformOrigin: 'center', fill: palette[40], stroke: 'none' } })] })); }; Clock.displayName = 'onesy-Clock'; export default Clock;