@servicetitan/design-system 14.5.3 → 14.5.12
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.
- package/dist/components/ButtonToggle/ButtonToggle.js +39 -3
- package/dist/components/ButtonToggle/ButtonToggle.js.map +1 -1
- package/dist/components/ButtonToggle/ButtonToggle.test.js +77 -1
- package/dist/components/ButtonToggle/ButtonToggle.test.js.map +1 -1
- package/dist/components/ButtonToggle/ButtonToggleItem.d.ts +2 -0
- package/dist/components/ButtonToggle/ButtonToggleItem.js +2 -2
- package/dist/components/ButtonToggle/ButtonToggleItem.js.map +1 -1
- package/dist/components/DatePicker/utils/utils.d.ts +1 -1
- package/dist/components/DatePicker/utils/utils.js +10 -7
- package/dist/components/DatePicker/utils/utils.js.map +1 -1
- package/dist/components/DatePicker/utils/utils.missingTimeZone.test.d.ts +1 -0
- package/dist/components/DatePicker/utils/utils.missingTimeZone.test.js +35 -0
- package/dist/components/DatePicker/utils/utils.missingTimeZone.test.js.map +1 -0
- package/dist/components/TimePicker/utils/TimePickerUtils.test.js +73 -0
- package/dist/components/TimePicker/utils/TimePickerUtils.test.js.map +1 -1
- package/dist/components/TimePicker/utils/timezoneUtils.d.ts +1 -0
- package/dist/components/TimePicker/utils/timezoneUtils.js +25 -15
- package/dist/components/TimePicker/utils/timezoneUtils.js.map +1 -1
- package/dist/global/index.css +1 -1
- package/dist/global/version.css +1 -1
- package/dist/system-light.css +1 -1
- package/dist/system-light.min.css +1 -1
- package/dist/system.css +1 -1
- package/dist/system.css.map +1 -1
- package/dist/system.min.css +1 -1
- package/dist/utilities/TimezoneProvider/TimezoneProvider.js +9 -6
- package/dist/utilities/TimezoneProvider/TimezoneProvider.js.map +1 -1
- package/dist/utilities/TimezoneProvider/TimezoneProvider.test.js +41 -0
- package/dist/utilities/TimezoneProvider/TimezoneProvider.test.js.map +1 -1
- package/package.json +7 -7
|
@@ -15,18 +15,36 @@ import { ButtonToggleItem } from './ButtonToggleItem';
|
|
|
15
15
|
import { useArrowNav } from '../../hooks/useArrowNav';
|
|
16
16
|
import { useMergedRef } from '../../hooks';
|
|
17
17
|
export const ButtonToggle = React.forwardRef(function (_a, ref) {
|
|
18
|
-
var { color = 'primary', primary, negative, className, disabled, el: ButtonToggleElement = 'div', clearable, options = [], large, small, xsmall, name, size = 'medium', value, onChange } = _a, props = __rest(_a, ["color", "primary", "negative", "className", "disabled", "el", "clearable", "options", "large", "small", "xsmall", "name", "size", "value", "onChange"]);
|
|
18
|
+
var { color = 'primary', primary, negative, className, disabled, el: ButtonToggleElement = 'div', clearable, options = [], large, small, xsmall, name, size = 'medium', value, onChange, onFocus, onBlur } = _a, props = __rest(_a, ["color", "primary", "negative", "className", "disabled", "el", "clearable", "options", "large", "small", "xsmall", "name", "size", "value", "onChange", "onFocus", "onBlur"]);
|
|
19
19
|
const [selected, setSelected] = React.useState(value);
|
|
20
|
+
const [focusedIndex, setFocusedIndex] = React.useState(null);
|
|
21
|
+
const lastTabStopIndex = React.useRef(0);
|
|
20
22
|
React.useEffect(() => {
|
|
21
23
|
setSelected(value);
|
|
22
24
|
}, [value]);
|
|
25
|
+
const handleFocus = (e) => {
|
|
26
|
+
const buttons = Array.from(e.currentTarget.querySelectorAll('button'));
|
|
27
|
+
const index = buttons.indexOf(e.target);
|
|
28
|
+
if (index >= 0)
|
|
29
|
+
setFocusedIndex(index);
|
|
30
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(e);
|
|
31
|
+
};
|
|
32
|
+
const handleBlur = (e) => {
|
|
33
|
+
setFocusedIndex(null);
|
|
34
|
+
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
|
35
|
+
};
|
|
23
36
|
const arrowNavRef = React.useRef(null);
|
|
24
37
|
const mergedRef = useMergedRef(ref, arrowNavRef);
|
|
25
38
|
useArrowNav({
|
|
26
39
|
scope: arrowNavRef,
|
|
27
40
|
handleY: false,
|
|
28
41
|
focusableSelector: { selector: 'button:not([disabled])' },
|
|
29
|
-
onKeyDown: (e, data) => { var _a; return (_a = data.next) === null || _a === void 0 ? void 0 : _a.click(); },
|
|
42
|
+
onKeyDown: (e, data) => { var _a; return (_a = data.next) === null || _a === void 0 ? void 0 : _a.click(); },
|
|
43
|
+
/*
|
|
44
|
+
* The tab stop is set while rendering; the hook's tab grouping would measure and
|
|
45
|
+
* rewrite every button after each render, forcing a layout per toggle.
|
|
46
|
+
*/
|
|
47
|
+
tabIndexGroup: false,
|
|
30
48
|
});
|
|
31
49
|
const handleClick = (select) => {
|
|
32
50
|
const realSelected = select !== selected || !clearable ? select : undefined;
|
|
@@ -82,7 +100,9 @@ export const ButtonToggle = React.forwardRef(function (_a, ref) {
|
|
|
82
100
|
realColor = 'negative';
|
|
83
101
|
return realColor;
|
|
84
102
|
};
|
|
103
|
+
let tabStopIndex = -1;
|
|
85
104
|
const getButtonToggleItemProps = (options) => {
|
|
105
|
+
var _a, _b, _c;
|
|
86
106
|
let realSize = size;
|
|
87
107
|
// Override string props with boolean values
|
|
88
108
|
if (xsmall)
|
|
@@ -91,6 +111,16 @@ export const ButtonToggle = React.forwardRef(function (_a, ref) {
|
|
|
91
111
|
realSize = 'small';
|
|
92
112
|
if (large)
|
|
93
113
|
realSize = 'large';
|
|
114
|
+
const isFocusable = (option) => !option.disabled && !option.hidden;
|
|
115
|
+
const focusableAt = (index) => index !== null && options[index] && isFocusable(options[index])
|
|
116
|
+
? options[index]
|
|
117
|
+
: undefined;
|
|
118
|
+
/*
|
|
119
|
+
* Same order as the tab grouping of useArrowNav: the focused option, the selected one,
|
|
120
|
+
* the previous tab stop, then the first option that can be focused.
|
|
121
|
+
*/
|
|
122
|
+
const tabStop = (_c = (_b = (_a = focusableAt(focusedIndex)) !== null && _a !== void 0 ? _a : options.find((option) => option.selected && isFocusable(option))) !== null && _b !== void 0 ? _b : focusableAt(lastTabStopIndex.current)) !== null && _c !== void 0 ? _c : options.find(isFocusable);
|
|
123
|
+
tabStopIndex = tabStop ? options.indexOf(tabStop) : -1;
|
|
94
124
|
const buttonToggleItemProps = options.map((option) => {
|
|
95
125
|
return {
|
|
96
126
|
name,
|
|
@@ -108,6 +138,7 @@ export const ButtonToggle = React.forwardRef(function (_a, ref) {
|
|
|
108
138
|
},
|
|
109
139
|
icon: option.icon,
|
|
110
140
|
iconName: option.iconName,
|
|
141
|
+
tabIndex: option === tabStop ? 0 : -1,
|
|
111
142
|
};
|
|
112
143
|
});
|
|
113
144
|
return buttonToggleItemProps;
|
|
@@ -116,6 +147,11 @@ export const ButtonToggle = React.forwardRef(function (_a, ref) {
|
|
|
116
147
|
const onlySelectedOptions = getOnlySelected(copiedOptions);
|
|
117
148
|
const normalizedOptions = getNormalized(onlySelectedOptions);
|
|
118
149
|
const buttonToggleItemProps = getButtonToggleItemProps(normalizedOptions);
|
|
150
|
+
// Written after commit so that a render React discards cannot move the tab stop
|
|
151
|
+
React.useEffect(() => {
|
|
152
|
+
if (tabStopIndex >= 0)
|
|
153
|
+
lastTabStopIndex.current = tabStopIndex;
|
|
154
|
+
});
|
|
119
155
|
const contents = buttonToggleItemProps.map((buttonProp, index) => {
|
|
120
156
|
return React.createElement(ButtonToggleItem, Object.assign({ key: index }, buttonProp));
|
|
121
157
|
});
|
|
@@ -124,6 +160,6 @@ export const ButtonToggle = React.forwardRef(function (_a, ref) {
|
|
|
124
160
|
'ButtonToggle--disabled': disabled,
|
|
125
161
|
[`ButtonToggle--${realColor}`]: realColor,
|
|
126
162
|
});
|
|
127
|
-
return (React.createElement(ButtonToggleElement, Object.assign({ className: ButtonToggleClasses, "data-anvil-component": "ButtonToggle", ref: mergedRef, role: clearable ? null : 'radiogroup' }, props), contents));
|
|
163
|
+
return (React.createElement(ButtonToggleElement, Object.assign({ className: ButtonToggleClasses, "data-anvil-component": "ButtonToggle", ref: mergedRef, role: clearable ? null : 'radiogroup', onFocus: handleFocus, onBlur: handleBlur }, props), contents));
|
|
128
164
|
});
|
|
129
165
|
//# sourceMappingURL=ButtonToggle.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ButtonToggle.js","sourceRoot":"","sources":["../../../src/components/ButtonToggle/ButtonToggle.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport classnames from 'classnames';\n\nimport { ButtonToggleItem, ButtonToggleItemProps } from './ButtonToggleItem';\nimport { useArrowNav } from '../../hooks/useArrowNav';\nimport { useMergedRef } from '../../hooks';\n\n/**\n * Option properties\n */\nexport interface OptionProps<T> {\n\t/** Content of the element, i.e. the displayed text */\n\ttext?: ButtonToggleItemProps<T>['text'];\n\n\t/** Value of the option */\n\tvalue: ButtonToggleItemProps<T>['value'];\n\n\t/** Returns value of option for the click event */\n\tonClick?: ButtonToggleItemProps<T>['onClick'];\n\n\t/** Indicates whether the element is disabled or not. It makes the option unselectable */\n\tdisabled?: ButtonToggleItemProps<T>['disabled'];\n\n\t/** Sets the option's selected state */\n\tselected?: ButtonToggleItemProps<T>['selected'];\n\n\t/** Hide the option (not commonly used) */\n\thidden?: ButtonToggleItemProps<T>['hidden'];\n\n\t/** React element to be rendered as an icon */\n\ticon?: ButtonToggleItemProps<T>['icon'];\n\n\t/** Name of icon to render inside of button */\n\ticonName?: ButtonToggleItemProps<T>['iconName'];\n}\n\n/**\n * ButtonToggle properties\n */\nexport interface ButtonTogglePropsStrict<T> {\n\t/** String of class names to add to component */\n\tclassName?: string;\n\n\t/** Switch on unselected state */\n\tclearable?: boolean;\n\n\t/** Selected button background color.<br /> Possible values: <i>'primary'</i>, <i>'blue'</i>, <i>'negative'</i>, <i>'red'</i> */\n\tcolor?: ButtonToggleItemProps<T>['color']; // 'default', 'grey' are colors of not selected buttons\n\n\t/** Sets all the option's unselectable */\n\tdisabled?: ButtonToggleItemProps<T>['disabled'];\n\n\t/** Element to use for base component */\n\tel?: any;\n\n\t/** Shortcut for large-sized buttons */\n\tlarge?: boolean;\n\n\t/** Specifies the name of the ButtonToggle element */\n\tname?: string;\n\n\t/** Negative styling (red) */\n\tnegative?: boolean;\n\n\t/** Called when the user changes the value */\n\tonChange?: (value: T | undefined) => void;\n\n\t/** List of options */\n\toptions?: OptionProps<T>[];\n\n\t/** Primary button */\n\tprimary?: boolean;\n\n\t/** Sets size of the buttons.<br /> Possible values: <i>'large'</i>, <i>'small'</i> */\n\tsize?: ButtonToggleItemProps<T>['size'];\n\n\t/** Shortcut for small-sized buttons */\n\tsmall?: boolean;\n\n\t/** Value of the ButtonToggle element. Sets corresponding option selected */\n\tvalue?: ButtonToggleItemProps<T>['value'];\n\n\t/** Shortcut for x-small-sized buttons */\n\txsmall?: boolean;\n}\n\nexport interface ButtonToggleProps<T> extends ButtonTogglePropsStrict<T> {\n\t/** Unstrict Props */\n\t[propName: string]: any;\n}\n\ninterface ButtonToggleState<T> {\n\tselected: ButtonToggleItemProps<T>['value'];\n}\n\nexport const ButtonToggle: <T>(\n\tprops: React.PropsWithoutRef<ButtonToggleProps<T>> &\n\t\tReact.RefAttributes<HTMLElement>\n) => React.ReactElement | null = React.forwardRef(function <T>(\n\t{\n\t\tcolor = 'primary',\n\t\tprimary,\n\t\tnegative,\n\t\tclassName,\n\t\tdisabled,\n\t\tel: ButtonToggleElement = 'div',\n\t\tclearable,\n\t\toptions = [],\n\t\tlarge,\n\t\tsmall,\n\t\txsmall,\n\t\tname,\n\t\tsize = 'medium',\n\t\tvalue,\n\t\tonChange,\n\t\t...props\n\t}: ButtonToggleProps<T>,\n\tref: React.RefObject<HTMLElement>\n) {\n\tconst [selected, setSelected] =\n\t\tReact.useState<ButtonToggleState<T>['selected']>(value);\n\n\tReact.useEffect(() => {\n\t\tsetSelected(value);\n\t}, [value]);\n\n\tconst arrowNavRef = React.useRef(null);\n\tconst mergedRef = useMergedRef(ref, arrowNavRef);\n\n\tuseArrowNav({\n\t\tscope: arrowNavRef,\n\t\thandleY: false,\n\t\tfocusableSelector: { selector: 'button:not([disabled])' },\n\t\tonKeyDown: (e: KeyboardEvent, data: any) => data.next?.click(), // Auto-selection\n\t});\n\n\tconst handleClick = (select: ButtonToggleItemProps<T>['value']) => {\n\t\tconst realSelected =\n\t\t\tselect !== selected || !clearable ? select : undefined;\n\n\t\tif (onChange) onChange(realSelected);\n\t\tsetSelected(realSelected);\n\t};\n\n\tconst copyOptions = (options: OptionProps<T>[]): OptionProps<T>[] => {\n\t\tconst copiedOptions = options.map((option) => {\n\t\t\treturn {\n\t\t\t\ttext: option.text,\n\t\t\t\tvalue: option.value,\n\t\t\t\tonClick: option.onClick,\n\t\t\t\tdisabled: option.disabled,\n\t\t\t\tselected: option.selected,\n\t\t\t\thidden: option.hidden,\n\t\t\t\ticon: option.icon,\n\t\t\t\ticonName: option.iconName,\n\t\t\t};\n\t\t});\n\n\t\treturn copiedOptions;\n\t};\n\n\tconst getOnlySelected = (options: OptionProps<T>[]): OptionProps<T>[] => {\n\t\tlet isSelected = false;\n\n\t\tconst onlyOneSelectedOptions = options.map((option) => {\n\t\t\tif (option.selected && !isSelected) {\n\t\t\t\tisSelected = true;\n\t\t\t} else {\n\t\t\t\toption.selected = false;\n\t\t\t}\n\t\t\treturn option;\n\t\t});\n\n\t\treturn onlyOneSelectedOptions;\n\t};\n\n\tconst getNormalized = (options: OptionProps<T>[]): OptionProps<T>[] => {\n\t\tconst realOptions = options.map((option) => {\n\t\t\tif (disabled) option.disabled = disabled;\n\t\t\tif (option.text === undefined) option.text = null;\n\t\t\tif (selected !== undefined || clearable)\n\t\t\t\toption.selected = selected === option.value;\n\n\t\t\treturn option;\n\t\t});\n\n\t\treturn realOptions;\n\t};\n\n\tconst getRealColor = () => {\n\t\tlet realColor: ButtonToggleItemProps<T>['color'] = color;\n\n\t\tif (primary) realColor = 'primary';\n\t\tif (negative) realColor = 'negative';\n\n\t\treturn realColor;\n\t};\n\n\tconst getButtonToggleItemProps = (\n\t\toptions: OptionProps<T>[]\n\t): ButtonToggleItemProps<T>[] => {\n\t\tlet realSize: ButtonToggleItemProps<T>['size'] = size;\n\n\t\t// Override string props with boolean values\n\t\tif (xsmall) realSize = 'xsmall';\n\t\tif (small) realSize = 'small';\n\t\tif (large) realSize = 'large';\n\n\t\tconst buttonToggleItemProps = options.map((option) => {\n\t\t\treturn {\n\t\t\t\tname,\n\t\t\t\ttext: option.text,\n\t\t\t\tvalue: option.value,\n\t\t\t\tdisabled: option.disabled,\n\t\t\t\tselected: option.selected,\n\t\t\t\tcolor: getRealColor(),\n\t\t\t\tsize: realSize,\n\t\t\t\thidden: option.hidden,\n\t\t\t\tonClick: (value: ButtonToggleItemProps<T>['value']) => {\n\t\t\t\t\tif (option.onClick) option.onClick(value);\n\t\t\t\t\thandleClick(value);\n\t\t\t\t},\n\t\t\t\ticon: option.icon,\n\t\t\t\ticonName: option.iconName,\n\t\t\t};\n\t\t});\n\n\t\treturn buttonToggleItemProps;\n\t};\n\n\tconst copiedOptions = copyOptions(options);\n\n\tconst onlySelectedOptions = getOnlySelected(copiedOptions);\n\n\tconst normalizedOptions = getNormalized(onlySelectedOptions);\n\n\tconst buttonToggleItemProps = getButtonToggleItemProps(normalizedOptions);\n\n\tconst contents = buttonToggleItemProps.map((buttonProp, index) => {\n\t\treturn <ButtonToggleItem<T> key={index} {...buttonProp} />;\n\t});\n\n\tconst realColor = getRealColor();\n\n\tconst ButtonToggleClasses = classnames('ButtonToggle', className, {\n\t\t'ButtonToggle--disabled': disabled,\n\t\t[`ButtonToggle--${realColor}`]: realColor,\n\t});\n\n\treturn (\n\t\t<ButtonToggleElement\n\t\t\tclassName={ButtonToggleClasses}\n\t\t\tdata-anvil-component=\"ButtonToggle\"\n\t\t\tref={mergedRef}\n\t\t\trole={clearable ? null : 'radiogroup'}\n\t\t\t{...props}\n\t\t>\n\t\t\t{contents}\n\t\t</ButtonToggleElement>\n\t);\n});\n"]}
|
|
1
|
+
{"version":3,"file":"ButtonToggle.js","sourceRoot":"","sources":["../../../src/components/ButtonToggle/ButtonToggle.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport classnames from 'classnames';\n\nimport { ButtonToggleItem, ButtonToggleItemProps } from './ButtonToggleItem';\nimport { useArrowNav } from '../../hooks/useArrowNav';\nimport { useMergedRef } from '../../hooks';\n\n/**\n * Option properties\n */\nexport interface OptionProps<T> {\n\t/** Content of the element, i.e. the displayed text */\n\ttext?: ButtonToggleItemProps<T>['text'];\n\n\t/** Value of the option */\n\tvalue: ButtonToggleItemProps<T>['value'];\n\n\t/** Returns value of option for the click event */\n\tonClick?: ButtonToggleItemProps<T>['onClick'];\n\n\t/** Indicates whether the element is disabled or not. It makes the option unselectable */\n\tdisabled?: ButtonToggleItemProps<T>['disabled'];\n\n\t/** Sets the option's selected state */\n\tselected?: ButtonToggleItemProps<T>['selected'];\n\n\t/** Hide the option (not commonly used) */\n\thidden?: ButtonToggleItemProps<T>['hidden'];\n\n\t/** React element to be rendered as an icon */\n\ticon?: ButtonToggleItemProps<T>['icon'];\n\n\t/** Name of icon to render inside of button */\n\ticonName?: ButtonToggleItemProps<T>['iconName'];\n}\n\n/**\n * ButtonToggle properties\n */\nexport interface ButtonTogglePropsStrict<T> {\n\t/** String of class names to add to component */\n\tclassName?: string;\n\n\t/** Switch on unselected state */\n\tclearable?: boolean;\n\n\t/** Selected button background color.<br /> Possible values: <i>'primary'</i>, <i>'blue'</i>, <i>'negative'</i>, <i>'red'</i> */\n\tcolor?: ButtonToggleItemProps<T>['color']; // 'default', 'grey' are colors of not selected buttons\n\n\t/** Sets all the option's unselectable */\n\tdisabled?: ButtonToggleItemProps<T>['disabled'];\n\n\t/** Element to use for base component */\n\tel?: any;\n\n\t/** Shortcut for large-sized buttons */\n\tlarge?: boolean;\n\n\t/** Specifies the name of the ButtonToggle element */\n\tname?: string;\n\n\t/** Negative styling (red) */\n\tnegative?: boolean;\n\n\t/** Called when the user changes the value */\n\tonChange?: (value: T | undefined) => void;\n\n\t/** List of options */\n\toptions?: OptionProps<T>[];\n\n\t/** Primary button */\n\tprimary?: boolean;\n\n\t/** Sets size of the buttons.<br /> Possible values: <i>'large'</i>, <i>'small'</i> */\n\tsize?: ButtonToggleItemProps<T>['size'];\n\n\t/** Shortcut for small-sized buttons */\n\tsmall?: boolean;\n\n\t/** Value of the ButtonToggle element. Sets corresponding option selected */\n\tvalue?: ButtonToggleItemProps<T>['value'];\n\n\t/** Shortcut for x-small-sized buttons */\n\txsmall?: boolean;\n}\n\nexport interface ButtonToggleProps<T> extends ButtonTogglePropsStrict<T> {\n\t/** Unstrict Props */\n\t[propName: string]: any;\n}\n\ninterface ButtonToggleState<T> {\n\tselected: ButtonToggleItemProps<T>['value'];\n}\n\nexport const ButtonToggle: <T>(\n\tprops: React.PropsWithoutRef<ButtonToggleProps<T>> &\n\t\tReact.RefAttributes<HTMLElement>\n) => React.ReactElement | null = React.forwardRef(function <T>(\n\t{\n\t\tcolor = 'primary',\n\t\tprimary,\n\t\tnegative,\n\t\tclassName,\n\t\tdisabled,\n\t\tel: ButtonToggleElement = 'div',\n\t\tclearable,\n\t\toptions = [],\n\t\tlarge,\n\t\tsmall,\n\t\txsmall,\n\t\tname,\n\t\tsize = 'medium',\n\t\tvalue,\n\t\tonChange,\n\t\tonFocus,\n\t\tonBlur,\n\t\t...props\n\t}: ButtonToggleProps<T>,\n\tref: React.RefObject<HTMLElement>\n) {\n\tconst [selected, setSelected] =\n\t\tReact.useState<ButtonToggleState<T>['selected']>(value);\n\tconst [focusedIndex, setFocusedIndex] = React.useState<number | null>(null);\n\tconst lastTabStopIndex = React.useRef(0);\n\n\tReact.useEffect(() => {\n\t\tsetSelected(value);\n\t}, [value]);\n\n\tconst handleFocus = (e: React.FocusEvent<HTMLElement>) => {\n\t\tconst buttons = Array.from(e.currentTarget.querySelectorAll('button'));\n\t\tconst index = buttons.indexOf(e.target as HTMLButtonElement);\n\t\tif (index >= 0) setFocusedIndex(index);\n\t\tonFocus?.(e);\n\t};\n\n\tconst handleBlur = (e: React.FocusEvent<HTMLElement>) => {\n\t\tsetFocusedIndex(null);\n\t\tonBlur?.(e);\n\t};\n\n\tconst arrowNavRef = React.useRef(null);\n\tconst mergedRef = useMergedRef(ref, arrowNavRef);\n\n\tuseArrowNav({\n\t\tscope: arrowNavRef,\n\t\thandleY: false,\n\t\tfocusableSelector: { selector: 'button:not([disabled])' },\n\t\tonKeyDown: (e: KeyboardEvent, data: any) => data.next?.click(), // Auto-selection\n\t\t/*\n\t\t * The tab stop is set while rendering; the hook's tab grouping would measure and\n\t\t * rewrite every button after each render, forcing a layout per toggle.\n\t\t */\n\t\ttabIndexGroup: false,\n\t});\n\n\tconst handleClick = (select: ButtonToggleItemProps<T>['value']) => {\n\t\tconst realSelected =\n\t\t\tselect !== selected || !clearable ? select : undefined;\n\n\t\tif (onChange) onChange(realSelected);\n\t\tsetSelected(realSelected);\n\t};\n\n\tconst copyOptions = (options: OptionProps<T>[]): OptionProps<T>[] => {\n\t\tconst copiedOptions = options.map((option) => {\n\t\t\treturn {\n\t\t\t\ttext: option.text,\n\t\t\t\tvalue: option.value,\n\t\t\t\tonClick: option.onClick,\n\t\t\t\tdisabled: option.disabled,\n\t\t\t\tselected: option.selected,\n\t\t\t\thidden: option.hidden,\n\t\t\t\ticon: option.icon,\n\t\t\t\ticonName: option.iconName,\n\t\t\t};\n\t\t});\n\n\t\treturn copiedOptions;\n\t};\n\n\tconst getOnlySelected = (options: OptionProps<T>[]): OptionProps<T>[] => {\n\t\tlet isSelected = false;\n\n\t\tconst onlyOneSelectedOptions = options.map((option) => {\n\t\t\tif (option.selected && !isSelected) {\n\t\t\t\tisSelected = true;\n\t\t\t} else {\n\t\t\t\toption.selected = false;\n\t\t\t}\n\t\t\treturn option;\n\t\t});\n\n\t\treturn onlyOneSelectedOptions;\n\t};\n\n\tconst getNormalized = (options: OptionProps<T>[]): OptionProps<T>[] => {\n\t\tconst realOptions = options.map((option) => {\n\t\t\tif (disabled) option.disabled = disabled;\n\t\t\tif (option.text === undefined) option.text = null;\n\t\t\tif (selected !== undefined || clearable)\n\t\t\t\toption.selected = selected === option.value;\n\n\t\t\treturn option;\n\t\t});\n\n\t\treturn realOptions;\n\t};\n\n\tconst getRealColor = () => {\n\t\tlet realColor: ButtonToggleItemProps<T>['color'] = color;\n\n\t\tif (primary) realColor = 'primary';\n\t\tif (negative) realColor = 'negative';\n\n\t\treturn realColor;\n\t};\n\n\tlet tabStopIndex = -1;\n\n\tconst getButtonToggleItemProps = (\n\t\toptions: OptionProps<T>[]\n\t): ButtonToggleItemProps<T>[] => {\n\t\tlet realSize: ButtonToggleItemProps<T>['size'] = size;\n\n\t\t// Override string props with boolean values\n\t\tif (xsmall) realSize = 'xsmall';\n\t\tif (small) realSize = 'small';\n\t\tif (large) realSize = 'large';\n\n\t\tconst isFocusable = (option: OptionProps<T>) =>\n\t\t\t!option.disabled && !option.hidden;\n\t\tconst focusableAt = (index: number | null) =>\n\t\t\tindex !== null && options[index] && isFocusable(options[index])\n\t\t\t\t? options[index]\n\t\t\t\t: undefined;\n\n\t\t/*\n\t\t * Same order as the tab grouping of useArrowNav: the focused option, the selected one,\n\t\t * the previous tab stop, then the first option that can be focused.\n\t\t */\n\t\tconst tabStop =\n\t\t\tfocusableAt(focusedIndex) ??\n\t\t\toptions.find((option) => option.selected && isFocusable(option)) ??\n\t\t\tfocusableAt(lastTabStopIndex.current) ??\n\t\t\toptions.find(isFocusable);\n\t\ttabStopIndex = tabStop ? options.indexOf(tabStop) : -1;\n\n\t\tconst buttonToggleItemProps = options.map((option) => {\n\t\t\treturn {\n\t\t\t\tname,\n\t\t\t\ttext: option.text,\n\t\t\t\tvalue: option.value,\n\t\t\t\tdisabled: option.disabled,\n\t\t\t\tselected: option.selected,\n\t\t\t\tcolor: getRealColor(),\n\t\t\t\tsize: realSize,\n\t\t\t\thidden: option.hidden,\n\t\t\t\tonClick: (value: ButtonToggleItemProps<T>['value']) => {\n\t\t\t\t\tif (option.onClick) option.onClick(value);\n\t\t\t\t\thandleClick(value);\n\t\t\t\t},\n\t\t\t\ticon: option.icon,\n\t\t\t\ticonName: option.iconName,\n\t\t\t\ttabIndex: option === tabStop ? 0 : -1,\n\t\t\t};\n\t\t});\n\n\t\treturn buttonToggleItemProps;\n\t};\n\n\tconst copiedOptions = copyOptions(options);\n\n\tconst onlySelectedOptions = getOnlySelected(copiedOptions);\n\n\tconst normalizedOptions = getNormalized(onlySelectedOptions);\n\n\tconst buttonToggleItemProps = getButtonToggleItemProps(normalizedOptions);\n\n\t// Written after commit so that a render React discards cannot move the tab stop\n\tReact.useEffect(() => {\n\t\tif (tabStopIndex >= 0) lastTabStopIndex.current = tabStopIndex;\n\t});\n\n\tconst contents = buttonToggleItemProps.map((buttonProp, index) => {\n\t\treturn <ButtonToggleItem<T> key={index} {...buttonProp} />;\n\t});\n\n\tconst realColor = getRealColor();\n\n\tconst ButtonToggleClasses = classnames('ButtonToggle', className, {\n\t\t'ButtonToggle--disabled': disabled,\n\t\t[`ButtonToggle--${realColor}`]: realColor,\n\t});\n\n\treturn (\n\t\t<ButtonToggleElement\n\t\t\tclassName={ButtonToggleClasses}\n\t\t\tdata-anvil-component=\"ButtonToggle\"\n\t\t\tref={mergedRef}\n\t\t\trole={clearable ? null : 'radiogroup'}\n\t\t\tonFocus={handleFocus}\n\t\t\tonBlur={handleBlur}\n\t\t\t{...props}\n\t\t>\n\t\t\t{contents}\n\t\t</ButtonToggleElement>\n\t);\n});\n"]}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import React from 'react';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
11
|
+
import { act, render, screen } from '@testing-library/react';
|
|
12
|
+
import userEvent from '@testing-library/user-event';
|
|
3
13
|
import { ButtonToggle } from './ButtonToggle';
|
|
14
|
+
import { mockElementOffset } from '../../utilities/KeyboardAccessTests/utils';
|
|
15
|
+
mockElementOffset();
|
|
16
|
+
const tabStops = () => screen
|
|
17
|
+
.getAllByRole('button')
|
|
18
|
+
.filter((button) => !button.disabled && button.tabIndex === 0)
|
|
19
|
+
.map((button) => button.textContent);
|
|
4
20
|
describe('ButtonToggle', () => {
|
|
5
21
|
beforeEach(() => jest.spyOn(console, 'error'));
|
|
6
22
|
it('should pass through data automation attributes', () => {
|
|
@@ -11,5 +27,65 @@ describe('ButtonToggle', () => {
|
|
|
11
27
|
const element = container.querySelector('[data-auto-sel="automation-selector"]');
|
|
12
28
|
expect(element).not.toBeNull();
|
|
13
29
|
});
|
|
30
|
+
it('should make only the selected option tabbable', () => {
|
|
31
|
+
render(React.createElement(ButtonToggle, { value: 2, options: [
|
|
32
|
+
{ text: '1', value: 1 },
|
|
33
|
+
{ text: '2', value: 2 },
|
|
34
|
+
{ text: '3', value: 3 },
|
|
35
|
+
] }));
|
|
36
|
+
expect(tabStops()).toEqual(['2']);
|
|
37
|
+
});
|
|
38
|
+
it('should make the first enabled and visible option tabbable when nothing is selected', () => {
|
|
39
|
+
render(React.createElement(ButtonToggle, { clearable: true, options: [
|
|
40
|
+
{ text: '1', value: 1, disabled: true },
|
|
41
|
+
{ text: '2', value: 2, hidden: true },
|
|
42
|
+
{ text: '3', value: 3 },
|
|
43
|
+
] }));
|
|
44
|
+
expect(tabStops()).toEqual(['3']);
|
|
45
|
+
});
|
|
46
|
+
it('should move the tab stop to the first visible option when the selected one is hidden', () => {
|
|
47
|
+
render(React.createElement(ButtonToggle, { value: 1, options: [
|
|
48
|
+
{ text: '1', value: 1, hidden: true },
|
|
49
|
+
{ text: '2', value: 2 },
|
|
50
|
+
{ text: '3', value: 3 },
|
|
51
|
+
] }));
|
|
52
|
+
expect(tabStops()).toEqual(['2']);
|
|
53
|
+
});
|
|
54
|
+
it('should make the focused option tabbable until focus leaves', () => {
|
|
55
|
+
render(React.createElement(ButtonToggle, { value: 1, options: [
|
|
56
|
+
{ text: '1', value: 1 },
|
|
57
|
+
{ text: '2', value: 2 },
|
|
58
|
+
] }));
|
|
59
|
+
const second = screen.getByRole('button', { name: '2' });
|
|
60
|
+
act(() => second.focus());
|
|
61
|
+
expect(tabStops()).toEqual(['2']);
|
|
62
|
+
act(() => second.blur());
|
|
63
|
+
expect(tabStops()).toEqual(['1']);
|
|
64
|
+
});
|
|
65
|
+
it('should keep the cleared option tabbable', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
66
|
+
const user = userEvent.setup();
|
|
67
|
+
const ClearableToggle = () => {
|
|
68
|
+
const [value, setValue] = React.useState(2);
|
|
69
|
+
return (React.createElement(ButtonToggle, { clearable: true, value: value, onChange: setValue, options: [
|
|
70
|
+
{ text: '1', value: 1 },
|
|
71
|
+
{ text: '2', value: 2 },
|
|
72
|
+
] }));
|
|
73
|
+
};
|
|
74
|
+
render(React.createElement(ClearableToggle, null));
|
|
75
|
+
yield user.click(screen.getByRole('button', { name: '2' }));
|
|
76
|
+
yield user.tab();
|
|
77
|
+
expect(tabStops()).toEqual(['2']);
|
|
78
|
+
}));
|
|
79
|
+
it('should not rewrite tab indexes when re-rendered with the same props', () => {
|
|
80
|
+
const options = [
|
|
81
|
+
{ text: '1', value: 1 },
|
|
82
|
+
{ text: '2', value: 2 },
|
|
83
|
+
];
|
|
84
|
+
const { rerender } = render(React.createElement(ButtonToggle, { value: 2, options: options }));
|
|
85
|
+
const setTabIndex = jest.spyOn(HTMLElement.prototype, 'tabIndex', 'set');
|
|
86
|
+
rerender(React.createElement(ButtonToggle, { value: 2, options: options }));
|
|
87
|
+
expect(setTabIndex).not.toHaveBeenCalled();
|
|
88
|
+
setTabIndex.mockRestore();
|
|
89
|
+
});
|
|
14
90
|
});
|
|
15
91
|
//# sourceMappingURL=ButtonToggle.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ButtonToggle.test.js","sourceRoot":"","sources":["../../../src/components/ButtonToggle/ButtonToggle.test.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"ButtonToggle.test.js","sourceRoot":"","sources":["../../../src/components/ButtonToggle/ButtonToggle.test.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\nimport { act, render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { ButtonToggle } from './ButtonToggle';\nimport { mockElementOffset } from '../../utilities/KeyboardAccessTests/utils';\n\nmockElementOffset();\n\nconst tabStops = () =>\n\tscreen\n\t\t.getAllByRole('button')\n\t\t.filter(\n\t\t\t(button: HTMLButtonElement) =>\n\t\t\t\t!button.disabled && button.tabIndex === 0\n\t\t)\n\t\t.map((button) => button.textContent);\n\ndescribe('ButtonToggle', () => {\n\tbeforeEach(() => jest.spyOn(console, 'error'));\n\n\tit('should pass through data automation attributes', () => {\n\t\tconst { container } = render(\n\t\t\t<ButtonToggle\n\t\t\t\tvalue={2}\n\t\t\t\tsmall\n\t\t\t\toptions={[\n\t\t\t\t\t{ text: '1', value: 1 },\n\t\t\t\t\t{ text: '2', value: 2 },\n\t\t\t\t]}\n\t\t\t\tdata-auto-sel=\"automation-selector\"\n\t\t\t/>\n\t\t);\n\n\t\tconst element = container.querySelector(\n\t\t\t'[data-auto-sel=\"automation-selector\"]'\n\t\t);\n\n\t\texpect(element).not.toBeNull();\n\t});\n\n\tit('should make only the selected option tabbable', () => {\n\t\trender(\n\t\t\t<ButtonToggle\n\t\t\t\tvalue={2}\n\t\t\t\toptions={[\n\t\t\t\t\t{ text: '1', value: 1 },\n\t\t\t\t\t{ text: '2', value: 2 },\n\t\t\t\t\t{ text: '3', value: 3 },\n\t\t\t\t]}\n\t\t\t/>\n\t\t);\n\n\t\texpect(tabStops()).toEqual(['2']);\n\t});\n\n\tit('should make the first enabled and visible option tabbable when nothing is selected', () => {\n\t\trender(\n\t\t\t<ButtonToggle\n\t\t\t\tclearable\n\t\t\t\toptions={[\n\t\t\t\t\t{ text: '1', value: 1, disabled: true },\n\t\t\t\t\t{ text: '2', value: 2, hidden: true },\n\t\t\t\t\t{ text: '3', value: 3 },\n\t\t\t\t]}\n\t\t\t/>\n\t\t);\n\n\t\texpect(tabStops()).toEqual(['3']);\n\t});\n\n\tit('should move the tab stop to the first visible option when the selected one is hidden', () => {\n\t\trender(\n\t\t\t<ButtonToggle\n\t\t\t\tvalue={1}\n\t\t\t\toptions={[\n\t\t\t\t\t{ text: '1', value: 1, hidden: true },\n\t\t\t\t\t{ text: '2', value: 2 },\n\t\t\t\t\t{ text: '3', value: 3 },\n\t\t\t\t]}\n\t\t\t/>\n\t\t);\n\n\t\texpect(tabStops()).toEqual(['2']);\n\t});\n\n\tit('should make the focused option tabbable until focus leaves', () => {\n\t\trender(\n\t\t\t<ButtonToggle\n\t\t\t\tvalue={1}\n\t\t\t\toptions={[\n\t\t\t\t\t{ text: '1', value: 1 },\n\t\t\t\t\t{ text: '2', value: 2 },\n\t\t\t\t]}\n\t\t\t/>\n\t\t);\n\n\t\tconst second = screen.getByRole('button', { name: '2' });\n\n\t\tact(() => second.focus());\n\t\texpect(tabStops()).toEqual(['2']);\n\n\t\tact(() => second.blur());\n\t\texpect(tabStops()).toEqual(['1']);\n\t});\n\n\tit('should keep the cleared option tabbable', async () => {\n\t\tconst user = userEvent.setup();\n\t\tconst ClearableToggle = () => {\n\t\t\tconst [value, setValue] = React.useState<number>(2);\n\n\t\t\treturn (\n\t\t\t\t<ButtonToggle\n\t\t\t\t\tclearable\n\t\t\t\t\tvalue={value}\n\t\t\t\t\tonChange={setValue}\n\t\t\t\t\toptions={[\n\t\t\t\t\t\t{ text: '1', value: 1 },\n\t\t\t\t\t\t{ text: '2', value: 2 },\n\t\t\t\t\t]}\n\t\t\t\t/>\n\t\t\t);\n\t\t};\n\t\trender(<ClearableToggle />);\n\n\t\tawait user.click(screen.getByRole('button', { name: '2' }));\n\t\tawait user.tab();\n\n\t\texpect(tabStops()).toEqual(['2']);\n\t});\n\n\tit('should not rewrite tab indexes when re-rendered with the same props', () => {\n\t\tconst options = [\n\t\t\t{ text: '1', value: 1 },\n\t\t\t{ text: '2', value: 2 },\n\t\t];\n\t\tconst { rerender } = render(\n\t\t\t<ButtonToggle value={2} options={options} />\n\t\t);\n\t\tconst setTabIndex = jest.spyOn(\n\t\t\tHTMLElement.prototype,\n\t\t\t'tabIndex',\n\t\t\t'set'\n\t\t);\n\n\t\trerender(<ButtonToggle value={2} options={options} />);\n\n\t\texpect(setTabIndex).not.toHaveBeenCalled();\n\t\tsetTabIndex.mockRestore();\n\t});\n});\n"]}
|
|
@@ -26,5 +26,7 @@ export interface ButtonToggleItemProps<T> {
|
|
|
26
26
|
icon?: ButtonProps['icon'];
|
|
27
27
|
/** Name of icon to render inside of button */
|
|
28
28
|
iconName?: ButtonProps['iconName'];
|
|
29
|
+
/** Tab index of the Button; 0 makes it the group's tab stop */
|
|
30
|
+
tabIndex?: number;
|
|
29
31
|
}
|
|
30
32
|
export declare const ButtonToggleItem: <T>(props: React.PropsWithoutRef<ButtonToggleItemProps<T>> & React.RefAttributes<HTMLDivElement>) => React.ReactElement | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import classnames from 'classnames';
|
|
3
3
|
import { Button } from '../Button';
|
|
4
|
-
export const ButtonToggleItem = React.forwardRef(function ({ color, disabled, hidden, icon, iconName, name, onClick, selected, size, text, value, }, ref) {
|
|
4
|
+
export const ButtonToggleItem = React.forwardRef(function ({ color, disabled, hidden, icon, iconName, name, onClick, selected, size, tabIndex, text, value, }, ref) {
|
|
5
5
|
let realColor = 'default';
|
|
6
6
|
if (selected)
|
|
7
7
|
realColor = color;
|
|
@@ -13,6 +13,6 @@ export const ButtonToggleItem = React.forwardRef(function ({ color, disabled, hi
|
|
|
13
13
|
});
|
|
14
14
|
return (React.createElement("div", { className: ButtonToggleItemClasses, ref: ref },
|
|
15
15
|
React.createElement("input", { name: name, type: "radio", defaultChecked: selected, className: "ButtonToggle__radio", tabIndex: -1 }),
|
|
16
|
-
React.createElement(Button, { color: realColor, disabled: disabled, fill: "solid", full: true, icon: icon, iconName: iconName, onClick: onClick.bind(null, value), size: size, "aria-pressed": selected ? true : null }, text)));
|
|
16
|
+
React.createElement(Button, { color: realColor, disabled: disabled, fill: "solid", full: true, icon: icon, iconName: iconName, onClick: onClick.bind(null, value), size: size, tabIndex: tabIndex, "aria-pressed": selected ? true : null }, text)));
|
|
17
17
|
});
|
|
18
18
|
//# sourceMappingURL=ButtonToggleItem.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ButtonToggleItem.js","sourceRoot":"","sources":["../../../src/components/ButtonToggle/ButtonToggleItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,MAAM,EAAe,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"ButtonToggleItem.js","sourceRoot":"","sources":["../../../src/components/ButtonToggle/ButtonToggleItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,MAAM,EAAe,MAAM,WAAW,CAAC;AA2ChD,MAAM,CAAC,MAAM,gBAAgB,GAGI,KAAK,CAAC,UAAU,CAAC,UACjD,EACC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,KAAK,GACqB,EAC3B,GAAoC;IAEpC,IAAI,SAAS,GAAsC,SAAS,CAAC;IAE7D,IAAI,QAAQ;QAAE,SAAS,GAAG,KAAK,CAAC;IAEhC,MAAM,uBAAuB,GAAG,UAAU,CAAC,oBAAoB,EAAE;QAChE,4BAA4B,EAAE,MAAM;QACpC,8BAA8B,EAAE,QAAQ;QACxC,kCAAkC,EAAE,CAAC,QAAQ;QAC7C,8BAA8B,EAAE,QAAQ;KACxC,CAAC,CAAC;IAEH,OAAO,CACN,6BAAK,SAAS,EAAE,uBAAuB,EAAE,GAAG,EAAE,GAAG;QAChD,+BACC,IAAI,EAAE,IAAI,EACV,IAAI,EAAC,OAAO,EACZ,cAAc,EAAE,QAAQ,EACxB,SAAS,EAAC,qBAAqB,EAC/B,QAAQ,EAAE,CAAC,CAAC,GACX;QACF,oBAAC,MAAM,IACN,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAC,OAAO,EACZ,IAAI,QACJ,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,EAClC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,QAAQ,kBACJ,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAEnC,IAAI,CACG,CACJ,CACN,CAAC;AACH,CAAC,CAAC,CAAC","sourcesContent":["import React from 'react';\nimport classnames from 'classnames';\nimport { Button, ButtonProps } from '../Button';\n\n/**\n * ButtonToggleItem properties\n */\nexport interface ButtonToggleItemProps<T> {\n\t/** Content of the Button, i.e. the displayed text */\n\ttext: React.ReactNode;\n\n\t/** Value of the Button */\n\tvalue: T;\n\n\t/** Returns value of option for the click event */\n\tonClick?: (value: T) => void;\n\n\t/** Indicates whether the Button is disabled or not. It makes the Button unselectable */\n\tdisabled?: ButtonProps['disabled'];\n\n\t/** Sets the option's selected state */\n\tselected?: boolean;\n\n\t/** Selected button background color.<br /> Possible values: <i>'primary'</i>, <i>'blue'</i>, <i>'negative'</i>, <i>'red'</i> */\n\tcolor?: ButtonProps['color'];\n\n\t/** Sets size of the button.<br /> Possible values: <i>'large'</i>, <i>'small'</i> */\n\tsize?: ButtonProps['size'];\n\n\t/** Specifies the name of the Button */\n\tname: string;\n\n\t/** Hide the option (not commonly used) */\n\thidden?: boolean;\n\n\t/** React element to be rendered as an icon */\n\ticon?: ButtonProps['icon'];\n\n\t/** Name of icon to render inside of button */\n\ticonName?: ButtonProps['iconName'];\n\n\t/** Tab index of the Button; 0 makes it the group's tab stop */\n\ttabIndex?: number;\n}\n\nexport const ButtonToggleItem: <T>(\n\tprops: React.PropsWithoutRef<ButtonToggleItemProps<T>> &\n\t\tReact.RefAttributes<HTMLDivElement>\n) => React.ReactElement | null = React.forwardRef(function <T>(\n\t{\n\t\tcolor,\n\t\tdisabled,\n\t\thidden,\n\t\ticon,\n\t\ticonName,\n\t\tname,\n\t\tonClick,\n\t\tselected,\n\t\tsize,\n\t\ttabIndex,\n\t\ttext,\n\t\tvalue,\n\t}: ButtonToggleItemProps<T>,\n\tref: React.RefObject<HTMLDivElement>\n) {\n\tlet realColor: ButtonToggleItemProps<T>['color'] = 'default';\n\n\tif (selected) realColor = color;\n\n\tconst ButtonToggleItemClasses = classnames('ButtonToggle__Item', {\n\t\t'ButtonToggle__Item--hidden': hidden,\n\t\t'ButtonToggle__Item--selected': selected,\n\t\t'ButtonToggle__Item--not-selected': !selected,\n\t\t'ButtonToggle__Item--disabled': disabled,\n\t});\n\n\treturn (\n\t\t<div className={ButtonToggleItemClasses} ref={ref}>\n\t\t\t<input\n\t\t\t\tname={name}\n\t\t\t\ttype=\"radio\"\n\t\t\t\tdefaultChecked={selected}\n\t\t\t\tclassName=\"ButtonToggle__radio\"\n\t\t\t\ttabIndex={-1} // Prevent focusing on invisible input\n\t\t\t/>\n\t\t\t<Button\n\t\t\t\tcolor={realColor}\n\t\t\t\tdisabled={disabled}\n\t\t\t\tfill=\"solid\"\n\t\t\t\tfull\n\t\t\t\ticon={icon}\n\t\t\t\ticonName={iconName}\n\t\t\t\tonClick={onClick.bind(null, value)}\n\t\t\t\tsize={size}\n\t\t\t\ttabIndex={tabIndex}\n\t\t\t\taria-pressed={selected ? true : null}\n\t\t\t>\n\t\t\t\t{text}\n\t\t\t</Button>\n\t\t</div>\n\t);\n});\n"]}
|
|
@@ -22,6 +22,6 @@ export declare const toDateObj: ({ date, tz, isValid, invalidReason, ...rest }:
|
|
|
22
22
|
export declare const toDateTime: (value: Date | DateTime | string | undefined, tz?: string) => DateTime | null;
|
|
23
23
|
export declare const toFormat: (date: any, format?: string) => any;
|
|
24
24
|
export declare const toTimeZone: (date: Date, tz?: string, opts?: any) => Date;
|
|
25
|
-
export declare const fromTimeZone: (date: Date, tz
|
|
25
|
+
export declare const fromTimeZone: (date: Date, tz?: string) => Date;
|
|
26
26
|
export declare const compareObj: (value?: Date | string) => string | number;
|
|
27
27
|
export {};
|
|
@@ -22,7 +22,7 @@ export const isValidDate = (date) => {
|
|
|
22
22
|
return !isInvalidDate(date);
|
|
23
23
|
return false;
|
|
24
24
|
};
|
|
25
|
-
const defaultTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
25
|
+
const defaultTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;
|
|
26
26
|
const isBoolean = (value) => typeof value === 'boolean';
|
|
27
27
|
export const toDateObj = (_a) => {
|
|
28
28
|
var { date, tz = defaultTimezone, isValid, invalidReason } = _a, rest = __rest(_a, ["date", "tz", "isValid", "invalidReason"]);
|
|
@@ -50,15 +50,16 @@ export const toDateObj = (_a) => {
|
|
|
50
50
|
return result;
|
|
51
51
|
};
|
|
52
52
|
export const toDateTime = (value, tz) => {
|
|
53
|
-
var _a, _b;
|
|
54
53
|
if (!value)
|
|
55
54
|
return undefined;
|
|
55
|
+
// Undefined zone names fall through to Luxon's system zone.
|
|
56
56
|
if (value instanceof DateTime)
|
|
57
57
|
return value.setZone(tz);
|
|
58
58
|
const timezone = tz || defaultTimezone;
|
|
59
|
-
|
|
60
|
-
?
|
|
61
|
-
:
|
|
59
|
+
const dateTime = value instanceof Date
|
|
60
|
+
? DateTime.fromJSDate(value) // System zone
|
|
61
|
+
: DateTime.fromISO(value); // System zone
|
|
62
|
+
return timezone ? dateTime === null || dateTime === void 0 ? void 0 : dateTime.setZone(timezone) : dateTime;
|
|
62
63
|
};
|
|
63
64
|
export const toFormat = (date, format = 'LL/dd/yyyy') => {
|
|
64
65
|
if (date instanceof Date)
|
|
@@ -81,9 +82,11 @@ export const toTimeZone = (date, tz, opts) => {
|
|
|
81
82
|
};
|
|
82
83
|
// Inverse function for toTimeZone
|
|
83
84
|
export const fromTimeZone = (date, tz) => {
|
|
84
|
-
var _a, _b;
|
|
85
|
+
var _a, _b, _c;
|
|
85
86
|
const unzoned = (_a = DateTime.fromJSDate(date)) === null || _a === void 0 ? void 0 : _a.toISO({ includeOffset: false });
|
|
86
|
-
|
|
87
|
+
if (!tz)
|
|
88
|
+
return (_b = DateTime.fromISO(unzoned)) === null || _b === void 0 ? void 0 : _b.toJSDate();
|
|
89
|
+
return (_c = DateTime.fromISO(unzoned, { zone: tz })) === null || _c === void 0 ? void 0 : _c.toJSDate();
|
|
87
90
|
};
|
|
88
91
|
export const compareObj = (value) => {
|
|
89
92
|
if (value instanceof Date) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/components/DatePicker/utils/utils.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,+CAA+C;AAC/C,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;AAExC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAmB,EAAW,EAAE;IAC7D,IAAI,OAAO,CAAC,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC;IAC3C,OAAO,CAAC,YAAY,IAAI,IAAI,KAAK,CAAC,CAAQ,CAAC,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAU,EAAE,EAAE;IACzC,IAAI,IAAI,YAAY,IAAI;QAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IACtD,OAAO,KAAK,CAAC;AACd,CAAC,CAAC;AAoBF,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../../src/components/DatePicker/utils/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { DateTime } from 'luxon';\n\n// Generate Date object with value Invalid Date\nexport const invalidDate = new Date('');\n\nexport const isInvalidDate = (d: Date | undefined): boolean => {\n\tif (typeof d === 'undefined') return false;\n\treturn d instanceof Date && isNaN(d as any);\n};\n\nexport const isValidDate = (date: Date) => {\n\tif (date instanceof Date) return !isInvalidDate(date);\n\treturn false;\n};\n\nexport interface DateObj {\n\tdateTime: DateTime | undefined;\n\ttimezone: string;\n\tisValid: boolean;\n\tinvalidReason?: string;\n\ttoISO?: () => string;\n\ttoJSDate?: () => Date;\n\tisEqualDate?: (obj: DateObj) => boolean;\n\tisEqual?: (obj: DateObj) => boolean;\n}\n\ninterface ToDateObj {\n\tdate: Date | DateTime | string | undefined;\n\ttz?: string;\n\tisValid?: boolean;\n\tinvalidReason?: string;\n}\n\nconst defaultTimezone =\n\tIntl.DateTimeFormat().resolvedOptions().timeZone || undefined;\nconst isBoolean = (value: any) => typeof value === 'boolean';\n\nexport const toDateObj = ({\n\tdate,\n\ttz = defaultTimezone,\n\tisValid,\n\tinvalidReason,\n\t...rest\n}: ToDateObj): DateObj => {\n\tconst dateTime = toDateTime(date, tz);\n\tconst valid = isBoolean(isValid) ? isValid : dateTime?.isValid;\n\tconst reason = invalidReason;\n\n\tconst result: DateObj = {\n\t\tdateTime,\n\t\ttimezone: tz,\n\t\tisValid: valid,\n\t\tinvalidReason: reason,\n\t\t...rest,\n\t};\n\n\tresult.toISO = (): string =>\n\t\tresult.dateTime ? result.dateTime.toISO() : undefined;\n\tresult.toJSDate = (): Date =>\n\t\tresult.dateTime ? result.dateTime.toJSDate() : undefined;\n\tresult.isEqualDate = (other: DateObj): boolean => {\n\t\tconst dt1 = result.dateTime;\n\t\tconst dt2 = other.dateTime;\n\t\tif ((!dt1 && !!dt2) || (!!dt1 && !dt2)) return false; // One is valid, other not\n\t\tif (typeof dt1 === 'undefined' && typeof dt2 === 'undefined')\n\t\t\treturn true; // Both undefined\n\t\tif (!dt1.isValid && !dt2.isValid) return true; // Both invalid\n\t\treturn +dt1 === +dt2; // Compare millis\n\t};\n\tresult.isEqual = (other: DateObj): boolean =>\n\t\tresult.isValid === other.isValid &&\n\t\tresult.timezone === other.timezone &&\n\t\tresult.invalidReason === other.invalidReason &&\n\t\tresult.isEqualDate(other); // Equal dates\n\n\treturn result;\n};\n\nexport const toDateTime = (\n\tvalue: Date | DateTime | string | undefined,\n\ttz?: string\n): DateTime | null => {\n\tif (!value) return undefined;\n\t// Undefined zone names fall through to Luxon's system zone.\n\tif (value instanceof DateTime) return value.setZone(tz);\n\n\tconst timezone = tz || defaultTimezone;\n\tconst dateTime =\n\t\tvalue instanceof Date\n\t\t\t? DateTime.fromJSDate(value) // System zone\n\t\t\t: DateTime.fromISO(value); // System zone\n\n\treturn timezone ? dateTime?.setZone(timezone) : dateTime;\n};\n\nexport const toFormat = (date: any, format = 'LL/dd/yyyy') => {\n\tif (date instanceof Date) return DateTime.fromJSDate(date).toFormat(format);\n\tif (date instanceof DateTime) return date.toFormat(format);\n\treturn date;\n};\n\n/*\n * Shifts date to timezone, so that date.toString()\n * to have same digits as corresponding date in timezone.\n */\nexport const toTimeZone = (date: Date, tz?: string, opts?: any) => {\n\tconst dateTime = opts?.startOfDay\n\t\t? toDateTime(date, tz).startOf('day')\n\t\t: toDateTime(date, tz);\n\treturn dateTime?.isValid\n\t\t? new Date(dateTime.toISO({ includeOffset: false })) // Main routine\n\t\t: undefined;\n};\n\n// Inverse function for toTimeZone\nexport const fromTimeZone = (date: Date, tz?: string) => {\n\tconst unzoned = DateTime.fromJSDate(date)?.toISO({ includeOffset: false });\n\tif (!tz) return DateTime.fromISO(unzoned)?.toJSDate();\n\treturn DateTime.fromISO(unzoned, { zone: tz })?.toJSDate();\n};\n\nexport const compareObj = (value?: Date | string) => {\n\tif (value instanceof Date) {\n\t\tif (isInvalidDate(value)) return 'Invalid Date';\n\t\treturn value.getTime();\n\t}\n\tif (typeof value === 'undefined') return 'undefined';\n\treturn value;\n};\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DateTime } from 'luxon';
|
|
2
|
+
const OriginalDateTimeFormat = Intl.DateTimeFormat;
|
|
3
|
+
describe('DatePicker utils when resolvedOptions().timeZone is undefined', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(((locales, options) => {
|
|
6
|
+
const formatter = new OriginalDateTimeFormat(locales, options);
|
|
7
|
+
if (!(options === null || options === void 0 ? void 0 : options.timeZone)) {
|
|
8
|
+
const resolved = formatter.resolvedOptions.bind(formatter);
|
|
9
|
+
formatter.resolvedOptions = () => (Object.assign(Object.assign({}, resolved()), { timeZone: undefined }));
|
|
10
|
+
}
|
|
11
|
+
return formatter;
|
|
12
|
+
}));
|
|
13
|
+
});
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
jest.restoreAllMocks();
|
|
16
|
+
});
|
|
17
|
+
const loadUtils = () => {
|
|
18
|
+
let utils;
|
|
19
|
+
jest.isolateModules(() => {
|
|
20
|
+
utils = require('./utils');
|
|
21
|
+
});
|
|
22
|
+
return utils;
|
|
23
|
+
};
|
|
24
|
+
it('falls back to the system zone instead of calling setZone with a missing name', () => {
|
|
25
|
+
const { toDateTime, fromTimeZone } = loadUtils();
|
|
26
|
+
const date = new Date('2023-01-01T00:00:00Z');
|
|
27
|
+
const dateTime = toDateTime(date, undefined);
|
|
28
|
+
expect(dateTime.isValid).toBe(true);
|
|
29
|
+
expect(dateTime.toUTC().toISO()).toBe(DateTime.fromJSDate(date).toUTC().toISO());
|
|
30
|
+
expect(toDateTime(date, 'UTC').toFormat('HH:mm')).toBe('00:00');
|
|
31
|
+
const roundTrip = fromTimeZone(date, undefined);
|
|
32
|
+
expect(roundTrip.toISOString()).toBe(date.toISOString());
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=utils.missingTimeZone.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.missingTimeZone.test.js","sourceRoot":"","sources":["../../../../src/components/DatePicker/utils/utils.missingTimeZone.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,MAAM,sBAAsB,GAAG,IAAI,CAAC,cAAc,CAAC;AAEnD,QAAQ,CAAC,+DAA+D,EAAE,GAAG,EAAE;IAC9E,UAAU,CAAC,GAAG,EAAE;QACf,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,kBAAkB,CAAC,CAAC,CACtD,OAA2B,EAC3B,OAAoC,EACnC,EAAE;YACH,MAAM,SAAS,GAAG,IAAI,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC/D,IAAI,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,CAAA,EAAE;gBACvB,MAAM,QAAQ,GAAG,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAE1D,SACA,CAAC,eAAe,GAAG,GAAG,EAAE,CAAC,iCACtB,QAAQ,EAAE,KACb,QAAQ,EAAE,SAAS,IAClB,CAAC;aACH;YACD,OAAO,SAAS,CAAC;QAClB,CAAC,CAA+B,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,eAAe,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,GAAG,EAAE;QACtB,IAAI,KAA+B,CAAC;QACpC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE;YACxB,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACd,CAAC,CAAC;IAEF,EAAE,CAAC,8EAA8E,EAAE,GAAG,EAAE;QACvF,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,SAAS,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,sBAAsB,CAAC,CAAC;QAE9C,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC7C,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CACpC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CACzC,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEhE,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAChD,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import { DateTime } from 'luxon';\n\nconst OriginalDateTimeFormat = Intl.DateTimeFormat;\n\ndescribe('DatePicker utils when resolvedOptions().timeZone is undefined', () => {\n\tbeforeEach(() => {\n\t\tjest.spyOn(Intl, 'DateTimeFormat').mockImplementation(((\n\t\t\tlocales?: string | string[],\n\t\t\toptions?: Intl.DateTimeFormatOptions\n\t\t) => {\n\t\t\tconst formatter = new OriginalDateTimeFormat(locales, options);\n\t\t\tif (!options?.timeZone) {\n\t\t\t\tconst resolved = formatter.resolvedOptions.bind(formatter);\n\t\t\t\t(\n\t\t\t\t\tformatter as { resolvedOptions: () => unknown }\n\t\t\t\t).resolvedOptions = () => ({\n\t\t\t\t\t...resolved(),\n\t\t\t\t\ttimeZone: undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn formatter;\n\t\t}) as typeof Intl.DateTimeFormat);\n\t});\n\n\tafterEach(() => {\n\t\tjest.restoreAllMocks();\n\t});\n\n\tconst loadUtils = () => {\n\t\tlet utils: typeof import('./utils');\n\t\tjest.isolateModules(() => {\n\t\t\tutils = require('./utils');\n\t\t});\n\t\treturn utils;\n\t};\n\n\tit('falls back to the system zone instead of calling setZone with a missing name', () => {\n\t\tconst { toDateTime, fromTimeZone } = loadUtils();\n\t\tconst date = new Date('2023-01-01T00:00:00Z');\n\n\t\tconst dateTime = toDateTime(date, undefined);\n\t\texpect(dateTime.isValid).toBe(true);\n\t\texpect(dateTime.toUTC().toISO()).toBe(\n\t\t\tDateTime.fromJSDate(date).toUTC().toISO()\n\t\t);\n\t\texpect(toDateTime(date, 'UTC').toFormat('HH:mm')).toBe('00:00');\n\n\t\tconst roundTrip = fromTimeZone(date, undefined);\n\t\texpect(roundTrip.toISOString()).toBe(date.toISOString());\n\t});\n});\n"]}
|
|
@@ -53,6 +53,15 @@ describe('TimePickerUtils', () => {
|
|
|
53
53
|
[minT, maxT] = ['12:00 AM', '12:00 PM'];
|
|
54
54
|
expect(consumeMinMax(minT, maxT).min).toEqual('00:00');
|
|
55
55
|
expect(consumeMinMax(minT, maxT).max).toEqual('12:00');
|
|
56
|
+
/*
|
|
57
|
+
* String bounds are clock times. A tenant zone does not shift them.
|
|
58
|
+
*/
|
|
59
|
+
expect(consumeMinMax('01:00', '02:00', 'Asia/Tokyo')).toEqual({
|
|
60
|
+
min: '01:00',
|
|
61
|
+
max: '02:00',
|
|
62
|
+
});
|
|
63
|
+
// Empty timezone stays on the system zone, so same-day Date bounds keep their clock times.
|
|
64
|
+
expect(consumeMinMax(new Date(2023, 0, 1, 9, 0), new Date(2023, 0, 1, 17, 0), '')).toEqual({ min: '09:00', max: '17:00' });
|
|
56
65
|
// undefined, null, wrong entry
|
|
57
66
|
[minT, maxT] = [undefined, undefined];
|
|
58
67
|
expect(consumeMinMax(minT, maxT).min).toEqual('00:00');
|
|
@@ -301,4 +310,68 @@ describe('timezone utils', () => {
|
|
|
301
310
|
expect(date.toISOString()).toBe('2022-12-31T15:00:00.000Z'); // 15:00 UTC = 00:00 seoul
|
|
302
311
|
});
|
|
303
312
|
});
|
|
313
|
+
const OriginalDateTimeFormat = Intl.DateTimeFormat;
|
|
314
|
+
const mockMissingBrowserTimeZone = () => {
|
|
315
|
+
jest.spyOn(Intl, 'DateTimeFormat').mockImplementation(((locales, options) => {
|
|
316
|
+
const formatter = new OriginalDateTimeFormat(locales, options);
|
|
317
|
+
if (!(options === null || options === void 0 ? void 0 : options.timeZone)) {
|
|
318
|
+
const resolved = formatter.resolvedOptions.bind(formatter);
|
|
319
|
+
formatter.resolvedOptions =
|
|
320
|
+
() => (Object.assign(Object.assign({}, resolved()), { timeZone: undefined }));
|
|
321
|
+
}
|
|
322
|
+
return formatter;
|
|
323
|
+
}));
|
|
324
|
+
};
|
|
325
|
+
describe('when resolvedOptions().timeZone is undefined', () => {
|
|
326
|
+
beforeEach(() => {
|
|
327
|
+
mockMissingBrowserTimeZone();
|
|
328
|
+
jest.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
329
|
+
});
|
|
330
|
+
afterEach(() => {
|
|
331
|
+
jest.restoreAllMocks();
|
|
332
|
+
});
|
|
333
|
+
const loadTimezoneUtils = () => {
|
|
334
|
+
let utils;
|
|
335
|
+
jest.isolateModules(() => {
|
|
336
|
+
utils = require('./timezoneUtils');
|
|
337
|
+
});
|
|
338
|
+
return utils;
|
|
339
|
+
};
|
|
340
|
+
it('normalizes the browser zone to undefined and formats in the system zone', () => {
|
|
341
|
+
const { dateToTime, localTimezone, timeToDate } = loadTimezoneUtils();
|
|
342
|
+
const localMidnight = new Date(2023, 0, 1, 0, 0);
|
|
343
|
+
expect(localTimezone).toBeUndefined();
|
|
344
|
+
expect(() => dateToTime(localMidnight)).not.toThrow();
|
|
345
|
+
expect(dateToTime(localMidnight, undefined, 'hh:mm')).toBe('00:00');
|
|
346
|
+
const date = timeToDate('10:30', undefined);
|
|
347
|
+
expect(date.getHours()).toBe(10);
|
|
348
|
+
expect(date.getMinutes()).toBe(30);
|
|
349
|
+
expect(dateToTime(date, undefined)).toBe('10:30 AM');
|
|
350
|
+
});
|
|
351
|
+
it('still converts a valid tenant timezone', () => {
|
|
352
|
+
const { dateToTime, timeToDate } = loadTimezoneUtils();
|
|
353
|
+
const utc0 = new Date('2023-01-01T00:00:00Z');
|
|
354
|
+
expect(dateToTime(utc0, tz.la, 'hh:mm')).toBe('16:00');
|
|
355
|
+
const date = timeToDate('01:23 AM', tz.la, utc0);
|
|
356
|
+
expect(dateToTime(date, tz.la, 'hh:mm')).toBe('01:23');
|
|
357
|
+
expect(date.toISOString()).toBe('2022-12-31T09:23:00.000Z');
|
|
358
|
+
});
|
|
359
|
+
it('keeps string min and max as clock times', () => {
|
|
360
|
+
const { consumeMinMax } = loadTimezoneUtils();
|
|
361
|
+
expect(consumeMinMax('01:00', '02:00', 'Asia/Tokyo')).toEqual({
|
|
362
|
+
min: '01:00',
|
|
363
|
+
max: '02:00',
|
|
364
|
+
});
|
|
365
|
+
expect(consumeMinMax('08:00', undefined, undefined)).toEqual({
|
|
366
|
+
min: '08:00',
|
|
367
|
+
max: '23:59',
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
it('does not throw when the timezone name is invalid', () => {
|
|
371
|
+
const { dateToTime, timeToDate } = loadTimezoneUtils();
|
|
372
|
+
const utc0 = new Date('2023-01-01T00:00:00Z');
|
|
373
|
+
expect(dateToTime(utc0, 'Not/AZone')).toBeUndefined();
|
|
374
|
+
expect(timeToDate('10:30', 'Not/AZone')).toBeUndefined();
|
|
375
|
+
});
|
|
376
|
+
});
|
|
304
377
|
//# sourceMappingURL=TimePickerUtils.test.js.map
|