@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.
Files changed (30) hide show
  1. package/dist/components/ButtonToggle/ButtonToggle.js +39 -3
  2. package/dist/components/ButtonToggle/ButtonToggle.js.map +1 -1
  3. package/dist/components/ButtonToggle/ButtonToggle.test.js +77 -1
  4. package/dist/components/ButtonToggle/ButtonToggle.test.js.map +1 -1
  5. package/dist/components/ButtonToggle/ButtonToggleItem.d.ts +2 -0
  6. package/dist/components/ButtonToggle/ButtonToggleItem.js +2 -2
  7. package/dist/components/ButtonToggle/ButtonToggleItem.js.map +1 -1
  8. package/dist/components/DatePicker/utils/utils.d.ts +1 -1
  9. package/dist/components/DatePicker/utils/utils.js +10 -7
  10. package/dist/components/DatePicker/utils/utils.js.map +1 -1
  11. package/dist/components/DatePicker/utils/utils.missingTimeZone.test.d.ts +1 -0
  12. package/dist/components/DatePicker/utils/utils.missingTimeZone.test.js +35 -0
  13. package/dist/components/DatePicker/utils/utils.missingTimeZone.test.js.map +1 -0
  14. package/dist/components/TimePicker/utils/TimePickerUtils.test.js +73 -0
  15. package/dist/components/TimePicker/utils/TimePickerUtils.test.js.map +1 -1
  16. package/dist/components/TimePicker/utils/timezoneUtils.d.ts +1 -0
  17. package/dist/components/TimePicker/utils/timezoneUtils.js +25 -15
  18. package/dist/components/TimePicker/utils/timezoneUtils.js.map +1 -1
  19. package/dist/global/index.css +1 -1
  20. package/dist/global/version.css +1 -1
  21. package/dist/system-light.css +1 -1
  22. package/dist/system-light.min.css +1 -1
  23. package/dist/system.css +1 -1
  24. package/dist/system.css.map +1 -1
  25. package/dist/system.min.css +1 -1
  26. package/dist/utilities/TimezoneProvider/TimezoneProvider.js +9 -6
  27. package/dist/utilities/TimezoneProvider/TimezoneProvider.js.map +1 -1
  28. package/dist/utilities/TimezoneProvider/TimezoneProvider.test.js +41 -0
  29. package/dist/utilities/TimezoneProvider/TimezoneProvider.test.js.map +1 -1
  30. 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(); }, // Auto-selection
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;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC7B,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAE/C,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACzD,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC3B,oBAAC,YAAY,IACZ,KAAK,EAAE,CAAC,EACR,KAAK,QACL,OAAO,EAAE;gBACR,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE;gBACvB,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE;aACvB,mBACa,qBAAqB,GAClC,CACF,CAAC;QAEF,MAAM,OAAO,GAAG,SAAS,CAAC,aAAa,CACtC,uCAAuC,CACvC,CAAC;QAEF,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;IAChC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import React from 'react';\nimport { render } from '@testing-library/react';\nimport { ButtonToggle } from './ButtonToggle';\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"]}
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;AAwChD,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,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,kBACI,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\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\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\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"]}
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: string) => Date;
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
- return value instanceof Date
60
- ? (_a = DateTime.fromJSDate(value)) === null || _a === void 0 ? void 0 : _a.setZone(timezone) // Parse Date
61
- : (_b = DateTime.fromISO(value)) === null || _b === void 0 ? void 0 : _b.setZone(timezone); // Parse String
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
- return (_b = DateTime.fromISO(unzoned, { zone: tz })) === null || _b === void 0 ? void 0 : _b.toJSDate();
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":"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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 = Intl.DateTimeFormat().resolvedOptions().timeZone;\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\tif (value instanceof DateTime) return value.setZone(tz);\n\n\tconst timezone = tz || defaultTimezone;\n\n\treturn value instanceof Date\n\t\t? DateTime.fromJSDate(value)?.setZone(timezone) // Parse Date\n\t\t: DateTime.fromISO(value)?.setZone(timezone); // Parse String\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\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"]}
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,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