1 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
2 |
|
3 | import { h, Component } from 'preact';
|
4 | import { Icon } from './icon';
|
5 | import { Button } from './button';
|
6 | import cn from 'classnames';
|
7 | import omit from './utils/omit';
|
8 | import { FullScreenOverlay } from './full-screen-overlay';
|
9 |
|
10 | const cellStyle = {
|
11 | width: '80px',
|
12 | height: '80px'
|
13 | };
|
14 |
|
15 | const stopEvent = e => {
|
16 | e.preventDefault();
|
17 | e.stopImmediatePropagation();
|
18 | };
|
19 |
|
20 | const quickTap = e => {
|
21 | e.preventDefault();
|
22 | e.target.click();
|
23 | };
|
24 |
|
25 | const NumberCell = ({ number, callback }) => h(
|
26 | 'td',
|
27 | null,
|
28 | h(
|
29 | Button,
|
30 | {
|
31 | onClick: e => {
|
32 | e.stopImmediatePropagation();
|
33 | callback(number);
|
34 | },
|
35 | onTouchStart: quickTap,
|
36 | style: cellStyle
|
37 | },
|
38 | number
|
39 | )
|
40 | );
|
41 |
|
42 | export class Numpad extends Component {
|
43 | constructor(props) {
|
44 | super(props);
|
45 | this.addNumber = this.addNumber.bind(this);
|
46 | this.backspace = this.backspace.bind(this);
|
47 | this.close = this.close.bind(this);
|
48 | this.onKey = this.onKey.bind(this);
|
49 | this.state = {
|
50 | value: '0'
|
51 | };
|
52 | }
|
53 |
|
54 | addNumber(number) {
|
55 | const { value } = this.state;
|
56 | const { max, maxDecimals } = this.props;
|
57 | if (number === '.' && value.includes('.')) {
|
58 | return;
|
59 | }
|
60 | let newNumber = this.state.value + '' + number;
|
61 | if (newNumber.slice(0, 1) === '0') {
|
62 | newNumber = newNumber.slice(1);
|
63 | }
|
64 | const decimals = newNumber.split('.')[1];
|
65 | if (decimals && decimals.length > maxDecimals) {
|
66 | return;
|
67 | }
|
68 | if (max && Number(newNumber) > max) {
|
69 | return;
|
70 | }
|
71 | this.setState({ value: newNumber });
|
72 | }
|
73 |
|
74 | backspace(e) {
|
75 | if (e) e.stopImmediatePropagation();
|
76 | const number = (this.state.value + '').slice(0, -1);
|
77 | this.setState({ value: number || '0' });
|
78 | }
|
79 |
|
80 | onKey(e) {
|
81 | const value = e.which - 48;
|
82 | if (value <= 9 && value >= 0) {
|
83 | this.addNumber(value);
|
84 | } else if (e.which === 8 || e.which === 48) {
|
85 | this.backspace();
|
86 | } else if (e.which === 27) {
|
87 | this.close();
|
88 | } else if (e.which === 13) {
|
89 | this.close(this.state.value);
|
90 | } else if (e.which === 190) {
|
91 | this.addNumber('.');
|
92 | }
|
93 | }
|
94 |
|
95 | componentDidMount() {
|
96 | window.addEventListener('keydown', this.onKey);
|
97 | }
|
98 |
|
99 | componentWillUnmount() {
|
100 | window.removeEventListener('keydown', this.onKey);
|
101 | }
|
102 |
|
103 | close(value) {
|
104 | if (typeof value === 'object') {
|
105 | value = undefined;
|
106 | }
|
107 | this.props.onClose(value !== undefined ? Number(value) : undefined);
|
108 | }
|
109 |
|
110 | render(props, { value }) {
|
111 | const { unit, className, max } = props;
|
112 | const rest = omit(props, ['unit', 'className', 'max']);
|
113 | return h(
|
114 | 'div',
|
115 | _extends({
|
116 | onClick: stopEvent,
|
117 | className: cn('ba br2 b--gray bg-light-gray pa2 shadow-2 top-0 left-0', className)
|
118 | }, rest),
|
119 | h(
|
120 | 'div',
|
121 | { className: 'flex items-center justify-between' },
|
122 | h(
|
123 | 'div',
|
124 | { className: 'ml2' },
|
125 | max && `max: ${max}`
|
126 | ),
|
127 | h(
|
128 | 'div',
|
129 | { className: 'flex items-center justify-end' },
|
130 | h(
|
131 | 'span',
|
132 | { className: 'fw6 f4 mr2' },
|
133 | value,
|
134 | ' ',
|
135 | unit
|
136 | ),
|
137 | h(
|
138 | 'button',
|
139 | {
|
140 | className: 'bn bg-transparent',
|
141 | onClick: this.backspace,
|
142 | onTouchStart: e => {
|
143 | e.preventDefault();
|
144 | e.target.click();
|
145 | },
|
146 | style: cellStyle
|
147 | },
|
148 | h(Icon, { icon: 'backspace' })
|
149 | )
|
150 | )
|
151 | ),
|
152 | h(
|
153 | 'table',
|
154 | null,
|
155 | h(
|
156 | 'tr',
|
157 | null,
|
158 | h(NumberCell, { callback: this.addNumber, number: '1' }),
|
159 | h(NumberCell, { callback: this.addNumber, number: '2' }),
|
160 | h(NumberCell, { callback: this.addNumber, number: '3' }),
|
161 | h(
|
162 | 'td',
|
163 | { style: cellStyle },
|
164 | h(
|
165 | 'div',
|
166 | { className: 'relative', style: cellStyle },
|
167 | h(
|
168 | Button,
|
169 | {
|
170 | style: {
|
171 | width: '80px',
|
172 | height: '162px',
|
173 | top: '1px'
|
174 | },
|
175 | stop: true,
|
176 | className: 'absolute top-0 left-0',
|
177 | onClick: () => this.close()
|
178 | },
|
179 | 'Cancel'
|
180 | )
|
181 | )
|
182 | )
|
183 | ),
|
184 | h(
|
185 | 'tr',
|
186 | null,
|
187 | h(NumberCell, { callback: this.addNumber, number: '4' }),
|
188 | h(NumberCell, { callback: this.addNumber, number: '5' }),
|
189 | h(NumberCell, { callback: this.addNumber, number: '6' }),
|
190 | h('td', null)
|
191 | ),
|
192 | h(
|
193 | 'tr',
|
194 | null,
|
195 | h(NumberCell, { callback: this.addNumber, number: '7' }),
|
196 | h(NumberCell, { callback: this.addNumber, number: '8' }),
|
197 | h(NumberCell, { callback: this.addNumber, number: '9' }),
|
198 | h(
|
199 | 'td',
|
200 | { style: cellStyle },
|
201 | h(
|
202 | 'div',
|
203 | { className: 'relative', style: cellStyle },
|
204 | h(
|
205 | Button,
|
206 | {
|
207 | style: {
|
208 | width: '80px',
|
209 | height: '162px',
|
210 | top: '1px'
|
211 | },
|
212 | primary: true,
|
213 | className: 'absolute left-0',
|
214 | onClick: () => {
|
215 | this.close(value);
|
216 | }
|
217 | },
|
218 | 'Save'
|
219 | )
|
220 | )
|
221 | )
|
222 | ),
|
223 | h(
|
224 | 'tr',
|
225 | null,
|
226 | h(NumberCell, {
|
227 | callback: () => this.setState({ value: '0' }),
|
228 | number: 'Clear'
|
229 | }),
|
230 | h(NumberCell, { callback: this.addNumber, number: '0' }),
|
231 | h(NumberCell, { callback: this.addNumber, number: '.' }),
|
232 | h('td', null)
|
233 | )
|
234 | )
|
235 | );
|
236 | }
|
237 | }
|
238 |
|
239 | export class NumpadOverlay extends Component {
|
240 | componentDidMount() {
|
241 | setTimeout(() => {
|
242 | document.activeElement.blur();
|
243 | }, 200);
|
244 | }
|
245 |
|
246 | render({ unit = 'mg', onValue, onCancel, max = null, maxDecimals = 2 }) {
|
247 | return h(
|
248 | FullScreenOverlay,
|
249 | null,
|
250 | h(Numpad, {
|
251 | unit: unit,
|
252 | max: max,
|
253 | maxDecimals: maxDecimals,
|
254 | onClose: value => {
|
255 | if (value !== undefined) {
|
256 | onValue(value);
|
257 | } else {
|
258 | onCancel();
|
259 | }
|
260 | }
|
261 | })
|
262 | );
|
263 | }
|
264 | }
|
265 |
|
266 | export class NumpadButton extends Component {
|
267 | constructor(props) {
|
268 | super(props);
|
269 | this.state = {
|
270 | open: false
|
271 | };
|
272 | }
|
273 |
|
274 | render({ unit, max, maxDecimals, onValue, onCancel }, { open }) {
|
275 | return h(
|
276 | Button,
|
277 | {
|
278 | onClick: () => {
|
279 | this.setState({ open: true });
|
280 | }
|
281 | },
|
282 | h(Icon, { icon: 'dialpad' }),
|
283 | open && h(NumpadOverlay, {
|
284 | unit: unit,
|
285 | max: max,
|
286 | maxDecimals: maxDecimals,
|
287 | onCancel: () => {
|
288 | onCancel && onCancel();
|
289 | this.setState({ open: false });
|
290 | },
|
291 | onValue: val => {
|
292 | onValue(val);
|
293 | this.setState({ open: false });
|
294 | }
|
295 | })
|
296 | );
|
297 | }
|
298 | } |
\ | No newline at end of file |