@mackin.com/styleguide 8.0.0-beta.7 → 8.0.0

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 (3) hide show
  1. package/index.d.ts +159 -92
  2. package/index.js +1209 -893
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
- var css = require('@emotion/css');
4
- var lodash = require('lodash');
5
3
  var React = require('react');
6
- var nanoid = require('nanoid');
4
+ var css = require('@emotion/css');
7
5
  var proRegularSvgIcons = require('@fortawesome/pro-regular-svg-icons');
8
6
  var proSolidSvgIcons = require('@fortawesome/pro-solid-svg-icons');
9
7
  var proLightSvgIcons = require('@fortawesome/pro-light-svg-icons');
10
8
  var reactFontawesome = require('@fortawesome/react-fontawesome');
9
+ var lodash = require('lodash');
11
10
  var dateFns = require('date-fns');
11
+ var nanoid = require('nanoid');
12
12
  var reactDom = require('react-dom');
13
13
  var reactTinyPopover = require('react-tiny-popover');
14
14
  var reactRouterDom = require('react-router-dom');
@@ -17,29 +17,97 @@ var ReactSlider = require('react-slider');
17
17
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
18
18
 
19
19
  function _interopNamespace(e) {
20
- if (e && e.__esModule) return e;
21
- var n = Object.create(null);
22
- if (e) {
23
- Object.keys(e).forEach(function (k) {
24
- if (k !== 'default') {
25
- var d = Object.getOwnPropertyDescriptor(e, k);
26
- Object.defineProperty(n, k, d.get ? d : {
27
- enumerable: true,
28
- get: function () {
29
- return e[k];
30
- }
31
- });
32
- }
33
- });
34
- }
35
- n['default'] = e;
36
- return Object.freeze(n);
20
+ if (e && e.__esModule) return e;
21
+ var n = Object.create(null);
22
+ if (e) {
23
+ Object.keys(e).forEach(function (k) {
24
+ if (k !== 'default') {
25
+ var d = Object.getOwnPropertyDescriptor(e, k);
26
+ Object.defineProperty(n, k, d.get ? d : {
27
+ enumerable: true,
28
+ get: function () {
29
+ return e[k];
30
+ }
31
+ });
32
+ }
33
+ });
34
+ }
35
+ n['default'] = e;
36
+ return Object.freeze(n);
37
37
  }
38
38
 
39
- var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
40
39
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
40
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
41
41
  var ReactSlider__default = /*#__PURE__*/_interopDefaultLegacy(ReactSlider);
42
42
 
43
+ /*! *****************************************************************************
44
+ Copyright (c) Microsoft Corporation.
45
+
46
+ Permission to use, copy, modify, and/or distribute this software for any
47
+ purpose with or without fee is hereby granted.
48
+
49
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
50
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
51
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
52
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
53
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
54
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
55
+ PERFORMANCE OF THIS SOFTWARE.
56
+ ***************************************************************************** */
57
+
58
+ function __rest(s, e) {
59
+ var t = {};
60
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
61
+ t[p] = s[p];
62
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
63
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
64
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
65
+ t[p[i]] = s[p[i]];
66
+ }
67
+ return t;
68
+ }
69
+
70
+ const ICONS = {
71
+ add: proSolidSvgIcons.faPlus,
72
+ delete: proSolidSvgIcons.faTrashAlt,
73
+ save: proSolidSvgIcons.faSave,
74
+ activate: proRegularSvgIcons.faCheckCircle,
75
+ deactivate: proRegularSvgIcons.faCircle,
76
+ online: proLightSvgIcons.faWifi,
77
+ offline: proLightSvgIcons.faWifiSlash,
78
+ noIcon: proSolidSvgIcons.faCrow,
79
+ close: proSolidSvgIcons.faTimes,
80
+ waiting: proSolidSvgIcons.faSync,
81
+ refresh: proSolidSvgIcons.faSync,
82
+ menu: proLightSvgIcons.faBars,
83
+ search: proLightSvgIcons.faSearch,
84
+ expand: proRegularSvgIcons.faChevronDown,
85
+ collapse: proRegularSvgIcons.faChevronUp,
86
+ help: proLightSvgIcons.faQuestionCircle,
87
+ debug: proLightSvgIcons.faNarwhal,
88
+ goTo: proLightSvgIcons.faChevronRight,
89
+ goBack: proLightSvgIcons.faChevronLeft,
90
+ download: proLightSvgIcons.faCloudDownload,
91
+ upload: proLightSvgIcons.faCloudUpload,
92
+ selected: proSolidSvgIcons.faCheckSquare,
93
+ unselected: proRegularSvgIcons.faSquare,
94
+ pagerLeft: proLightSvgIcons.faChevronLeft,
95
+ pagerRight: proLightSvgIcons.faChevronRight,
96
+ sortAsc: proRegularSvgIcons.faChevronUp,
97
+ sortDesc: proRegularSvgIcons.faChevronDown,
98
+ pickDate: proLightSvgIcons.faCalendarAlt,
99
+ copy: proLightSvgIcons.faCopy,
100
+ paste: proLightSvgIcons.faPaste,
101
+ clear: proRegularSvgIcons.faTimesCircle,
102
+ hide: proLightSvgIcons.faEyeSlash,
103
+ show: proLightSvgIcons.faEye
104
+ };
105
+ const Icon = (props) => {
106
+ var _a;
107
+ const icon = (_a = ICONS[props.id]) !== null && _a !== void 0 ? _a : ICONS['noIcon'];
108
+ return React__namespace.createElement(reactFontawesome.FontAwesomeIcon, { style: props.style, onClick: props.onClick, spin: props.spin, className: css.cx('icon', props.className), icon: icon });
109
+ };
110
+
43
111
  /** Call this on your theme after messing with the props. It will re-build things that depend on sizes and colors. */
44
112
  const calcDynamicThemeProps = (theme) => {
45
113
  theme.controls.border = `${theme.controls.borderWidth} solid ${theme.colors.border}`;
@@ -47,6 +115,9 @@ const calcDynamicThemeProps = (theme) => {
47
115
  theme.controls.focusOutlineShadow = `0px 0px 4px 2px ${theme.colors.focusOutline}`;
48
116
  theme.controls.focusOutlineRequiredShadow = `0px 0px 4px 2px ${theme.colors.focusOutlineRequired}`;
49
117
  theme.controls.dividerBorder = `2px solid ${theme.colors.divider}`;
118
+ theme.controls.inputErrorMinHeight = `calc(${theme.fonts.sizeSmall} * 1.5 + 4px)`;
119
+ theme.mediaQueries.desktop = `@media(min-width:${theme.breakpoints.desktop})`;
120
+ theme.mediaQueries.tablet = `@media(min-width:${theme.breakpoints.tablet})`;
50
121
  };
51
122
  const defaultTheme = {
52
123
  colors: {
@@ -76,7 +147,7 @@ const defaultTheme = {
76
147
  divider: 'rgba(0, 0, 0, 0.50)',
77
148
  nav: '#7851a9',
78
149
  navFont: 'rgba(255, 255, 255, 0.9)',
79
- focusOutline: 'hsl(155deg 67% 85%)',
150
+ focusOutline: 'rgb(0 188 212 / 75%)',
80
151
  progressBg: '#007bff63',
81
152
  progressFill: '#007bff',
82
153
  modalBg: 'white',
@@ -115,7 +186,8 @@ const defaultTheme = {
115
186
  gap: '1rem',
116
187
  dividerMargin: '1rem',
117
188
  dividerBorder: '',
118
- headerBoxShadow: '0px 2px 12px 6px rgba(0, 0, 0, 0.2)'
189
+ headerBoxShadow: '0px 2px 12px 6px rgba(0, 0, 0, 0.2)',
190
+ inputErrorMinHeight: ''
119
191
  },
120
192
  zIndexes: {
121
193
  header: 50,
@@ -133,6 +205,10 @@ const defaultTheme = {
133
205
  breakpoints: {
134
206
  desktop: '800px',
135
207
  tablet: '768px'
208
+ },
209
+ mediaQueries: {
210
+ desktop: '',
211
+ tablet: ''
136
212
  }
137
213
  };
138
214
  calcDynamicThemeProps(defaultTheme);
@@ -153,193 +229,9 @@ const useThemeSafely = () => {
153
229
  return defaultTheme;
154
230
  };
155
231
 
156
- /** @deprecated Use Backdrop2 going forward. */
157
- const Backdrop$1 = (props) => {
158
- var _a;
159
- const showTimeMs = (_a = props.showTimeMs) !== null && _a !== void 0 ? _a : 250;
160
- const backdropId = React__namespace.useRef('Backdrop' + nanoid.nanoid());
161
- const theme = useThemeSafely();
162
- const backdropStyles = css.css `
163
- opacity: 0;
164
- position: fixed;
165
- top: 0;
166
- left: 0;
167
- right: 0;
168
- bottom: 0;
169
- background-color: ${theme.colors.backdrop};
170
- transition: opacity ${showTimeMs}ms ease-in-out;
171
- visibility: hidden;
172
- user-select: none;
173
- -webkit-tap-highlight-color: transparent;
174
- `;
175
- const showStyles = css.css `
176
- z-index: ${theme.zIndexes.backdrop} !important;
177
- opacity: 1.0 !important;
178
- label:${backdropId.current};
179
- `;
180
- const bodyStyles = css.css `
181
- overflow: hidden !important;
182
- label:${backdropId.current};
183
- `;
184
- const bodyResponsiveStyles = css.css `
185
- label:${backdropId.current};
186
- @media(min-width:${theme.breakpoints.desktop}) {
187
- overflow: auto !important;
188
- }
189
- `;
190
- const bodyReverseResponsiveStyles = css.css `
191
- ${bodyStyles}
192
- overflow: auto !important;
193
- @media(min-width:${theme.breakpoints.desktop}) {
194
- overflow: hidden !important;
195
- }
196
- `;
197
- const styles = css.css `
198
- ${backdropStyles}
199
- ${props.onClick && `
200
- cursor: pointer;
201
- `}
202
- ${props.transparent && `
203
- background-color: transparent;
204
- transition: none;
205
- `}
206
- ${props.children && `
207
- display: flex;
208
- justify-content:center;
209
- align-items: center;
210
- `}
211
- ${props.responsive && `
212
- @media(min-width:${theme.breakpoints.desktop}) {
213
- display: none;
214
- }
215
- `}
216
- ${props.reverseResponsive && `
217
- display: none;
218
- @media(min-width:${theme.breakpoints.desktop}) {
219
- display: flex;
220
- }
221
- `}
222
- `;
223
- const backdrop = React__namespace.useRef(null);
224
- React__namespace.useEffect(() => {
225
- if (backdrop && backdrop.current) {
226
- if (props.show && backdrop.current.style.visibility !== 'visible') {
227
- backdrop.current.style.visibility = 'visible';
228
- backdrop.current.classList.add(showStyles);
229
- if (!props.allowScroll) {
230
- document.body.classList.add(bodyStyles);
231
- if (props.responsive) {
232
- document.body.classList.add(bodyResponsiveStyles);
233
- }
234
- else if (props.reverseResponsive) {
235
- document.body.classList.add(bodyReverseResponsiveStyles);
236
- }
237
- }
238
- }
239
- else if (!props.show && backdrop.current.style.visibility === 'visible') {
240
- backdrop.current.classList.remove(showStyles);
241
- if (backdrop && backdrop.current) {
242
- backdrop.current.style.visibility = 'hidden';
243
- if (!props.allowScroll) {
244
- document.body.classList.remove(bodyStyles);
245
- if (props.responsive) {
246
- document.body.classList.remove(bodyResponsiveStyles);
247
- }
248
- else if (props.reverseResponsive) {
249
- document.body.classList.remove(bodyReverseResponsiveStyles);
250
- }
251
- }
252
- }
253
- }
254
- }
255
- return () => {
256
- if (backdrop && backdrop.current && !props.allowScroll) {
257
- document.body.classList.remove(bodyStyles);
258
- }
259
- };
260
- }, [props.show]);
261
- return (React__namespace.createElement("div", { onMouseDown: e => {
262
- var _a;
263
- e.stopPropagation();
264
- e.preventDefault();
265
- (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
266
- }, onClick: e => {
267
- e.stopPropagation();
268
- e.preventDefault();
269
- }, ref: backdrop, className: css.cx('backdrop', styles, props.className) }, props.children));
270
- };
271
-
272
- /*! *****************************************************************************
273
- Copyright (c) Microsoft Corporation.
274
-
275
- Permission to use, copy, modify, and/or distribute this software for any
276
- purpose with or without fee is hereby granted.
277
-
278
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
279
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
280
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
281
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
282
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
283
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
284
- PERFORMANCE OF THIS SOFTWARE.
285
- ***************************************************************************** */
286
-
287
- function __rest(s, e) {
288
- var t = {};
289
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
290
- t[p] = s[p];
291
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
292
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
293
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
294
- t[p[i]] = s[p[i]];
295
- }
296
- return t;
297
- }
298
-
299
- const ICONS = {
300
- add: proSolidSvgIcons.faPlus,
301
- delete: proSolidSvgIcons.faTrashAlt,
302
- save: proSolidSvgIcons.faSave,
303
- activate: proRegularSvgIcons.faCheckCircle,
304
- deactivate: proRegularSvgIcons.faCircle,
305
- online: proLightSvgIcons.faWifi,
306
- offline: proLightSvgIcons.faWifiSlash,
307
- noIcon: proSolidSvgIcons.faCrow,
308
- close: proSolidSvgIcons.faTimes,
309
- waiting: proSolidSvgIcons.faSync,
310
- refresh: proSolidSvgIcons.faSync,
311
- menu: proLightSvgIcons.faBars,
312
- search: proLightSvgIcons.faSearch,
313
- expand: proRegularSvgIcons.faChevronDown,
314
- collapse: proRegularSvgIcons.faChevronUp,
315
- help: proLightSvgIcons.faQuestionCircle,
316
- debug: proLightSvgIcons.faNarwhal,
317
- goTo: proLightSvgIcons.faChevronRight,
318
- goBack: proLightSvgIcons.faChevronLeft,
319
- download: proLightSvgIcons.faCloudDownload,
320
- upload: proLightSvgIcons.faCloudUpload,
321
- selected: proSolidSvgIcons.faCheckSquare,
322
- unselected: proRegularSvgIcons.faSquare,
323
- pagerLeft: proLightSvgIcons.faChevronLeft,
324
- pagerRight: proLightSvgIcons.faChevronRight,
325
- sortAsc: proRegularSvgIcons.faChevronUp,
326
- sortDesc: proRegularSvgIcons.faChevronDown,
327
- pickDate: proLightSvgIcons.faCalendarAlt,
328
- copy: proLightSvgIcons.faCopy,
329
- paste: proLightSvgIcons.faPaste,
330
- clear: proRegularSvgIcons.faTimesCircle,
331
- hide: proLightSvgIcons.faEyeSlash,
332
- show: proLightSvgIcons.faEye
333
- };
334
- const Icon = (props) => {
232
+ const Button = React__namespace.forwardRef((props, ref) => {
335
233
  var _a;
336
- const icon = (_a = ICONS[props.id]) !== null && _a !== void 0 ? _a : ICONS['noIcon'];
337
- return React__namespace.createElement(reactFontawesome.FontAwesomeIcon, { style: props.style, onClick: props.onClick, spin: props.spin, className: css.cx('icon', props.className), icon: icon });
338
- };
339
-
340
- const Button = (props) => {
341
- var _a, _b;
342
- const nativeProps = __rest(props, ["variant", "textAlign", "block", "round", "rightIcon", "leftIcon", "iconBlock", "small", "readonly", "waiting", "enforceMinWidth"]);
234
+ const nativeProps = __rest(props, ["variant", "round", "rightIcon", "leftIcon", "iconBlock", "small", "readOnly", "waiting", "enforceMinWidth"]);
343
235
  const theme = useThemeSafely();
344
236
  const buttonStyles = css.css `
345
237
  padding-left: ${theme.controls.padding};
@@ -356,7 +248,6 @@ const Button = (props) => {
356
248
  font-weight: bold;
357
249
  flex-shrink: 0;
358
250
  min-width: ${theme.controls.height};
359
- text-align: ${(_a = props.textAlign) !== null && _a !== void 0 ? _a : 'center'};
360
251
 
361
252
  &:disabled {
362
253
  opacity: ${theme.controls.disabledOpacity};
@@ -475,7 +366,7 @@ const Button = (props) => {
475
366
  ${props.enforceMinWidth && `
476
367
  min-width: ${theme.controls.formButtonMinWidth};
477
368
  `}
478
- ${props.readonly && `
369
+ ${props.readOnly && `
479
370
  cursor: default;
480
371
  box-shadow: none;
481
372
  pointer-events:none;
@@ -495,9 +386,6 @@ const Button = (props) => {
495
386
  ${props.round && `
496
387
  border-radius: ${theme.controls.roundRadius};
497
388
  `}
498
- ${props.block && `
499
- width: 100%;
500
- `}
501
389
  ${props.iconBlock && `
502
390
  display: flex;
503
391
  justify-content: space-between;
@@ -505,10 +393,94 @@ const Button = (props) => {
505
393
  `}
506
394
  `;
507
395
  const disabled = props.disabled || props.waiting;
508
- return (React__namespace.createElement("button", Object.assign({}, nativeProps, { disabled: disabled, className: css.cx('button', styles, props.className), type: (_b = props.type) !== null && _b !== void 0 ? _b : 'button' }),
396
+ return (React__namespace.createElement("button", Object.assign({}, nativeProps, { ref: ref, disabled: disabled, className: css.cx('button', styles, props.className), type: (_a = props.type) !== null && _a !== void 0 ? _a : 'button' }),
509
397
  props.leftIcon && React__namespace.createElement("span", { className: css.css({ marginRight: '0.5rem' }) }, props.leftIcon),
510
398
  props.waiting ? React__namespace.createElement(Icon, { id: "waiting", spin: true }) : props.children,
511
399
  props.rightIcon && React__namespace.createElement("span", { className: css.css({ marginLeft: '0.5rem' }) }, props.rightIcon)));
400
+ });
401
+
402
+ const accordianExpandTimeMs = 250;
403
+ const accordianMaxHeight = 1020;
404
+ const accordianTimingFunction = 'ease-in-out';
405
+ // we need to apply the seperately so stuff doesn't hang over during expand/collapse.
406
+ // if we remove this and just keep overflow:hidden, autocompletes will get cut off in the subpanels.
407
+ const visibleStyle = css.css({
408
+ overflow: 'visible !important'
409
+ });
410
+ const Accordian = (props) => {
411
+ var _a, _b, _c, _d;
412
+ const [open, setOpen] = React__namespace.useState(false);
413
+ const theme = useThemeSafely();
414
+ const content = React__namespace.useRef(null);
415
+ const contentStyles = css.css({
416
+ overflow: 'hidden',
417
+ maxHeight: 0,
418
+ transition: `max-height ${(_a = props.expandTimeMs) !== null && _a !== void 0 ? _a : accordianExpandTimeMs}ms ${(_b = props.transitionTimingFunction) !== null && _b !== void 0 ? _b : accordianTimingFunction}`
419
+ });
420
+ const expandedContentStyles = css.css({
421
+ maxHeight: (_c = props.maxHeight) !== null && _c !== void 0 ? _c : accordianMaxHeight
422
+ });
423
+ const expandedContentWrapperStyles = !props.noPad ? css.css({
424
+ padding: '0 1rem 1rem 1rem'
425
+ }) : undefined;
426
+ React__namespace.useEffect(() => {
427
+ const currentContent = content.current;
428
+ if (currentContent) {
429
+ if (open) {
430
+ currentContent.classList.add(expandedContentStyles);
431
+ window.setTimeout(() => {
432
+ currentContent.classList.add(visibleStyle);
433
+ }, accordianExpandTimeMs);
434
+ }
435
+ else {
436
+ currentContent.classList.remove(visibleStyle, expandedContentStyles);
437
+ }
438
+ }
439
+ }, [open]);
440
+ React__namespace.useEffect(() => {
441
+ var _a;
442
+ if (props.open === undefined) {
443
+ // technically, we only need to use this effect if props.open was initialized with a boolean.
444
+ // you can't have conditional effects so here we go...
445
+ return;
446
+ }
447
+ setOpen((_a = props.open) !== null && _a !== void 0 ? _a : false);
448
+ }, [props.open]);
449
+ return (React__namespace.createElement("div", { className: "accordian" },
450
+ React__namespace.createElement(Button, { readOnly: props.disabled, variant: props.variant, className: css.cx(css.css({
451
+ display: 'flex',
452
+ alignItems: 'center',
453
+ justifyContent: 'space-between',
454
+ height: 'auto',
455
+ minHeight: theme.controls.height,
456
+ width: ((_d = props.block) !== null && _d !== void 0 ? _d : true) ? '100%' : 'auto'
457
+ }, props.className)), onClick: e => {
458
+ e.stopPropagation();
459
+ if (props.onChange) {
460
+ props.onChange(!open);
461
+ }
462
+ else {
463
+ setOpen(!open);
464
+ }
465
+ }, rightIcon: !props.disabled ? React__namespace.createElement(Icon, { id: open ? 'collapse' : 'expand' }) : undefined },
466
+ React__namespace.createElement("span", null, props.header)),
467
+ React__namespace.createElement("div", { ref: content, className: css.cx('accordian__body', contentStyles) },
468
+ React__namespace.createElement("div", { className: expandedContentWrapperStyles }, props.children))));
469
+ };
470
+ const useAccordianState = (count, openIndex) => {
471
+ const [panels, setShowPanel] = React__namespace.useState(new Array(count).fill(false).map((b, i) => {
472
+ return i === openIndex;
473
+ }));
474
+ return [
475
+ panels,
476
+ (index, open) => {
477
+ setShowPanel(previousState => {
478
+ const newState = previousState.slice().fill(false);
479
+ newState[index] = open;
480
+ return newState;
481
+ });
482
+ }
483
+ ];
512
484
  };
513
485
 
514
486
  const DEFAULT_DEBOUNCE_MS = 250;
@@ -852,25 +824,12 @@ const Text = (props) => {
852
824
  }, props.children);
853
825
  };
854
826
 
855
- const DEFAULT_MAX_SHOWN_VALUES = 7;
856
- const getAutocompleteValueText = (v) => {
857
- if (!v) {
858
- return '';
859
- }
860
- if (typeof v === 'string') {
861
- return v;
862
- }
863
- return v.name;
864
- };
865
- const getAutocompleteValueId = (v) => {
866
- if (typeof v === 'string') {
867
- return v;
868
- }
869
- return v.id;
870
- };
871
- //TB: will need to use the new input
827
+ //TB: FUTURE will need to use the new input
828
+ const defaultMaxShownValues = 7;
829
+ const buttonMarkerClass = 'ListItem__button';
872
830
  const Autocomplete = (p) => {
873
831
  var _a;
832
+ const theme = useThemeSafely();
874
833
  const element = React__namespace.useRef(null);
875
834
  const input = React__namespace.useRef(null);
876
835
  const list = React__namespace.useRef(null);
@@ -878,7 +837,7 @@ const Autocomplete = (p) => {
878
837
  const showValues = React__namespace.useMemo(() => values.length > 0, [values]);
879
838
  const shownValues = React__namespace.useMemo(() => {
880
839
  var _a;
881
- return values.slice(0, (_a = p.maxShownValues) !== null && _a !== void 0 ? _a : DEFAULT_MAX_SHOWN_VALUES);
840
+ return values.slice(0, (_a = p.maxShownValues) !== null && _a !== void 0 ? _a : defaultMaxShownValues);
882
841
  }, [values]);
883
842
  const onChangeForOptions = React__namespace.useRef(lodash.debounce((value) => {
884
843
  if (!p.minChars || value.length >= p.minChars) {
@@ -892,45 +851,7 @@ const Autocomplete = (p) => {
892
851
  else {
893
852
  setValues([]);
894
853
  }
895
- }, (_a = p.getOptionsDebounceMs) !== null && _a !== void 0 ? _a : 0, { leading: true, trailing: true }));
896
- const theme = useThemeSafely();
897
- const baseClass = css.css `
898
- label: Autocomplete;
899
- position: relative;
900
- width: 100%;
901
- `;
902
- let listBorderRadius = '';
903
- if (p.round || theme.controls.borderRadius) {
904
- listBorderRadius = theme.controls.borderRadius || '0.5rem';
905
- }
906
- const listClass = css.css({
907
- position: 'absolute',
908
- width: '100%',
909
- border: theme.controls.border,
910
- borderRadius: listBorderRadius,
911
- boxShadow: theme.controls.boxShadow,
912
- backgroundColor: theme.colors.bg,
913
- marginTop: `-4px !important`,
914
- zIndex: theme.zIndexes.backdrop,
915
- 'li:first-child button': {
916
- borderTopRightRadius: listBorderRadius,
917
- borderTopLeftRadius: listBorderRadius,
918
- },
919
- 'li:last-child button': {
920
- borderBottomRightRadius: listBorderRadius,
921
- borderBottomLeftRadius: listBorderRadius,
922
- }
923
- });
924
- const inputClass = css.css `
925
- ${showValues && `
926
- z-index: ${theme.zIndexes.backdrop};
927
- position: relative;
928
- `}
929
- `;
930
- const buttonStyles = css.css({
931
- borderRadius: 0,
932
- });
933
- const buttonMarkerClass = 'ListItem__button';
854
+ }, (_a = p.getOptionsDebounceMs) !== null && _a !== void 0 ? _a : 0, { leading: false, trailing: true }));
934
855
  const getNextTabElement = (fromIndex, direction) => {
935
856
  var _a, _b, _c;
936
857
  if (fromIndex === -1) {
@@ -950,15 +871,41 @@ const Autocomplete = (p) => {
950
871
  }
951
872
  }
952
873
  };
953
- return (React__namespace.createElement("div", { ref: element, className: css.cx(baseClass, 'autocomplete') },
954
- React__namespace.createElement(Backdrop$1, { onClick: () => setValues([]), show: showValues, allowScroll: true, transparent: true }),
874
+ React__namespace.useEffect(() => {
875
+ const clearItems = () => {
876
+ if (values.length > 0) {
877
+ setValues([]);
878
+ }
879
+ };
880
+ document.addEventListener('click', clearItems);
881
+ return () => {
882
+ document.removeEventListener('click', clearItems);
883
+ };
884
+ }, [values]);
885
+ let listBorderRadius = '';
886
+ if (p.round || theme.controls.borderRadius) {
887
+ listBorderRadius = theme.controls.borderRadius || '0.5rem';
888
+ }
889
+ return (React__namespace.createElement("div", { onClick: e => {
890
+ e.stopPropagation();
891
+ }, onKeyDown: e => {
892
+ var _a;
893
+ if (e.key === 'Escape') {
894
+ setValues([]);
895
+ (_a = input.current) === null || _a === void 0 ? void 0 : _a.focus();
896
+ }
897
+ }, ref: element, className: css.cx(css.css({
898
+ position: 'relative',
899
+ width: '100%',
900
+ label: 'Autocomplete'
901
+ }), 'autocomplete') },
955
902
  React__namespace.createElement(TabLocker, { disabled: !showValues, style: { position: 'relative' } },
956
- React__namespace.createElement(Input, { inputAriaAttributes: p.inputAriaAttributes, ref: input, debounceMs: 0, type: "text", value: getAutocompleteValueText(p.value), round: p.round, rightControl: p.rightControl, placeholder: p.placeholder, id: p.id, disabled: p.disabled, className: p.className, inputClassName: css.cx(inputClass, p.inputClassName), maxLength: p.maxLength, required: p.required, onChange: v => {
903
+ React__namespace.createElement(Input, { inputAriaAttributes: p.inputAriaAttributes, ref: input, debounceMs: 0, type: "text", value: getAutocompleteValueText(p.value), round: p.round, rightControl: p.rightControl, placeholder: p.placeholder, id: p.id, disabled: p.disabled, className: p.className, inputClassName: p.inputClassName, maxLength: p.maxLength, required: p.required, onChange: v => {
957
904
  const value = v;
958
905
  p.onChange(value);
959
906
  onChangeForOptions.current(value);
960
907
  }, onKeyDown: e => {
961
- var _a, _b;
908
+ var _a, _b, _c;
962
909
  if (showValues) {
963
910
  if (e.key === 'ArrowDown') {
964
911
  e.preventDefault();
@@ -971,8 +918,26 @@ const Autocomplete = (p) => {
971
918
  (_b = getNextTabElement(-1, -1)) === null || _b === void 0 ? void 0 : _b.focus();
972
919
  }
973
920
  }
921
+ (_c = p.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(p, e);
974
922
  }, onKeyPress: e => { var _a; return (_a = p.onKeyPress) === null || _a === void 0 ? void 0 : _a.call(p, e); } }),
975
- showValues && (React__namespace.createElement(List, { ref: list, className: listClass },
923
+ showValues && (React__namespace.createElement(List, { ref: list, className: css.css({
924
+ position: 'absolute',
925
+ width: '100%',
926
+ border: theme.controls.border,
927
+ borderRadius: listBorderRadius,
928
+ boxShadow: theme.controls.boxShadow,
929
+ backgroundColor: theme.colors.bg,
930
+ marginTop: `-4px !important`,
931
+ zIndex: theme.zIndexes.backdrop,
932
+ 'li:first-child button': {
933
+ borderTopRightRadius: listBorderRadius,
934
+ borderTopLeftRadius: listBorderRadius,
935
+ },
936
+ 'li:last-child button': {
937
+ borderBottomRightRadius: listBorderRadius,
938
+ borderBottomLeftRadius: listBorderRadius,
939
+ }
940
+ }) },
976
941
  shownValues.map((value, listItemIndex) => {
977
942
  return (React__namespace.createElement(ListItem, { key: getAutocompleteValueId(value), variant: "full" },
978
943
  React__namespace.createElement(Button, { onKeyDown: e => {
@@ -987,7 +952,9 @@ const Autocomplete = (p) => {
987
952
  e.preventDefault();
988
953
  (_b = getNextTabElement(listItemIndex, -1)) === null || _b === void 0 ? void 0 : _b.focus();
989
954
  }
990
- }, className: css.cx(buttonMarkerClass + listItemIndex, buttonStyles), onClick: () => {
955
+ }, className: css.cx(buttonMarkerClass + listItemIndex, css.css({
956
+ borderRadius: 0,
957
+ })), onClick: () => {
991
958
  p.onPick(value);
992
959
  setValues([]);
993
960
  setTimeout(() => {
@@ -1006,68 +973,208 @@ const Autocomplete = (p) => {
1006
973
  " of ",
1007
974
  values.length.toLocaleString(),
1008
975
  " results."))))))));
976
+ };
977
+ const getAutocompleteValueText = (v) => {
978
+ if (!v) {
979
+ return '';
980
+ }
981
+ if (typeof v === 'string') {
982
+ return v;
983
+ }
984
+ return v.name;
985
+ };
986
+ const getAutocompleteValueId = (v) => {
987
+ if (typeof v === 'string') {
988
+ return v;
989
+ }
990
+ return v.id;
991
+ };
992
+
993
+ /** @deprecated Use Backdrop2 going forward. */
994
+ const Backdrop$1 = (props) => {
995
+ var _a;
996
+ const showTimeMs = (_a = props.showTimeMs) !== null && _a !== void 0 ? _a : 250;
997
+ const backdropId = React__namespace.useRef('Backdrop' + nanoid.nanoid());
998
+ const theme = useThemeSafely();
999
+ const backdropStyles = css.css `
1000
+ opacity: 0;
1001
+ position: fixed;
1002
+ top: 0;
1003
+ left: 0;
1004
+ right: 0;
1005
+ bottom: 0;
1006
+ background-color: ${theme.colors.backdrop};
1007
+ transition: opacity ${showTimeMs}ms ease-in-out;
1008
+ visibility: hidden;
1009
+ user-select: none;
1010
+ -webkit-tap-highlight-color: transparent;
1011
+ `;
1012
+ const showStyles = css.css `
1013
+ z-index: ${theme.zIndexes.backdrop} !important;
1014
+ opacity: 1.0 !important;
1015
+ label:${backdropId.current};
1016
+ `;
1017
+ const bodyStyles = css.css `
1018
+ overflow: hidden !important;
1019
+ label:${backdropId.current};
1020
+ `;
1021
+ const bodyResponsiveStyles = css.css `
1022
+ label:${backdropId.current};
1023
+ @media(min-width:${theme.breakpoints.desktop}) {
1024
+ overflow: auto !important;
1025
+ }
1026
+ `;
1027
+ const bodyReverseResponsiveStyles = css.css `
1028
+ ${bodyStyles}
1029
+ overflow: auto !important;
1030
+ @media(min-width:${theme.breakpoints.desktop}) {
1031
+ overflow: hidden !important;
1032
+ }
1033
+ `;
1034
+ const styles = css.css `
1035
+ ${backdropStyles}
1036
+ ${props.onClick && `
1037
+ cursor: pointer;
1038
+ `}
1039
+ ${props.transparent && `
1040
+ background-color: transparent;
1041
+ transition: none;
1042
+ `}
1043
+ ${props.children && `
1044
+ display: flex;
1045
+ justify-content:center;
1046
+ align-items: center;
1047
+ `}
1048
+ ${props.responsive && `
1049
+ @media(min-width:${theme.breakpoints.desktop}) {
1050
+ display: none;
1051
+ }
1052
+ `}
1053
+ ${props.reverseResponsive && `
1054
+ display: none;
1055
+ @media(min-width:${theme.breakpoints.desktop}) {
1056
+ display: flex;
1057
+ }
1058
+ `}
1059
+ `;
1060
+ const backdrop = React__namespace.useRef(null);
1061
+ React__namespace.useEffect(() => {
1062
+ if (backdrop && backdrop.current) {
1063
+ if (props.show && backdrop.current.style.visibility !== 'visible') {
1064
+ backdrop.current.style.visibility = 'visible';
1065
+ backdrop.current.classList.add(showStyles);
1066
+ if (!props.allowScroll) {
1067
+ document.body.classList.add(bodyStyles);
1068
+ if (props.responsive) {
1069
+ document.body.classList.add(bodyResponsiveStyles);
1070
+ }
1071
+ else if (props.reverseResponsive) {
1072
+ document.body.classList.add(bodyReverseResponsiveStyles);
1073
+ }
1074
+ }
1075
+ }
1076
+ else if (!props.show && backdrop.current.style.visibility === 'visible') {
1077
+ backdrop.current.classList.remove(showStyles);
1078
+ if (backdrop && backdrop.current) {
1079
+ backdrop.current.style.visibility = 'hidden';
1080
+ if (!props.allowScroll) {
1081
+ document.body.classList.remove(bodyStyles);
1082
+ if (props.responsive) {
1083
+ document.body.classList.remove(bodyResponsiveStyles);
1084
+ }
1085
+ else if (props.reverseResponsive) {
1086
+ document.body.classList.remove(bodyReverseResponsiveStyles);
1087
+ }
1088
+ }
1089
+ }
1090
+ }
1091
+ }
1092
+ return () => {
1093
+ if (backdrop && backdrop.current && !props.allowScroll) {
1094
+ document.body.classList.remove(bodyStyles);
1095
+ }
1096
+ };
1097
+ }, [props.show]);
1098
+ return (React__namespace.createElement("div", { onMouseDown: e => {
1099
+ var _a;
1100
+ e.stopPropagation();
1101
+ e.preventDefault();
1102
+ (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
1103
+ }, onClick: e => {
1104
+ e.stopPropagation();
1105
+ e.preventDefault();
1106
+ }, ref: backdrop, className: css.cx('backdrop', styles, props.className) }, props.children));
1107
+ };
1108
+
1109
+ /** useEffect but ignores the first call on component mount. */
1110
+ const useIgnoreMount = (effect, deps) => {
1111
+ const mounted = React__default['default'].useRef(false);
1112
+ React__default['default'].useEffect(() => {
1113
+ if (!mounted.current) {
1114
+ mounted.current = true;
1115
+ }
1116
+ else {
1117
+ effect();
1118
+ }
1119
+ }, deps);
1009
1120
  };
1010
1121
 
1011
1122
  const useLogger = (componentName, enabled) => {
1012
1123
  return (...messages) => {
1013
1124
  if (enabled) {
1014
1125
  // tslint:disable-next-line
1015
- console.log(componentName, ...messages);
1126
+ console.log(`[${componentName}]`, ...messages);
1016
1127
  }
1017
1128
  };
1018
1129
  };
1019
1130
 
1131
+ const portalId = 'backdrop';
1020
1132
  const BackdropContext = React__default['default'].createContext({
1021
1133
  showing: false,
1022
1134
  showCount: 0,
1023
- portalId: 'backdrop',
1024
- setShow: s => {
1135
+ portalId: portalId,
1136
+ setShow: (s, f) => {
1025
1137
  /* empty */
1026
1138
  }
1027
1139
  });
1028
1140
  const BackdropContextProvider = (p) => {
1029
1141
  var _a;
1030
1142
  const [showCount, setShowCount] = React.useState(0);
1031
- const log = useLogger('BackdropContextProvider', (_a = p.debug) !== null && _a !== void 0 ? _a : false);
1032
- if (p.debug) {
1143
+ const log = useLogger('BackdropContextProvider', (_a = p.__debug) !== null && _a !== void 0 ? _a : false);
1144
+ if (p.__debug) {
1033
1145
  React.useEffect(() => {
1034
1146
  log('mounted');
1035
1147
  return () => {
1036
1148
  log('un-mounted');
1037
1149
  };
1038
1150
  }, []);
1039
- React.useEffect(() => {
1040
- log('re-rendered');
1041
- });
1042
- React.useEffect(() => {
1151
+ useIgnoreMount(() => {
1043
1152
  log('showCount changed', showCount);
1044
1153
  }, [showCount]);
1045
1154
  }
1046
1155
  return (React__default['default'].createElement(BackdropContext.Provider, { value: {
1047
- portalId: 'backdrop',
1156
+ portalId: portalId,
1048
1157
  showing: showCount > 0,
1049
1158
  showCount: showCount,
1050
- setShow: (show) => {
1159
+ setShow: (show, from) => {
1051
1160
  if (show) {
1052
1161
  setShowCount(s => {
1053
1162
  const count = s + 1;
1054
- log('setShowCount', count);
1055
- log('showCount was', showCount);
1163
+ log(`setShow from ${from} ${s} -> ${count}`);
1056
1164
  return count;
1057
1165
  });
1058
1166
  }
1059
1167
  else {
1060
1168
  setShowCount(s => {
1061
1169
  const count = Math.max(0, s - 1);
1062
- log('setShowCount', count);
1063
- log('showCount was', showCount);
1170
+ log(`setShow from ${from} ${s} -> ${count}`);
1064
1171
  return count;
1065
1172
  });
1066
1173
  }
1067
1174
  }
1068
1175
  } },
1069
1176
  p.children,
1070
- p.debug && (React__default['default'].createElement("p", { className: css.css({
1177
+ p.__debug && (React__default['default'].createElement("p", { className: css.css({
1071
1178
  position: 'fixed',
1072
1179
  top: 0, right: 0,
1073
1180
  backgroundColor: '#ff00004f',
@@ -1084,23 +1191,20 @@ const BackdropOverlay = (p) => {
1084
1191
  const context = React.useContext(BackdropContext);
1085
1192
  const theme = useThemeSafely();
1086
1193
  const showTimeMs = (_a = p.showTimeMs) !== null && _a !== void 0 ? _a : 250;
1087
- const log = useLogger('BackdropOverlay', (_b = p.debug) !== null && _b !== void 0 ? _b : false);
1088
- if (p.debug) {
1194
+ const log = useLogger('BackdropOverlay', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1195
+ if (p.__debug) {
1089
1196
  React.useEffect(() => {
1090
1197
  log('mounted');
1091
1198
  return () => {
1092
1199
  log('unmounted');
1093
1200
  };
1094
1201
  }, []);
1095
- React.useEffect(() => {
1096
- log('re-rendered');
1097
- });
1098
- React.useEffect(() => {
1202
+ useIgnoreMount(() => {
1099
1203
  log('context.showing changed', context.showing);
1100
1204
  }, [context.showing]);
1101
1205
  }
1102
1206
  return (React__default['default'].createElement("div", { onClick: () => {
1103
- context === null || context === void 0 ? void 0 : context.setShow(false);
1207
+ context === null || context === void 0 ? void 0 : context.setShow(false, 'BackdropOverlay');
1104
1208
  log('onClick', 'setShow', false);
1105
1209
  }, id: context === null || context === void 0 ? void 0 : context.portalId, className: css.css({
1106
1210
  cursor: 'pointer',
@@ -1116,12 +1220,12 @@ const BackdropOverlay = (p) => {
1116
1220
  }) }));
1117
1221
  };
1118
1222
  const Backdrop = (p) => {
1119
- return (React__default['default'].createElement(BackdropContextProvider, { debug: p.debug },
1223
+ return (React__default['default'].createElement(BackdropContextProvider, { __debug: p.__debug },
1120
1224
  React__default['default'].createElement("div", { className: css.css({
1121
1225
  height: '100%'
1122
1226
  }) },
1123
1227
  p.children,
1124
- React__default['default'].createElement(BackdropOverlay, { showTimeMs: p.showTimeMs, debug: p.debug }))));
1228
+ React__default['default'].createElement(BackdropOverlay, { showTimeMs: p.showTimeMs, __debug: p.__debug }))));
1125
1229
  };
1126
1230
 
1127
1231
  const Calendar = (p) => {
@@ -1217,7 +1321,7 @@ const Calendar = (p) => {
1217
1321
  };
1218
1322
 
1219
1323
  const Checkbox = (props) => {
1220
- const inputProps = __rest(props, ["checked", "onChange", "label", "checkedIcon", "uncheckedIcon", "checkedThemeColor", "checkedColor", "readonly"]);
1324
+ const inputProps = __rest(props, ["checked", "onChange", "label", "checkedIcon", "uncheckedIcon", "checkedThemeColor", "checkedColor", "readOnly"]);
1221
1325
  const selected = props.checkedIcon || 'selected';
1222
1326
  const unselected = props.uncheckedIcon || 'unselected';
1223
1327
  const theme = useThemeSafely();
@@ -1232,7 +1336,7 @@ const Checkbox = (props) => {
1232
1336
  }
1233
1337
  const checkboxStyles = css.css `
1234
1338
  display: inline-block;
1235
- ${!props.disabled && !props.readonly && `
1339
+ ${!props.disabled && !props.readOnly && `
1236
1340
  &:hover {
1237
1341
  filter: ${theme.controls.hoverBrightness};
1238
1342
  }
@@ -1246,7 +1350,7 @@ const Checkbox = (props) => {
1246
1350
  ${props.disabled && `
1247
1351
  cursor: not-allowed;
1248
1352
  `}
1249
- ${props.readonly && `
1353
+ ${props.readOnly && `
1250
1354
  cursor: default;
1251
1355
  `}
1252
1356
  `;
@@ -1258,7 +1362,7 @@ const Checkbox = (props) => {
1258
1362
  width: 0;
1259
1363
  opacity: 0;
1260
1364
 
1261
- ${!props.readonly && `
1365
+ ${!props.readOnly && `
1262
1366
  &:focus + .checkboxIcon {
1263
1367
  box-shadow: ${theme.controls.focusOutlineShadow};
1264
1368
  }
@@ -1272,7 +1376,7 @@ const Checkbox = (props) => {
1272
1376
  background-color: ${theme.colors.disabled};
1273
1377
  cursor: not-allowed;
1274
1378
  `}
1275
- ${props.readonly && `
1379
+ ${props.readOnly && `
1276
1380
  cursor: default;
1277
1381
  `}
1278
1382
  ${props.checked && `
@@ -1281,8 +1385,8 @@ const Checkbox = (props) => {
1281
1385
  `;
1282
1386
  return (React__namespace.createElement("span", { className: css.cx('checkbox', checkboxStyles, props.className) },
1283
1387
  React__namespace.createElement("label", { className: labelStyles },
1284
- React__namespace.createElement("input", Object.assign({}, inputProps, { tabIndex: props.readonly ? -1 : undefined, className: nativeCheckboxStyles, type: "checkbox", onChange: e => {
1285
- if (props.readonly) {
1388
+ React__namespace.createElement("input", Object.assign({}, inputProps, { tabIndex: props.readOnly ? -1 : undefined, className: nativeCheckboxStyles, type: "checkbox", onChange: e => {
1389
+ if (props.readOnly) {
1286
1390
  e.preventDefault();
1287
1391
  return;
1288
1392
  }
@@ -1293,11 +1397,41 @@ const Checkbox = (props) => {
1293
1397
  props.children)));
1294
1398
  };
1295
1399
 
1400
+ /** useEffect but it will only fire when the actual truthiness of the value changes.
1401
+ * Use for comparing previous states to next states without all the bullshit around useEffect and component mounting.
1402
+ */
1403
+ const useBooleanChanged = (effect, dep) => {
1404
+ /*
1405
+ Why?
1406
+ useEffect with a dependency array will fire once on mount even though the dependency list doesn't change.
1407
+ Components like Modal need to communicate when their show status changes.
1408
+ useIgnoreMount is not enough because it only ignores the first render and is therefore a kludge.
1409
+ This is what we want regardless of mount status:
1410
+ true > false = Change
1411
+ false > true = Change
1412
+ true > true = No Change
1413
+ false > false = No Change
1414
+ undefined > false = No Change
1415
+ undefined > true = Change
1416
+ */
1417
+ const lastValue = React.useRef(undefined);
1418
+ React.useEffect(() => {
1419
+ //console.log('[useBooleanChanged] useEffect called with', dep, 'was', lastValue.current ?? 'undefined')
1420
+ if (!!lastValue.current !== !!dep) {
1421
+ const previous = lastValue.current;
1422
+ lastValue.current = dep;
1423
+ effect(!!lastValue.current, !!previous);
1424
+ //console.log('[useBooleanChanged] change called')
1425
+ }
1426
+ }, [dep]);
1427
+ };
1428
+
1296
1429
  // Taken from: https://github.com/react-bootstrap/dom-helpers/blob/master/src/scrollbarSize.ts
1297
1430
  const canUseDom = !!(typeof window !== 'undefined' &&
1298
1431
  window.document &&
1299
1432
  window.document.createElement);
1300
1433
  let size;
1434
+ /** Tells you actual width of the scroll bar. This can vary by browser. */
1301
1435
  const useScrollbarSize = (recalc) => {
1302
1436
  if ((!size && size !== 0) || recalc) {
1303
1437
  if (canUseDom) {
@@ -1315,7 +1449,6 @@ const useScrollbarSize = (recalc) => {
1315
1449
  return size;
1316
1450
  };
1317
1451
 
1318
- let htmlBodyStyles;
1319
1452
  const Modal = (p) => {
1320
1453
  var _a, _b, _c, _d;
1321
1454
  const backdrop = React.useContext(BackdropContext);
@@ -1323,42 +1456,54 @@ const Modal = (p) => {
1323
1456
  const theme = useThemeSafely();
1324
1457
  const hasHeader = p.closeButton || p.heading;
1325
1458
  const contentRef = React__default['default'].useRef(null);
1326
- const log = useLogger(`Modal ${(_a = p.id) !== null && _a !== void 0 ? _a : '?'}`, (_b = p.debug) !== null && _b !== void 0 ? _b : false);
1459
+ const log = useLogger((_a = p.id) !== null && _a !== void 0 ? _a : 'Modal', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1460
+ const showing = React.useRef(p.show);
1461
+ const bodyStyles = React.useRef('');
1462
+ const tryRemoveBodyStyles = () => {
1463
+ if (bodyStyles.current) {
1464
+ log('removing singleton', bodyStyles.current);
1465
+ document.body.classList.remove(bodyStyles.current);
1466
+ }
1467
+ };
1327
1468
  React.useEffect(() => {
1328
1469
  log('mounted');
1329
1470
  return () => {
1330
- // handle the use of modals that are rendered only as show=true and then unmounted.
1331
- backdrop.setShow(false);
1332
- log('backdrop.setShow', false);
1333
- if (backdrop.showCount <= 1 && htmlBodyStyles) {
1334
- log('backdrop.showCount', backdrop.showCount, 'removing htmlBodyStyles');
1335
- document.body.classList.remove(htmlBodyStyles);
1471
+ var _a;
1472
+ if (showing.current) {
1473
+ log(`un-mount in progress and this modal is showing. decrement the backdrop and try to remove singleton body styles.`);
1474
+ backdrop.setShow(false, (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal');
1475
+ log('backdrop.setShow', false);
1476
+ tryRemoveBodyStyles();
1477
+ }
1478
+ else {
1479
+ log(`un-mount in progress but this modal is not showing. do nothing with the backdrop.`);
1336
1480
  }
1337
1481
  log('un-mounted');
1338
1482
  };
1339
1483
  }, []);
1340
- React.useEffect(() => {
1341
- log('show changed', p.show);
1342
- backdrop.setShow(p.show);
1343
- log('backdrop.setShow', true);
1344
- if (!htmlBodyStyles) {
1345
- log('creating singleton htmlBodyStyles');
1346
- htmlBodyStyles = css.css({
1484
+ useBooleanChanged((show, previousShow) => {
1485
+ var _a, _b, _c;
1486
+ log('show changed', `${previousShow !== null && previousShow !== void 0 ? previousShow : 'undefined'} > ${show}`);
1487
+ backdrop.setShow(show, (_a = p.id) !== null && _a !== void 0 ? _a : 'Modal');
1488
+ showing.current = show;
1489
+ log('backdrop.setShow', show);
1490
+ if (!bodyStyles.current) {
1491
+ bodyStyles.current = css.css({
1492
+ label: 'ModalBodyOverrides_' + ((_c = (_b = p.id) === null || _b === void 0 ? void 0 : _b.replace(/\s+/, '')) !== null && _c !== void 0 ? _c : nanoid.nanoid()),
1347
1493
  overflow: 'hidden',
1348
1494
  paddingRight: `${useScrollbarSize()}px`
1349
1495
  });
1496
+ log('creating singleton bodyStyles', bodyStyles.current);
1350
1497
  }
1351
- if (p.show) {
1352
- log('adding htmlBodyStyles');
1353
- document.body.classList.add(htmlBodyStyles);
1498
+ if (show) {
1499
+ log('this modal is showing. adding singleton bodyStyles', bodyStyles.current);
1500
+ document.body.classList.add(bodyStyles.current);
1354
1501
  }
1355
1502
  else {
1356
- if (backdrop.showCount <= 1) {
1357
- log('backdrop.showCount', backdrop.showCount, 'removing htmlBodyStyles');
1358
- document.body.classList.remove(htmlBodyStyles);
1359
- }
1503
+ log('this modal is hiding. try removing singleton bodyStyles');
1504
+ tryRemoveBodyStyles();
1360
1505
  }
1361
- }, [p.show]);
1506
+ }, p.show);
1362
1507
  React__default['default'].useLayoutEffect(() => {
1363
1508
  var _a;
1364
1509
  if (p.show === true) {
@@ -1454,320 +1599,47 @@ const ConfirmModal = (props) => {
1454
1599
  const modalStyle = css.css `
1455
1600
  ${props.variant === 'omg' && `
1456
1601
  .modalHeader {
1457
- background-color:${theme.colors.omg};
1458
- color:${theme.colors.omgFont};
1459
- }
1460
- `}
1461
- `;
1462
- return (React__namespace.createElement(Modal, { id: props.id, debug: props.debug, className: css.cx('confirmModal', modalStyle, props.className), heading: props.header, closeButton: true, show: props.show, onClick: props.onCancel },
1463
- React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1464
- React__namespace.createElement(Text, { align: "center" }, props.text),
1465
- React__namespace.createElement("div", { className: css.css({ textAlign: 'center' }) },
1466
- React__namespace.createElement(Button, { className: css.css({ margin: '0 0.5rem' }), enforceMinWidth: true, variant: props.variant === 'omg' ? "omg" : 'primary2', onClick: props.onConfirm }, props.confirmText || 'OK'),
1467
- React__namespace.createElement(Button, { className: css.css({ margin: '0 0.5rem' }), enforceMinWidth: true, onClick: props.onCancel }, props.cancelText || 'Cancel')))));
1468
- };
1469
-
1470
- const CopyButton = (props) => {
1471
- const [copied, setCopied] = React__namespace.useState(false);
1472
- return (React__namespace.createElement(Button, { title: copied ? 'Copied!' : (props.title || 'Copy to clipboard'), variant: "icon", onBlur: () => {
1473
- setCopied(false);
1474
- }, onClick: e => {
1475
- const button = e.currentTarget;
1476
- let copySuccess = false;
1477
- try {
1478
- const text = document.querySelector(props.selector);
1479
- text.select();
1480
- copySuccess = document.execCommand('copy');
1481
- // the input gets focus on select. bring it back.
1482
- button.focus();
1483
- }
1484
- catch (err) {
1485
- // You done wrong.
1486
- }
1487
- setCopied(copySuccess);
1488
- } },
1489
- React__namespace.createElement(Icon, { id: copied ? 'paste' : 'copy' })));
1490
- };
1491
-
1492
- const defaultMaxLength$1 = 100;
1493
- const BaseInput = React__namespace.forwardRef((props, ref) => {
1494
- var _a;
1495
- const theme = useThemeSafely();
1496
- const { rightControl, round, wrapperClassName } = props, nativeProps = __rest(props, ["rightControl", "round", "wrapperClassName"]);
1497
- const inputStyles = css.css({
1498
- fontFamily: theme.fonts.family,
1499
- fontSize: theme.fonts.size,
1500
- width: '100%',
1501
- border: theme.controls.border,
1502
- borderRadius: theme.controls.borderRadius,
1503
- color: theme.colors.font,
1504
- paddingLeft: theme.controls.padding,
1505
- paddingRight: theme.controls.padding,
1506
- height: theme.controls.height,
1507
- transition: theme.controls.transition,
1508
- ':focus': {
1509
- outline: 'none',
1510
- boxShadow: theme.controls.focusOutlineShadow
1511
- },
1512
- ':disabled': {
1513
- backgroundColor: theme.colors.disabled,
1514
- cursor: 'not-allowed'
1515
- },
1516
- ':invalid': {
1517
- borderColor: theme.colors.required,
1518
- ':focus': {
1519
- boxShadow: theme.controls.focusOutlineRequiredShadow
1520
- }
1521
- },
1522
- }, props.round && {
1523
- borderRadius: theme.controls.roundRadius,
1524
- paddingLeft: `calc(${theme.controls.padding} * 2)`,
1525
- paddingRight: `calc(${theme.controls.padding} * 2)`
1526
- }, props.readOnly && {
1527
- backgroundColor: 'transparent',
1528
- cursor: 'default',
1529
- border: 'none',
1530
- ':focus': {
1531
- outline: 'none',
1532
- boxShadow: 'none'
1533
- }
1534
- }, props.rightControl && {
1535
- paddingRight: theme.controls.height
1536
- });
1537
- const inputElement = React__namespace.createElement("input", Object.assign({}, nativeProps, { ref: ref, autoComplete: (_a = nativeProps.autoComplete) !== null && _a !== void 0 ? _a : 'off', tabIndex: nativeProps.readOnly ? -1 : nativeProps.tabIndex, maxLength: nativeProps.maxLength || defaultMaxLength$1, className: css.cx(inputStyles, props.className) }));
1538
- const inputWrapperStyles = css.css `
1539
- width:100%;
1540
- ${props.rightControl && `
1541
- position: relative;
1542
- `}
1543
- `;
1544
- const rightControlStyles = props.rightControl && css.css `
1545
- position: absolute;
1546
- right: ${theme.controls.padding};
1547
- top: 0;
1548
- bottom: 0;
1549
- display: flex;
1550
- align-items: center;
1551
- ${props.round && `
1552
- right: calc(${theme.controls.padding} * 2);
1553
- `}
1554
- `;
1555
- return (React__namespace.createElement("div", { className: css.cx('input', inputWrapperStyles, wrapperClassName) },
1556
- inputElement,
1557
- props.rightControl && React__namespace.createElement("div", { className: rightControlStyles }, props.rightControl)));
1558
- });
1559
-
1560
- const tryClampRange = (value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) => {
1561
- if (value === undefined) {
1562
- return value;
1563
- }
1564
- if (isNaN(value)) {
1565
- return undefined;
1566
- }
1567
- return Math.min(Math.max(value, min), max);
1568
- };
1569
-
1570
- const dateRegex = /(\d{1,2})(?:\/|-)(\d{1,2})(?:\/|-)(\d{4})/;
1571
- const datePattern = dateRegex.source;
1572
- //TB: INPUT these need to be moved into the on change handler
1573
- const validityChecks = [
1574
- (value) => {
1575
- return isOutOfRange(parseDateMs(value)) ? 'Out of range' : '';
1576
- },
1577
- (value) => {
1578
- return !dateExists(parseDateMs(value)) ? 'Invalid Date' : '';
1579
- },
1580
- ];
1581
- const DateInput = React__namespace.forwardRef((props, ref) => {
1582
- const [dateMsValue, setDateMsValue] = React__namespace.useState(undefined);
1583
- const [textValue, setTextValue] = React__namespace.useState(undefined);
1584
- const inputRef = (ref !== null && ref !== void 0 ? ref : React__namespace.useRef(null));
1585
- const nativeProps = __rest(props, ["customValidityChecks"]);
1586
- React__namespace.useEffect(() => {
1587
- console.log('[DateInput] setting local value to ', props.value);
1588
- // sync local value to outer value
1589
- setDateMsValue(props.value);
1590
- setTextValue(formatDateStringValue(props.value));
1591
- }, [props.value]);
1592
- return (React__namespace.createElement(BaseInput, Object.assign({}, nativeProps, { type: "text", ref: inputRef, value: textValue !== null && textValue !== void 0 ? textValue : '', pattern: datePattern, maxLength: 10, onChange: e => {
1593
- var _a, _b;
1594
- const text = e.target.value || undefined;
1595
- let error;
1596
- validityChecks.some(func => {
1597
- error = func(text);
1598
- return !!error;
1599
- });
1600
- (_a = props.customValidityChecks) === null || _a === void 0 ? void 0 : _a.some(func => {
1601
- error = func(text);
1602
- return !!error;
1603
- });
1604
- e.target.setCustomValidity(error !== null && error !== void 0 ? error : '');
1605
- console.log(e.target.validationMessage);
1606
- setTextValue(text);
1607
- setDateMsValue(parseDateMs(text));
1608
- console.log(e.target.validationMessage);
1609
- (_b = props.onChange) === null || _b === void 0 ? void 0 : _b.call(props, e);
1610
- }, onBlur: e => {
1611
- var _a;
1612
- if (props.onBlur) {
1613
- let currentValue = dateMsValue;
1614
- if ((_a = props.constrainOnBlur) !== null && _a !== void 0 ? _a : true) {
1615
- currentValue = tryClampRange(currentValue, props.min, props.max);
1616
- }
1617
- if (currentValue !== dateMsValue) {
1618
- // we've constrained the value. update all values
1619
- setTextValue(formatDateStringValue(currentValue));
1620
- setDateMsValue(currentValue);
1621
- }
1622
- else if (dateMsValue === undefined && !!textValue) {
1623
- // invalid date but the bad text is still showing
1624
- // clear the text and reset the validity
1625
- setTextValue(undefined);
1626
- e.target.setCustomValidity('');
1627
- }
1628
- props.onBlur(dateMsValue, e);
1629
- }
1630
- }, onKeyDown: e => {
1631
- var _a, _b;
1632
- if (e.key === 'Enter') {
1633
- // handle form submission on enter for onBlur-only situations.
1634
- // onBlur will not be fired so any local change will not be communicated to the outside.
1635
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
1636
- }
1637
- (_b = props.onKeyDown) === null || _b === void 0 ? void 0 : _b.call(props, e);
1638
- } })));
1639
- });
1640
- const isOutOfRange = (value, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) => {
1641
- if (typeof value === 'number') {
1642
- return value < min || value > max;
1643
- }
1644
- return false;
1645
- };
1646
- const parseDateMs = (rawValue) => {
1647
- if (!rawValue) {
1648
- return undefined;
1649
- }
1650
- let dateMs;
1651
- const dateParts = dateRegex.exec(rawValue);
1652
- if (dateParts) {
1653
- const month = parseInt(dateParts[1], 10) - 1;
1654
- const day = parseInt(dateParts[2], 10);
1655
- const year = parseInt(dateParts[3], 10);
1656
- if (dateFns.isExists(year, month, day)) {
1657
- dateMs = new Date(year, month, day).valueOf();
1658
- }
1659
- }
1660
- return dateMs;
1661
- };
1662
- const dateExists = (dateMs) => {
1663
- if (dateMs === undefined) {
1664
- return false;
1665
- }
1666
- const d = new Date(dateMs);
1667
- return dateFns.isExists(d.getFullYear(), d.getMonth() - 1, d.getDate());
1668
- };
1669
- const formatDateStringValue = (dateMs) => {
1670
- if (typeof dateMs === 'number') {
1671
- return dateFns.format(dateMs, 'MM/dd/yyyy');
1672
- }
1673
- return '';
1674
- };
1675
-
1676
- const Popover = (p) => {
1677
- var _a, _b;
1678
- const theme = useThemeSafely();
1679
- const resposition = (_a = p.reposition) !== null && _a !== void 0 ? _a : true;
1680
- return (React__namespace.createElement(reactTinyPopover.Popover, { containerClassName: css.css({
1681
- zIndex: theme.zIndexes.tooltip
1682
- }), reposition: resposition, isOpen: p.isOpen, positions: (_b = p.positions) !== null && _b !== void 0 ? _b : ['right', 'top', 'left', 'bottom'], onClickOutside: p.onClickOutside, content: ({ position, childRect, popoverRect }) => {
1683
- var _a, _b, _c, _d;
1684
- return (React__namespace.createElement(reactTinyPopover.ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: (_a = p.arrorColor) !== null && _a !== void 0 ? _a : theme.colors.border, arrowSize: 10 },
1685
- React__namespace.createElement(TabLocker, null,
1686
- React__namespace.createElement("div", { className: css.css({
1687
- border: (_b = p.border) !== null && _b !== void 0 ? _b : theme.controls.border,
1688
- borderRadius: (_c = p.border) !== null && _c !== void 0 ? _c : theme.controls.borderRadius,
1689
- boxShadow: theme.controls.boxShadow,
1690
- backgroundColor: (_d = p.backgroundColor) !== null && _d !== void 0 ? _d : theme.colors.bg,
1691
- }) }, p.content))));
1692
- } },
1693
- React__namespace.createElement("span", null, p.parent)));
1694
- };
1695
-
1696
- const DatePicker = (p) => {
1697
- var _a;
1698
- const inputProps = __rest(p, ["reposition", "onPick"]);
1699
- const [showCalendar, setShowCalendar] = React__namespace.useState(false);
1700
- const [calendarDate, setCalendarDate] = React__namespace.useState(getCalendarDate(p.value, p.min, p.max));
1701
- // controls the one-time focus set on show
1702
- const needsFocus = React__namespace.useRef(false);
1703
- const popover = React__namespace.useRef(null);
1704
- React__namespace.useEffect(() => {
1705
- var _a;
1706
- setCalendarDate(getCalendarDate((_a = p.value) !== null && _a !== void 0 ? _a : 0, p.min, p.max));
1707
- }, [p.value]);
1708
- React__namespace.useLayoutEffect(() => {
1709
- var _a, _b;
1710
- if (needsFocus.current) {
1711
- (_b = (_a = popover.current) === null || _a === void 0 ? void 0 : _a.querySelector('button')) === null || _b === void 0 ? void 0 : _b.focus();
1712
- needsFocus.current = false;
1713
- }
1714
- });
1715
- return (React__namespace.createElement(Popover, { reposition: p.reposition, isOpen: showCalendar, onClickOutside: () => {
1716
- needsFocus.current = false;
1717
- setShowCalendar(false);
1718
- }, parent: (React__namespace.createElement(DateInput, Object.assign({}, inputProps, { placeholder: (_a = p.placeholder) !== null && _a !== void 0 ? _a : 'MM/DD/YYYY', rightControl: !p.readOnly ? (React__namespace.createElement(Button, { variant: "icon", readonly: p.readOnly, disabled: p.disabled, small: true, style: {
1719
- fontSize: '1rem'
1720
- }, onClick: () => {
1721
- needsFocus.current = !showCalendar;
1722
- setShowCalendar(!showCalendar);
1723
- } },
1724
- React__namespace.createElement(Icon, { id: "pickDate" }))) : undefined, onFocus: e => {
1725
- var _a;
1726
- needsFocus.current = false;
1727
- setShowCalendar(false);
1728
- (_a = p.onFocus) === null || _a === void 0 ? void 0 : _a.call(p, e);
1729
- } }))), content: (React__namespace.createElement("div", { ref: popover, className: css.css({
1730
- paddingLeft: '1rem',
1731
- paddingRight: '1rem',
1732
- paddingBottom: '1rem'
1733
- }) },
1734
- React__namespace.createElement(Calendar, { onClick: date => {
1735
- var _a;
1736
- (_a = p.onPick) === null || _a === void 0 ? void 0 : _a.call(p, date.valueOf());
1737
- needsFocus.current = false;
1738
- setShowCalendar(false);
1739
- }, customTitle: React__namespace.createElement("div", { className: css.css({
1740
- display: 'flex',
1741
- justifyContent: 'space-between',
1742
- alignItems: 'center'
1743
- }) },
1744
- React__namespace.createElement(Button, { disabled: !!p.min && dateFns.isBefore(dateFns.endOfMonth(dateFns.addMonths(calendarDate, -1)), p.min), small: true, variant: "icon", onClick: () => setCalendarDate(dateFns.addMonths(calendarDate, -1)) },
1745
- React__namespace.createElement(Icon, { id: "pagerLeft" })),
1746
- React__namespace.createElement(Text, { align: "center" },
1747
- dateFns.format(calendarDate, 'LLLL'),
1748
- " ",
1749
- calendarDate.getFullYear()),
1750
- React__namespace.createElement(Button, { disabled: !!p.max && dateFns.isAfter(dateFns.startOfMonth(dateFns.addMonths(calendarDate, 1)), p.max), small: true, variant: "icon", onClick: () => setCalendarDate(dateFns.addMonths(calendarDate, 1)) },
1751
- React__namespace.createElement(Icon, { id: "pagerRight" }))), month: calendarDate.getMonth() + 1, year: calendarDate.getFullYear(), showCurrent: true, smallHeader: true, selectedValue: p.value, cellSize: '2rem', min: p.min, max: p.max }))) }));
1752
- };
1753
- const getCalendarDate = (date, min, max) => {
1754
- let calendarDate = date ? new Date(date) : new Date();
1755
- // if there is a min/max we don't want the calendar to open to the current date if it's out of range
1756
- if (min && dateFns.isBefore(calendarDate, min)) {
1757
- calendarDate = new Date(min);
1758
- }
1759
- else if (max && dateFns.isAfter(calendarDate, max)) {
1760
- calendarDate = new Date(max);
1761
- }
1762
- return calendarDate;
1602
+ background-color:${theme.colors.omg};
1603
+ color:${theme.colors.omgFont};
1604
+ }
1605
+ `}
1606
+ `;
1607
+ return (React__namespace.createElement(Modal, { id: props.id, __debug: props.__debug, className: css.cx('confirmModal', modalStyle, props.className), heading: props.header, closeButton: true, show: props.show, onClick: props.onCancel },
1608
+ React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1609
+ React__namespace.createElement(Text, { align: "center" }, props.text),
1610
+ React__namespace.createElement("div", { className: css.css({ textAlign: 'center' }) },
1611
+ React__namespace.createElement(Button, { className: css.css({ margin: '0 0.5rem' }), enforceMinWidth: true, variant: props.variant === 'omg' ? "omg" : 'primary2', onClick: props.onConfirm }, props.confirmText || 'OK'),
1612
+ React__namespace.createElement(Button, { className: css.css({ margin: '0 0.5rem' }), enforceMinWidth: true, onClick: props.onCancel }, props.cancelText || 'Cancel')))));
1613
+ };
1614
+
1615
+ const CopyButton = (props) => {
1616
+ const [copied, setCopied] = React__namespace.useState(false);
1617
+ return (React__namespace.createElement(Button, { title: copied ? 'Copied!' : (props.title || 'Copy to clipboard'), variant: "icon", onBlur: () => {
1618
+ setCopied(false);
1619
+ }, onClick: e => {
1620
+ const button = e.currentTarget;
1621
+ let copySuccess = false;
1622
+ try {
1623
+ const text = document.querySelector(props.selector);
1624
+ text.select();
1625
+ copySuccess = document.execCommand('copy');
1626
+ // the input gets focus on select. bring it back.
1627
+ button.focus();
1628
+ }
1629
+ catch (err) {
1630
+ // You done wrong.
1631
+ }
1632
+ setCopied(copySuccess);
1633
+ } },
1634
+ React__namespace.createElement(Icon, { id: copied ? 'paste' : 'copy' })));
1763
1635
  };
1764
1636
 
1765
- const Divider = () => {
1637
+ const Divider = (p) => {
1766
1638
  const theme = useThemeSafely();
1767
- return (React__namespace.createElement("hr", { className: css.cx("divider", css.css({
1639
+ return (React__namespace.createElement("hr", Object.assign({}, p, { className: css.cx("divider", css.css({
1768
1640
  margin: theme.controls.dividerMargin,
1769
1641
  border: theme.controls.dividerBorder
1770
- })) }));
1642
+ }), p.className) })));
1771
1643
  };
1772
1644
 
1773
1645
  const ErrorModal = (props) => {
@@ -1782,7 +1654,7 @@ const ErrorModal = (props) => {
1782
1654
  color: ${theme.colors.omgFont};
1783
1655
  }
1784
1656
  `;
1785
- return (React__namespace.createElement(Modal, { id: props.id, debug: props.debug, className: css.cx('errorModal', modalStyles), heading: "Error", closeButton: true, show: props.show, onClick: props.close },
1657
+ return (React__namespace.createElement(Modal, { id: props.id, __debug: props.__debug, className: css.cx('errorModal', modalStyles), heading: "Error", closeButton: true, show: props.show, onClick: props.close },
1786
1658
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1787
1659
  React__namespace.createElement(Text, { align: "center" }, message))));
1788
1660
  };
@@ -1910,7 +1782,7 @@ const hoverClass = css.css({
1910
1782
  backgroundColor: 'rgba(0,0,0,0.25) !important'
1911
1783
  });
1912
1784
  const FileUploader = (p) => {
1913
- var _a, _b, _c, _d, _e, _f, _g;
1785
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1914
1786
  const [message, setMessage] = React.useState(undefined);
1915
1787
  const [canUpload, setCanUpload] = React.useState(false);
1916
1788
  const [uploading, setUploading] = React.useState(false);
@@ -1926,7 +1798,7 @@ const FileUploader = (p) => {
1926
1798
  filesDisplay = (_b = p.instructionMessage) !== null && _b !== void 0 ? _b : `Click or drag and drop files.`;
1927
1799
  }
1928
1800
  else {
1929
- filesDisplay = `${files.length.toLocaleString()} file${files.length > 1 ? 's' : ''} selected (${getSizeString(totalFileSize)}): ${files.files.map(f => f.name).join(', ')}`;
1801
+ filesDisplay = `${files.length.toLocaleString()} file${files.length > 1 ? 's' : ''} selected (${getFileSizeDisplay(totalFileSize)}): ${files.files.map(f => f.name).join(', ')}`;
1930
1802
  }
1931
1803
  }
1932
1804
  const setAllFiles = (inputFiles) => {
@@ -1948,8 +1820,9 @@ const FileUploader = (p) => {
1948
1820
  maxBytes: p.maxBytes
1949
1821
  });
1950
1822
  };
1823
+ const showInfoOnPick = (_c = p.showInfoOnPick) !== null && _c !== void 0 ? _c : true;
1951
1824
  let infoMessage;
1952
- if (p.infoMessage) {
1825
+ if (p.infoMessage && (!files || showInfoOnPick)) {
1953
1826
  if (typeof p.infoMessage === 'string') {
1954
1827
  infoMessage = React__default['default'].createElement(Text, { noPad: true }, p.infoMessage);
1955
1828
  }
@@ -1966,6 +1839,8 @@ const FileUploader = (p) => {
1966
1839
  padding: '1rem',
1967
1840
  overflow: 'hidden',
1968
1841
  backgroundColor: theme.colors.lightBg,
1842
+ }, p.disabled && {
1843
+ opacity: theme.controls.disabledOpacity
1969
1844
  }), onDragOver: e => {
1970
1845
  var _a, _b;
1971
1846
  e.preventDefault();
@@ -1999,7 +1874,7 @@ const FileUploader = (p) => {
1999
1874
  setCanUpload(false);
2000
1875
  });
2001
1876
  } },
2002
- React__default['default'].createElement("input", { ref: input, className: css.css({
1877
+ React__default['default'].createElement("input", { disabled: p.disabled, ref: input, className: css.css({
2003
1878
  position: 'absolute',
2004
1879
  top: -50,
2005
1880
  left: 0,
@@ -2008,6 +1883,8 @@ const FileUploader = (p) => {
2008
1883
  width: '100%',
2009
1884
  cursor: 'pointer',
2010
1885
  opacity: 0
1886
+ }, p.disabled && {
1887
+ cursor: 'not-allowed'
2011
1888
  }), type: "file", multiple: p.multiple, accept: p.accept, onChange: e => {
2012
1889
  try {
2013
1890
  if (!e.target.files) {
@@ -2030,65 +1907,68 @@ const FileUploader = (p) => {
2030
1907
  zIndex: !!(files === null || files === void 0 ? void 0 : files.length) ? 1 : undefined
2031
1908
  }) },
2032
1909
  !(files === null || files === void 0 ? void 0 : files.length) && React__default['default'].createElement(Icon, { style: { fontSize: '2rem' }, id: "upload" }),
2033
- React__default['default'].createElement(Text, { align: "center", noPad: true, spacedOut: true },
1910
+ React__default['default'].createElement(Text, { align: "center", noPad: true, spacedOut: true, className: css.css({
1911
+ width: '100%'
1912
+ }) },
2034
1913
  filesDisplay,
2035
1914
  !!(files === null || files === void 0 ? void 0 : files.length) && (React__default['default'].createElement(Button, { onClick: e => {
2036
1915
  e.stopPropagation();
2037
1916
  onFilesChange(undefined);
2038
1917
  }, className: css.css({ marginLeft: '1rem', color: theme.colors.negative }), rightIcon: React__default['default'].createElement(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear"))),
2039
1918
  infoMessage,
2040
- !!(files === null || files === void 0 ? void 0 : files.invalidFiles.length) && (React__default['default'].createElement(InfoPanel, { variant: "error" },
1919
+ !!(files === null || files === void 0 ? void 0 : files.invalidFiles.length) && (React__default['default'].createElement(InfoPanel, { variant: "error", className: css.css({ width: '100%' }) },
2041
1920
  "Invalid files: ",
2042
1921
  files.invalidFiles.map(f => f.name).join(', '),
2043
1922
  ".")),
2044
- (files === null || files === void 0 ? void 0 : files.overMaxBytes) && (React__default['default'].createElement(InfoPanel, { variant: "error" },
1923
+ (files === null || files === void 0 ? void 0 : files.overMaxBytes) && (React__default['default'].createElement(InfoPanel, { variant: "error", className: css.css({ width: '100%' }) },
2045
1924
  "Max file size exceeded (",
2046
- getSizeString((_c = p.maxBytes) !== null && _c !== void 0 ? _c : 0),
1925
+ getFileSizeDisplay((_d = p.maxBytes) !== null && _d !== void 0 ? _d : 0),
2047
1926
  ").")))),
2048
1927
  canUpload && !(files === null || files === void 0 ? void 0 : files.hasErrors) && (React__default['default'].createElement(Button, { className: css.css({
2049
- width: (_d = p.buttonWidth) !== null && _d !== void 0 ? _d : '10rem',
1928
+ width: (_e = p.buttonWidth) !== null && _e !== void 0 ? _e : '10rem',
2050
1929
  zIndex: 1,
2051
- }), waiting: uploading, type: "submit", variant: (_e = p.buttonVariant) !== null && _e !== void 0 ? _e : 'primary' }, (_f = p.buttonText) !== null && _f !== void 0 ? _f : 'Upload')),
2052
- message === 'success' && (React__default['default'].createElement(UploadInfoPanel, { variant: "positive", message: (_g = p.successMessage) !== null && _g !== void 0 ? _g : 'Upload successful.', onClear: () => setMessage(undefined) })),
1930
+ }), waiting: uploading, type: "submit", variant: (_f = p.buttonVariant) !== null && _f !== void 0 ? _f : 'primary' }, (_g = p.buttonText) !== null && _g !== void 0 ? _g : 'Upload')),
1931
+ message === 'success' && (React__default['default'].createElement(UploadInfoPanel, { variant: "positive", message: (_h = p.successMessage) !== null && _h !== void 0 ? _h : 'Upload successful.', onClear: () => setMessage(undefined) })),
2053
1932
  message === 'failure' && (React__default['default'].createElement(UploadInfoPanel, { variant: "error", message: fullFailureMessage, onClear: () => setMessage(undefined) }))));
2054
1933
  };
2055
1934
  const UploadInfoPanel = (p) => {
2056
1935
  return (React__default['default'].createElement(InfoPanel, { variant: p.variant, className: css.css({ zIndex: 1 }) },
2057
1936
  p.message,
2058
- React__default['default'].createElement(Button, { block: true, className: css.css({
1937
+ React__default['default'].createElement(Button, { className: css.css({
2059
1938
  color: 'inherit',
2060
- marginTop: '1rem'
1939
+ marginTop: '1rem',
1940
+ width: '100%'
2061
1941
  }), onClick: e => {
2062
1942
  e.stopPropagation();
2063
1943
  p.onClear();
2064
1944
  }, rightIcon: React__default['default'].createElement(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear")));
2065
1945
  };
2066
- const bytesInMb = 1048576;
2067
- const bytesInKb = 1024;
2068
- const getSizeString = (size) => {
2069
- if (size < bytesInKb) {
2070
- return size.toLocaleString() + ' B';
1946
+ // OMG this is dumb. We're sticking with decimals for ease of use.
1947
+ // https://stackoverflow.com/questions/40949135/gigabyte-or-gibibyte-1000-or-1024
1948
+ const getFileSizeDisplay = (size) => {
1949
+ let value = 0;
1950
+ let suffix = '';
1951
+ if (size >= 1000000000) {
1952
+ value = size / 1000000000;
1953
+ suffix = 'GB';
2071
1954
  }
2072
- else if (size < bytesInMb) {
2073
- return (size / bytesInKb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
1955
+ else if (size >= 1000000) {
1956
+ value = size / 1000000;
1957
+ suffix = 'MB';
2074
1958
  }
2075
- else {
2076
- return (size / bytesInMb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' MB';
1959
+ else if (size >= 1000) {
1960
+ value = size / 1000;
1961
+ suffix = 'KB';
2077
1962
  }
2078
- };
2079
-
2080
- const getCurrencyDisplay = (value, isCents, denomination = '$') => {
2081
- let actualValue = value || 0;
2082
- if (isCents) {
2083
- actualValue /= 100;
1963
+ else {
1964
+ value = size;
1965
+ suffix = 'B';
2084
1966
  }
2085
- return `${denomination}${actualValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
2086
- };
2087
- const noop = () => {
2088
- // lil' noop would be a great rap name. (thanks linter)
1967
+ return value.toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ` ${suffix}`;
2089
1968
  };
2090
1969
 
2091
1970
  const Header = (props) => {
1971
+ var _a;
2092
1972
  const theme = useThemeSafely();
2093
1973
  const bodyStyles = css.css `
2094
1974
  padding-top: calc(${theme.layout.headerHeight} + ${theme.layout.headerBodyOffset});
@@ -2100,7 +1980,9 @@ const Header = (props) => {
2100
1980
  document.body.classList.remove(bodyStyles);
2101
1981
  };
2102
1982
  });
2103
- const toggleMenu = props.toggleMenu || noop;
1983
+ const toggleMenu = (_a = props.toggleMenu) !== null && _a !== void 0 ? _a : (() => {
1984
+ /* noop */
1985
+ });
2104
1986
  const headerStyles = css.css `
2105
1987
  display: flex;
2106
1988
  gap: ${theme.controls.gap};
@@ -2199,160 +2081,545 @@ const Highlight = (props) => {
2199
2081
  text = props.text.replace(new RegExp(`(${replaceText})`, 'gi'), `<mark>$1</mark>`);
2200
2082
  }
2201
2083
  }
2202
- return (React__namespace.createElement("span", { className: css.cx('highlight', highlightStyles), dangerouslySetInnerHTML: { __html: text } }));
2203
- };
2204
-
2205
- const Image = (props) => {
2206
- return (React__namespace.createElement("img", { alt: props.alt, style: props.style, className: css.cx('image', css.css({
2207
- maxWidth: '100%',
2208
- maxHeight: '100%',
2209
- }), props.className), src: props.src }));
2084
+ return (React__namespace.createElement("span", { className: css.cx('highlight', highlightStyles), dangerouslySetInnerHTML: { __html: text } }));
2085
+ };
2086
+
2087
+ const Image = (props) => {
2088
+ return (React__namespace.createElement("img", { alt: props.alt, style: props.style, className: css.cx('image', css.css({
2089
+ maxWidth: '100%',
2090
+ maxHeight: '100%',
2091
+ }), props.className), src: props.src }));
2092
+ };
2093
+
2094
+ const Popover = (p) => {
2095
+ var _a, _b;
2096
+ const theme = useThemeSafely();
2097
+ const resposition = (_a = p.reposition) !== null && _a !== void 0 ? _a : true;
2098
+ return (React__namespace.createElement(reactTinyPopover.Popover, { containerClassName: css.css({
2099
+ zIndex: theme.zIndexes.tooltip
2100
+ }), reposition: resposition, isOpen: p.isOpen, positions: (_b = p.positions) !== null && _b !== void 0 ? _b : ['right', 'top', 'left', 'bottom'], onClickOutside: p.onClickOutside, content: ({ position, childRect, popoverRect }) => {
2101
+ var _a, _b, _c, _d;
2102
+ return (React__namespace.createElement(reactTinyPopover.ArrowContainer, { position: position, childRect: childRect, popoverRect: popoverRect, arrowColor: (_a = p.arrorColor) !== null && _a !== void 0 ? _a : theme.colors.border, arrowSize: 10 },
2103
+ React__namespace.createElement(TabLocker, null,
2104
+ React__namespace.createElement("div", { className: css.css({
2105
+ border: (_b = p.border) !== null && _b !== void 0 ? _b : theme.controls.border,
2106
+ borderRadius: (_c = p.border) !== null && _c !== void 0 ? _c : theme.controls.borderRadius,
2107
+ boxShadow: theme.controls.boxShadow,
2108
+ backgroundColor: (_d = p.backgroundColor) !== null && _d !== void 0 ? _d : theme.colors.bg,
2109
+ }) }, p.content))));
2110
+ } },
2111
+ React__namespace.createElement("span", null, p.parent)));
2112
+ };
2113
+
2114
+ const InfoTip = (props) => {
2115
+ var _a, _b, _c;
2116
+ const [showTip, setShowTip] = React__namespace.useState(false);
2117
+ const theme = useThemeSafely();
2118
+ const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2119
+ const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2120
+ const onClick = () => {
2121
+ if (props.onClick) {
2122
+ props.onClick();
2123
+ }
2124
+ else if (props.content) {
2125
+ openTip();
2126
+ }
2127
+ };
2128
+ const onMouseOver = () => {
2129
+ if (props.variant === 'modal') {
2130
+ return;
2131
+ }
2132
+ if (props.loadOnHover) {
2133
+ props.loadOnHover().then(() => {
2134
+ openTip();
2135
+ }).catch(err => {
2136
+ /* Not my responsiblity. */
2137
+ });
2138
+ }
2139
+ else {
2140
+ openTip();
2141
+ }
2142
+ };
2143
+ const onMouseOut = () => {
2144
+ if (props.variant === 'modal') {
2145
+ return;
2146
+ }
2147
+ closeTip();
2148
+ };
2149
+ const openTip = () => {
2150
+ if (!props.content) {
2151
+ return;
2152
+ }
2153
+ setShowTip(props.disabled ? false : true);
2154
+ };
2155
+ const closeTip = () => {
2156
+ if (!props.content) {
2157
+ return;
2158
+ }
2159
+ setShowTip(false);
2160
+ if (props.onClose) {
2161
+ props.onClose();
2162
+ }
2163
+ };
2164
+ const buttonStyles = css.css `
2165
+ font-weight: bold;
2166
+ width: 1.5rem;
2167
+ min-width:1.5rem;
2168
+ height: 1.5rem;
2169
+ padding: 0 !important;
2170
+ font-family: serif;
2171
+ display:inline-block;
2172
+ `;
2173
+ const button = React__namespace.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2174
+ if (props.variant === 'modal') {
2175
+ return (React__namespace.createElement(React__namespace.Fragment, null,
2176
+ button,
2177
+ React__namespace.createElement(Modal, { id: props.modalId, __debug: props.__modalDebug, show: showTip, heading: props.modalHeader, onClick: closeTip, className: css.css({
2178
+ whiteSpace: 'normal'
2179
+ }), closeButton: true },
2180
+ React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) }, props.content))));
2181
+ }
2182
+ else {
2183
+ return (React__namespace.createElement(Popover, { positions: props.positions, reposition: (_c = props.reposition) !== null && _c !== void 0 ? _c : false, isOpen: showTip, onClickOutside: closeTip, arrorColor: bgColor, border: '', backgroundColor: bgColor, parent: button, content: (React__namespace.createElement("div", { className: css.css({
2184
+ padding: '0.5rem',
2185
+ fontSize: '0.75rem',
2186
+ maxWidth: '22rem'
2187
+ }), style: { backgroundColor: bgColor, color: fontColor }, onMouseOut: closeTip }, props.content)) }));
2188
+ }
2189
+ };
2190
+
2191
+ const InputErrorDisplay = (props) => {
2192
+ const theme = useThemeSafely();
2193
+ return (React__namespace.createElement(Text, { className: css.css({
2194
+ minHeight: theme.controls.inputErrorMinHeight,
2195
+ lineHeight: theme.controls.inputErrorMinHeight,
2196
+ color: theme.colors.negative
2197
+ }), smaller: true, noPad: true }, props.error));
2198
+ };
2199
+
2200
+ const defaultMaxLength$1 = 100;
2201
+ const BaseInput = React__namespace.forwardRef((props, ref) => {
2202
+ var _a;
2203
+ const theme = useThemeSafely();
2204
+ const { rightControl, round, wrapperClassName } = props, nativeProps = __rest(props, ["rightControl", "round", "wrapperClassName"]);
2205
+ const inputStyles = css.css({
2206
+ fontFamily: theme.fonts.family,
2207
+ fontSize: theme.fonts.size,
2208
+ width: '100%',
2209
+ border: theme.controls.border,
2210
+ borderRadius: theme.controls.borderRadius,
2211
+ color: theme.colors.font,
2212
+ paddingLeft: theme.controls.padding,
2213
+ paddingRight: theme.controls.padding,
2214
+ height: theme.controls.height,
2215
+ transition: theme.controls.transition,
2216
+ ':focus': {
2217
+ outline: 'none',
2218
+ boxShadow: theme.controls.focusOutlineShadow
2219
+ },
2220
+ ':disabled': {
2221
+ backgroundColor: theme.colors.disabled,
2222
+ cursor: 'not-allowed'
2223
+ },
2224
+ ':invalid': {
2225
+ borderColor: theme.colors.required,
2226
+ ':focus': {
2227
+ boxShadow: theme.controls.focusOutlineRequiredShadow
2228
+ }
2229
+ },
2230
+ }, props.round && {
2231
+ borderRadius: theme.controls.roundRadius,
2232
+ paddingLeft: `calc(${theme.controls.padding} * 2)`,
2233
+ paddingRight: `calc(${theme.controls.padding} * 2)`
2234
+ }, props.readOnly && {
2235
+ backgroundColor: 'transparent',
2236
+ cursor: 'default',
2237
+ border: 'none',
2238
+ ':focus': {
2239
+ outline: 'none',
2240
+ boxShadow: 'none'
2241
+ }
2242
+ }, props.rightControl && {
2243
+ paddingRight: theme.controls.height
2244
+ });
2245
+ const inputElement = React__namespace.createElement("input", Object.assign({}, nativeProps, { ref: ref, autoComplete: (_a = nativeProps.autoComplete) !== null && _a !== void 0 ? _a : 'off', tabIndex: nativeProps.readOnly ? -1 : nativeProps.tabIndex, maxLength: nativeProps.maxLength || defaultMaxLength$1, className: css.cx(inputStyles, props.className) }));
2246
+ const inputWrapperStyles = css.css `
2247
+ width:100%;
2248
+ ${props.rightControl && `
2249
+ position: relative;
2250
+ `}
2251
+ `;
2252
+ const rightControlStyles = props.rightControl && css.css `
2253
+ position: absolute;
2254
+ right: ${theme.controls.padding};
2255
+ top: 0;
2256
+ bottom: 0;
2257
+ display: flex;
2258
+ align-items: center;
2259
+ ${props.round && `
2260
+ right: calc(${theme.controls.padding} * 2);
2261
+ `}
2262
+ `;
2263
+ return (React__namespace.createElement("div", { className: css.css({
2264
+ width: '100%'
2265
+ }) },
2266
+ React__namespace.createElement("div", { className: css.cx('input', inputWrapperStyles, wrapperClassName) },
2267
+ inputElement,
2268
+ props.rightControl && (React__namespace.createElement("div", { className: rightControlStyles }, props.rightControl))),
2269
+ React__namespace.createElement(InputErrorDisplay, { error: props.readOnly ? undefined : props.error })));
2270
+ });
2271
+
2272
+ const tryClampRange = (value, min, max) => {
2273
+ if (value === undefined) {
2274
+ return value;
2275
+ }
2276
+ if (isNaN(value)) {
2277
+ return undefined;
2278
+ }
2279
+ if (min !== undefined && value < min) {
2280
+ return min;
2281
+ }
2282
+ if (max !== undefined && value > max) {
2283
+ return max;
2284
+ }
2285
+ return value;
2286
+ };
2287
+ const isOutOfRange = (value, min, max) => {
2288
+ if (min !== undefined && value < min) {
2289
+ return true;
2290
+ }
2291
+ if (max !== undefined && value > max) {
2292
+ return true;
2293
+ }
2294
+ return false;
2295
+ };
2296
+ const getStepDecimalPlaces = (step) => {
2297
+ var _a, _b;
2298
+ if (!step) {
2299
+ return 0;
2300
+ }
2301
+ const strStep = typeof step === 'number' ? step.toString() : step;
2302
+ return (_b = (_a = strStep.split('.')[1]) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
2303
+ };
2304
+ const tryClampDecimals = (value, step = 0) => {
2305
+ if (value === undefined) {
2306
+ return value;
2307
+ }
2308
+ if (isNaN(value)) {
2309
+ return undefined;
2310
+ }
2311
+ const decimals = getStepDecimalPlaces(step);
2312
+ if (decimals === 0) {
2313
+ return Math.floor(value);
2314
+ }
2315
+ return parseFloat(value.toFixed(decimals));
2316
+ };
2317
+
2318
+ /** Common state handling for displaying validation messages with inputs. */
2319
+ const useInputValidationMessage = (ref, props) => {
2320
+ const [validationError, setValidationError] = React__default['default'].useState('');
2321
+ const updateErrorMessage = (customErrorOverride) => {
2322
+ var _a;
2323
+ const customError = customErrorOverride || props.customError || '';
2324
+ // set it OR clear it. either way, update it.
2325
+ (_a = ref.current) === null || _a === void 0 ? void 0 : _a.setCustomValidity(customError);
2326
+ setValidationError(customError || getValidationMessage(ref.current, props.patternErrorMessage));
2327
+ };
2328
+ React.useEffect(() => {
2329
+ updateErrorMessage();
2330
+ }, [props.customError]);
2331
+ React__default['default'].useEffect(() => {
2332
+ updateErrorMessage();
2333
+ }, []);
2334
+ return [validationError, updateErrorMessage];
2335
+ };
2336
+ const getValidationMessage = (element, patternErrorMessage) => {
2337
+ var _a;
2338
+ if (!element) {
2339
+ return '';
2340
+ }
2341
+ const validity = element.validity;
2342
+ if (validity.valid) {
2343
+ return '';
2344
+ }
2345
+ if (validity.customError) {
2346
+ return element.validationMessage;
2347
+ }
2348
+ if (validity.typeMismatch) {
2349
+ switch (element.type) {
2350
+ case 'url':
2351
+ return `Invalid URL.`;
2352
+ case 'email':
2353
+ return `Invalid email.`;
2354
+ default:
2355
+ return element.validationMessage;
2356
+ }
2357
+ }
2358
+ if (element instanceof HTMLInputElement) {
2359
+ if (validity.rangeOverflow) {
2360
+ return `Must be less than or equal to ${element.max}.`;
2361
+ }
2362
+ if (validity.rangeUnderflow) {
2363
+ return `Must be greater than or equal to ${element.min}.`;
2364
+ }
2365
+ if (validity.stepMismatch) {
2366
+ const decimalPlaces = getStepDecimalPlaces(element.step);
2367
+ if (decimalPlaces > 0) {
2368
+ const place = decimalPlaces === 1 ? 'place' : 'places';
2369
+ return `Limited to ${getStepDecimalPlaces(element.step)} decimal ${place}.`;
2370
+ }
2371
+ else {
2372
+ /*
2373
+ step is buggy!
2374
+
2375
+ at least in Chrome, setting step=5 will cause the browser to mark the field as invalid if the number is not divisible
2376
+ by 5. 55 is ok. 50 is ok. 59 is not ok.
2377
+
2378
+ to make things worse, if you enter an invalid number like 59 with step=5 and then clear the input, Chrome will tell
2379
+ you the number 5 is now invalid and should be 4 or 9.
2380
+ */
2381
+ return `Must be an integer.`;
2382
+ }
2383
+ }
2384
+ }
2385
+ if (validity.tooShort) {
2386
+ return `Must be at least ${((_a = element.minLength) !== null && _a !== void 0 ? _a : 0).toLocaleString()} characters in length.`;
2387
+ }
2388
+ if (validity.valueMissing) {
2389
+ return 'Required.';
2390
+ }
2391
+ if (validity.patternMismatch && patternErrorMessage) {
2392
+ return patternErrorMessage;
2393
+ }
2394
+ // unhandled. let the browser decide.
2395
+ return element.validationMessage;
2210
2396
  };
2211
2397
 
2212
- const InfoTip = (props) => {
2213
- var _a, _b;
2214
- const [showTip, setShowTip] = React__namespace.useState(false);
2215
- const theme = useThemeSafely();
2216
- const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
2217
- const fontColor = (_b = props.fontColor) !== null && _b !== void 0 ? _b : theme.colors.navFont;
2218
- const onClick = () => {
2219
- if (props.onClick) {
2220
- props.onClick();
2398
+ const dateRegex = /(\d{1,2})(?:\/|-)(\d{1,2})(?:\/|-)(\d{4})/;
2399
+ const datePattern = dateRegex.source;
2400
+ const invalidDateMessage = 'Invalid date.';
2401
+ const DateInput = React__namespace.forwardRef((props, ref) => {
2402
+ var _a;
2403
+ const [dateValue, setDateValue] = React__namespace.useState(props.value);
2404
+ const [textValue, setTextValue] = React__namespace.useState(parseDateString(props.value));
2405
+ const updateValues = React__namespace.useCallback((value) => {
2406
+ let newDateValue;
2407
+ let newTextValue;
2408
+ if (typeof value === 'number') {
2409
+ newDateValue = value;
2410
+ newTextValue = parseDateString(value);
2221
2411
  }
2222
- else if (props.content) {
2223
- openTip();
2412
+ else if (typeof value === 'string') {
2413
+ newDateValue = parseDateNumber(value);
2414
+ newTextValue = value;
2224
2415
  }
2225
- };
2226
- const onMouseOver = () => {
2227
- if (props.variant === 'modal') {
2416
+ setDateValue(newDateValue);
2417
+ setTextValue(newTextValue);
2418
+ }, []);
2419
+ const inputRef = (ref !== null && ref !== void 0 ? ref : React__namespace.useRef(null));
2420
+ const [validationError, updateErrorMessage] = useInputValidationMessage(inputRef, { customError: props.customError, patternErrorMessage: invalidDateMessage });
2421
+ const updateDateErrorMessages = React__namespace.useCallback(() => {
2422
+ let dateError = '';
2423
+ if (dateValue === undefined) {
2424
+ if (!!textValue) {
2425
+ // the text pattern is a valid date format, but the numbers are wrong. example: 05/35/2000.
2426
+ dateError = invalidDateMessage;
2427
+ }
2428
+ }
2429
+ else if (isOutOfRange(dateValue, props.min, props.max)) {
2430
+ // out of range
2431
+ if (props.min !== undefined && props.max !== undefined) {
2432
+ dateError = `Must be be between ${parseDateString(props.min)} and ${parseDateString(props.max)}.`;
2433
+ }
2434
+ else if (props.min !== undefined) {
2435
+ dateError = `Must be greater than or equal to ${parseDateString(props.min)}.`;
2436
+ }
2437
+ else {
2438
+ dateError = `Must be less than or equal to ${parseDateString(props.max)}.`;
2439
+ }
2440
+ }
2441
+ updateErrorMessage(dateError);
2442
+ }, [dateValue, textValue]);
2443
+ const [showCalendar, setShowCalendar] = React__namespace.useState(false);
2444
+ const [calendarDate, setCalendarDate] = React__namespace.useState(getCalendarDate(props.value, props.min, props.max));
2445
+ // controls the one-time focus set on show
2446
+ const needsFocus = React__namespace.useRef(false);
2447
+ const toggleCalendar = React__namespace.useCallback((show) => {
2448
+ var _a;
2449
+ if (show === showCalendar) {
2228
2450
  return;
2229
2451
  }
2230
- if (props.loadOnHover) {
2231
- props.loadOnHover().then(() => {
2232
- openTip();
2233
- }).catch(err => {
2234
- /* Not my responsiblity. */
2235
- });
2452
+ needsFocus.current = show;
2453
+ setShowCalendar(show);
2454
+ if (show) {
2455
+ setCalendarDate(getCalendarDate(dateValue, props.min, props.max));
2236
2456
  }
2237
2457
  else {
2238
- openTip();
2458
+ (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
2239
2459
  }
2240
- };
2241
- const onMouseOut = () => {
2242
- if (props.variant === 'modal') {
2243
- return;
2460
+ }, [dateValue, showCalendar]);
2461
+ const popover = React__namespace.useRef(null);
2462
+ const nativeProps = __rest(props, ["customError", "reposition", "onValueChange"]);
2463
+ useIgnoreMount(() => {
2464
+ var _a;
2465
+ if ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.checkValidity()) {
2466
+ props.onValueChange(dateValue);
2244
2467
  }
2245
- closeTip();
2246
- };
2247
- const openTip = () => {
2248
- if (!props.content) {
2249
- return;
2468
+ else {
2469
+ props.onValueChange(undefined);
2250
2470
  }
2251
- setShowTip(props.disabled ? false : true);
2252
- };
2253
- const closeTip = () => {
2254
- if (!props.content) {
2255
- return;
2471
+ updateDateErrorMessages();
2472
+ }, [dateValue]);
2473
+ useIgnoreMount(() => {
2474
+ updateDateErrorMessages();
2475
+ }, [textValue]);
2476
+ useIgnoreMount(() => {
2477
+ var _a;
2478
+ if (document.activeElement !== inputRef.current) {
2479
+ updateValues(props.value);
2480
+ setCalendarDate(getCalendarDate((_a = props.value) !== null && _a !== void 0 ? _a : 0, props.min, props.max));
2256
2481
  }
2257
- setShowTip(false);
2258
- if (props.onClose) {
2259
- props.onClose();
2482
+ updateDateErrorMessages();
2483
+ }, [props.value]);
2484
+ React__namespace.useLayoutEffect(() => {
2485
+ var _a, _b;
2486
+ if (needsFocus.current) {
2487
+ (_b = (_a = popover.current) === null || _a === void 0 ? void 0 : _a.querySelector('button')) === null || _b === void 0 ? void 0 : _b.focus();
2488
+ needsFocus.current = false;
2260
2489
  }
2261
- };
2262
- const buttonStyles = css.css `
2263
- font-weight: bold;
2264
- width: 1.5rem;
2265
- min-width:1.5rem;
2266
- height: 1.5rem;
2267
- padding: 0 !important;
2268
- font-family: serif;
2269
- display:inline-block;
2270
- `;
2271
- const button = React__namespace.createElement(Button, { className: buttonStyles, disabled: props.disabled, variant: 'circle', tabIndex: props.tabIndex, onClick: onClick, onMouseEnter: onMouseOver, onMouseLeave: onMouseOut }, "i");
2272
- if (props.variant === 'modal') {
2273
- return (React__namespace.createElement(React__namespace.Fragment, null,
2274
- button,
2275
- React__namespace.createElement(Modal, { id: props.modalId, debug: props.modalDebug, show: showTip, heading: props.modalHeader, onClick: closeTip, className: css.css({
2276
- whiteSpace: 'normal'
2277
- }), closeButton: true },
2278
- React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) }, props.content))));
2490
+ });
2491
+ const input = (React__namespace.createElement(BaseInput, Object.assign({}, nativeProps, { error: validationError, type: "text", ref: inputRef, value: textValue !== null && textValue !== void 0 ? textValue : '', maxLength: 10, placeholder: (_a = props.placeholder) !== null && _a !== void 0 ? _a : 'MM/DD/YYYY', pattern: datePattern, rightControl: (!props.readOnly && !props.disabled) ? (React__namespace.createElement(Button, { variant: "icon", readOnly: props.readOnly, disabled: props.disabled, small: true, style: {
2492
+ fontSize: '1rem'
2493
+ }, onClick: () => {
2494
+ toggleCalendar(!showCalendar);
2495
+ } },
2496
+ React__namespace.createElement(Icon, { id: "pickDate" }))) : undefined, onChange: e => {
2497
+ var _a;
2498
+ updateValues(e.target.value || undefined);
2499
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
2500
+ }, onFocus: e => {
2501
+ var _a;
2502
+ toggleCalendar(false);
2503
+ (_a = props.onFocus) === null || _a === void 0 ? void 0 : _a.call(props, e);
2504
+ }, onBlur: e => {
2505
+ var _a, _b;
2506
+ if (!((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.checkValidity())) {
2507
+ if (dateValue !== undefined) {
2508
+ if (isOutOfRange(dateValue, props.min, props.max)) {
2509
+ // try and fix the range
2510
+ updateValues(tryClampRange(dateValue, props.min, props.max));
2511
+ }
2512
+ }
2513
+ else {
2514
+ // just wipe it all
2515
+ updateValues(undefined);
2516
+ }
2517
+ }
2518
+ (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e);
2519
+ } })));
2520
+ return (React__namespace.createElement(Popover, { reposition: props.reposition, isOpen: showCalendar, onClickOutside: () => {
2521
+ toggleCalendar(false);
2522
+ }, parent: input, content: (React__namespace.createElement("div", { ref: popover, className: css.css({
2523
+ paddingLeft: '1rem',
2524
+ paddingRight: '1rem',
2525
+ paddingBottom: '1rem'
2526
+ }) },
2527
+ React__namespace.createElement(Calendar, { onClick: date => {
2528
+ updateValues(date.valueOf());
2529
+ toggleCalendar(false);
2530
+ }, customTitle: React__namespace.createElement("div", { className: css.css({
2531
+ display: 'flex',
2532
+ justifyContent: 'space-between',
2533
+ alignItems: 'center'
2534
+ }) },
2535
+ React__namespace.createElement(Button, { disabled: !!props.min && dateFns.isBefore(dateFns.endOfMonth(dateFns.addMonths(calendarDate, -1)), props.min), small: true, variant: "icon", onClick: () => setCalendarDate(dateFns.addMonths(calendarDate, -1)) },
2536
+ React__namespace.createElement(Icon, { id: "pagerLeft" })),
2537
+ React__namespace.createElement(Text, { align: "center" },
2538
+ dateFns.format(calendarDate, 'LLLL'),
2539
+ " ",
2540
+ calendarDate.getFullYear()),
2541
+ React__namespace.createElement(Button, { disabled: !!props.max && dateFns.isAfter(dateFns.startOfMonth(dateFns.addMonths(calendarDate, 1)), props.max), small: true, variant: "icon", onClick: () => setCalendarDate(dateFns.addMonths(calendarDate, 1)) },
2542
+ React__namespace.createElement(Icon, { id: "pagerRight" }))), month: calendarDate.getMonth() + 1, year: calendarDate.getFullYear(), showCurrent: true, smallHeader: true, selectedValue: dateValue, cellSize: '2rem', min: props.min, max: props.max }))) }));
2543
+ });
2544
+ const parseDateNumber = (rawValue) => {
2545
+ if (!rawValue) {
2546
+ return undefined;
2279
2547
  }
2280
- else {
2281
- return (React__namespace.createElement(Popover, { reposition: false, isOpen: showTip, onClickOutside: closeTip, arrorColor: bgColor, border: '', backgroundColor: bgColor, parent: button, content: (React__namespace.createElement("div", { className: css.css({
2282
- padding: '0.5rem',
2283
- fontSize: '0.75rem',
2284
- maxWidth: '22rem'
2285
- }), style: { backgroundColor: bgColor, color: fontColor }, onMouseOut: closeTip }, props.content)) }));
2548
+ let dateMs;
2549
+ const dateParts = dateRegex.exec(rawValue);
2550
+ if (dateParts) {
2551
+ const month = parseInt(dateParts[1], 10) - 1;
2552
+ const day = parseInt(dateParts[2], 10);
2553
+ const year = parseInt(dateParts[3], 10);
2554
+ if (dateFns.isExists(year, month, day)) {
2555
+ dateMs = new Date(year, month, day).valueOf();
2556
+ }
2557
+ }
2558
+ return dateMs;
2559
+ };
2560
+ const parseDateString = (dateMs) => {
2561
+ if (typeof dateMs === 'number') {
2562
+ return dateFns.format(dateMs, 'MM/dd/yyyy');
2563
+ }
2564
+ return undefined;
2565
+ };
2566
+ const getCalendarDate = (date, min, max) => {
2567
+ let calendarDate = date ? new Date(date) : new Date();
2568
+ // if there is a min/max we don't want the calendar to open to the current date if it's out of range
2569
+ if (min && dateFns.isBefore(calendarDate, min)) {
2570
+ calendarDate = new Date(min);
2286
2571
  }
2572
+ else if (max && dateFns.isAfter(calendarDate, max)) {
2573
+ calendarDate = new Date(max);
2574
+ }
2575
+ return calendarDate;
2287
2576
  };
2288
2577
 
2289
2578
  const NumberInput = React__namespace.forwardRef((props, ref) => {
2290
- const [localValue, setLocalValue] = React__namespace.useState(undefined);
2579
+ const [localValue, setLocalValue] = React__namespace.useState(props.value);
2291
2580
  const inputRef = (ref !== null && ref !== void 0 ? ref : React__namespace.useRef(null));
2292
- const nativeProps = __rest(props, ["customValidityChecks", "constrainOnBlur"]);
2293
- React__namespace.useEffect(() => {
2294
- //TB: INPUT apply validation
2295
- // sync local value to outer value
2296
- setLocalValue(props.value);
2581
+ const [validationError, updateErrorMessage] = useInputValidationMessage(inputRef, props);
2582
+ const nativeProps = __rest(props, ["customError", "onValueChange"]);
2583
+ useIgnoreMount(() => {
2584
+ var _a;
2585
+ if ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.checkValidity()) {
2586
+ props.onValueChange(localValue);
2587
+ }
2588
+ else {
2589
+ props.onValueChange(undefined);
2590
+ }
2591
+ updateErrorMessage();
2592
+ }, [localValue]);
2593
+ useIgnoreMount(() => {
2594
+ if (document.activeElement !== inputRef.current) {
2595
+ setLocalValue(props.value);
2596
+ }
2597
+ updateErrorMessage();
2297
2598
  }, [props.value]);
2298
- return (React__namespace.createElement(BaseInput, Object.assign({}, nativeProps, { ref: ref, maxLength: 20, type: "number", value: localValue !== null && localValue !== void 0 ? localValue : '', onChange: e => {
2599
+ return (React__namespace.createElement(BaseInput, Object.assign({}, nativeProps, { error: validationError, type: "number", ref: inputRef, value: localValue !== null && localValue !== void 0 ? localValue : '', maxLength: 20, onChange: e => {
2299
2600
  var _a;
2300
2601
  const newLocalValue = parseNumber(e.target.value);
2301
- if (props.customValidityChecks) {
2302
- let error;
2303
- for (const func of props.customValidityChecks) {
2304
- error = func(newLocalValue);
2305
- if (error) {
2306
- break;
2307
- }
2308
- }
2309
- e.target.setCustomValidity(error !== null && error !== void 0 ? error : '');
2310
- }
2311
2602
  setLocalValue(newLocalValue);
2312
- (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, newLocalValue, e);
2603
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
2313
2604
  }, onBlur: e => {
2314
- var _a;
2315
- if (props.onBlur) {
2316
- let currentValue = localValue;
2317
- if ((_a = props.constrainOnBlur) !== null && _a !== void 0 ? _a : true) {
2318
- currentValue = tryClampRange(currentValue, props.min, props.max);
2319
- currentValue = tryClampDecimals(currentValue, props.step);
2320
- }
2321
- if (currentValue !== localValue) {
2322
- setLocalValue(currentValue);
2323
- }
2324
- // after all we've done, it's still possible to have strange characters in the box.
2325
- // examples are '-' and 'e'. the box will show as invalid and no number will be sent from the control or be included in the local state.
2326
- // here we will clean that up locally in the control.
2327
- if (!e.target.checkValidity()) {
2328
- e.target.value = '';
2329
- }
2330
- props.onBlur(currentValue, e);
2331
- }
2332
- }, onKeyDown: e => {
2333
2605
  var _a, _b;
2334
- if (e.key === 'Enter') {
2335
- // handle form submission on enter for onBlur-only situations.
2336
- // onBlur will not be fired so any local change will not be communicated to the outside.
2337
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
2606
+ let adjustedValue = localValue;
2607
+ if (e.target.validity.customError) {
2608
+ // if we're invalid due to a custom error, just wipe everything
2609
+ adjustedValue = undefined;
2610
+ }
2611
+ else {
2612
+ // try and fix the value
2613
+ adjustedValue = tryClampRange(adjustedValue, props.min, props.max);
2614
+ adjustedValue = tryClampDecimals(adjustedValue, props.step);
2338
2615
  }
2339
- (_b = props.onKeyDown) === null || _b === void 0 ? void 0 : _b.call(props, e);
2616
+ setLocalValue(adjustedValue);
2617
+ // makes our displayed value always matches the adjusted value
2618
+ // examples of failures are 'e', '-', and 5.0 in an integer (step=0) field.
2619
+ e.target.value = (_a = adjustedValue === null || adjustedValue === void 0 ? void 0 : adjustedValue.toString()) !== null && _a !== void 0 ? _a : '';
2620
+ (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e);
2340
2621
  } })));
2341
2622
  });
2342
- const tryClampDecimals = (value, step = 0) => {
2343
- var _a;
2344
- if (value === undefined) {
2345
- return value;
2346
- }
2347
- if (isNaN(value)) {
2348
- return undefined;
2349
- }
2350
- const decimals = step === 0 ? 0 : (_a = step.toString().split('.')[1]) === null || _a === void 0 ? void 0 : _a.length;
2351
- if (decimals === 0) {
2352
- return Math.floor(value);
2353
- }
2354
- return parseFloat(value.toFixed(decimals));
2355
- };
2356
2623
  const parseNumber = (rawValue) => {
2357
2624
  let value;
2358
2625
  if (rawValue) {
@@ -2366,39 +2633,41 @@ const parseNumber = (rawValue) => {
2366
2633
 
2367
2634
  const TextInput = React__namespace.forwardRef((props, ref) => {
2368
2635
  var _a;
2369
- const [localValue, setLocalValue] = React__namespace.useState(undefined);
2636
+ const [localValue, setLocalValue] = React__namespace.useState(props.value);
2370
2637
  const inputRef = (ref !== null && ref !== void 0 ? ref : React__namespace.useRef(null));
2371
- const nativeProps = __rest(props, ["customValidityChecks"]);
2372
- React__namespace.useEffect(() => {
2373
- // sync local value to outer value
2374
- setLocalValue(props.value);
2638
+ const [validationError, updateErrorMessage] = useInputValidationMessage(inputRef, props);
2639
+ const nativeProps = __rest(props, ["emptyString", "onValueChange", "customError", "patternErrorMessage"]);
2640
+ useIgnoreMount(() => {
2641
+ var _a;
2642
+ if ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.checkValidity()) {
2643
+ props.onValueChange(localValue);
2644
+ }
2645
+ else {
2646
+ props.onValueChange(undefined);
2647
+ }
2648
+ updateErrorMessage();
2649
+ }, [localValue]);
2650
+ useIgnoreMount(() => {
2651
+ if (document.activeElement !== inputRef.current) {
2652
+ setLocalValue(props.value);
2653
+ }
2654
+ updateErrorMessage();
2375
2655
  }, [props.value]);
2376
- return (React__namespace.createElement(BaseInput, Object.assign({}, nativeProps, { type: (_a = props.type) !== null && _a !== void 0 ? _a : 'text', ref: inputRef, value: localValue !== null && localValue !== void 0 ? localValue : '', onChange: e => {
2656
+ return (React__namespace.createElement(BaseInput, Object.assign({}, nativeProps, { error: validationError, type: (_a = props.type) !== null && _a !== void 0 ? _a : 'text', ref: inputRef, value: localValue !== null && localValue !== void 0 ? localValue : '', onChange: e => {
2377
2657
  var _a;
2378
- const value = e.target.value || undefined;
2379
- if (props.customValidityChecks) {
2380
- let error;
2381
- for (const func of props.customValidityChecks) {
2382
- error = func(value);
2383
- if (error) {
2384
- break;
2385
- }
2386
- }
2387
- e.target.setCustomValidity(error !== null && error !== void 0 ? error : '');
2388
- }
2389
- setLocalValue(value);
2390
- (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, value, e);
2658
+ setLocalValue(props.emptyString ? e.target.value : e.target.value || undefined);
2659
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
2391
2660
  }, onBlur: e => {
2392
- var _a;
2393
- (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, localValue, e);
2394
- }, onKeyDown: e => {
2395
2661
  var _a, _b;
2396
- if (e.key === 'Enter') {
2397
- // handle form submission on enter for onBlur-only situations.
2398
- // onBlur will not be fired so any local change will not be communicated to the outside.
2399
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
2662
+ if (!e.target.checkValidity()) {
2663
+ setLocalValue(undefined);
2664
+ }
2665
+ else if ((_a = props.trim) !== null && _a !== void 0 ? _a : true) {
2666
+ setLocalValue(currentValue => {
2667
+ return currentValue === null || currentValue === void 0 ? void 0 : currentValue.trim();
2668
+ });
2400
2669
  }
2401
- (_b = props.onKeyDown) === null || _b === void 0 ? void 0 : _b.call(props, e);
2670
+ (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e);
2402
2671
  } })));
2403
2672
  });
2404
2673
 
@@ -2494,6 +2763,9 @@ const Label = (props) => {
2494
2763
  From https://fireship.io/snippets/use-media-query-hook/.
2495
2764
  Tried using https://www.npmjs.com/package/react-media, but it cause Webpack build issues.
2496
2765
  */
2766
+ /** React wrapper around window resizing and window.matchMedia.
2767
+ * https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia
2768
+ */
2497
2769
  const useMediaQuery = (query) => {
2498
2770
  const [matches, setMatches] = React.useState(false);
2499
2771
  React.useEffect(() => {
@@ -2509,11 +2781,13 @@ const useMediaQuery = (query) => {
2509
2781
  };
2510
2782
 
2511
2783
  const Nav = (props) => {
2784
+ var _a, _b;
2512
2785
  const nav = React__namespace.useRef(null);
2513
2786
  const theme = useThemeSafely();
2514
2787
  const totalNavOffset = `calc(${theme.layout.navWidth} + 20px)`;
2515
2788
  const backdrop = React__namespace.useContext(BackdropContext);
2516
2789
  const isLargeScreen = useMediaQuery(`(min-width:${theme.breakpoints.desktop})`);
2790
+ const log = useLogger(`Nav ${(_a = props.id) !== null && _a !== void 0 ? _a : '?'}`, (_b = props.__debug) !== null && _b !== void 0 ? _b : false);
2517
2791
  const slideRight = css.keyframes `
2518
2792
  0% {
2519
2793
  transform: translateX(0);
@@ -2574,9 +2848,11 @@ const Nav = (props) => {
2574
2848
  props.toggle(false);
2575
2849
  }
2576
2850
  }, [backdrop.showing]);
2577
- React__namespace.useEffect(() => {
2578
- backdrop.setShow(props.show);
2579
- }, [props.show]);
2851
+ useBooleanChanged((current, previous) => {
2852
+ var _a;
2853
+ log('show changed', `${previous !== null && previous !== void 0 ? previous : 'undefined'} > ${current}`);
2854
+ backdrop.setShow(current, (_a = props.id) !== null && _a !== void 0 ? _a : 'Nav');
2855
+ }, props.show);
2580
2856
  React__namespace.useLayoutEffect(() => {
2581
2857
  if (nav && nav.current) {
2582
2858
  if (props.show) {
@@ -2607,6 +2883,7 @@ const Nav = (props) => {
2607
2883
  return (React__namespace.createElement("nav", { ref: nav, className: css.cx('nav', navStyles, props.className) }, props.children));
2608
2884
  };
2609
2885
 
2886
+ //TB: FUTURE make just a basic styled link so you don't have to go with React Router if you don't want to.
2610
2887
  const OmniLink = (props) => {
2611
2888
  var _a, _b, _c;
2612
2889
  const theme = useThemeSafely();
@@ -2783,7 +3060,7 @@ const OmniLink = (props) => {
2783
3060
  const Picker = (props) => {
2784
3061
  const selectProps = __rest(props
2785
3062
  // if we put numbers in, we expect them out
2786
- , ["value", "options", "onChange", "readonly", "round"]);
3063
+ , ["value", "options", "onValueChange", "readOnly", "round", "controlAlign"]);
2787
3064
  // if we put numbers in, we expect them out
2788
3065
  let isNumber = false;
2789
3066
  if (props.options && props.options.length) {
@@ -2819,7 +3096,7 @@ const Picker = (props) => {
2819
3096
  ${props.round && `
2820
3097
  border-radius: ${theme.controls.roundRadius};
2821
3098
  `}
2822
- ${props.readonly && `
3099
+ ${props.readOnly && `
2823
3100
  background-color: transparent !important;
2824
3101
  border: none;
2825
3102
  -webkit-appearance: none;
@@ -2830,9 +3107,9 @@ const Picker = (props) => {
2830
3107
  }
2831
3108
  `}
2832
3109
  `;
2833
- return (React__namespace.createElement("select", Object.assign({}, selectProps, { tabIndex: props.readonly ? -1 : selectProps.tabIndex, className: css.cx('picker', selectStyles, props.className), value: props.value, onKeyDown: e => {
3110
+ const select = (React__namespace.createElement("select", Object.assign({}, selectProps, { tabIndex: props.readOnly ? -1 : selectProps.tabIndex, className: css.cx('picker', selectStyles, props.className), value: props.value, onKeyDown: e => {
2834
3111
  var _a;
2835
- if (props.readonly) {
3112
+ if (props.readOnly) {
2836
3113
  if (e.keyCode === 9) {
2837
3114
  //TAB
2838
3115
  return;
@@ -2845,7 +3122,7 @@ const Picker = (props) => {
2845
3122
  }
2846
3123
  }, onMouseDown: e => {
2847
3124
  var _a;
2848
- if (props.readonly) {
3125
+ if (props.readOnly) {
2849
3126
  e.preventDefault();
2850
3127
  e.stopPropagation();
2851
3128
  }
@@ -2853,6 +3130,7 @@ const Picker = (props) => {
2853
3130
  (_a = selectProps.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(selectProps, e);
2854
3131
  }
2855
3132
  }, onChange: e => {
3133
+ var _a;
2856
3134
  let val = e.target.value;
2857
3135
  if (isNumber) {
2858
3136
  val = parseInt(val, 10);
@@ -2860,7 +3138,8 @@ const Picker = (props) => {
2860
3138
  val = '';
2861
3139
  }
2862
3140
  }
2863
- props.onChange(val, e);
3141
+ props.onValueChange(val);
3142
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
2864
3143
  } }), (props.options || []).map(o => {
2865
3144
  var _a;
2866
3145
  let val;
@@ -2875,6 +3154,14 @@ const Picker = (props) => {
2875
3154
  }
2876
3155
  return React__namespace.createElement("option", { key: val, value: val }, label);
2877
3156
  })));
3157
+ if (props.controlAlign) {
3158
+ return (React__namespace.createElement("span", { className: css.css({
3159
+ display: 'inline-block',
3160
+ width: '100%',
3161
+ paddingBottom: theme.controls.inputErrorMinHeight
3162
+ }) }, select));
3163
+ }
3164
+ return select;
2878
3165
  };
2879
3166
 
2880
3167
  const Pager = (props) => {
@@ -2939,8 +3226,8 @@ const BoundMemoryPager = (p) => {
2939
3226
  var _a, _b, _c;
2940
3227
  const { pager, showPageText } = p, rest = __rest(p, ["pager", "showPageText"]);
2941
3228
  return (React__namespace.createElement(Pager, Object.assign({}, rest, { pageIndex: p.showPageText ? pager.page : undefined, totalPages: p.showPageText ? pager.totalPages : undefined, canGoNext: pager.hasNext, canGoPrevious: pager.hasPrevious, minItem: pager.minItemIndex + 1, maxItem: pager.maxItemIndex + 1, totalItems: pager.totalItems, leftControls: pager.limitOptions.length > 1 && p.onLimit ? (React__namespace.createElement(Label, { text: (_a = p.limitText) !== null && _a !== void 0 ? _a : 'Limit', orientation: "horizontal" },
2942
- React__namespace.createElement(Picker, { value: pager.limit, options: pager.limitOptions, onChange: v => { var _a; return (_a = p.onLimit) === null || _a === void 0 ? void 0 : _a.call(p, v); } }))) : undefined, rightControls: pager.sortOptions.length > 1 && p.onSort ? (React__namespace.createElement(Label, { text: (_b = p.sortText) !== null && _b !== void 0 ? _b : 'Sort', orientation: "horizontalReverse" },
2943
- React__namespace.createElement(Picker, { value: (_c = pager.sort) !== null && _c !== void 0 ? _c : '', options: pager.sortOptions, onChange: v => { var _a; return (_a = p.onSort) === null || _a === void 0 ? void 0 : _a.call(p, v); } }))) : undefined, page: d => {
3229
+ React__namespace.createElement(Picker, { value: pager.limit, options: pager.limitOptions, onValueChange: v => { var _a; return (_a = p.onLimit) === null || _a === void 0 ? void 0 : _a.call(p, v); } }))) : undefined, rightControls: pager.sortOptions.length > 1 && p.onSort ? (React__namespace.createElement(Label, { text: (_b = p.sortText) !== null && _b !== void 0 ? _b : 'Sort', orientation: "horizontalReverse" },
3230
+ React__namespace.createElement(Picker, { value: (_c = pager.sort) !== null && _c !== void 0 ? _c : '', options: pager.sortOptions, onValueChange: v => { var _a; return (_a = p.onSort) === null || _a === void 0 ? void 0 : _a.call(p, v); } }))) : undefined, page: d => {
2944
3231
  p.onPage(d);
2945
3232
  } })));
2946
3233
  };
@@ -2951,8 +3238,8 @@ const BoundStaticPager = (p) => {
2951
3238
  const showLimit = !!(result.limit && p.limitOptions && p.limitOptions.length > 1 && p.onLimit);
2952
3239
  const showSort = !!(p.sort !== undefined && p.sortOptions && p.sortOptions.length > 1 && p.onSort);
2953
3240
  return (React__namespace.createElement(Pager, Object.assign({}, rest, { pageIndex: p.showPageText ? result.page : undefined, totalPages: p.showPageText ? result.totalPages : undefined, canGoNext: result.hasNext, canGoPrevious: result.hasPrevious, minItem: result.minPageItemIndex + 1, maxItem: result.maxPageItemIndex + 1, totalItems: result.total, leftControls: showLimit ? (React__namespace.createElement(Label, { text: (_a = p.limitText) !== null && _a !== void 0 ? _a : 'Limit', orientation: "horizontal" },
2954
- React__namespace.createElement(Picker, { value: (_b = result.limit) !== null && _b !== void 0 ? _b : 1, options: (_c = p.limitOptions) !== null && _c !== void 0 ? _c : [1], onChange: v => { var _a; return (_a = p.onLimit) === null || _a === void 0 ? void 0 : _a.call(p, v); } }))) : undefined, rightControls: showSort ? (React__namespace.createElement(Label, { text: (_d = p.sortText) !== null && _d !== void 0 ? _d : 'Sort', orientation: "horizontalReverse" },
2955
- React__namespace.createElement(Picker, { value: (_e = p.sort) !== null && _e !== void 0 ? _e : '', options: (_f = p.sortOptions) !== null && _f !== void 0 ? _f : [], onChange: v => {
3241
+ React__namespace.createElement(Picker, { value: (_b = result.limit) !== null && _b !== void 0 ? _b : 1, options: (_c = p.limitOptions) !== null && _c !== void 0 ? _c : [1], onValueChange: v => { var _a; return (_a = p.onLimit) === null || _a === void 0 ? void 0 : _a.call(p, v); } }))) : undefined, rightControls: showSort ? (React__namespace.createElement(Label, { text: (_d = p.sortText) !== null && _d !== void 0 ? _d : 'Sort', orientation: "horizontalReverse" },
3242
+ React__namespace.createElement(Picker, { value: (_e = p.sort) !== null && _e !== void 0 ? _e : '', options: (_f = p.sortOptions) !== null && _f !== void 0 ? _f : [], onValueChange: v => {
2956
3243
  var _a;
2957
3244
  (_a = p.onSort) === null || _a === void 0 ? void 0 : _a.call(p, v);
2958
3245
  } }))) : undefined, page: d => {
@@ -2960,6 +3247,7 @@ const BoundStaticPager = (p) => {
2960
3247
  } })));
2961
3248
  };
2962
3249
 
3250
+ /** A page of data with helpers props. */
2963
3251
  class PagedResult {
2964
3252
  constructor(items = [], total = 0, page = 0, limit = 0) {
2965
3253
  this.items = items;
@@ -2970,10 +3258,6 @@ class PagedResult {
2970
3258
  static fromDto(dto) {
2971
3259
  return new PagedResult(dto.items, dto.total, dto.page, dto.limit);
2972
3260
  }
2973
- // allItems: T[]
2974
- // get pageItems(): T[] {
2975
- // return this.allItems.slice(this.minPageItemIndex, this.maxPageItemIndex + 1)
2976
- // }
2977
3261
  /** Helper for items.length */
2978
3262
  get length() {
2979
3263
  return this.items ? this.items.length : 0;
@@ -3097,7 +3381,7 @@ class PagedResult {
3097
3381
  }
3098
3382
  }
3099
3383
 
3100
- /** For in-memory paging. */
3384
+ /** In-memory pager. */
3101
3385
  class ItemPager {
3102
3386
  constructor(allItems, options) {
3103
3387
  var _a, _b, _c, _d;
@@ -3347,6 +3631,7 @@ const cleanPct = (value) => {
3347
3631
  return 0;
3348
3632
  };
3349
3633
 
3634
+ /** Provides stateful notifications around async calls. */
3350
3635
  const useWaiting = (func) => {
3351
3636
  // Guard against the owner of this hook being unmounted at the time of .finally.
3352
3637
  const isCancelled = React.useRef(false);
@@ -3379,12 +3664,12 @@ const SearchBox = (props) => {
3379
3664
  return (_c = props.onSubmit) === null || _c === void 0 ? void 0 : _c.call(props);
3380
3665
  });
3381
3666
  const theme = useThemeSafely();
3382
- const submitButton = (React__namespace.createElement(Button, { disabled: waiting, readonly: !props.onSubmit, type: "submit", className: css.css({
3667
+ const submitButton = (React__namespace.createElement(Button, { disabled: waiting, readOnly: !props.onSubmit, type: "submit", className: css.css({
3383
3668
  color: `${theme.colors.font} !important;`,
3384
3669
  fontSize: '1rem'
3385
3670
  }), variant: "icon", small: true },
3386
3671
  React__namespace.createElement(Icon, { id: waiting ? 'waiting' : 'search', spin: waiting })));
3387
- //TB: replace with new inputs
3672
+ //TB: FUTURE replace with new inputs
3388
3673
  return (React__namespace.createElement(Form, { role: "search", className: css.cx('searchBox', props.className), onSubmit: onSubmit },
3389
3674
  React__namespace.createElement(Input, { id: props.id, debounceMs: props.debounceMs, disabled: waiting, type: "text", value: props.value, placeholder: props.placeholder, round: props.round, onChange: props.onChange, rightControl: submitButton })));
3390
3675
  };
@@ -3417,6 +3702,28 @@ const GlobalStyles = () => {
3417
3702
  return null;
3418
3703
  };
3419
3704
 
3705
+ const getCurrencyDisplay = (value, isCents, denomination = '$') => {
3706
+ let actualValue = value || 0;
3707
+ if (isCents) {
3708
+ actualValue /= 100;
3709
+ }
3710
+ return `${denomination}${actualValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
3711
+ };
3712
+ /** Converts an enum to an array of entities with id and name. The enum can be an integer or string enum.*/
3713
+ const enumToEntities = (enumObj) => {
3714
+ const entities = [];
3715
+ for (const key in enumObj) {
3716
+ if (isNaN(parseInt(key, 10))) {
3717
+ entities.push({
3718
+ id: enumObj[key],
3719
+ name: key
3720
+ });
3721
+ }
3722
+ }
3723
+ return entities;
3724
+ };
3725
+ //TB: FUTURE Expose in UI.
3726
+
3420
3727
  const Slider = (p) => {
3421
3728
  const theme = useThemeSafely();
3422
3729
  const currentValue = React.useRef(p.value);
@@ -3562,7 +3869,7 @@ const TabHeader = (p) => {
3562
3869
  });
3563
3870
  }
3564
3871
  return (React__namespace.createElement("li", { key: index, className: tabStyles },
3565
- React__namespace.createElement(Button, { className: buttonStyles, variant: buttonVariant, title: tab.name, readonly: active, onClick: () => {
3872
+ React__namespace.createElement(Button, { className: buttonStyles, variant: buttonVariant, title: tab.name, readOnly: active, onClick: () => {
3566
3873
  setTabIndex(index);
3567
3874
  if (p.onTabChanged) {
3568
3875
  p.onTabChanged(index);
@@ -3698,13 +4005,29 @@ const defaultMaxLength = 200;
3698
4005
  const defaultRows = 10;
3699
4006
  const TextArea = React__namespace.forwardRef((props, ref) => {
3700
4007
  var _a, _b;
3701
- const [localValue, setLocalValue] = React__namespace.useState(undefined);
4008
+ const [localValue, setLocalValue] = React__namespace.useState(props.value);
3702
4009
  const inputRef = (ref !== null && ref !== void 0 ? ref : React__namespace.useRef(null));
3703
- const nativeProps = __rest(props, ["customValidityChecks"]);
4010
+ const [validationError, updateErrorMessage] = useInputValidationMessage(inputRef, props);
4011
+ const nativeProps = __rest(props, ["onValueChange"]);
3704
4012
  const theme = useThemeSafely();
3705
4013
  React__namespace.useEffect(() => {
3706
- // sync local value to outer value
3707
- setLocalValue(props.value);
4014
+ updateErrorMessage();
4015
+ }, []);
4016
+ useIgnoreMount(() => {
4017
+ var _a;
4018
+ if ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.checkValidity()) {
4019
+ props.onValueChange(localValue);
4020
+ }
4021
+ else {
4022
+ props.onValueChange(undefined);
4023
+ }
4024
+ updateErrorMessage();
4025
+ }, [localValue]);
4026
+ useIgnoreMount(() => {
4027
+ if (document.activeElement !== inputRef.current) {
4028
+ setLocalValue(props.value);
4029
+ }
4030
+ updateErrorMessage();
3708
4031
  }, [props.value]);
3709
4032
  const styles = css.css({
3710
4033
  maxWidth: '100%',
@@ -3743,35 +4066,24 @@ const TextArea = React__namespace.forwardRef((props, ref) => {
3743
4066
  boxShadow: 'none'
3744
4067
  }
3745
4068
  });
3746
- return (React__namespace.createElement("textarea", Object.assign({}, nativeProps, { className: css.cx(styles, props.className), autoComplete: (_a = props.autoComplete) !== null && _a !== void 0 ? _a : 'off', tabIndex: props.readOnly ? -1 : props.tabIndex, maxLength: props.maxLength || defaultMaxLength, rows: (_b = props.rows) !== null && _b !== void 0 ? _b : defaultRows, ref: ref, value: localValue !== null && localValue !== void 0 ? localValue : '', onChange: e => {
3747
- var _a;
3748
- const value = e.target.value || undefined;
3749
- if (props.customValidityChecks) {
3750
- let error;
3751
- for (const func of props.customValidityChecks) {
3752
- error = func(value);
3753
- if (error) {
3754
- break;
3755
- }
3756
- }
3757
- e.target.setCustomValidity(error !== null && error !== void 0 ? error : '');
3758
- }
3759
- setLocalValue(value);
3760
- (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, value, e);
3761
- }, onBlur: e => {
3762
- var _a;
3763
- (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, localValue, e);
3764
- }, onKeyDown: e => {
3765
- var _a, _b;
3766
- if (e.key === 'Enter') {
3767
- // handle form submission on enter for onBlur-only situations.
3768
- // onBlur will not be fired so any local change will not be communicated to the outside.
3769
- if (!props.onChange) {
3770
- (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur();
4069
+ return (React__namespace.createElement("span", { className: css.css({
4070
+ display: 'inline-block',
4071
+ width: '100%'
4072
+ }) },
4073
+ React__namespace.createElement("textarea", Object.assign({}, nativeProps, { className: css.cx(styles, props.className), autoComplete: (_a = props.autoComplete) !== null && _a !== void 0 ? _a : 'off', tabIndex: props.readOnly ? -1 : props.tabIndex, maxLength: props.maxLength || defaultMaxLength, rows: (_b = props.rows) !== null && _b !== void 0 ? _b : defaultRows, ref: inputRef, value: localValue !== null && localValue !== void 0 ? localValue : '', onChange: e => {
4074
+ var _a;
4075
+ setLocalValue(e.target.value || undefined);
4076
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
4077
+ }, onBlur: e => {
4078
+ var _a;
4079
+ if (!props.noTrim) {
4080
+ setLocalValue(currentValue => {
4081
+ return currentValue === null || currentValue === void 0 ? void 0 : currentValue.trim();
4082
+ });
3771
4083
  }
3772
- }
3773
- (_b = props.onKeyDown) === null || _b === void 0 ? void 0 : _b.call(props, e);
3774
- } })));
4084
+ (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
4085
+ } })),
4086
+ React__namespace.createElement(InputErrorDisplay, { error: validationError })));
3775
4087
  });
3776
4088
 
3777
4089
  const ToggleButton = (props) => {
@@ -3841,10 +4153,9 @@ const ToggleButtonGroup = (props) => {
3841
4153
  })));
3842
4154
  };
3843
4155
 
3844
- const TogglePasswordInput = (p) => {
4156
+ const TogglePasswordInput = React__namespace.forwardRef((props, ref) => {
3845
4157
  const [show, setShow] = React__namespace.useState(false);
3846
- //TB: replace with new inputs
3847
- return (React__namespace.createElement(Input, Object.assign({}, p, { type: show ? 'text' : 'password', rightControl: (React__namespace.createElement(Button, { small: true, style: {
4158
+ return (React__namespace.createElement(TextInput, Object.assign({}, props, { ref: ref, type: show ? 'text' : 'password', rightControl: (React__namespace.createElement(Button, { small: true, style: {
3848
4159
  // small button is required here due to the icon pushing outside the boundries of the
3849
4160
  // parent textbox. increasing the font size here to fill the small button.
3850
4161
  fontSize: '1rem'
@@ -3852,24 +4163,21 @@ const TogglePasswordInput = (p) => {
3852
4163
  setShow(previous => !previous);
3853
4164
  } },
3854
4165
  React__namespace.createElement(Icon, { id: show ? 'show' : 'hide' }))) })));
3855
- };
4166
+ });
3856
4167
 
3857
4168
  const WaitingIndicator = (p) => {
3858
4169
  var _a, _b;
3859
4170
  const [show, setShow] = React.useState(p.show);
3860
4171
  const hideTimer = React.useRef(0);
3861
4172
  const lastShowStatus = React.useRef(false);
3862
- const log = useLogger(`WaitingIndicator ${(_a = p.id) !== null && _a !== void 0 ? _a : '?'}`, (_b = p.debug) !== null && _b !== void 0 ? _b : false);
3863
- if (p.debug) {
4173
+ const log = useLogger(`WaitingIndicator ${(_a = p.id) !== null && _a !== void 0 ? _a : '?'}`, (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
4174
+ if (p.__debug) {
3864
4175
  React.useEffect(() => {
3865
4176
  log('mounted');
3866
4177
  return () => {
3867
4178
  log('unmounted');
3868
4179
  };
3869
4180
  }, []);
3870
- React.useEffect(() => {
3871
- log('re-rendered');
3872
- });
3873
4181
  }
3874
4182
  React.useEffect(() => {
3875
4183
  log('show changed', p.show);
@@ -3910,7 +4218,7 @@ const WaitingIndicator = (p) => {
3910
4218
  }
3911
4219
  }
3912
4220
  }, [p.show]);
3913
- return (React__default['default'].createElement(Modal, { id: p.id, debug: p.debug, className: "waitingIndicator", show: show, noBackground: true },
4221
+ return (React__default['default'].createElement(Modal, { id: p.id, __debug: p.__debug, className: "waitingIndicator", show: show, noBackground: true },
3914
4222
  React__default['default'].createElement("div", { className: css.css({
3915
4223
  color: 'white',
3916
4224
  fontSize: '3rem'
@@ -3918,6 +4226,7 @@ const WaitingIndicator = (p) => {
3918
4226
  React__default['default'].createElement(Icon, { id: "waiting", spin: true }))));
3919
4227
  };
3920
4228
 
4229
+ exports.Accordian = Accordian;
3921
4230
  exports.Autocomplete = Autocomplete;
3922
4231
  exports.Backdrop = Backdrop$1;
3923
4232
  exports.Backdrop2 = Backdrop;
@@ -3928,7 +4237,7 @@ exports.Calendar = Calendar;
3928
4237
  exports.Checkbox = Checkbox;
3929
4238
  exports.ConfirmModal = ConfirmModal;
3930
4239
  exports.CopyButton = CopyButton;
3931
- exports.DatePicker = DatePicker;
4240
+ exports.DateInput = DateInput;
3932
4241
  exports.Divider = Divider;
3933
4242
  exports.ErrorModal = ErrorModal;
3934
4243
  exports.FileUploader = FileUploader;
@@ -3978,7 +4287,14 @@ exports.Tr = Tr;
3978
4287
  exports.WaitingIndicator = WaitingIndicator;
3979
4288
  exports.calcDynamicThemeProps = calcDynamicThemeProps;
3980
4289
  exports.defaultTheme = defaultTheme;
4290
+ exports.enumToEntities = enumToEntities;
3981
4291
  exports.getCurrencyDisplay = getCurrencyDisplay;
4292
+ exports.getFileSizeDisplay = getFileSizeDisplay;
4293
+ exports.useAccordianState = useAccordianState;
4294
+ exports.useBooleanChanged = useBooleanChanged;
4295
+ exports.useIgnoreMount = useIgnoreMount;
3982
4296
  exports.useMediaQuery = useMediaQuery;
4297
+ exports.useScrollbarSize = useScrollbarSize;
3983
4298
  exports.useThemeSafely = useThemeSafely;
4299
+ exports.useWaiting = useWaiting;
3984
4300
  //# sourceMappingURL=index.js.map