@itwin/itwinui-react 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/cjs/core/ProgressIndicators/ProgressLinear.js +6 -1
  3. package/cjs/core/ProgressIndicators/ProgressRadial.js +8 -3
  4. package/cjs/core/Select/Select.d.ts +3 -1
  5. package/cjs/core/Select/Select.js +1 -1
  6. package/cjs/core/ToggleSwitch/ToggleSwitch.d.ts +4 -2
  7. package/cjs/core/ToggleSwitch/ToggleSwitch.js +5 -11
  8. package/cjs/core/Typography/Anchor.d.ts +21 -1
  9. package/cjs/core/Typography/Anchor.js +38 -3
  10. package/cjs/core/VisuallyHidden/VisuallyHidden.js +19 -3
  11. package/cjs/core/utils/components/ShadowRoot.d.ts +2 -1
  12. package/cjs/core/utils/components/ShadowRoot.js +19 -4
  13. package/cjs/core/utils/functions/polymorphic.d.ts +5 -3
  14. package/cjs/core/utils/functions/polymorphic.js +20 -5
  15. package/cjs/core/utils/icons/Svg.js +5 -1
  16. package/esm/core/ProgressIndicators/ProgressLinear.js +7 -2
  17. package/esm/core/ProgressIndicators/ProgressRadial.js +9 -4
  18. package/esm/core/Select/Select.d.ts +3 -1
  19. package/esm/core/Select/Select.js +1 -1
  20. package/esm/core/ToggleSwitch/ToggleSwitch.d.ts +4 -2
  21. package/esm/core/ToggleSwitch/ToggleSwitch.js +6 -12
  22. package/esm/core/Typography/Anchor.d.ts +21 -1
  23. package/esm/core/Typography/Anchor.js +11 -2
  24. package/esm/core/VisuallyHidden/VisuallyHidden.js +19 -3
  25. package/esm/core/utils/components/ShadowRoot.d.ts +2 -1
  26. package/esm/core/utils/components/ShadowRoot.js +19 -4
  27. package/esm/core/utils/functions/polymorphic.d.ts +5 -3
  28. package/esm/core/utils/functions/polymorphic.js +20 -5
  29. package/esm/core/utils/icons/Svg.js +5 -1
  30. package/package.json +4 -4
  31. package/styles.css +34 -33
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1844](https://github.com/iTwin/iTwinUI/pull/1844): Interactive elements will now be correctly focused on Safari 17+.
8
+ - [#1860](https://github.com/iTwin/iTwinUI/pull/1860): `ThemeProvider` will now automatically specify an `accent-color` to match the current theme.
9
+ - [#1856](https://github.com/iTwin/iTwinUI/pull/1856): Added new `isExternal` prop to `Anchor`. When true, displays an icon at the end of link text.
10
+ - [#1858](https://github.com/iTwin/iTwinUI/pull/1858): `ToggleSwitch` will now show a checkmark icon by default in the checked state. `SvgCheckmark` no longer needs to be manually passed into the `icon` prop.
11
+ - [#1862](https://github.com/iTwin/iTwinUI/pull/1862): `Select` will now correctly reset its `value` if `null` is passed.
12
+ - [#1864](https://github.com/iTwin/iTwinUI/pull/1864): Added a new `underline` prop to `Anchor`. When true, anchors will be underlined by default instead of only on hover.
13
+ - [#1856](https://github.com/iTwin/iTwinUI/pull/1856): `Anchor` links that open in a new tab will now add a visually-hidden warning for screen-reader users. This can be combined with the `isExternal` prop, which adds a visual indication for sighted users.
14
+ - [#1859](https://github.com/iTwin/iTwinUI/pull/1859): `ProgressRadial` and `ProgressLinear` will now include a visually hidden "Loading" text alternative for non-sighted users.
15
+
16
+ ### Patch Changes
17
+
18
+ - [#1845](https://github.com/iTwin/iTwinUI/pull/1845): Removed `:focus-visible` fallback styles for older browsers.
19
+ - [#1861](https://github.com/iTwin/iTwinUI/pull/1861): Added `width`/`height` attributes to inlined svgs, to make them more resilient in case CSS fails.
20
+
21
+ ## 3.4.2
22
+
23
+ ### Patch Changes
24
+
25
+ - [#1848](https://github.com/iTwin/iTwinUI/pull/1848): Removed `jsdom` from direct dependencies.
26
+
3
27
  ## 3.4.1
4
28
 
5
29
  ### Patch Changes
@@ -34,6 +34,7 @@ exports.ProgressLinear = void 0;
34
34
  const React = __importStar(require("react"));
35
35
  const classnames_1 = __importDefault(require("classnames"));
36
36
  const index_js_1 = require("../utils/index.js");
37
+ const VisuallyHidden_js_1 = require("../VisuallyHidden/VisuallyHidden.js");
37
38
  /**
38
39
  * Shows progress on a linear bar
39
40
  * @example
@@ -62,5 +63,9 @@ exports.ProgressLinear = React.forwardRef((props, forwardedRef) => {
62
63
  const boundedValue = (0, index_js_1.getBoundedValue)(value ?? 100, 0, 100);
63
64
  return (React.createElement(index_js_1.Box, { className: (0, classnames_1.default)('iui-progress-indicator-linear', className), ref: forwardedRef, "data-iui-status": status, "data-iui-indeterminate": indeterminate ? 'true' : undefined, "data-iui-animated": isAnimated ? 'true' : undefined, style: {
64
65
  '--iui-progress-percentage': `${boundedValue}%`,
65
- }, ...rest }, labels.length > 0 && (React.createElement(index_js_1.Box, { as: 'div', ...labelGroupProps, className: (0, classnames_1.default)('iui-progress-indicator-linear-label', labelGroupProps?.className) }, labels.map((label, index) => (React.createElement("span", { key: index }, label)))))));
66
+ }, ...rest },
67
+ React.createElement(index_js_1.ShadowRoot, null,
68
+ value !== 100 && React.createElement(VisuallyHidden_js_1.VisuallyHidden, null, "Loading."),
69
+ React.createElement("slot", null)),
70
+ labels.length > 0 && (React.createElement(index_js_1.Box, { as: 'div', ...labelGroupProps, className: (0, classnames_1.default)('iui-progress-indicator-linear-label', labelGroupProps?.className) }, labels.map((label, index) => (React.createElement("span", { key: index }, label)))))));
66
71
  });
@@ -34,6 +34,7 @@ exports.ProgressRadial = void 0;
34
34
  const classnames_1 = __importDefault(require("classnames"));
35
35
  const React = __importStar(require("react"));
36
36
  const index_js_1 = require("../utils/index.js");
37
+ const VisuallyHidden_js_1 = require("../VisuallyHidden/VisuallyHidden.js");
37
38
  /**
38
39
  * Circular Progress Indicator
39
40
  * @example
@@ -62,7 +63,11 @@ exports.ProgressRadial = React.forwardRef((props, forwardedRef) => {
62
63
  '--iui-progress-percentage': `${(0, index_js_1.getBoundedValue)(value, 0, 100)}%`,
63
64
  }),
64
65
  ...style,
65
- }, ...rest }, size !== 'x-small'
66
- ? children ?? (!!status ? statusMap[status] : null)
67
- : null));
66
+ }, ...rest },
67
+ React.createElement(index_js_1.ShadowRoot, null,
68
+ value !== 100 && React.createElement(VisuallyHidden_js_1.VisuallyHidden, null, "Loading."),
69
+ React.createElement("slot", null)),
70
+ size !== 'x-small'
71
+ ? children ?? (!!status ? statusMap[status] : null)
72
+ : null));
68
73
  });
@@ -64,8 +64,10 @@ export type SelectMultipleTypeProps<T> = {
64
64
  /**
65
65
  * Selected option value.
66
66
  * If `multiple` is enabled, it is an array of values.
67
+ *
68
+ * Pass `null` to reset the value.
67
69
  */
68
- value?: T;
70
+ value?: T | null;
69
71
  /**
70
72
  * Callback function handling change event on select.
71
73
  */
@@ -102,7 +102,7 @@ exports.Select = React.forwardRef((props, forwardedRef) => {
102
102
  const [isOpen, setIsOpen] = React.useState(false);
103
103
  const [liveRegionSelection, setLiveRegionSelection] = React.useState('');
104
104
  const [uncontrolledValue, setUncontrolledValue] = React.useState();
105
- const value = valueProp ?? uncontrolledValue;
105
+ const value = valueProp !== undefined ? valueProp : uncontrolledValue;
106
106
  const onChangeRef = (0, index_js_1.useLatestRef)(onChangeProp);
107
107
  const selectRef = React.useRef(null);
108
108
  const show = React.useCallback(() => {
@@ -17,9 +17,11 @@ type ToggleSwitchProps = {
17
17
  */
18
18
  size?: 'default';
19
19
  /**
20
- * Icon inside the toggle switch. Shown only when toggle is checked and size is not small.
20
+ * Custom icon inside the toggle switch. Shown only when toggle is checked and size is not small.
21
+ *
22
+ * Will override the default checkmark icon.
21
23
  */
22
- icon?: JSX.Element;
24
+ icon?: JSX.Element | null;
23
25
  } | {
24
26
  size: 'small';
25
27
  icon?: never;
@@ -53,21 +53,15 @@ const index_js_1 = require("../utils/index.js");
53
53
  * <ToggleSwitch label='With icon toggle' icon={<svg viewBox='0 0 16 16'><path d='M1 1v14h14V1H1zm13 1.7v10.6L8.7 8 14 2.7zM8 7.3L2.7 2h10.6L8 7.3zm-.7.7L2 13.3V2.7L7.3 8zm.7.7l5.3 5.3H2.7L8 8.7z' /></svg>} />
54
54
  */
55
55
  exports.ToggleSwitch = React.forwardRef((props, ref) => {
56
- let icon;
57
- if (props.size !== 'small') {
58
- icon = props.icon;
59
- props = { ...props };
60
- delete props.icon;
61
- }
62
- const { disabled = false, labelPosition = 'right', label, className, style, size = 'default', ...rest } = props;
63
- const inputElementRef = React.useRef(null);
64
- const refs = (0, index_js_1.useMergedRefs)(inputElementRef, ref);
56
+ const { disabled = false, labelPosition = 'right', label, className, style, size = 'default', icon: iconProp, ...rest } = props;
57
+ // Disallow custom icon for small size, but keep the default checkmark when prop is not passed.
58
+ const shouldShowIcon = iconProp === undefined || (iconProp !== null && size !== 'small');
65
59
  return (React.createElement(index_js_1.Box, { as: label ? 'label' : 'div', className: (0, classnames_1.default)('iui-toggle-switch-wrapper', {
66
60
  'iui-disabled': disabled,
67
61
  'iui-label-on-right': label && labelPosition === 'right',
68
62
  'iui-label-on-left': label && labelPosition === 'left',
69
63
  }, className), "data-iui-size": size, style: style },
70
- React.createElement(index_js_1.Box, { as: 'input', className: 'iui-toggle-switch', type: 'checkbox', role: 'switch', disabled: disabled, ref: refs, ...rest }),
71
- icon && size !== 'small' && (React.createElement(index_js_1.Box, { as: 'span', className: 'iui-toggle-switch-icon', "aria-hidden": true }, icon)),
64
+ React.createElement(index_js_1.Box, { as: 'input', className: 'iui-toggle-switch', type: 'checkbox', role: 'switch', disabled: disabled, ref: ref, ...rest }),
65
+ shouldShowIcon && (React.createElement(index_js_1.Box, { as: 'span', className: 'iui-toggle-switch-icon', "aria-hidden": true }, iconProp || React.createElement(index_js_1.SvgCheckmark, null))),
72
66
  label && (React.createElement(index_js_1.Box, { as: 'span', className: 'iui-toggle-switch-label' }, label))));
73
67
  });
@@ -1,3 +1,21 @@
1
+ import type { PolymorphicForwardRefComponent } from '../utils/index.js';
2
+ type AnchorProps = {
3
+ /**
4
+ * Whether the anchor links to an external site.
5
+ *
6
+ * When true, there will be an icon added at the end of the anchor text. This is useful
7
+ * to indicate that the link will open in a new tab.
8
+ *
9
+ * Not all external links should open in a new tab, so this prop should be used with caution.
10
+ */
11
+ isExternal?: boolean;
12
+ /**
13
+ * Whether the anchor should be underlined in its idle state.
14
+ *
15
+ * By default, the anchor is underlined only on hover, or when using a high-contrast theme.
16
+ */
17
+ underline?: boolean;
18
+ };
1
19
  /**
2
20
  * A consistently styled anchor component.
3
21
  *
@@ -7,9 +25,11 @@
7
25
  * @example
8
26
  * <Anchor href='/'>Home</Anchor>
9
27
  * <Anchor href='/projects'>Projects</Anchor>
28
+ * <Anchor href='/help' underline>Help</Anchor>
10
29
  *
11
30
  * @example
12
31
  * <Anchor as={Link} to='/'>Home</Anchor>
13
32
  * <Anchor as='button' onClick={() => {}}>click me</Anchor>
14
33
  */
15
- export declare const Anchor: import("../utils/props.js").PolymorphicForwardRefComponent<"a", {}>;
34
+ export declare const Anchor: PolymorphicForwardRefComponent<"a", AnchorProps>;
35
+ export {};
@@ -1,11 +1,40 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Anchor = void 0;
4
2
  /*---------------------------------------------------------------------------------------------
5
3
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
6
4
  * See LICENSE.md in the project root for license terms and full copyright notice.
7
5
  *--------------------------------------------------------------------------------------------*/
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || function (mod) {
23
+ if (mod && mod.__esModule) return mod;
24
+ var result = {};
25
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
26
+ __setModuleDefault(result, mod);
27
+ return result;
28
+ };
29
+ var __importDefault = (this && this.__importDefault) || function (mod) {
30
+ return (mod && mod.__esModule) ? mod : { "default": mod };
31
+ };
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.Anchor = void 0;
34
+ const React = __importStar(require("react"));
35
+ const classnames_1 = __importDefault(require("classnames"));
8
36
  const index_js_1 = require("../utils/index.js");
37
+ const VisuallyHidden_js_1 = require("../VisuallyHidden/VisuallyHidden.js");
9
38
  /**
10
39
  * A consistently styled anchor component.
11
40
  *
@@ -15,9 +44,15 @@ const index_js_1 = require("../utils/index.js");
15
44
  * @example
16
45
  * <Anchor href='/'>Home</Anchor>
17
46
  * <Anchor href='/projects'>Projects</Anchor>
47
+ * <Anchor href='/help' underline>Help</Anchor>
18
48
  *
19
49
  * @example
20
50
  * <Anchor as={Link} to='/'>Home</Anchor>
21
51
  * <Anchor as='button' onClick={() => {}}>click me</Anchor>
22
52
  */
23
- exports.Anchor = index_js_1.polymorphic.a('iui-anchor');
53
+ exports.Anchor = React.forwardRef((props, forwardedRef) => {
54
+ const { isExternal, underline, children, ...rest } = props;
55
+ return (React.createElement(index_js_1.Box, { as: 'a', "data-iui-underline": underline ? 'true' : undefined, ...rest, ref: forwardedRef, className: (0, classnames_1.default)('iui-anchor', { 'iui-anchor-external': isExternal }, props.className) },
56
+ children,
57
+ props.target === '_blank' && (React.createElement(VisuallyHidden_js_1.VisuallyHidden, null, " (opens in new tab)"))));
58
+ });
@@ -33,7 +33,7 @@ exports.VisuallyHidden = void 0;
33
33
  *--------------------------------------------------------------------------------------------*/
34
34
  const React = __importStar(require("react"));
35
35
  const classnames_1 = __importDefault(require("classnames"));
36
- const Box_js_1 = require("../utils/components/Box.js");
36
+ const index_js_1 = require("../utils/index.js");
37
37
  /**
38
38
  * Hides content visually but keeps it still accessible to screen readers
39
39
  * and other assistive technologies.
@@ -45,6 +45,22 @@ const Box_js_1 = require("../utils/components/Box.js");
45
45
  * @see https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html
46
46
  */
47
47
  exports.VisuallyHidden = React.forwardRef((props, ref) => {
48
- const { className, unhideOnFocus = true, ...rest } = props;
49
- return (React.createElement(Box_js_1.Box, { as: 'span', className: (0, classnames_1.default)('iui-visually-hidden', className), "data-iui-unhide-on-focus": unhideOnFocus ? true : undefined, ref: ref, ...rest }));
48
+ const { as: asProp = 'span', className, unhideOnFocus = true, children: childrenProp, ...rest } = props;
49
+ // ShadowRoot is not supported on all elements, so we only use it for few common ones.
50
+ const children = !['div', 'span', 'p'].includes(asProp) ? (childrenProp) : (React.createElement(React.Fragment, null,
51
+ React.createElement(index_js_1.ShadowRoot, { css: css },
52
+ React.createElement("slot", null)),
53
+ childrenProp));
54
+ return (React.createElement(index_js_1.Box, { as: asProp, className: (0, classnames_1.default)('iui-visually-hidden', className), "data-iui-unhide-on-focus": unhideOnFocus ? true : undefined, ref: ref, ...rest }, children));
50
55
  });
56
+ // ----------------------------------------------------------------------------
57
+ const css = /* css */ `
58
+ :host(:where(:not([data-iui-unhide-on-focus]:is(:focus-within, :active)))) {
59
+ clip-path: inset(50%) !important;
60
+ overflow: hidden !important;
61
+ position: absolute !important;
62
+ white-space: nowrap !important;
63
+ block-size: 1px !important;
64
+ inline-size: 1px !important;
65
+ }
66
+ `;
@@ -5,6 +5,7 @@ import * as React from 'react';
5
5
  *
6
6
  * @private
7
7
  */
8
- export declare const ShadowRoot: ({ children }: {
8
+ export declare const ShadowRoot: ({ children, css, }: {
9
9
  children: React.ReactNode;
10
+ css?: string | undefined;
10
11
  }) => React.JSX.Element | null;
@@ -32,24 +32,39 @@ const React = __importStar(require("react"));
32
32
  const ReactDOM = __importStar(require("react-dom"));
33
33
  const isBrowser = typeof document !== 'undefined';
34
34
  const supportsDSD = isBrowser && 'shadowRootMode' in HTMLTemplateElement.prototype;
35
+ const supportsAdoptedStylesheets = isBrowser && 'adoptedStyleSheets' in Document.prototype;
35
36
  /**
36
37
  * Wrapper around `<template>` element that attaches shadow root to its parent
37
38
  * and moves its children into the shadow root.
38
39
  *
39
40
  * @private
40
41
  */
41
- const ShadowRoot = ({ children }) => {
42
+ const ShadowRoot = ({ children, css, }) => {
42
43
  const [shadowRoot, setShadowRoot] = React.useState();
43
44
  const isFirstRender = useIsFirstRender();
45
+ const styleSheet = React.useRef();
44
46
  const attachShadowRef = React.useCallback((template) => {
45
47
  const parent = template?.parentElement;
46
48
  if (!template || !parent) {
47
49
  return;
48
50
  }
49
- queueMicrotask(() => ReactDOM.flushSync(() => setShadowRoot(parent.shadowRoot || parent.attachShadow({ mode: 'open' }))));
50
- }, []);
51
+ if (parent.shadowRoot) {
52
+ parent.shadowRoot.replaceChildren(); // Remove previous shadowroot content
53
+ }
54
+ queueMicrotask(() => {
55
+ const shadow = parent.shadowRoot || parent.attachShadow({ mode: 'open' });
56
+ if (css && supportsAdoptedStylesheets) {
57
+ styleSheet.current || (styleSheet.current = new CSSStyleSheet());
58
+ styleSheet.current.replaceSync(css);
59
+ shadow.adoptedStyleSheets = [styleSheet.current];
60
+ }
61
+ ReactDOM.flushSync(() => setShadowRoot(shadow));
62
+ });
63
+ }, [css]);
51
64
  if (!isBrowser) {
52
- return React.createElement("template", { shadowrootmode: 'open' }, children);
65
+ return (React.createElement("template", { shadowrootmode: 'open' },
66
+ css && React.createElement("style", null, css),
67
+ children));
53
68
  }
54
69
  // In browsers that support DSD, the template will be automatically removed as soon as it's parsed.
55
70
  // To pass hydration, the first client render needs to emulate this browser behavior and return null.
@@ -3,11 +3,13 @@ import type { PolymorphicForwardRefComponent } from '../props.js';
3
3
  /**
4
4
  * Utility to create a type-safe polymorphic component with a simple class.
5
5
  *
6
- * Can be called directly or as a property of the `Polymorphic` object.
6
+ * Can be called directly or as a property of the `polymorphic` object.
7
7
  * In both cases, returns a component that:
8
+ * - uses CSS-modules scoped classes
8
9
  * - supports `as` prop with default element
9
- * - forwards ref and rest props
10
- * - adds and merges css classes
10
+ * - forwards ref and spreads rest props
11
+ * - adds and merges CSS classes
12
+ * - adds tabIndex to interactive elements (Safari workaround)
11
13
  *
12
14
  * @example
13
15
  * const MyPolyDiv = polymorphic('my-poly-div');
@@ -31,6 +31,7 @@ exports.polymorphic = void 0;
31
31
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
32
32
  * See LICENSE.md in the project root for license terms and full copyright notice.
33
33
  *--------------------------------------------------------------------------------------------*/
34
+ /* eslint-disable @typescript-eslint/no-explicit-any */
34
35
  const React = __importStar(require("react"));
35
36
  const classnames_1 = __importDefault(require("classnames"));
36
37
  const useGlobals_js_1 = require("../hooks/useGlobals.js");
@@ -38,9 +39,21 @@ const styles_js_1 = __importDefault(require("../../../styles.js"));
38
39
  const _base = (defaultElement) => {
39
40
  return (className, attrs) => {
40
41
  const Comp = React.forwardRef(({ as = defaultElement, ...props }, ref) => {
41
- const Element = as || 'div'; // eslint-disable-line
42
+ props = {
43
+ ...attrs,
44
+ ...props,
45
+ className: getScopedClassName((0, classnames_1.default)(className, attrs?.className, props.className)),
46
+ };
47
+ const Element = as || 'div';
48
+ // Add tabIndex to interactive elements if not already set.
49
+ // Workaround for Safari refusing to focus links/buttons/non-text inputs.
50
+ if (Element === 'button' ||
51
+ Element === 'a' ||
52
+ (Element === 'input' && props.type === 'checkbox')) {
53
+ props.tabIndex ?? (props.tabIndex = 0);
54
+ }
42
55
  (0, useGlobals_js_1.useGlobals)();
43
- return (React.createElement(Element, { ref: ref, ...attrs, ...props, className: getScopedClassName((0, classnames_1.default)(className, attrs?.className, props.className)) }));
56
+ return React.createElement(Element, { ref: ref, ...props });
44
57
  });
45
58
  Comp.displayName = getDisplayNameFromClass(className);
46
59
  return Comp;
@@ -49,11 +62,13 @@ const _base = (defaultElement) => {
49
62
  /**
50
63
  * Utility to create a type-safe polymorphic component with a simple class.
51
64
  *
52
- * Can be called directly or as a property of the `Polymorphic` object.
65
+ * Can be called directly or as a property of the `polymorphic` object.
53
66
  * In both cases, returns a component that:
67
+ * - uses CSS-modules scoped classes
54
68
  * - supports `as` prop with default element
55
- * - forwards ref and rest props
56
- * - adds and merges css classes
69
+ * - forwards ref and spreads rest props
70
+ * - adds and merges CSS classes
71
+ * - adds tabIndex to interactive elements (Safari workaround)
57
72
  *
58
73
  * @example
59
74
  * const MyPolyDiv = polymorphic('my-poly-div');
@@ -6,4 +6,8 @@ exports.Svg = void 0;
6
6
  * See LICENSE.md in the project root for license terms and full copyright notice.
7
7
  *--------------------------------------------------------------------------------------------*/
8
8
  const polymorphic_js_1 = require("../functions/polymorphic.js");
9
- exports.Svg = polymorphic_js_1.polymorphic.svg('', { viewBox: '0 0 16 16' });
9
+ exports.Svg = polymorphic_js_1.polymorphic.svg('', {
10
+ viewBox: '0 0 16 16',
11
+ width: 16,
12
+ height: 16,
13
+ });
@@ -4,7 +4,8 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { Box, getBoundedValue } from '../utils/index.js';
7
+ import { Box, ShadowRoot, getBoundedValue } from '../utils/index.js';
8
+ import { VisuallyHidden } from '../VisuallyHidden/VisuallyHidden.js';
8
9
  /**
9
10
  * Shows progress on a linear bar
10
11
  * @example
@@ -33,5 +34,9 @@ export const ProgressLinear = React.forwardRef((props, forwardedRef) => {
33
34
  const boundedValue = getBoundedValue(value ?? 100, 0, 100);
34
35
  return (React.createElement(Box, { className: cx('iui-progress-indicator-linear', className), ref: forwardedRef, "data-iui-status": status, "data-iui-indeterminate": indeterminate ? 'true' : undefined, "data-iui-animated": isAnimated ? 'true' : undefined, style: {
35
36
  '--iui-progress-percentage': `${boundedValue}%`,
36
- }, ...rest }, labels.length > 0 && (React.createElement(Box, { as: 'div', ...labelGroupProps, className: cx('iui-progress-indicator-linear-label', labelGroupProps?.className) }, labels.map((label, index) => (React.createElement("span", { key: index }, label)))))));
37
+ }, ...rest },
38
+ React.createElement(ShadowRoot, null,
39
+ value !== 100 && React.createElement(VisuallyHidden, null, "Loading."),
40
+ React.createElement("slot", null)),
41
+ labels.length > 0 && (React.createElement(Box, { as: 'div', ...labelGroupProps, className: cx('iui-progress-indicator-linear-label', labelGroupProps?.className) }, labels.map((label, index) => (React.createElement("span", { key: index }, label)))))));
37
42
  });
@@ -4,7 +4,8 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import cx from 'classnames';
6
6
  import * as React from 'react';
7
- import { SvgCheckmarkSmall, SvgImportantSmall, Box, getBoundedValue, } from '../utils/index.js';
7
+ import { SvgCheckmarkSmall, SvgImportantSmall, Box, getBoundedValue, ShadowRoot, } from '../utils/index.js';
8
+ import { VisuallyHidden } from '../VisuallyHidden/VisuallyHidden.js';
8
9
  /**
9
10
  * Circular Progress Indicator
10
11
  * @example
@@ -33,7 +34,11 @@ export const ProgressRadial = React.forwardRef((props, forwardedRef) => {
33
34
  '--iui-progress-percentage': `${getBoundedValue(value, 0, 100)}%`,
34
35
  }),
35
36
  ...style,
36
- }, ...rest }, size !== 'x-small'
37
- ? children ?? (!!status ? statusMap[status] : null)
38
- : null));
37
+ }, ...rest },
38
+ React.createElement(ShadowRoot, null,
39
+ value !== 100 && React.createElement(VisuallyHidden, null, "Loading."),
40
+ React.createElement("slot", null)),
41
+ size !== 'x-small'
42
+ ? children ?? (!!status ? statusMap[status] : null)
43
+ : null));
39
44
  });
@@ -64,8 +64,10 @@ export type SelectMultipleTypeProps<T> = {
64
64
  /**
65
65
  * Selected option value.
66
66
  * If `multiple` is enabled, it is an array of values.
67
+ *
68
+ * Pass `null` to reset the value.
67
69
  */
68
- value?: T;
70
+ value?: T | null;
69
71
  /**
70
72
  * Callback function handling change event on select.
71
73
  */
@@ -73,7 +73,7 @@ export const Select = React.forwardRef((props, forwardedRef) => {
73
73
  const [isOpen, setIsOpen] = React.useState(false);
74
74
  const [liveRegionSelection, setLiveRegionSelection] = React.useState('');
75
75
  const [uncontrolledValue, setUncontrolledValue] = React.useState();
76
- const value = valueProp ?? uncontrolledValue;
76
+ const value = valueProp !== undefined ? valueProp : uncontrolledValue;
77
77
  const onChangeRef = useLatestRef(onChangeProp);
78
78
  const selectRef = React.useRef(null);
79
79
  const show = React.useCallback(() => {
@@ -17,9 +17,11 @@ type ToggleSwitchProps = {
17
17
  */
18
18
  size?: 'default';
19
19
  /**
20
- * Icon inside the toggle switch. Shown only when toggle is checked and size is not small.
20
+ * Custom icon inside the toggle switch. Shown only when toggle is checked and size is not small.
21
+ *
22
+ * Will override the default checkmark icon.
21
23
  */
22
- icon?: JSX.Element;
24
+ icon?: JSX.Element | null;
23
25
  } | {
24
26
  size: 'small';
25
27
  icon?: never;
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { useMergedRefs, Box } from '../utils/index.js';
7
+ import { Box, SvgCheckmark } from '../utils/index.js';
8
8
  /**
9
9
  * A switch for turning on and off.
10
10
  * @example
@@ -24,21 +24,15 @@ import { useMergedRefs, Box } from '../utils/index.js';
24
24
  * <ToggleSwitch label='With icon toggle' icon={<svg viewBox='0 0 16 16'><path d='M1 1v14h14V1H1zm13 1.7v10.6L8.7 8 14 2.7zM8 7.3L2.7 2h10.6L8 7.3zm-.7.7L2 13.3V2.7L7.3 8zm.7.7l5.3 5.3H2.7L8 8.7z' /></svg>} />
25
25
  */
26
26
  export const ToggleSwitch = React.forwardRef((props, ref) => {
27
- let icon;
28
- if (props.size !== 'small') {
29
- icon = props.icon;
30
- props = { ...props };
31
- delete props.icon;
32
- }
33
- const { disabled = false, labelPosition = 'right', label, className, style, size = 'default', ...rest } = props;
34
- const inputElementRef = React.useRef(null);
35
- const refs = useMergedRefs(inputElementRef, ref);
27
+ const { disabled = false, labelPosition = 'right', label, className, style, size = 'default', icon: iconProp, ...rest } = props;
28
+ // Disallow custom icon for small size, but keep the default checkmark when prop is not passed.
29
+ const shouldShowIcon = iconProp === undefined || (iconProp !== null && size !== 'small');
36
30
  return (React.createElement(Box, { as: label ? 'label' : 'div', className: cx('iui-toggle-switch-wrapper', {
37
31
  'iui-disabled': disabled,
38
32
  'iui-label-on-right': label && labelPosition === 'right',
39
33
  'iui-label-on-left': label && labelPosition === 'left',
40
34
  }, className), "data-iui-size": size, style: style },
41
- React.createElement(Box, { as: 'input', className: 'iui-toggle-switch', type: 'checkbox', role: 'switch', disabled: disabled, ref: refs, ...rest }),
42
- icon && size !== 'small' && (React.createElement(Box, { as: 'span', className: 'iui-toggle-switch-icon', "aria-hidden": true }, icon)),
35
+ React.createElement(Box, { as: 'input', className: 'iui-toggle-switch', type: 'checkbox', role: 'switch', disabled: disabled, ref: ref, ...rest }),
36
+ shouldShowIcon && (React.createElement(Box, { as: 'span', className: 'iui-toggle-switch-icon', "aria-hidden": true }, iconProp || React.createElement(SvgCheckmark, null))),
43
37
  label && (React.createElement(Box, { as: 'span', className: 'iui-toggle-switch-label' }, label))));
44
38
  });
@@ -1,3 +1,21 @@
1
+ import type { PolymorphicForwardRefComponent } from '../utils/index.js';
2
+ type AnchorProps = {
3
+ /**
4
+ * Whether the anchor links to an external site.
5
+ *
6
+ * When true, there will be an icon added at the end of the anchor text. This is useful
7
+ * to indicate that the link will open in a new tab.
8
+ *
9
+ * Not all external links should open in a new tab, so this prop should be used with caution.
10
+ */
11
+ isExternal?: boolean;
12
+ /**
13
+ * Whether the anchor should be underlined in its idle state.
14
+ *
15
+ * By default, the anchor is underlined only on hover, or when using a high-contrast theme.
16
+ */
17
+ underline?: boolean;
18
+ };
1
19
  /**
2
20
  * A consistently styled anchor component.
3
21
  *
@@ -7,9 +25,11 @@
7
25
  * @example
8
26
  * <Anchor href='/'>Home</Anchor>
9
27
  * <Anchor href='/projects'>Projects</Anchor>
28
+ * <Anchor href='/help' underline>Help</Anchor>
10
29
  *
11
30
  * @example
12
31
  * <Anchor as={Link} to='/'>Home</Anchor>
13
32
  * <Anchor as='button' onClick={() => {}}>click me</Anchor>
14
33
  */
15
- export declare const Anchor: import("../utils/props.js").PolymorphicForwardRefComponent<"a", {}>;
34
+ export declare const Anchor: PolymorphicForwardRefComponent<"a", AnchorProps>;
35
+ export {};
@@ -2,7 +2,10 @@
2
2
  * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
3
  * See LICENSE.md in the project root for license terms and full copyright notice.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- import { polymorphic } from '../utils/index.js';
5
+ import * as React from 'react';
6
+ import cx from 'classnames';
7
+ import { Box } from '../utils/index.js';
8
+ import { VisuallyHidden } from '../VisuallyHidden/VisuallyHidden.js';
6
9
  /**
7
10
  * A consistently styled anchor component.
8
11
  *
@@ -12,9 +15,15 @@ import { polymorphic } from '../utils/index.js';
12
15
  * @example
13
16
  * <Anchor href='/'>Home</Anchor>
14
17
  * <Anchor href='/projects'>Projects</Anchor>
18
+ * <Anchor href='/help' underline>Help</Anchor>
15
19
  *
16
20
  * @example
17
21
  * <Anchor as={Link} to='/'>Home</Anchor>
18
22
  * <Anchor as='button' onClick={() => {}}>click me</Anchor>
19
23
  */
20
- export const Anchor = polymorphic.a('iui-anchor');
24
+ export const Anchor = React.forwardRef((props, forwardedRef) => {
25
+ const { isExternal, underline, children, ...rest } = props;
26
+ return (React.createElement(Box, { as: 'a', "data-iui-underline": underline ? 'true' : undefined, ...rest, ref: forwardedRef, className: cx('iui-anchor', { 'iui-anchor-external': isExternal }, props.className) },
27
+ children,
28
+ props.target === '_blank' && (React.createElement(VisuallyHidden, null, " (opens in new tab)"))));
29
+ });
@@ -4,7 +4,7 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
  import * as React from 'react';
6
6
  import cx from 'classnames';
7
- import { Box } from '../utils/components/Box.js';
7
+ import { Box, ShadowRoot } from '../utils/index.js';
8
8
  /**
9
9
  * Hides content visually but keeps it still accessible to screen readers
10
10
  * and other assistive technologies.
@@ -16,6 +16,22 @@ import { Box } from '../utils/components/Box.js';
16
16
  * @see https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html
17
17
  */
18
18
  export const VisuallyHidden = React.forwardRef((props, ref) => {
19
- const { className, unhideOnFocus = true, ...rest } = props;
20
- return (React.createElement(Box, { as: 'span', className: cx('iui-visually-hidden', className), "data-iui-unhide-on-focus": unhideOnFocus ? true : undefined, ref: ref, ...rest }));
19
+ const { as: asProp = 'span', className, unhideOnFocus = true, children: childrenProp, ...rest } = props;
20
+ // ShadowRoot is not supported on all elements, so we only use it for few common ones.
21
+ const children = !['div', 'span', 'p'].includes(asProp) ? (childrenProp) : (React.createElement(React.Fragment, null,
22
+ React.createElement(ShadowRoot, { css: css },
23
+ React.createElement("slot", null)),
24
+ childrenProp));
25
+ return (React.createElement(Box, { as: asProp, className: cx('iui-visually-hidden', className), "data-iui-unhide-on-focus": unhideOnFocus ? true : undefined, ref: ref, ...rest }, children));
21
26
  });
27
+ // ----------------------------------------------------------------------------
28
+ const css = /* css */ `
29
+ :host(:where(:not([data-iui-unhide-on-focus]:is(:focus-within, :active)))) {
30
+ clip-path: inset(50%) !important;
31
+ overflow: hidden !important;
32
+ position: absolute !important;
33
+ white-space: nowrap !important;
34
+ block-size: 1px !important;
35
+ inline-size: 1px !important;
36
+ }
37
+ `;