@performant-software/shared-components 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +0 -0
  3. package/build/index.js +2 -0
  4. package/build/index.js.map +1 -0
  5. package/build/main.css +11 -0
  6. package/index.js +1 -0
  7. package/package.json +36 -0
  8. package/src/api/Attachments.js +28 -0
  9. package/src/api/BaseService.js +127 -0
  10. package/src/api/BaseTransform.js +55 -0
  11. package/src/api/FormDataTransform.js +30 -0
  12. package/src/api/NestedAttributesTransform.js +63 -0
  13. package/src/components/EditContainer.css +0 -0
  14. package/src/components/EditContainer.js +448 -0
  15. package/src/components/GoogleAnalytics.css +0 -0
  16. package/src/components/GoogleAnalytics.js +118 -0
  17. package/src/components/GoogleScript.js +5 -0
  18. package/src/components/InfiniteScroll.css +1 -0
  19. package/src/components/InfiniteScroll.js +120 -0
  20. package/src/components/Keyboard.css +11 -0
  21. package/src/components/Keyboard.js +55 -0
  22. package/src/i18n/en.json +204 -0
  23. package/src/i18n/i18n.js +24 -0
  24. package/src/index.js +34 -0
  25. package/src/utils/Browser.js +8 -0
  26. package/src/utils/Calendar.js +232 -0
  27. package/src/utils/Date.js +10 -0
  28. package/src/utils/DragDrop.js +17 -0
  29. package/src/utils/Element.js +36 -0
  30. package/src/utils/Map.js +27 -0
  31. package/src/utils/Object.js +114 -0
  32. package/src/utils/String.js +20 -0
  33. package/src/utils/Timer.js +32 -0
  34. package/src/utils/Utility.js +14 -0
  35. package/test/api/Attachments.spec.js +32 -0
  36. package/types/api/Attachments.js.flow +28 -0
  37. package/types/api/BaseService.js.flow +127 -0
  38. package/types/api/BaseTransform.js.flow +55 -0
  39. package/types/api/FormDataTransform.js.flow +30 -0
  40. package/types/api/NestedAttributesTransform.js.flow +63 -0
  41. package/types/components/EditContainer.js.flow +448 -0
  42. package/types/components/GoogleAnalytics.js.flow +118 -0
  43. package/types/components/GoogleScript.js.flow +5 -0
  44. package/types/components/InfiniteScroll.js.flow +120 -0
  45. package/types/components/Keyboard.js.flow +55 -0
  46. package/types/i18n/i18n.js.flow +24 -0
  47. package/types/index.js.flow +34 -0
  48. package/types/utils/Browser.js.flow +8 -0
  49. package/types/utils/Calendar.js.flow +232 -0
  50. package/types/utils/Date.js.flow +10 -0
  51. package/types/utils/DragDrop.js.flow +17 -0
  52. package/types/utils/Element.js.flow +36 -0
  53. package/types/utils/Map.js.flow +27 -0
  54. package/types/utils/Object.js.flow +114 -0
  55. package/types/utils/String.js.flow +20 -0
  56. package/types/utils/Timer.js.flow +32 -0
  57. package/types/utils/Utility.js.flow +14 -0
  58. package/webpack.config.js +3 -0
@@ -0,0 +1,448 @@
1
+ // @flow
2
+
3
+ import React, { Component, type ComponentType, type Element } from 'react';
4
+ import _ from 'underscore';
5
+ import i18n from '../i18n/i18n';
6
+ import { isEqual } from '../utils/Object';
7
+
8
+ type Props = {
9
+ children?: Element<any>,
10
+ defaults?: any,
11
+ item?: any,
12
+ onClose: () => void,
13
+ onInitialize?: (id: number) => Promise<any>,
14
+ onSave: (item: any) => Promise<any>,
15
+ required?: Array<string>,
16
+ resolveValidationError?: ({ error: string, item: any, status: number, key: string }) => Array<string>,
17
+ validate?: (item: any) => Array<string>
18
+ };
19
+
20
+ type State = {
21
+ item: any,
22
+ loading: boolean,
23
+ originalItem: any,
24
+ saving: boolean,
25
+ validationErrors: any
26
+ };
27
+
28
+ const ERROR_EMPTY = 'can\'t be blank';
29
+ const ERROR_UNIQUE = 'has already been taken';
30
+
31
+ const useEditContainer = (WrappedComponent: ComponentType<any>) => (
32
+ class extends Component<Props, State> {
33
+ /**
34
+ * Constructs a new EditProvider component.
35
+ *
36
+ * @param props
37
+ */
38
+ constructor(props: Props) {
39
+ super(props);
40
+
41
+ const item = _.defaults(props.item || {}, props.defaults || {});
42
+
43
+ this.state = {
44
+ item,
45
+ loading: false,
46
+ originalItem: item,
47
+ saving: false,
48
+ validationErrors: []
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Loads the item from the API if an onInitialize prop is specified.
54
+ */
55
+ componentDidMount() {
56
+ if (this.props.onInitialize && this.props.item && this.props.item.id) {
57
+ this.setState({ loading: true }, () => {
58
+ if (this.props.onInitialize && this.props.item) {
59
+ this.props
60
+ .onInitialize(this.props.item.id)
61
+ .then((item) => this.setState({ item, originalItem: item, loading: false }));
62
+ }
63
+ });
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Resets the item on the state.
69
+ *
70
+ * @param prevProps
71
+ */
72
+ componentDidUpdate(prevProps: Props) {
73
+ if (prevProps.item !== this.props.item) {
74
+ this.setState({ item: this.props.item, originalItem: this.props.item });
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Sets the saving property to false.
80
+ */
81
+ componentWillUnmount() {
82
+ this.onSetState({ saving: false });
83
+ }
84
+
85
+ /**
86
+ * Returns true if the IDs or UIDs for the passed records match.
87
+ *
88
+ * @param a
89
+ * @param b
90
+ *
91
+ * @returns {*|boolean}
92
+ */
93
+ isChild(a: any, b: any) {
94
+ return (a.uid && b.uid && a.uid === b.uid) || (a.id && b.id && a.id === b.id);
95
+ }
96
+
97
+ /**
98
+ * Returns true if the passed property is required.
99
+ *
100
+ * @param prop
101
+ *
102
+ * @returns {*|boolean}
103
+ */
104
+ isRequired(prop: string) {
105
+ return this.props.required && _.contains(this.props.required, prop);
106
+ }
107
+
108
+ /**
109
+ * Returns true if the passed property is listed in the hash of validation errors.
110
+ *
111
+ * @param prop
112
+ *
113
+ * @returns {*|boolean}
114
+ */
115
+ isError(prop: string) {
116
+ return _.has(this.state.validationErrors, prop);
117
+ }
118
+
119
+ /**
120
+ * Closes the provider.
121
+ */
122
+ onClose() {
123
+ this.props.onClose();
124
+ }
125
+
126
+ /**
127
+ * Adds the passed child to the passed association for the current item.
128
+ *
129
+ * @param association
130
+ * @param child
131
+ */
132
+ onCreateChildAssociation(association: string, child: any) {
133
+ this.setState((state) => ({
134
+ item: {
135
+ ...state.item,
136
+ [association]: [
137
+ ...(state.item[association] || []),
138
+ child
139
+ ]
140
+ }
141
+ }));
142
+ }
143
+
144
+ /**
145
+ * Removes the passed child from the passed association.
146
+ *
147
+ * @param association
148
+ * @param child
149
+ */
150
+ onDeleteChildAssociation(association: string, child: any) {
151
+ return child.id
152
+ ? this.onMarkChildAssociationForDelete(association, child)
153
+ : this.onRemoveChildAssociation(association, child);
154
+ }
155
+
156
+ /**
157
+ * Resolves the validation errors from the passed HTTP response.
158
+ *
159
+ * @param errors
160
+ * @param status
161
+ */
162
+ onError({ response: { data: { errors = {} }, status } }: any) {
163
+ const validationErrors = {};
164
+
165
+ _.each(Object.keys(errors), (key) => {
166
+ const fieldErrors = errors[key];
167
+ const value = this.state.item[key];
168
+
169
+ _.each(fieldErrors, (error) => {
170
+ if (error === ERROR_UNIQUE) {
171
+ _.extend(validationErrors, { [key]: i18n.t('EditContainer.errors.unique', { key, value }) });
172
+ } else if (error === ERROR_EMPTY) {
173
+ _.extend(validationErrors, { [key]: i18n.t('EditContainer.errors.required', { key }) });
174
+ } else if (this.props.resolveValidationError) {
175
+ _.extend(validationErrors, this.props.resolveValidationError({
176
+ key,
177
+ error,
178
+ status,
179
+ item: this.state.item
180
+ }));
181
+ }
182
+ });
183
+ });
184
+
185
+ if (status === 400 && _.isEmpty(validationErrors)) {
186
+ _.extend(validationErrors, { error: i18n.t('EditContainer.errors.general') });
187
+ } else if (status === 500 && _.isEmpty(validationErrors)) {
188
+ _.extend(validationErrors, { error: i18n.t('EditContainer.errors.system') });
189
+ }
190
+
191
+ this.setState({ saving: false, validationErrors });
192
+ }
193
+
194
+ /**
195
+ * Marks the passed child for delete form the passed association. This function is called if the child record
196
+ * has an ID value.
197
+ *
198
+ * @param association
199
+ * @param child
200
+ */
201
+ onMarkChildAssociationForDelete(association: string, child: any) {
202
+ this.setState((state) => ({
203
+ item: {
204
+ ...state.item,
205
+ [association]: _.map(state.item[association] || [],
206
+ (c) => (c.id === child.id ? { ...c, _destroy: true } : c))
207
+ }
208
+ }));
209
+ }
210
+
211
+ /**
212
+ * Modifies the passed association based on the passed collection of children.
213
+ *
214
+ * @param association
215
+ * @param children
216
+ */
217
+ onMultiAddChildAssociations(association: string, children: any) {
218
+ const items = this.state.item[association];
219
+
220
+ // Add new children or update existing children
221
+ _.each(children, this.onSaveChildAssociation.bind(this, association));
222
+
223
+ // Remove any children that no longer exist
224
+ const childrenToRemove = _.filter(items, (item) => !_.find(children, this.isChild.bind(this, item)));
225
+ _.each(childrenToRemove, this.onDeleteChildAssociation.bind(this, association));
226
+ }
227
+
228
+ /**
229
+ * Removes the passed child record from the passed association. This function is called if the child record does
230
+ * not have an ID value.
231
+ *
232
+ * @param association
233
+ * @param child
234
+ */
235
+ onRemoveChildAssociation(association: string, child: any) {
236
+ this.setState((state) => ({
237
+ item: {
238
+ ...state.item,
239
+ [association]: _.filter(state.item[association] || [],
240
+ (c) => c !== child)
241
+ }
242
+ }));
243
+ }
244
+
245
+ /**
246
+ * Resets the item on the state to the default item and calls the onReset prop.
247
+ */
248
+ onReset() {
249
+ const item = this.props.defaults || {};
250
+
251
+ this.setState({
252
+ item,
253
+ originalItem: item
254
+ });
255
+ }
256
+
257
+ /**
258
+ * Saves the current item.
259
+ */
260
+ onSave() {
261
+ if (!this.validateForm()) {
262
+ return;
263
+ }
264
+
265
+ this.setState({ saving: true }, () => {
266
+ this.props
267
+ .onSave(this.state.item)
268
+ .catch(this.onError.bind(this))
269
+ .finally(() => this.setState({ saving: false }));
270
+ });
271
+ }
272
+
273
+ /**
274
+ * Adds the passed child record to the passed association.
275
+ *
276
+ * @param association
277
+ * @param child
278
+ */
279
+ onSaveChildAssociation(association: string, child: any) {
280
+ const children = this.state.item[association] || [];
281
+
282
+ return _.find(children, this.isChild.bind(this, child))
283
+ ? this.onUpdateChildAssociation(association, child)
284
+ : this.onCreateChildAssociation(association, child);
285
+ }
286
+
287
+ /**
288
+ * Updates the child record in the passed association.
289
+ *
290
+ * @param association
291
+ * @param child
292
+ */
293
+ onUpdateChildAssociation(association: string, child: any) {
294
+ this.setState((state) => ({
295
+ item: {
296
+ ...state.item,
297
+ [association]: _.map(state.item[association] || [], (c) => (this.isChild(child, c) ? child : c))
298
+ }
299
+ }));
300
+ }
301
+
302
+ /**
303
+ * Sets the associated ID and object.
304
+ *
305
+ * @param idKey
306
+ * @param valueKey
307
+ * @param record
308
+ */
309
+ onAssociationInputChange(idKey: string, valueKey: string, record: any = {}) {
310
+ this.setState((state) => ({
311
+ item: {
312
+ ...state.item,
313
+ [idKey]: record.id || '',
314
+ [valueKey]: record || {}
315
+ },
316
+ validationErrors: _.omit(state.validationErrors, idKey)
317
+ }));
318
+ }
319
+
320
+ /**
321
+ * Toggles the checkbox value for the passed key.
322
+ *
323
+ * @param key
324
+ */
325
+ onCheckboxInputChange(key: string) {
326
+ this.setState((state) => ({
327
+ item: {
328
+ ...state.item,
329
+ [key]: !state.item[key]
330
+ }
331
+ }));
332
+ }
333
+
334
+ /**
335
+ * Sets the passed properties on the item on the state.
336
+ *
337
+ * @param props
338
+ */
339
+ onSetState(props: any) {
340
+ this.setState((state) => ({
341
+ item: {
342
+ ...state.item,
343
+ ...props
344
+ }
345
+ }));
346
+ }
347
+
348
+ /**
349
+ * Sets the text input property for the passed key/value.
350
+ *
351
+ * @param key
352
+ * @param e
353
+ * @param value
354
+ */
355
+ onTextInputChange(key: string, e: Event, { value }: any) {
356
+ this.setState((state) => ({
357
+ item: {
358
+ ...state.item,
359
+ [key]: value
360
+ },
361
+ validationErrors: _.omit(state.validationErrors, key)
362
+ }));
363
+ }
364
+
365
+ render() {
366
+ return (
367
+ <WrappedComponent
368
+ {...this.props}
369
+ dirty={!!(this.state.item.id && !isEqual(this.state.item, this.state.originalItem))}
370
+ errors={_.values(this.state.validationErrors)}
371
+ isError={this.isError.bind(this)}
372
+ isRequired={this.isRequired.bind(this)}
373
+ item={this.state.item}
374
+ loading={this.state.loading}
375
+ onAssociationInputChange={this.onAssociationInputChange.bind(this)}
376
+ onCheckboxInputChange={this.onCheckboxInputChange.bind(this)}
377
+ onDeleteChildAssociation={this.onDeleteChildAssociation.bind(this)}
378
+ onMultiAddChildAssociations={this.onMultiAddChildAssociations.bind(this)}
379
+ onReset={this.onReset.bind(this)}
380
+ onSave={this.onSave.bind(this)}
381
+ onSaveChildAssociation={this.onSaveChildAssociation.bind(this)}
382
+ onTextInputChange={this.onTextInputChange.bind(this)}
383
+ onSetState={this.onSetState.bind(this)}
384
+ saving={this.state.saving}
385
+ />
386
+ );
387
+ }
388
+
389
+ /**
390
+ * Validates the form using the component props required attributes.
391
+ *
392
+ * @returns {boolean}
393
+ */
394
+ validateForm() {
395
+ const validationErrors = [];
396
+
397
+ // Custom validations
398
+ if (this.props.validate) {
399
+ _.extend(validationErrors, this.props.validate(this.state.item));
400
+ }
401
+
402
+ // Validate required properties
403
+ const required = this.props.required || [];
404
+
405
+ _.each(required, (key) => {
406
+ const value = this.state.item[key];
407
+
408
+ let invalid;
409
+
410
+ if (_.isNumber(value)) {
411
+ invalid = _.isEmpty(value.toString());
412
+ } else {
413
+ invalid = _.isEmpty(value);
414
+ }
415
+
416
+ if (invalid) {
417
+ _.extend(validationErrors, { [key]: i18n.t('EditContainer.errors.required', { key }) });
418
+ }
419
+ });
420
+
421
+ this.setState({ validationErrors });
422
+
423
+ return _.keys(validationErrors).length === 0;
424
+ }
425
+ }
426
+ );
427
+
428
+ export default useEditContainer;
429
+
430
+ export type EditContainerProps = {
431
+ children: Element<any>,
432
+ dirty: boolean,
433
+ errors: Array<string>,
434
+ isError: (property: string) => boolean,
435
+ isRequired: (property: string) => boolean,
436
+ item: any,
437
+ loading: boolean,
438
+ onAssociationInputChange: (idKey: string, valueKey: string, item: any) => void,
439
+ onCheckboxInputChange: (key: string, value: any) => void,
440
+ onDeleteChildAssociation: (association: string, child: any) => void,
441
+ onMultiAddChildAssociations: (association: string, Array<any>) => void,
442
+ onReset: () => void,
443
+ onSave: () => void,
444
+ onSaveChildAssociation: (association: string, child: any) => void,
445
+ onSetState: (any) => void,
446
+ onTextInputChange: (key: string, e: ?Event, value: any) => void,
447
+ saving: boolean
448
+ };
File without changes
@@ -0,0 +1,118 @@
1
+ // @flow
2
+
3
+ import React, {
4
+ useCallback,
5
+ useEffect,
6
+ useState,
7
+ type ComponentType
8
+ } from 'react';
9
+ import GoogleAnalytics from 'react-ga4';
10
+
11
+ type Props = {
12
+ id: string,
13
+ location?: any,
14
+ storageKey: string,
15
+ };
16
+
17
+ /**
18
+ * Status constants.
19
+ *
20
+ * @type {{rejected: string, notSet: string, accepted: string}}
21
+ */
22
+ const Status = {
23
+ accepted: 'accepted',
24
+ notSet: 'not_set',
25
+ rejected: 'rejected'
26
+ };
27
+
28
+ /**
29
+ * Higher order component function used to wrap a "cookie consent" banner with Google Analytics.
30
+ *
31
+ * @param BannerComponent
32
+ *
33
+ * @returns {function(Props)}
34
+ */
35
+ const withGoogleAnalytics = (BannerComponent: ComponentType<any>) => (props: Props) => {
36
+ const [status, setStatus] = useState();
37
+
38
+ /**
39
+ * Returns the Google Analytics consent value from local storage.
40
+ *
41
+ * @param storageKey
42
+ *
43
+ * @returns {string|string}
44
+ */
45
+ const getGoogleAnalyticsConsent = (storageKey) => (
46
+ localStorage.getItem(storageKey) || Status.notSet
47
+ );
48
+
49
+ /**
50
+ * Sets the Google Analytics consent value in local storage.
51
+ *
52
+ * @param storageKey
53
+ * @param newStatus
54
+ */
55
+ const setGoogleAnalyticsConsent = (storageKey: string, newStatus: string) => (
56
+ localStorage.setItem(storageKey, newStatus)
57
+ );
58
+
59
+ /**
60
+ * Sets the "accepted" status and initializes analytics.
61
+ *
62
+ * @type {(function(): void)|*}
63
+ */
64
+ const onAccept = useCallback(() => setStatus(Status.accepted), []);
65
+
66
+ /**
67
+ * Sets the "rejected" status.
68
+ *
69
+ * @type {(function(): void)|*}
70
+ */
71
+ const onDecline = useCallback(() => setStatus(Status.rejected), []);
72
+
73
+ /**
74
+ * Sets the initial status on the state from local storage.
75
+ */
76
+ useEffect(() => {
77
+ setStatus(getGoogleAnalyticsConsent(props.storageKey));
78
+ }, [props.storageKey]);
79
+
80
+ /**
81
+ * Sets the status on local storage when the state changes.
82
+ */
83
+ useEffect(() => {
84
+ setGoogleAnalyticsConsent(props.storageKey, status || '');
85
+ }, [status, props.storageKey]);
86
+
87
+ /**
88
+ * Sends the page view event if the location changes.
89
+ */
90
+ useEffect(() => {
91
+ if (props.location && status === Status.accepted) {
92
+ GoogleAnalytics.send('pageview');
93
+ }
94
+ }, [status, props.location]);
95
+
96
+ /**
97
+ * Initializes GoogleAnalytics when the status is set to accepted.
98
+ */
99
+ useEffect(() => {
100
+ if (status === Status.accepted) {
101
+ GoogleAnalytics.initialize(props.id);
102
+ }
103
+ }, [status]);
104
+
105
+ if (!props.id || !props.storageKey || status !== Status.notSet) {
106
+ return null;
107
+ }
108
+
109
+ return (
110
+ <BannerComponent
111
+ {...props}
112
+ onAccept={onAccept}
113
+ onDecline={onDecline}
114
+ />
115
+ );
116
+ };
117
+
118
+ export default withGoogleAnalytics;
@@ -0,0 +1,5 @@
1
+ // @flow
2
+
3
+ import { LoadScript } from '@react-google-maps/api';
4
+
5
+ export default LoadScript;
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,120 @@
1
+ // @flow
2
+
3
+ import React, {
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ type Element
8
+ } from 'react';
9
+ import { isBrowser } from '../utils/Browser';
10
+
11
+ type Props = {
12
+ children: Element<any>,
13
+ context?: { current: HTMLElement },
14
+ offset: number,
15
+ onBottomReached: () => void
16
+ };
17
+
18
+ const InfiniteScroll = (props: Props) => {
19
+ const [height, setHeight] = useState(0);
20
+ const containerRef = useRef();
21
+
22
+ /**
23
+ * Returns the scrolling element.
24
+ *
25
+ * @returns {*}
26
+ */
27
+ const getScrollElement = () => {
28
+ let scrollElement;
29
+
30
+ if (props.context) {
31
+ scrollElement = props.context.current;
32
+ } else if (isBrowser()) {
33
+ scrollElement = document.documentElement;
34
+ }
35
+
36
+ return scrollElement;
37
+ };
38
+
39
+ /**
40
+ * Calls the onBottomReached prop if the scroll has reached the end.
41
+ */
42
+ const onScroll = () => {
43
+ const element = getScrollElement();
44
+
45
+ if (element) {
46
+ const { scrollTop, clientHeight, scrollHeight } = element;
47
+
48
+ if ((scrollTop + clientHeight) >= (scrollHeight - props.offset)) {
49
+ props.onBottomReached();
50
+ }
51
+ }
52
+ };
53
+
54
+ /**
55
+ * Sets up the container scroll event listeners.
56
+ */
57
+ useEffect(() => {
58
+ let scrollContainer;
59
+
60
+ if (props.context) {
61
+ scrollContainer = props.context.current;
62
+ } else if (isBrowser()) {
63
+ scrollContainer = window;
64
+ }
65
+
66
+ if (!scrollContainer) {
67
+ return undefined;
68
+ }
69
+
70
+ scrollContainer.addEventListener('scroll', onScroll);
71
+ return () => scrollContainer && scrollContainer.removeEventListener('scroll', onScroll);
72
+ }, [props.context]);
73
+
74
+ /**
75
+ * Returns true if the context element is scrollable.
76
+ *
77
+ * @returns {boolean}
78
+ */
79
+ const isScrollable = () => {
80
+ let scrollable = false;
81
+
82
+ const element = getScrollElement();
83
+ if (element) {
84
+ const { clientHeight, scrollHeight } = element;
85
+ scrollable = scrollHeight > clientHeight;
86
+ }
87
+
88
+ return scrollable;
89
+ };
90
+
91
+ /**
92
+ * Upon initial render, the DOM may not be tall enough to scroll and trigger the onScroll event. In this case,
93
+ * we'll call the onBottomReached prop when the component is mounted until the container's scrollHeight is greater
94
+ * than the height of the container.
95
+ */
96
+ useEffect(() => {
97
+ if (!isScrollable() && containerRef && containerRef.current) {
98
+ const { clientHeight } = containerRef.current;
99
+
100
+ if (clientHeight > height) {
101
+ setHeight(clientHeight);
102
+ props.onBottomReached();
103
+ }
104
+ }
105
+ });
106
+
107
+ return (
108
+ <div
109
+ ref={containerRef}
110
+ >
111
+ { props.children }
112
+ </div>
113
+ );
114
+ };
115
+
116
+ InfiniteScroll.defaultProps = {
117
+ offset: 0
118
+ };
119
+
120
+ export default InfiniteScroll;
@@ -0,0 +1,11 @@
1
+ /*!
2
+ *
3
+ * simple-keyboard v3.0.22
4
+ * https://github.com/hodgef/simple-keyboard
5
+ *
6
+ * Copyright (c) Francisco Hodge (https://github.com/hodgef) and project contributors.
7
+ *
8
+ * This source code is licensed under the MIT license found in the
9
+ * LICENSE file in the root directory of this source tree.
10
+ *
11
+ */.hg-theme-default{width:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;box-sizing:border-box;overflow:hidden;touch-action:manipulation;font-family:HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica,Arial,Lucida Grande,sans-serif;background-color:#ececec;padding:5px;border-radius:5px}.hg-theme-default .hg-button span{pointer-events:none}.hg-theme-default button.hg-button{border-width:0;outline:0;font-size:inherit}.hg-theme-default .hg-button{display:inline-block;flex-grow:1}.hg-theme-default .hg-row{display:flex}.hg-theme-default .hg-row:not(:last-child){margin-bottom:5px}.hg-theme-default .hg-row .hg-button-container,.hg-theme-default .hg-row .hg-button:not(:last-child){margin-right:5px}.hg-theme-default .hg-row>div:last-child{margin-right:0}.hg-theme-default .hg-row .hg-button-container{display:flex}.hg-theme-default .hg-button{box-shadow:0 0 3px -1px rgba(0,0,0,.3);height:40px;border-radius:5px;box-sizing:border-box;padding:5px;background:#fff;border-bottom:1px solid #b5b5b5;cursor:pointer;display:flex;align-items:center;justify-content:center;-webkit-tap-highlight-color:rgba(0,0,0,0)}.hg-theme-default .hg-button.hg-standardBtn{width:20px}.hg-theme-default .hg-button.hg-activeButton{background:#efefef}.hg-theme-default.hg-layout-numeric .hg-button{width:33.3%;height:60px;align-items:center;display:flex;justify-content:center}.hg-theme-default .hg-button.hg-button-numpadadd,.hg-theme-default .hg-button.hg-button-numpadenter{height:85px}.hg-theme-default .hg-button.hg-button-numpad0{width:105px}.hg-theme-default .hg-button.hg-button-com{max-width:85px}.hg-theme-default .hg-button.hg-standardBtn.hg-button-at{max-width:45px}.hg-theme-default .hg-button.hg-selectedButton{background:rgba(5,25,70,.53);color:#fff}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn=".com"]{max-width:82px}.hg-theme-default .hg-button.hg-standardBtn[data-skbtn="@"]{max-width:60px}.hg-candidate-box{display:inline-flex;border-radius:5px;position:absolute;background:#ececec;border-bottom:2px solid #b5b5b5;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-width:272px;transform:translateY(-100%);margin-top:-10px}ul.hg-candidate-box-list{display:flex;list-style:none;padding:0;margin:0;flex:1}li.hg-candidate-box-list-item{height:40px;width:40px;display:flex;align-items:center;justify-content:center}li.hg-candidate-box-list-item:hover{background:rgba(0,0,0,.03);cursor:pointer}li.hg-candidate-box-list-item:active{background:rgba(0,0,0,.1)}.hg-candidate-box-prev:before{content:"◄"}.hg-candidate-box-next:before{content:"►"}.hg-candidate-box-next,.hg-candidate-box-prev{display:flex;align-items:center;padding:0 10px;background:#d0d0d0;color:#969696;cursor:pointer}.hg-candidate-box-next{border-top-right-radius:5px;border-bottom-right-radius:5px}.hg-candidate-box-prev{border-top-left-radius:5px;border-bottom-left-radius:5px}.hg-candidate-box-btn-active{color:#444}