@mackin.com/styleguide 8.0.0-beta.9 → 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 +114 -36
  2. package/index.js +687 -481
  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}`;
@@ -48,6 +116,8 @@ const calcDynamicThemeProps = (theme) => {
48
116
  theme.controls.focusOutlineRequiredShadow = `0px 0px 4px 2px ${theme.colors.focusOutlineRequired}`;
49
117
  theme.controls.dividerBorder = `2px solid ${theme.colors.divider}`;
50
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})`;
51
121
  };
52
122
  const defaultTheme = {
53
123
  colors: {
@@ -77,7 +147,7 @@ const defaultTheme = {
77
147
  divider: 'rgba(0, 0, 0, 0.50)',
78
148
  nav: '#7851a9',
79
149
  navFont: 'rgba(255, 255, 255, 0.9)',
80
- focusOutline: 'hsl(155deg 67% 85%)',
150
+ focusOutline: 'rgb(0 188 212 / 75%)',
81
151
  progressBg: '#007bff63',
82
152
  progressFill: '#007bff',
83
153
  modalBg: 'white',
@@ -135,6 +205,10 @@ const defaultTheme = {
135
205
  breakpoints: {
136
206
  desktop: '800px',
137
207
  tablet: '768px'
208
+ },
209
+ mediaQueries: {
210
+ desktop: '',
211
+ tablet: ''
138
212
  }
139
213
  };
140
214
  calcDynamicThemeProps(defaultTheme);
@@ -155,193 +229,9 @@ const useThemeSafely = () => {
155
229
  return defaultTheme;
156
230
  };
157
231
 
158
- /** @deprecated Use Backdrop2 going forward. */
159
- const Backdrop$1 = (props) => {
160
- var _a;
161
- const showTimeMs = (_a = props.showTimeMs) !== null && _a !== void 0 ? _a : 250;
162
- const backdropId = React__namespace.useRef('Backdrop' + nanoid.nanoid());
163
- const theme = useThemeSafely();
164
- const backdropStyles = css.css `
165
- opacity: 0;
166
- position: fixed;
167
- top: 0;
168
- left: 0;
169
- right: 0;
170
- bottom: 0;
171
- background-color: ${theme.colors.backdrop};
172
- transition: opacity ${showTimeMs}ms ease-in-out;
173
- visibility: hidden;
174
- user-select: none;
175
- -webkit-tap-highlight-color: transparent;
176
- `;
177
- const showStyles = css.css `
178
- z-index: ${theme.zIndexes.backdrop} !important;
179
- opacity: 1.0 !important;
180
- label:${backdropId.current};
181
- `;
182
- const bodyStyles = css.css `
183
- overflow: hidden !important;
184
- label:${backdropId.current};
185
- `;
186
- const bodyResponsiveStyles = css.css `
187
- label:${backdropId.current};
188
- @media(min-width:${theme.breakpoints.desktop}) {
189
- overflow: auto !important;
190
- }
191
- `;
192
- const bodyReverseResponsiveStyles = css.css `
193
- ${bodyStyles}
194
- overflow: auto !important;
195
- @media(min-width:${theme.breakpoints.desktop}) {
196
- overflow: hidden !important;
197
- }
198
- `;
199
- const styles = css.css `
200
- ${backdropStyles}
201
- ${props.onClick && `
202
- cursor: pointer;
203
- `}
204
- ${props.transparent && `
205
- background-color: transparent;
206
- transition: none;
207
- `}
208
- ${props.children && `
209
- display: flex;
210
- justify-content:center;
211
- align-items: center;
212
- `}
213
- ${props.responsive && `
214
- @media(min-width:${theme.breakpoints.desktop}) {
215
- display: none;
216
- }
217
- `}
218
- ${props.reverseResponsive && `
219
- display: none;
220
- @media(min-width:${theme.breakpoints.desktop}) {
221
- display: flex;
222
- }
223
- `}
224
- `;
225
- const backdrop = React__namespace.useRef(null);
226
- React__namespace.useEffect(() => {
227
- if (backdrop && backdrop.current) {
228
- if (props.show && backdrop.current.style.visibility !== 'visible') {
229
- backdrop.current.style.visibility = 'visible';
230
- backdrop.current.classList.add(showStyles);
231
- if (!props.allowScroll) {
232
- document.body.classList.add(bodyStyles);
233
- if (props.responsive) {
234
- document.body.classList.add(bodyResponsiveStyles);
235
- }
236
- else if (props.reverseResponsive) {
237
- document.body.classList.add(bodyReverseResponsiveStyles);
238
- }
239
- }
240
- }
241
- else if (!props.show && backdrop.current.style.visibility === 'visible') {
242
- backdrop.current.classList.remove(showStyles);
243
- if (backdrop && backdrop.current) {
244
- backdrop.current.style.visibility = 'hidden';
245
- if (!props.allowScroll) {
246
- document.body.classList.remove(bodyStyles);
247
- if (props.responsive) {
248
- document.body.classList.remove(bodyResponsiveStyles);
249
- }
250
- else if (props.reverseResponsive) {
251
- document.body.classList.remove(bodyReverseResponsiveStyles);
252
- }
253
- }
254
- }
255
- }
256
- }
257
- return () => {
258
- if (backdrop && backdrop.current && !props.allowScroll) {
259
- document.body.classList.remove(bodyStyles);
260
- }
261
- };
262
- }, [props.show]);
263
- return (React__namespace.createElement("div", { onMouseDown: e => {
264
- var _a;
265
- e.stopPropagation();
266
- e.preventDefault();
267
- (_a = props.onClick) === null || _a === void 0 ? void 0 : _a.call(props);
268
- }, onClick: e => {
269
- e.stopPropagation();
270
- e.preventDefault();
271
- }, ref: backdrop, className: css.cx('backdrop', styles, props.className) }, props.children));
272
- };
273
-
274
- /*! *****************************************************************************
275
- Copyright (c) Microsoft Corporation.
276
-
277
- Permission to use, copy, modify, and/or distribute this software for any
278
- purpose with or without fee is hereby granted.
279
-
280
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
281
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
282
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
283
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
284
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
285
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
286
- PERFORMANCE OF THIS SOFTWARE.
287
- ***************************************************************************** */
288
-
289
- function __rest(s, e) {
290
- var t = {};
291
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
292
- t[p] = s[p];
293
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
294
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
295
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
296
- t[p[i]] = s[p[i]];
297
- }
298
- return t;
299
- }
300
-
301
- const ICONS = {
302
- add: proSolidSvgIcons.faPlus,
303
- delete: proSolidSvgIcons.faTrashAlt,
304
- save: proSolidSvgIcons.faSave,
305
- activate: proRegularSvgIcons.faCheckCircle,
306
- deactivate: proRegularSvgIcons.faCircle,
307
- online: proLightSvgIcons.faWifi,
308
- offline: proLightSvgIcons.faWifiSlash,
309
- noIcon: proSolidSvgIcons.faCrow,
310
- close: proSolidSvgIcons.faTimes,
311
- waiting: proSolidSvgIcons.faSync,
312
- refresh: proSolidSvgIcons.faSync,
313
- menu: proLightSvgIcons.faBars,
314
- search: proLightSvgIcons.faSearch,
315
- expand: proRegularSvgIcons.faChevronDown,
316
- collapse: proRegularSvgIcons.faChevronUp,
317
- help: proLightSvgIcons.faQuestionCircle,
318
- debug: proLightSvgIcons.faNarwhal,
319
- goTo: proLightSvgIcons.faChevronRight,
320
- goBack: proLightSvgIcons.faChevronLeft,
321
- download: proLightSvgIcons.faCloudDownload,
322
- upload: proLightSvgIcons.faCloudUpload,
323
- selected: proSolidSvgIcons.faCheckSquare,
324
- unselected: proRegularSvgIcons.faSquare,
325
- pagerLeft: proLightSvgIcons.faChevronLeft,
326
- pagerRight: proLightSvgIcons.faChevronRight,
327
- sortAsc: proRegularSvgIcons.faChevronUp,
328
- sortDesc: proRegularSvgIcons.faChevronDown,
329
- pickDate: proLightSvgIcons.faCalendarAlt,
330
- copy: proLightSvgIcons.faCopy,
331
- paste: proLightSvgIcons.faPaste,
332
- clear: proRegularSvgIcons.faTimesCircle,
333
- hide: proLightSvgIcons.faEyeSlash,
334
- show: proLightSvgIcons.faEye
335
- };
336
- const Icon = (props) => {
232
+ const Button = React__namespace.forwardRef((props, ref) => {
337
233
  var _a;
338
- const icon = (_a = ICONS[props.id]) !== null && _a !== void 0 ? _a : ICONS['noIcon'];
339
- return React__namespace.createElement(reactFontawesome.FontAwesomeIcon, { style: props.style, onClick: props.onClick, spin: props.spin, className: css.cx('icon', props.className), icon: icon });
340
- };
341
-
342
- const Button = (props) => {
343
- var _a, _b;
344
- 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"]);
345
235
  const theme = useThemeSafely();
346
236
  const buttonStyles = css.css `
347
237
  padding-left: ${theme.controls.padding};
@@ -358,7 +248,6 @@ const Button = (props) => {
358
248
  font-weight: bold;
359
249
  flex-shrink: 0;
360
250
  min-width: ${theme.controls.height};
361
- text-align: ${(_a = props.textAlign) !== null && _a !== void 0 ? _a : 'center'};
362
251
 
363
252
  &:disabled {
364
253
  opacity: ${theme.controls.disabledOpacity};
@@ -497,9 +386,6 @@ const Button = (props) => {
497
386
  ${props.round && `
498
387
  border-radius: ${theme.controls.roundRadius};
499
388
  `}
500
- ${props.block && `
501
- width: 100%;
502
- `}
503
389
  ${props.iconBlock && `
504
390
  display: flex;
505
391
  justify-content: space-between;
@@ -507,10 +393,94 @@ const Button = (props) => {
507
393
  `}
508
394
  `;
509
395
  const disabled = props.disabled || props.waiting;
510
- 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' }),
511
397
  props.leftIcon && React__namespace.createElement("span", { className: css.css({ marginRight: '0.5rem' }) }, props.leftIcon),
512
398
  props.waiting ? React__namespace.createElement(Icon, { id: "waiting", spin: true }) : props.children,
513
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
+ ];
514
484
  };
515
485
 
516
486
  const DEFAULT_DEBOUNCE_MS = 250;
@@ -854,25 +824,12 @@ const Text = (props) => {
854
824
  }, props.children);
855
825
  };
856
826
 
857
- const DEFAULT_MAX_SHOWN_VALUES = 7;
858
- const getAutocompleteValueText = (v) => {
859
- if (!v) {
860
- return '';
861
- }
862
- if (typeof v === 'string') {
863
- return v;
864
- }
865
- return v.name;
866
- };
867
- const getAutocompleteValueId = (v) => {
868
- if (typeof v === 'string') {
869
- return v;
870
- }
871
- return v.id;
872
- };
873
827
  //TB: FUTURE will need to use the new input
828
+ const defaultMaxShownValues = 7;
829
+ const buttonMarkerClass = 'ListItem__button';
874
830
  const Autocomplete = (p) => {
875
831
  var _a;
832
+ const theme = useThemeSafely();
876
833
  const element = React__namespace.useRef(null);
877
834
  const input = React__namespace.useRef(null);
878
835
  const list = React__namespace.useRef(null);
@@ -880,7 +837,7 @@ const Autocomplete = (p) => {
880
837
  const showValues = React__namespace.useMemo(() => values.length > 0, [values]);
881
838
  const shownValues = React__namespace.useMemo(() => {
882
839
  var _a;
883
- 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);
884
841
  }, [values]);
885
842
  const onChangeForOptions = React__namespace.useRef(lodash.debounce((value) => {
886
843
  if (!p.minChars || value.length >= p.minChars) {
@@ -894,182 +851,330 @@ const Autocomplete = (p) => {
894
851
  else {
895
852
  setValues([]);
896
853
  }
897
- }, (_a = p.getOptionsDebounceMs) !== null && _a !== void 0 ? _a : 0, { leading: true, trailing: true }));
898
- const theme = useThemeSafely();
899
- const baseClass = css.css `
900
- label: Autocomplete;
901
- position: relative;
902
- width: 100%;
903
- `;
854
+ }, (_a = p.getOptionsDebounceMs) !== null && _a !== void 0 ? _a : 0, { leading: false, trailing: true }));
855
+ const getNextTabElement = (fromIndex, direction) => {
856
+ var _a, _b, _c;
857
+ if (fromIndex === -1) {
858
+ let buttonIndex = 0;
859
+ if (direction === -1) {
860
+ buttonIndex = shownValues.length - 1;
861
+ }
862
+ return (_a = list.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${buttonMarkerClass}${buttonIndex}`);
863
+ }
864
+ else {
865
+ const nextIndex = fromIndex + direction;
866
+ if (nextIndex >= shownValues.length || nextIndex < 0) {
867
+ return (_b = input.current) !== null && _b !== void 0 ? _b : undefined;
868
+ }
869
+ else {
870
+ return (_c = list.current) === null || _c === void 0 ? void 0 : _c.querySelector(`.${buttonMarkerClass}${nextIndex}`);
871
+ }
872
+ }
873
+ };
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]);
904
885
  let listBorderRadius = '';
905
886
  if (p.round || theme.controls.borderRadius) {
906
887
  listBorderRadius = theme.controls.borderRadius || '0.5rem';
907
888
  }
908
- const listClass = css.css({
909
- position: 'absolute',
910
- width: '100%',
911
- border: theme.controls.border,
912
- borderRadius: listBorderRadius,
913
- boxShadow: theme.controls.boxShadow,
914
- backgroundColor: theme.colors.bg,
915
- marginTop: `-4px !important`,
916
- zIndex: theme.zIndexes.backdrop,
917
- 'li:first-child button': {
918
- borderTopRightRadius: listBorderRadius,
919
- borderTopLeftRadius: listBorderRadius,
920
- },
921
- 'li:last-child button': {
922
- borderBottomRightRadius: listBorderRadius,
923
- borderBottomLeftRadius: listBorderRadius,
924
- }
925
- });
926
- const inputClass = css.css `
927
- ${showValues && `
928
- z-index: ${theme.zIndexes.backdrop};
929
- position: relative;
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') },
902
+ React__namespace.createElement(TabLocker, { disabled: !showValues, style: { position: 'relative' } },
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 => {
904
+ const value = v;
905
+ p.onChange(value);
906
+ onChangeForOptions.current(value);
907
+ }, onKeyDown: e => {
908
+ var _a, _b, _c;
909
+ if (showValues) {
910
+ if (e.key === 'ArrowDown') {
911
+ e.preventDefault();
912
+ e.stopPropagation();
913
+ (_a = getNextTabElement(-1, 1)) === null || _a === void 0 ? void 0 : _a.focus();
914
+ }
915
+ else if (e.key === 'ArrowUp') {
916
+ e.preventDefault();
917
+ e.stopPropagation();
918
+ (_b = getNextTabElement(-1, -1)) === null || _b === void 0 ? void 0 : _b.focus();
919
+ }
920
+ }
921
+ (_c = p.onKeyDown) === null || _c === void 0 ? void 0 : _c.call(p, e);
922
+ }, onKeyPress: e => { var _a; return (_a = p.onKeyPress) === null || _a === void 0 ? void 0 : _a.call(p, e); } }),
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
+ }) },
941
+ shownValues.map((value, listItemIndex) => {
942
+ return (React__namespace.createElement(ListItem, { key: getAutocompleteValueId(value), variant: "full" },
943
+ React__namespace.createElement(Button, { onKeyDown: e => {
944
+ var _a, _b;
945
+ if (e.key === 'ArrowDown') {
946
+ e.stopPropagation();
947
+ e.preventDefault();
948
+ (_a = getNextTabElement(listItemIndex, 1)) === null || _a === void 0 ? void 0 : _a.focus();
949
+ }
950
+ else if (e.key === 'ArrowUp') {
951
+ e.stopPropagation();
952
+ e.preventDefault();
953
+ (_b = getNextTabElement(listItemIndex, -1)) === null || _b === void 0 ? void 0 : _b.focus();
954
+ }
955
+ }, className: css.cx(buttonMarkerClass + listItemIndex, css.css({
956
+ borderRadius: 0,
957
+ })), onClick: () => {
958
+ p.onPick(value);
959
+ setValues([]);
960
+ setTimeout(() => {
961
+ var _a;
962
+ // we need to wait until the component is re-rendered.
963
+ // outside changes to Inputs will be ignored if the component has focus.
964
+ (_a = input.current) === null || _a === void 0 ? void 0 : _a.focus();
965
+ }, 0);
966
+ } },
967
+ React__namespace.createElement(Text, { tag: "div", ellipsis: true, align: "left" }, getAutocompleteValueText(value)))));
968
+ }),
969
+ shownValues.length < values.length && (React__namespace.createElement(ListItem, null,
970
+ React__namespace.createElement(Text, { tag: "div", italics: true, align: "center" },
971
+ "Showing ",
972
+ shownValues.length.toLocaleString(),
973
+ " of ",
974
+ values.length.toLocaleString(),
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
+ }
930
1058
  `}
931
1059
  `;
932
- const buttonStyles = css.css({
933
- borderRadius: 0,
934
- });
935
- const buttonMarkerClass = 'ListItem__button';
936
- const getNextTabElement = (fromIndex, direction) => {
937
- var _a, _b, _c;
938
- if (fromIndex === -1) {
939
- let buttonIndex = 0;
940
- if (direction === -1) {
941
- buttonIndex = shownValues.length - 1;
942
- }
943
- return (_a = list.current) === null || _a === void 0 ? void 0 : _a.querySelector(`.${buttonMarkerClass}${buttonIndex}`);
944
- }
945
- else {
946
- const nextIndex = fromIndex + direction;
947
- if (nextIndex >= shownValues.length || nextIndex < 0) {
948
- return (_b = input.current) !== null && _b !== void 0 ? _b : undefined;
949
- }
950
- else {
951
- return (_c = list.current) === null || _c === void 0 ? void 0 : _c.querySelector(`.${buttonMarkerClass}${nextIndex}`);
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
+ }
952
1075
  }
953
- }
954
- };
955
- return (React__namespace.createElement("div", { ref: element, className: css.cx(baseClass, 'autocomplete') },
956
- React__namespace.createElement(Backdrop$1, { onClick: () => setValues([]), show: showValues, allowScroll: true, transparent: true }),
957
- React__namespace.createElement(TabLocker, { disabled: !showValues, style: { position: 'relative' } },
958
- 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 => {
959
- const value = v;
960
- p.onChange(value);
961
- onChangeForOptions.current(value);
962
- }, onKeyDown: e => {
963
- var _a, _b;
964
- if (showValues) {
965
- if (e.key === 'ArrowDown') {
966
- e.preventDefault();
967
- e.stopPropagation();
968
- (_a = getNextTabElement(-1, 1)) === null || _a === void 0 ? void 0 : _a.focus();
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);
969
1084
  }
970
- else if (e.key === 'ArrowUp') {
971
- e.preventDefault();
972
- e.stopPropagation();
973
- (_b = getNextTabElement(-1, -1)) === null || _b === void 0 ? void 0 : _b.focus();
1085
+ else if (props.reverseResponsive) {
1086
+ document.body.classList.remove(bodyReverseResponsiveStyles);
974
1087
  }
975
1088
  }
976
- }, onKeyPress: e => { var _a; return (_a = p.onKeyPress) === null || _a === void 0 ? void 0 : _a.call(p, e); } }),
977
- showValues && (React__namespace.createElement(List, { ref: list, className: listClass },
978
- shownValues.map((value, listItemIndex) => {
979
- return (React__namespace.createElement(ListItem, { key: getAutocompleteValueId(value), variant: "full" },
980
- React__namespace.createElement(Button, { onKeyDown: e => {
981
- var _a, _b;
982
- if (e.key === 'ArrowDown') {
983
- e.stopPropagation();
984
- e.preventDefault();
985
- (_a = getNextTabElement(listItemIndex, 1)) === null || _a === void 0 ? void 0 : _a.focus();
986
- }
987
- else if (e.key === 'ArrowUp') {
988
- e.stopPropagation();
989
- e.preventDefault();
990
- (_b = getNextTabElement(listItemIndex, -1)) === null || _b === void 0 ? void 0 : _b.focus();
991
- }
992
- }, className: css.cx(buttonMarkerClass + listItemIndex, buttonStyles), onClick: () => {
993
- p.onPick(value);
994
- setValues([]);
995
- setTimeout(() => {
996
- var _a;
997
- // we need to wait until the component is re-rendered.
998
- // outside changes to Inputs will be ignored if the component has focus.
999
- (_a = input.current) === null || _a === void 0 ? void 0 : _a.focus();
1000
- }, 0);
1001
- } },
1002
- React__namespace.createElement(Text, { tag: "div", ellipsis: true, align: "left" }, getAutocompleteValueText(value)))));
1003
- }),
1004
- shownValues.length < values.length && (React__namespace.createElement(ListItem, null,
1005
- React__namespace.createElement(Text, { tag: "div", italics: true, align: "center" },
1006
- "Showing ",
1007
- shownValues.length.toLocaleString(),
1008
- " of ",
1009
- values.length.toLocaleString(),
1010
- " results."))))))));
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);
1011
1120
  };
1012
1121
 
1013
1122
  const useLogger = (componentName, enabled) => {
1014
1123
  return (...messages) => {
1015
1124
  if (enabled) {
1016
1125
  // tslint:disable-next-line
1017
- console.log(componentName, ...messages);
1126
+ console.log(`[${componentName}]`, ...messages);
1018
1127
  }
1019
1128
  };
1020
1129
  };
1021
1130
 
1131
+ const portalId = 'backdrop';
1022
1132
  const BackdropContext = React__default['default'].createContext({
1023
1133
  showing: false,
1024
1134
  showCount: 0,
1025
- portalId: 'backdrop',
1026
- setShow: s => {
1135
+ portalId: portalId,
1136
+ setShow: (s, f) => {
1027
1137
  /* empty */
1028
1138
  }
1029
1139
  });
1030
1140
  const BackdropContextProvider = (p) => {
1031
1141
  var _a;
1032
1142
  const [showCount, setShowCount] = React.useState(0);
1033
- const log = useLogger('BackdropContextProvider', (_a = p.debug) !== null && _a !== void 0 ? _a : false);
1034
- if (p.debug) {
1143
+ const log = useLogger('BackdropContextProvider', (_a = p.__debug) !== null && _a !== void 0 ? _a : false);
1144
+ if (p.__debug) {
1035
1145
  React.useEffect(() => {
1036
1146
  log('mounted');
1037
1147
  return () => {
1038
1148
  log('un-mounted');
1039
1149
  };
1040
1150
  }, []);
1041
- React.useEffect(() => {
1042
- log('re-rendered');
1043
- });
1044
- React.useEffect(() => {
1151
+ useIgnoreMount(() => {
1045
1152
  log('showCount changed', showCount);
1046
1153
  }, [showCount]);
1047
1154
  }
1048
1155
  return (React__default['default'].createElement(BackdropContext.Provider, { value: {
1049
- portalId: 'backdrop',
1156
+ portalId: portalId,
1050
1157
  showing: showCount > 0,
1051
1158
  showCount: showCount,
1052
- setShow: (show) => {
1159
+ setShow: (show, from) => {
1053
1160
  if (show) {
1054
1161
  setShowCount(s => {
1055
1162
  const count = s + 1;
1056
- log('setShowCount', count);
1057
- log('showCount was', showCount);
1163
+ log(`setShow from ${from} ${s} -> ${count}`);
1058
1164
  return count;
1059
1165
  });
1060
1166
  }
1061
1167
  else {
1062
1168
  setShowCount(s => {
1063
1169
  const count = Math.max(0, s - 1);
1064
- log('setShowCount', count);
1065
- log('showCount was', showCount);
1170
+ log(`setShow from ${from} ${s} -> ${count}`);
1066
1171
  return count;
1067
1172
  });
1068
1173
  }
1069
1174
  }
1070
1175
  } },
1071
1176
  p.children,
1072
- p.debug && (React__default['default'].createElement("p", { className: css.css({
1177
+ p.__debug && (React__default['default'].createElement("p", { className: css.css({
1073
1178
  position: 'fixed',
1074
1179
  top: 0, right: 0,
1075
1180
  backgroundColor: '#ff00004f',
@@ -1086,23 +1191,20 @@ const BackdropOverlay = (p) => {
1086
1191
  const context = React.useContext(BackdropContext);
1087
1192
  const theme = useThemeSafely();
1088
1193
  const showTimeMs = (_a = p.showTimeMs) !== null && _a !== void 0 ? _a : 250;
1089
- const log = useLogger('BackdropOverlay', (_b = p.debug) !== null && _b !== void 0 ? _b : false);
1090
- if (p.debug) {
1194
+ const log = useLogger('BackdropOverlay', (_b = p.__debug) !== null && _b !== void 0 ? _b : false);
1195
+ if (p.__debug) {
1091
1196
  React.useEffect(() => {
1092
1197
  log('mounted');
1093
1198
  return () => {
1094
1199
  log('unmounted');
1095
1200
  };
1096
1201
  }, []);
1097
- React.useEffect(() => {
1098
- log('re-rendered');
1099
- });
1100
- React.useEffect(() => {
1202
+ useIgnoreMount(() => {
1101
1203
  log('context.showing changed', context.showing);
1102
1204
  }, [context.showing]);
1103
1205
  }
1104
1206
  return (React__default['default'].createElement("div", { onClick: () => {
1105
- context === null || context === void 0 ? void 0 : context.setShow(false);
1207
+ context === null || context === void 0 ? void 0 : context.setShow(false, 'BackdropOverlay');
1106
1208
  log('onClick', 'setShow', false);
1107
1209
  }, id: context === null || context === void 0 ? void 0 : context.portalId, className: css.css({
1108
1210
  cursor: 'pointer',
@@ -1118,12 +1220,12 @@ const BackdropOverlay = (p) => {
1118
1220
  }) }));
1119
1221
  };
1120
1222
  const Backdrop = (p) => {
1121
- return (React__default['default'].createElement(BackdropContextProvider, { debug: p.debug },
1223
+ return (React__default['default'].createElement(BackdropContextProvider, { __debug: p.__debug },
1122
1224
  React__default['default'].createElement("div", { className: css.css({
1123
1225
  height: '100%'
1124
1226
  }) },
1125
1227
  p.children,
1126
- React__default['default'].createElement(BackdropOverlay, { showTimeMs: p.showTimeMs, debug: p.debug }))));
1228
+ React__default['default'].createElement(BackdropOverlay, { showTimeMs: p.showTimeMs, __debug: p.__debug }))));
1127
1229
  };
1128
1230
 
1129
1231
  const Calendar = (p) => {
@@ -1295,11 +1397,41 @@ const Checkbox = (props) => {
1295
1397
  props.children)));
1296
1398
  };
1297
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
+
1298
1429
  // Taken from: https://github.com/react-bootstrap/dom-helpers/blob/master/src/scrollbarSize.ts
1299
1430
  const canUseDom = !!(typeof window !== 'undefined' &&
1300
1431
  window.document &&
1301
1432
  window.document.createElement);
1302
1433
  let size;
1434
+ /** Tells you actual width of the scroll bar. This can vary by browser. */
1303
1435
  const useScrollbarSize = (recalc) => {
1304
1436
  if ((!size && size !== 0) || recalc) {
1305
1437
  if (canUseDom) {
@@ -1317,7 +1449,6 @@ const useScrollbarSize = (recalc) => {
1317
1449
  return size;
1318
1450
  };
1319
1451
 
1320
- let htmlBodyStyles;
1321
1452
  const Modal = (p) => {
1322
1453
  var _a, _b, _c, _d;
1323
1454
  const backdrop = React.useContext(BackdropContext);
@@ -1325,42 +1456,54 @@ const Modal = (p) => {
1325
1456
  const theme = useThemeSafely();
1326
1457
  const hasHeader = p.closeButton || p.heading;
1327
1458
  const contentRef = React__default['default'].useRef(null);
1328
- 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
+ };
1329
1468
  React.useEffect(() => {
1330
1469
  log('mounted');
1331
1470
  return () => {
1332
- // handle the use of modals that are rendered only as show=true and then unmounted.
1333
- backdrop.setShow(false);
1334
- log('backdrop.setShow', false);
1335
- if (backdrop.showCount <= 1 && htmlBodyStyles) {
1336
- log('backdrop.showCount', backdrop.showCount, 'removing htmlBodyStyles');
1337
- 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.`);
1338
1480
  }
1339
1481
  log('un-mounted');
1340
1482
  };
1341
1483
  }, []);
1342
- React.useEffect(() => {
1343
- log('show changed', p.show);
1344
- backdrop.setShow(p.show);
1345
- log('backdrop.setShow', true);
1346
- if (!htmlBodyStyles) {
1347
- log('creating singleton htmlBodyStyles');
1348
- 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()),
1349
1493
  overflow: 'hidden',
1350
1494
  paddingRight: `${useScrollbarSize()}px`
1351
1495
  });
1496
+ log('creating singleton bodyStyles', bodyStyles.current);
1352
1497
  }
1353
- if (p.show) {
1354
- log('adding htmlBodyStyles');
1355
- 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);
1356
1501
  }
1357
1502
  else {
1358
- if (backdrop.showCount <= 1) {
1359
- log('backdrop.showCount', backdrop.showCount, 'removing htmlBodyStyles');
1360
- document.body.classList.remove(htmlBodyStyles);
1361
- }
1503
+ log('this modal is hiding. try removing singleton bodyStyles');
1504
+ tryRemoveBodyStyles();
1362
1505
  }
1363
- }, [p.show]);
1506
+ }, p.show);
1364
1507
  React__default['default'].useLayoutEffect(() => {
1365
1508
  var _a;
1366
1509
  if (p.show === true) {
@@ -1461,7 +1604,7 @@ const ConfirmModal = (props) => {
1461
1604
  }
1462
1605
  `}
1463
1606
  `;
1464
- 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 },
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 },
1465
1608
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1466
1609
  React__namespace.createElement(Text, { align: "center" }, props.text),
1467
1610
  React__namespace.createElement("div", { className: css.css({ textAlign: 'center' }) },
@@ -1491,12 +1634,12 @@ const CopyButton = (props) => {
1491
1634
  React__namespace.createElement(Icon, { id: copied ? 'paste' : 'copy' })));
1492
1635
  };
1493
1636
 
1494
- const Divider = () => {
1637
+ const Divider = (p) => {
1495
1638
  const theme = useThemeSafely();
1496
- 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({
1497
1640
  margin: theme.controls.dividerMargin,
1498
1641
  border: theme.controls.dividerBorder
1499
- })) }));
1642
+ }), p.className) })));
1500
1643
  };
1501
1644
 
1502
1645
  const ErrorModal = (props) => {
@@ -1511,7 +1654,7 @@ const ErrorModal = (props) => {
1511
1654
  color: ${theme.colors.omgFont};
1512
1655
  }
1513
1656
  `;
1514
- 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 },
1515
1658
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) },
1516
1659
  React__namespace.createElement(Text, { align: "center" }, message))));
1517
1660
  };
@@ -1639,7 +1782,7 @@ const hoverClass = css.css({
1639
1782
  backgroundColor: 'rgba(0,0,0,0.25) !important'
1640
1783
  });
1641
1784
  const FileUploader = (p) => {
1642
- var _a, _b, _c, _d, _e, _f, _g;
1785
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1643
1786
  const [message, setMessage] = React.useState(undefined);
1644
1787
  const [canUpload, setCanUpload] = React.useState(false);
1645
1788
  const [uploading, setUploading] = React.useState(false);
@@ -1655,7 +1798,7 @@ const FileUploader = (p) => {
1655
1798
  filesDisplay = (_b = p.instructionMessage) !== null && _b !== void 0 ? _b : `Click or drag and drop files.`;
1656
1799
  }
1657
1800
  else {
1658
- 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(', ')}`;
1659
1802
  }
1660
1803
  }
1661
1804
  const setAllFiles = (inputFiles) => {
@@ -1677,8 +1820,9 @@ const FileUploader = (p) => {
1677
1820
  maxBytes: p.maxBytes
1678
1821
  });
1679
1822
  };
1823
+ const showInfoOnPick = (_c = p.showInfoOnPick) !== null && _c !== void 0 ? _c : true;
1680
1824
  let infoMessage;
1681
- if (p.infoMessage) {
1825
+ if (p.infoMessage && (!files || showInfoOnPick)) {
1682
1826
  if (typeof p.infoMessage === 'string') {
1683
1827
  infoMessage = React__default['default'].createElement(Text, { noPad: true }, p.infoMessage);
1684
1828
  }
@@ -1695,6 +1839,8 @@ const FileUploader = (p) => {
1695
1839
  padding: '1rem',
1696
1840
  overflow: 'hidden',
1697
1841
  backgroundColor: theme.colors.lightBg,
1842
+ }, p.disabled && {
1843
+ opacity: theme.controls.disabledOpacity
1698
1844
  }), onDragOver: e => {
1699
1845
  var _a, _b;
1700
1846
  e.preventDefault();
@@ -1728,7 +1874,7 @@ const FileUploader = (p) => {
1728
1874
  setCanUpload(false);
1729
1875
  });
1730
1876
  } },
1731
- React__default['default'].createElement("input", { ref: input, className: css.css({
1877
+ React__default['default'].createElement("input", { disabled: p.disabled, ref: input, className: css.css({
1732
1878
  position: 'absolute',
1733
1879
  top: -50,
1734
1880
  left: 0,
@@ -1737,6 +1883,8 @@ const FileUploader = (p) => {
1737
1883
  width: '100%',
1738
1884
  cursor: 'pointer',
1739
1885
  opacity: 0
1886
+ }, p.disabled && {
1887
+ cursor: 'not-allowed'
1740
1888
  }), type: "file", multiple: p.multiple, accept: p.accept, onChange: e => {
1741
1889
  try {
1742
1890
  if (!e.target.files) {
@@ -1759,65 +1907,68 @@ const FileUploader = (p) => {
1759
1907
  zIndex: !!(files === null || files === void 0 ? void 0 : files.length) ? 1 : undefined
1760
1908
  }) },
1761
1909
  !(files === null || files === void 0 ? void 0 : files.length) && React__default['default'].createElement(Icon, { style: { fontSize: '2rem' }, id: "upload" }),
1762
- 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
+ }) },
1763
1913
  filesDisplay,
1764
1914
  !!(files === null || files === void 0 ? void 0 : files.length) && (React__default['default'].createElement(Button, { onClick: e => {
1765
1915
  e.stopPropagation();
1766
1916
  onFilesChange(undefined);
1767
1917
  }, className: css.css({ marginLeft: '1rem', color: theme.colors.negative }), rightIcon: React__default['default'].createElement(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear"))),
1768
1918
  infoMessage,
1769
- !!(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%' }) },
1770
1920
  "Invalid files: ",
1771
1921
  files.invalidFiles.map(f => f.name).join(', '),
1772
1922
  ".")),
1773
- (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%' }) },
1774
1924
  "Max file size exceeded (",
1775
- getSizeString((_c = p.maxBytes) !== null && _c !== void 0 ? _c : 0),
1925
+ getFileSizeDisplay((_d = p.maxBytes) !== null && _d !== void 0 ? _d : 0),
1776
1926
  ").")))),
1777
1927
  canUpload && !(files === null || files === void 0 ? void 0 : files.hasErrors) && (React__default['default'].createElement(Button, { className: css.css({
1778
- width: (_d = p.buttonWidth) !== null && _d !== void 0 ? _d : '10rem',
1928
+ width: (_e = p.buttonWidth) !== null && _e !== void 0 ? _e : '10rem',
1779
1929
  zIndex: 1,
1780
- }), waiting: uploading, type: "submit", variant: (_e = p.buttonVariant) !== null && _e !== void 0 ? _e : 'primary' }, (_f = p.buttonText) !== null && _f !== void 0 ? _f : 'Upload')),
1781
- 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) })),
1782
1932
  message === 'failure' && (React__default['default'].createElement(UploadInfoPanel, { variant: "error", message: fullFailureMessage, onClear: () => setMessage(undefined) }))));
1783
1933
  };
1784
1934
  const UploadInfoPanel = (p) => {
1785
1935
  return (React__default['default'].createElement(InfoPanel, { variant: p.variant, className: css.css({ zIndex: 1 }) },
1786
1936
  p.message,
1787
- React__default['default'].createElement(Button, { block: true, className: css.css({
1937
+ React__default['default'].createElement(Button, { className: css.css({
1788
1938
  color: 'inherit',
1789
- marginTop: '1rem'
1939
+ marginTop: '1rem',
1940
+ width: '100%'
1790
1941
  }), onClick: e => {
1791
1942
  e.stopPropagation();
1792
1943
  p.onClear();
1793
1944
  }, rightIcon: React__default['default'].createElement(Icon, { id: "clear" }), variant: "inlineLink" }, "Clear")));
1794
1945
  };
1795
- const bytesInMb = 1048576;
1796
- const bytesInKb = 1024;
1797
- const getSizeString = (size) => {
1798
- if (size < bytesInKb) {
1799
- return size.toLocaleString() + ' B';
1800
- }
1801
- else if (size < bytesInMb) {
1802
- return (size / bytesInKb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
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';
1954
+ }
1955
+ else if (size >= 1000000) {
1956
+ value = size / 1000000;
1957
+ suffix = 'MB';
1958
+ }
1959
+ else if (size >= 1000) {
1960
+ value = size / 1000;
1961
+ suffix = 'KB';
1803
1962
  }
1804
1963
  else {
1805
- return (size / bytesInMb).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' MB';
1806
- }
1807
- };
1808
-
1809
- const getCurrencyDisplay = (value, isCents, denomination = '$') => {
1810
- let actualValue = value || 0;
1811
- if (isCents) {
1812
- actualValue /= 100;
1964
+ value = size;
1965
+ suffix = 'B';
1813
1966
  }
1814
- return `${denomination}${actualValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
1815
- };
1816
- const noop = () => {
1817
- // lil' noop would be a great rap name. (thanks linter)
1967
+ return value.toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ` ${suffix}`;
1818
1968
  };
1819
1969
 
1820
1970
  const Header = (props) => {
1971
+ var _a;
1821
1972
  const theme = useThemeSafely();
1822
1973
  const bodyStyles = css.css `
1823
1974
  padding-top: calc(${theme.layout.headerHeight} + ${theme.layout.headerBodyOffset});
@@ -1829,7 +1980,9 @@ const Header = (props) => {
1829
1980
  document.body.classList.remove(bodyStyles);
1830
1981
  };
1831
1982
  });
1832
- const toggleMenu = props.toggleMenu || noop;
1983
+ const toggleMenu = (_a = props.toggleMenu) !== null && _a !== void 0 ? _a : (() => {
1984
+ /* noop */
1985
+ });
1833
1986
  const headerStyles = css.css `
1834
1987
  display: flex;
1835
1988
  gap: ${theme.controls.gap};
@@ -1959,7 +2112,7 @@ const Popover = (p) => {
1959
2112
  };
1960
2113
 
1961
2114
  const InfoTip = (props) => {
1962
- var _a, _b;
2115
+ var _a, _b, _c;
1963
2116
  const [showTip, setShowTip] = React__namespace.useState(false);
1964
2117
  const theme = useThemeSafely();
1965
2118
  const bgColor = (_a = props.bgColor) !== null && _a !== void 0 ? _a : theme.colors.nav;
@@ -2021,13 +2174,13 @@ const InfoTip = (props) => {
2021
2174
  if (props.variant === 'modal') {
2022
2175
  return (React__namespace.createElement(React__namespace.Fragment, null,
2023
2176
  button,
2024
- React__namespace.createElement(Modal, { id: props.modalId, debug: props.modalDebug, show: showTip, heading: props.modalHeader, onClick: closeTip, className: css.css({
2177
+ React__namespace.createElement(Modal, { id: props.modalId, __debug: props.__modalDebug, show: showTip, heading: props.modalHeader, onClick: closeTip, className: css.css({
2025
2178
  whiteSpace: 'normal'
2026
2179
  }), closeButton: true },
2027
2180
  React__namespace.createElement("div", { className: css.css({ padding: '1rem' }) }, props.content))));
2028
2181
  }
2029
2182
  else {
2030
- 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({
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({
2031
2184
  padding: '0.5rem',
2032
2185
  fontSize: '0.75rem',
2033
2186
  maxWidth: '22rem'
@@ -2107,11 +2260,13 @@ const BaseInput = React__namespace.forwardRef((props, ref) => {
2107
2260
  right: calc(${theme.controls.padding} * 2);
2108
2261
  `}
2109
2262
  `;
2110
- return (React__namespace.createElement("div", null,
2263
+ return (React__namespace.createElement("div", { className: css.css({
2264
+ width: '100%'
2265
+ }) },
2111
2266
  React__namespace.createElement("div", { className: css.cx('input', inputWrapperStyles, wrapperClassName) },
2112
2267
  inputElement,
2113
2268
  props.rightControl && (React__namespace.createElement("div", { className: rightControlStyles }, props.rightControl))),
2114
- React__namespace.createElement(InputErrorDisplay, { error: props.error })));
2269
+ React__namespace.createElement(InputErrorDisplay, { error: props.readOnly ? undefined : props.error })));
2115
2270
  });
2116
2271
 
2117
2272
  const tryClampRange = (value, min, max) => {
@@ -2145,19 +2300,19 @@ const getStepDecimalPlaces = (step) => {
2145
2300
  }
2146
2301
  const strStep = typeof step === 'number' ? step.toString() : step;
2147
2302
  return (_b = (_a = strStep.split('.')[1]) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
2148
- };
2149
-
2150
- /** useEffect but ignores the first call on component mount. */
2151
- const useIgnoreMount = (effect, deps) => {
2152
- const mounted = React__default['default'].useRef(false);
2153
- React__default['default'].useEffect(() => {
2154
- if (!mounted.current) {
2155
- mounted.current = true;
2156
- }
2157
- else {
2158
- effect();
2159
- }
2160
- }, deps);
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));
2161
2316
  };
2162
2317
 
2163
2318
  /** Common state handling for displaying validation messages with inputs. */
@@ -2170,6 +2325,9 @@ const useInputValidationMessage = (ref, props) => {
2170
2325
  (_a = ref.current) === null || _a === void 0 ? void 0 : _a.setCustomValidity(customError);
2171
2326
  setValidationError(customError || getValidationMessage(ref.current, props.patternErrorMessage));
2172
2327
  };
2328
+ React.useEffect(() => {
2329
+ updateErrorMessage();
2330
+ }, [props.customError]);
2173
2331
  React__default['default'].useEffect(() => {
2174
2332
  updateErrorMessage();
2175
2333
  }, []);
@@ -2205,7 +2363,23 @@ const getValidationMessage = (element, patternErrorMessage) => {
2205
2363
  return `Must be greater than or equal to ${element.min}.`;
2206
2364
  }
2207
2365
  if (validity.stepMismatch) {
2208
- return `Limited to ${getStepDecimalPlaces(element.step)} decimal places.`;
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
+ }
2209
2383
  }
2210
2384
  }
2211
2385
  if (validity.tooShort) {
@@ -2446,19 +2620,6 @@ const NumberInput = React__namespace.forwardRef((props, ref) => {
2446
2620
  (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e);
2447
2621
  } })));
2448
2622
  });
2449
- const tryClampDecimals = (value, step = 0) => {
2450
- if (value === undefined) {
2451
- return value;
2452
- }
2453
- if (isNaN(value)) {
2454
- return undefined;
2455
- }
2456
- const decimals = getStepDecimalPlaces(step);
2457
- if (decimals === 0) {
2458
- return Math.floor(value);
2459
- }
2460
- return parseFloat(value.toFixed(decimals));
2461
- };
2462
2623
  const parseNumber = (rawValue) => {
2463
2624
  let value;
2464
2625
  if (rawValue) {
@@ -2475,7 +2636,7 @@ const TextInput = React__namespace.forwardRef((props, ref) => {
2475
2636
  const [localValue, setLocalValue] = React__namespace.useState(props.value);
2476
2637
  const inputRef = (ref !== null && ref !== void 0 ? ref : React__namespace.useRef(null));
2477
2638
  const [validationError, updateErrorMessage] = useInputValidationMessage(inputRef, props);
2478
- const nativeProps = __rest(props, ["onValueChange", "customError", "patternErrorMessage"]);
2639
+ const nativeProps = __rest(props, ["emptyString", "onValueChange", "customError", "patternErrorMessage"]);
2479
2640
  useIgnoreMount(() => {
2480
2641
  var _a;
2481
2642
  if ((_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.checkValidity()) {
@@ -2494,14 +2655,19 @@ const TextInput = React__namespace.forwardRef((props, ref) => {
2494
2655
  }, [props.value]);
2495
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 => {
2496
2657
  var _a;
2497
- setLocalValue(e.target.value || undefined);
2658
+ setLocalValue(props.emptyString ? e.target.value : e.target.value || undefined);
2498
2659
  (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
2499
2660
  }, onBlur: e => {
2500
- var _a;
2661
+ var _a, _b;
2501
2662
  if (!e.target.checkValidity()) {
2502
2663
  setLocalValue(undefined);
2503
2664
  }
2504
- (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
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
+ });
2669
+ }
2670
+ (_b = props.onBlur) === null || _b === void 0 ? void 0 : _b.call(props, e);
2505
2671
  } })));
2506
2672
  });
2507
2673
 
@@ -2597,6 +2763,9 @@ const Label = (props) => {
2597
2763
  From https://fireship.io/snippets/use-media-query-hook/.
2598
2764
  Tried using https://www.npmjs.com/package/react-media, but it cause Webpack build issues.
2599
2765
  */
2766
+ /** React wrapper around window resizing and window.matchMedia.
2767
+ * https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia
2768
+ */
2600
2769
  const useMediaQuery = (query) => {
2601
2770
  const [matches, setMatches] = React.useState(false);
2602
2771
  React.useEffect(() => {
@@ -2612,11 +2781,13 @@ const useMediaQuery = (query) => {
2612
2781
  };
2613
2782
 
2614
2783
  const Nav = (props) => {
2784
+ var _a, _b;
2615
2785
  const nav = React__namespace.useRef(null);
2616
2786
  const theme = useThemeSafely();
2617
2787
  const totalNavOffset = `calc(${theme.layout.navWidth} + 20px)`;
2618
2788
  const backdrop = React__namespace.useContext(BackdropContext);
2619
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);
2620
2791
  const slideRight = css.keyframes `
2621
2792
  0% {
2622
2793
  transform: translateX(0);
@@ -2677,9 +2848,11 @@ const Nav = (props) => {
2677
2848
  props.toggle(false);
2678
2849
  }
2679
2850
  }, [backdrop.showing]);
2680
- React__namespace.useEffect(() => {
2681
- backdrop.setShow(props.show);
2682
- }, [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);
2683
2856
  React__namespace.useLayoutEffect(() => {
2684
2857
  if (nav && nav.current) {
2685
2858
  if (props.show) {
@@ -2710,6 +2883,7 @@ const Nav = (props) => {
2710
2883
  return (React__namespace.createElement("nav", { ref: nav, className: css.cx('nav', navStyles, props.className) }, props.children));
2711
2884
  };
2712
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.
2713
2887
  const OmniLink = (props) => {
2714
2888
  var _a, _b, _c;
2715
2889
  const theme = useThemeSafely();
@@ -2886,7 +3060,7 @@ const OmniLink = (props) => {
2886
3060
  const Picker = (props) => {
2887
3061
  const selectProps = __rest(props
2888
3062
  // if we put numbers in, we expect them out
2889
- , ["value", "options", "onChange", "readOnly", "round", "controlAlign"]);
3063
+ , ["value", "options", "onValueChange", "readOnly", "round", "controlAlign"]);
2890
3064
  // if we put numbers in, we expect them out
2891
3065
  let isNumber = false;
2892
3066
  if (props.options && props.options.length) {
@@ -2956,6 +3130,7 @@ const Picker = (props) => {
2956
3130
  (_a = selectProps.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(selectProps, e);
2957
3131
  }
2958
3132
  }, onChange: e => {
3133
+ var _a;
2959
3134
  let val = e.target.value;
2960
3135
  if (isNumber) {
2961
3136
  val = parseInt(val, 10);
@@ -2963,7 +3138,8 @@ const Picker = (props) => {
2963
3138
  val = '';
2964
3139
  }
2965
3140
  }
2966
- props.onChange(val, e);
3141
+ props.onValueChange(val);
3142
+ (_a = props.onChange) === null || _a === void 0 ? void 0 : _a.call(props, e);
2967
3143
  } }), (props.options || []).map(o => {
2968
3144
  var _a;
2969
3145
  let val;
@@ -3050,8 +3226,8 @@ const BoundMemoryPager = (p) => {
3050
3226
  var _a, _b, _c;
3051
3227
  const { pager, showPageText } = p, rest = __rest(p, ["pager", "showPageText"]);
3052
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" },
3053
- 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" },
3054
- 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 => {
3055
3231
  p.onPage(d);
3056
3232
  } })));
3057
3233
  };
@@ -3062,8 +3238,8 @@ const BoundStaticPager = (p) => {
3062
3238
  const showLimit = !!(result.limit && p.limitOptions && p.limitOptions.length > 1 && p.onLimit);
3063
3239
  const showSort = !!(p.sort !== undefined && p.sortOptions && p.sortOptions.length > 1 && p.onSort);
3064
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" },
3065
- 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" },
3066
- 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 => {
3067
3243
  var _a;
3068
3244
  (_a = p.onSort) === null || _a === void 0 ? void 0 : _a.call(p, v);
3069
3245
  } }))) : undefined, page: d => {
@@ -3071,6 +3247,7 @@ const BoundStaticPager = (p) => {
3071
3247
  } })));
3072
3248
  };
3073
3249
 
3250
+ /** A page of data with helpers props. */
3074
3251
  class PagedResult {
3075
3252
  constructor(items = [], total = 0, page = 0, limit = 0) {
3076
3253
  this.items = items;
@@ -3081,10 +3258,6 @@ class PagedResult {
3081
3258
  static fromDto(dto) {
3082
3259
  return new PagedResult(dto.items, dto.total, dto.page, dto.limit);
3083
3260
  }
3084
- // allItems: T[]
3085
- // get pageItems(): T[] {
3086
- // return this.allItems.slice(this.minPageItemIndex, this.maxPageItemIndex + 1)
3087
- // }
3088
3261
  /** Helper for items.length */
3089
3262
  get length() {
3090
3263
  return this.items ? this.items.length : 0;
@@ -3208,7 +3381,7 @@ class PagedResult {
3208
3381
  }
3209
3382
  }
3210
3383
 
3211
- /** For in-memory paging. */
3384
+ /** In-memory pager. */
3212
3385
  class ItemPager {
3213
3386
  constructor(allItems, options) {
3214
3387
  var _a, _b, _c, _d;
@@ -3458,6 +3631,7 @@ const cleanPct = (value) => {
3458
3631
  return 0;
3459
3632
  };
3460
3633
 
3634
+ /** Provides stateful notifications around async calls. */
3461
3635
  const useWaiting = (func) => {
3462
3636
  // Guard against the owner of this hook being unmounted at the time of .finally.
3463
3637
  const isCancelled = React.useRef(false);
@@ -3528,6 +3702,28 @@ const GlobalStyles = () => {
3528
3702
  return null;
3529
3703
  };
3530
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
+
3531
3727
  const Slider = (p) => {
3532
3728
  const theme = useThemeSafely();
3533
3729
  const currentValue = React.useRef(p.value);
@@ -3810,11 +4006,8 @@ const defaultRows = 10;
3810
4006
  const TextArea = React__namespace.forwardRef((props, ref) => {
3811
4007
  var _a, _b;
3812
4008
  const [localValue, setLocalValue] = React__namespace.useState(props.value);
3813
- const [validationError, setValidationError] = React__namespace.useState('');
3814
- const updateErrorMessage = React__namespace.useCallback(() => {
3815
- setValidationError(getValidationMessage(inputRef.current));
3816
- }, []);
3817
4009
  const inputRef = (ref !== null && ref !== void 0 ? ref : React__namespace.useRef(null));
4010
+ const [validationError, updateErrorMessage] = useInputValidationMessage(inputRef, props);
3818
4011
  const nativeProps = __rest(props, ["onValueChange"]);
3819
4012
  const theme = useThemeSafely();
3820
4013
  React__namespace.useEffect(() => {
@@ -3881,6 +4074,14 @@ const TextArea = React__namespace.forwardRef((props, ref) => {
3881
4074
  var _a;
3882
4075
  setLocalValue(e.target.value || undefined);
3883
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
+ });
4083
+ }
4084
+ (_a = props.onBlur) === null || _a === void 0 ? void 0 : _a.call(props, e);
3884
4085
  } })),
3885
4086
  React__namespace.createElement(InputErrorDisplay, { error: validationError })));
3886
4087
  });
@@ -3969,17 +4170,14 @@ const WaitingIndicator = (p) => {
3969
4170
  const [show, setShow] = React.useState(p.show);
3970
4171
  const hideTimer = React.useRef(0);
3971
4172
  const lastShowStatus = React.useRef(false);
3972
- const log = useLogger(`WaitingIndicator ${(_a = p.id) !== null && _a !== void 0 ? _a : '?'}`, (_b = p.debug) !== null && _b !== void 0 ? _b : false);
3973
- 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) {
3974
4175
  React.useEffect(() => {
3975
4176
  log('mounted');
3976
4177
  return () => {
3977
4178
  log('unmounted');
3978
4179
  };
3979
4180
  }, []);
3980
- React.useEffect(() => {
3981
- log('re-rendered');
3982
- });
3983
4181
  }
3984
4182
  React.useEffect(() => {
3985
4183
  log('show changed', p.show);
@@ -4020,7 +4218,7 @@ const WaitingIndicator = (p) => {
4020
4218
  }
4021
4219
  }
4022
4220
  }, [p.show]);
4023
- 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 },
4024
4222
  React__default['default'].createElement("div", { className: css.css({
4025
4223
  color: 'white',
4026
4224
  fontSize: '3rem'
@@ -4028,6 +4226,7 @@ const WaitingIndicator = (p) => {
4028
4226
  React__default['default'].createElement(Icon, { id: "waiting", spin: true }))));
4029
4227
  };
4030
4228
 
4229
+ exports.Accordian = Accordian;
4031
4230
  exports.Autocomplete = Autocomplete;
4032
4231
  exports.Backdrop = Backdrop$1;
4033
4232
  exports.Backdrop2 = Backdrop;
@@ -4088,7 +4287,14 @@ exports.Tr = Tr;
4088
4287
  exports.WaitingIndicator = WaitingIndicator;
4089
4288
  exports.calcDynamicThemeProps = calcDynamicThemeProps;
4090
4289
  exports.defaultTheme = defaultTheme;
4290
+ exports.enumToEntities = enumToEntities;
4091
4291
  exports.getCurrencyDisplay = getCurrencyDisplay;
4292
+ exports.getFileSizeDisplay = getFileSizeDisplay;
4293
+ exports.useAccordianState = useAccordianState;
4294
+ exports.useBooleanChanged = useBooleanChanged;
4295
+ exports.useIgnoreMount = useIgnoreMount;
4092
4296
  exports.useMediaQuery = useMediaQuery;
4297
+ exports.useScrollbarSize = useScrollbarSize;
4093
4298
  exports.useThemeSafely = useThemeSafely;
4299
+ exports.useWaiting = useWaiting;
4094
4300
  //# sourceMappingURL=index.js.map