@skyscanner/backpack-web 34.13.1 → 34.15.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 (27) hide show
  1. package/bpk-component-accordion/src/BpkAccordionItem.js +9 -1
  2. package/bpk-component-accordion/src/BpkAccordionItem.module.css +1 -1
  3. package/bpk-component-carousel/src/utils.js +3 -13
  4. package/bpk-component-datepicker/src/BpkDatepicker.d.ts +1 -1
  5. package/bpk-component-dialog/src/BpkDialog.js +23 -38
  6. package/bpk-component-drawer/src/BpkDrawer.js +42 -32
  7. package/bpk-component-drawer/src/BpkDrawerContent.js +56 -29
  8. package/bpk-component-drawer/src/themeAttributes.js +1 -4
  9. package/bpk-component-floating-notification/src/BpkFloatingNotification.module.css +1 -1
  10. package/bpk-component-image/src/BpkImage.module.css +1 -1
  11. package/bpk-component-mobile-scroll-container/src/BpkMobileScrollContainer.js +14 -18
  12. package/bpk-component-modal/src/BpkModal.d.ts +1 -1
  13. package/bpk-component-modal/src/BpkModal.js +27 -42
  14. package/bpk-component-nudger/src/BpkConfigurableNudger.d.ts +1 -1
  15. package/bpk-component-nudger/src/BpkConfigurableNudger.js +22 -5
  16. package/bpk-component-nudger/src/common-types.d.ts +1 -0
  17. package/bpk-react-utils/index.d.ts +3 -1
  18. package/bpk-react-utils/index.js +4 -2
  19. package/bpk-react-utils/src/Portal.d.ts +0 -4
  20. package/bpk-react-utils/src/Portal.js +0 -4
  21. package/bpk-react-utils/src/nativeEventHandler.d.ts +2 -0
  22. package/bpk-react-utils/src/nativeEventHandler.js +37 -0
  23. package/package.json +2 -2
  24. package/bpk-component-carousel/src/utils.test.js +0 -12
  25. package/bpk-component-drawer/src/BpkDrawer.d.ts +0 -43
  26. package/bpk-component-drawer/src/BpkDrawerContent.d.ts +0 -21
  27. package/bpk-component-drawer/src/themeAttributes.d.ts +0 -2
@@ -31,6 +31,7 @@ const BpkAccordionItem = props => {
31
31
  divider,
32
32
  onDark
33
33
  } = useContext(BpkAccordionContext);
34
+ const itemClassNames = [getClassName('bpk-accordion__item')];
34
35
  const iconClassNames = [getClassName('bpk-accordion__item-expand-icon')];
35
36
  const titleTextClassNames = [getClassName('bpk-accordion__title-text')];
36
37
  const titleClassNames = [getClassName('bpk-accordion__title')];
@@ -52,6 +53,9 @@ const BpkAccordionItem = props => {
52
53
  // it, but the benefit of a better container api versus this was worth it
53
54
  // $FlowFixMe[prop-missing] - see above
54
55
  delete rest.initiallyExpanded;
56
+ if (onDark) {
57
+ itemClassNames.push(getClassName('bpk-accordion__item--on-dark'));
58
+ }
55
59
  if (expanded && !onDark) {
56
60
  iconClassNames.push(getClassName('bpk-accordion__item-expand-icon--flipped'));
57
61
  if (divider) {
@@ -85,6 +89,7 @@ const BpkAccordionItem = props => {
85
89
  // $FlowFixMe[cannot-spread-inexact] - inexact rest. See decisions/flowfixme.md
86
90
  _jsxs("div", {
87
91
  id: id,
92
+ className: getClassName('bpk-accordion__item'),
88
93
  ...rest,
89
94
  children: [/*#__PURE__*/_jsx("div", {
90
95
  className: titleClassNames.join(' '),
@@ -115,7 +120,10 @@ const BpkAccordionItem = props => {
115
120
  children: /*#__PURE__*/_jsx(AnimateHeight, {
116
121
  duration: 200,
117
122
  height: expanded ? 'auto' : 0,
118
- children: children
123
+ children: /*#__PURE__*/_jsx("div", {
124
+ className: getClassName('bpk-accordion__content-inner-container'),
125
+ children: children
126
+ })
119
127
  })
120
128
  })]
121
129
  })
@@ -15,4 +15,4 @@
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */
18
- .bpk-accordion__title{height:auto}.bpk-accordion__title--collapsed{box-shadow:0 -1px 0 0 #c1c7cf inset}.bpk-accordion__title--collapsed-on-dark{box-shadow:0 -1px 0 0 rgba(255,255,255,.5) inset}.bpk-accordion__toggle-button{width:100%;padding:0;border:0;background-color:rgba(0,0,0,0);color:#161616;text-align:left;cursor:pointer;appearance:none}html[dir=rtl] .bpk-accordion__toggle-button{text-align:right}.bpk-accordion__flex-container{display:inline-flex;width:100%;margin:1rem 0;flex-direction:row}.bpk-accordion__title-text{flex-grow:1}.bpk-accordion__title-text--on-dark{color:#fff}.bpk-accordion__icon-wrapper{display:inline-block}.bpk-accordion__leading-icon{margin-left:0;margin-right:.5rem}html[dir=rtl] .bpk-accordion__leading-icon{margin-left:.5rem;margin-right:0}.bpk-accordion__item-expand-icon{fill:#161616}.bpk-accordion__item-expand-icon--flipped{transform:scaleY(-1)}.bpk-accordion__item-expand-icon--on-dark{fill:#fff}.bpk-accordion__content-container{margin:0}.bpk-accordion__content-container--expanded{padding-bottom:1rem;box-shadow:0 -1px 0 0 #c1c7cf inset}.bpk-accordion__content-container--expanded-on-dark{padding-bottom:1rem;box-shadow:0 -1px 0 0 rgba(255,255,255,.5) inset}
18
+ .bpk-accordion__item{box-shadow:0 -1px 0 0 #c1c7cf inset}.bpk-accordion__item-on-dark{box-shadow:0 -1px 0 0 rgba(255,255,255,.5) inset}.bpk-accordion__title{height:auto}.bpk-accordion__toggle-button{width:100%;padding:0;border:0;background-color:rgba(0,0,0,0);color:#161616;text-align:left;cursor:pointer;appearance:none}html[dir=rtl] .bpk-accordion__toggle-button{text-align:right}.bpk-accordion__flex-container{display:inline-flex;width:100%;margin:1rem 0;flex-direction:row}.bpk-accordion__title-text{flex-grow:1}.bpk-accordion__title-text--on-dark{color:#fff}.bpk-accordion__icon-wrapper{display:inline-block}.bpk-accordion__leading-icon{margin-left:0;margin-right:.5rem}html[dir=rtl] .bpk-accordion__leading-icon{margin-left:.5rem;margin-right:0}.bpk-accordion__item-expand-icon{fill:#161616}.bpk-accordion__item-expand-icon--flipped{transform:scaleY(-1)}.bpk-accordion__item-expand-icon--on-dark{fill:#fff}.bpk-accordion__content-container{margin:0}.bpk-accordion__content-inner-container{padding-bottom:1rem}
@@ -18,25 +18,15 @@
18
18
 
19
19
  import { useEffect, useMemo, useRef } from 'react';
20
20
  export function useScrollToInitialImage(initialImageIndex, imagesRef) {
21
- const handleIntersecting = index => {
22
- const element = imagesRef.current[index];
21
+ useEffect(() => {
22
+ const element = imagesRef.current[initialImageIndex];
23
23
  if (element) {
24
24
  element.scrollIntoView({
25
25
  block: 'nearest',
26
26
  inline: 'start'
27
27
  });
28
28
  }
29
- };
30
- const observe = useIntersectionObserver(handleIntersecting, {
31
- root: null,
32
- threshold: 0.1
33
- });
34
- useEffect(() => {
35
- const element = imagesRef.current[initialImageIndex];
36
- if (element) {
37
- observe(element);
38
- }
39
- }, [initialImageIndex, imagesRef, observe]);
29
+ }, [initialImageIndex, imagesRef]);
40
30
  }
41
31
  export function useIntersectionObserver(onIntersecting, {
42
32
  root,
@@ -40,7 +40,7 @@ type Props = {
40
40
  onOpenChange?: (arg0: boolean) => void | null;
41
41
  selectionConfiguration?: SelectionConfiguration;
42
42
  initiallyFocusedDate?: Date;
43
- renderTarget?: (() => HTMLElement | null) | HTMLElement | null;
43
+ renderTarget?: null | HTMLElement | (() => null | HTMLElement);
44
44
  isOpen?: boolean;
45
45
  valid?: boolean;
46
46
  onClose?: () => void;
@@ -16,16 +16,13 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- import { useEffect } from 'react';
20
- import { FloatingPortal } from '@floating-ui/react';
21
-
22
19
  // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
23
20
  import BpkCloseButton from "../../bpk-component-close-button";
24
- import { cssModules } from "../../bpk-react-utils";
21
+ import { cssModules, Portal } from "../../bpk-react-utils";
25
22
  import BpkDialogInner from "./BpkDialogInner";
26
23
  import { HEADER_ICON_TYPES } from "./common-types";
27
24
  import STYLES from "./BpkDialog.module.css";
28
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
25
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
29
26
  const getClassName = cssModules(STYLES);
30
27
  const BpkDialog = ({
31
28
  children,
@@ -44,39 +41,27 @@ const BpkDialog = ({
44
41
  // eslint-disable-next-line no-console
45
42
  console.warn('BpkDialog: dismissible is true but no onClose prop was provided. Dialog will not be dismissible.');
46
43
  }
47
- useEffect(() => {
48
- const handleKeyDown = event => {
49
- if (event.key === 'Escape' && dismissible && onClose) {
50
- onClose();
51
- }
52
- };
53
- if (isOpen) {
54
- window.addEventListener('keydown', handleKeyDown);
55
- }
56
- return () => {
57
- window.removeEventListener('keydown', handleKeyDown);
58
- };
59
- }, [isOpen, onClose, dismissible]);
60
- return /*#__PURE__*/_jsx(_Fragment, {
61
- children: isOpen && /*#__PURE__*/_jsx(FloatingPortal, {
62
- root: renderTarget(),
63
- children: /*#__PURE__*/_jsxs(BpkDialogInner, {
64
- onClose: onClose,
65
- closeOnScrimClick: dismissible,
66
- containerClassName: getClassName('bpk-dialog__container'),
67
- contentClassName: headerIcon ? getClassName('bpk-dialog--with-icon') : undefined,
68
- ...rest,
69
- children: [headerIcon && /*#__PURE__*/_jsx("div", {
70
- className: headerIconClassNames,
71
- children: headerIcon
72
- }), dismissible && /*#__PURE__*/_jsx("span", {
73
- className: closeButtonClassNames,
74
- children: /*#__PURE__*/_jsx(BpkCloseButton, {
75
- label: closeLabel,
76
- onClick: onClose
77
- })
78
- }), children]
79
- })
44
+ return /*#__PURE__*/_jsx(Portal, {
45
+ isOpen: isOpen,
46
+ onClose: onClose,
47
+ renderTarget: renderTarget,
48
+ closeOnEscPressed: dismissible,
49
+ children: /*#__PURE__*/_jsxs(BpkDialogInner, {
50
+ onClose: onClose,
51
+ closeOnScrimClick: dismissible,
52
+ containerClassName: getClassName('bpk-dialog__container'),
53
+ contentClassName: headerIcon ? getClassName('bpk-dialog--with-icon') : undefined,
54
+ ...rest,
55
+ children: [headerIcon && /*#__PURE__*/_jsx("div", {
56
+ className: headerIconClassNames,
57
+ children: headerIcon
58
+ }), dismissible && /*#__PURE__*/_jsx("span", {
59
+ className: closeButtonClassNames,
60
+ children: /*#__PURE__*/_jsx(BpkCloseButton, {
61
+ label: closeLabel,
62
+ onClick: onClose
63
+ })
64
+ }), children]
80
65
  })
81
66
  });
82
67
  };
@@ -14,25 +14,16 @@
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */
18
-
17
+ */import PropTypes from 'prop-types';
19
18
  import { Component } from 'react';
20
- import { FloatingPortal } from '@floating-ui/react';
19
+ import { Portal } from "../../bpk-react-utils";
21
20
  import { withScrim } from "../../bpk-scrim-utils";
22
21
  import BpkDrawerContent from "./BpkDrawerContent";
23
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
22
+ import { jsx as _jsx } from "react/jsx-runtime";
24
23
  const BpkScrimDrawerContent = withScrim(BpkDrawerContent);
25
24
  class BpkDrawer extends Component {
26
- static defaultProps = {
27
- renderTarget: () => null,
28
- className: null,
29
- contentClassName: null,
30
- closeLabel: null,
31
- closeText: null,
32
- hideTitle: false
33
- };
34
- constructor(props) {
35
- super(props);
25
+ constructor() {
26
+ super();
36
27
  this.state = {
37
28
  isDrawerShown: true
38
29
  };
@@ -42,19 +33,11 @@ class BpkDrawer extends Component {
42
33
  this.setState({
43
34
  isDrawerShown: true
44
35
  });
45
- window.addEventListener('keydown', this.handleKeyDown);
46
- } else {
47
- window.removeEventListener('keydown', this.handleKeyDown);
48
36
  }
49
37
  }
50
38
  onCloseAnimationComplete = () => {
51
39
  this.props.onClose();
52
40
  };
53
- handleKeyDown = event => {
54
- if (event.key === 'Escape' && this.props.onClose) {
55
- this.props.onClose();
56
- }
57
- };
58
41
  hide = () => {
59
42
  this.setState({
60
43
  isDrawerShown: false
@@ -70,18 +53,45 @@ class BpkDrawer extends Component {
70
53
  const {
71
54
  isDrawerShown
72
55
  } = this.state;
73
- const renTarget = typeof renderTarget === 'function' ? renderTarget() : renderTarget;
74
- return /*#__PURE__*/_jsx(_Fragment, {
75
- children: isOpen && /*#__PURE__*/_jsx(FloatingPortal, {
76
- root: renTarget,
77
- children: /*#__PURE__*/_jsx(BpkScrimDrawerContent, {
78
- isDrawerShown: isDrawerShown,
79
- onClose: this.hide,
80
- onCloseAnimationComplete: this.onCloseAnimationComplete,
81
- ...rest
82
- })
56
+ return /*#__PURE__*/_jsx(Portal, {
57
+ isOpen: isOpen,
58
+ onClose: this.hide,
59
+ renderTarget: renderTarget,
60
+ children: /*#__PURE__*/_jsx(BpkScrimDrawerContent, {
61
+ isDrawerShown: isDrawerShown,
62
+ onClose: this.hide,
63
+ onCloseAnimationComplete: this.onCloseAnimationComplete,
64
+ ...rest
83
65
  })
84
66
  });
85
67
  }
86
68
  }
69
+ BpkDrawer.propTypes = {
70
+ id: PropTypes.string.isRequired,
71
+ children: PropTypes.node.isRequired,
72
+ isOpen: PropTypes.bool.isRequired,
73
+ onClose: PropTypes.func.isRequired,
74
+ title: PropTypes.string.isRequired,
75
+ /**
76
+ * **Note:** In order to "hide" your application from screen readers whilst the drawer is open you need to let it know what
77
+ * the root element for your application is by returning it's DOM node via the function passed to the
78
+ * `getApplicationElement` prop (see the example above). The `pagewrap` element id is a convention we use internally at Skyscanner. In most cases it should "just work".
79
+ */
80
+ getApplicationElement: PropTypes.func.isRequired,
81
+ renderTarget: PropTypes.func,
82
+ dialogRef: PropTypes.func,
83
+ className: PropTypes.string,
84
+ contentClassName: PropTypes.string,
85
+ closeLabel: PropTypes.string,
86
+ closeText: PropTypes.string,
87
+ hideTitle: PropTypes.bool
88
+ };
89
+ BpkDrawer.defaultProps = {
90
+ renderTarget: null,
91
+ className: null,
92
+ contentClassName: null,
93
+ closeLabel: null,
94
+ closeText: null,
95
+ hideTitle: false
96
+ };
87
97
  export default BpkDrawer;
@@ -14,41 +14,37 @@
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */
18
-
19
- // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
17
+ */import PropTypes from 'prop-types';
20
18
  import Transition from 'react-transition-group/Transition';
21
19
  import { animations } from '@skyscanner/bpk-foundations-web/tokens/base.es6';
22
-
23
- // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
24
20
  import BpkCloseButton from "../../bpk-component-close-button";
25
- // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
26
21
  import { BpkButtonLink } from "../../bpk-component-link";
27
22
  import { cssModules } from "../../bpk-react-utils";
28
23
  import STYLES from "./BpkDrawerContent.module.css";
29
24
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
30
25
  const getClassName = cssModules(STYLES);
31
- const BpkDrawerContent = ({
32
- children,
33
- className = null,
34
- closeLabel = null,
35
- closeOnScrimClick = true,
36
- // Unused from withScrim scrim HOC
37
- closeText = null,
38
- contentClassName = null,
39
- dialogRef,
40
- hideTitle = false,
41
- id,
42
- isDrawerShown = true,
43
- isIpad = false,
44
- // Unused from withScrim scrim HOC
45
- isIphone = false,
46
- // Unused from withScrim scrim HOC
47
- onClose,
48
- onCloseAnimationComplete,
49
- title,
50
- ...rest
51
- }) => {
26
+ const BpkDrawerContent = props => {
27
+ const {
28
+ children,
29
+ className,
30
+ closeLabel,
31
+ closeOnScrimClick,
32
+ // Unused from withScrim scrim HOC
33
+ closeText,
34
+ contentClassName,
35
+ dialogRef,
36
+ hideTitle,
37
+ id,
38
+ isDrawerShown,
39
+ isIpad,
40
+ // Unused from withScrim scrim HOC
41
+ isIphone,
42
+ // Unused from withScrim scrim HOC
43
+ onClose,
44
+ onCloseAnimationComplete,
45
+ title,
46
+ ...rest
47
+ } = props;
52
48
  const drawerClassNames = [getClassName('bpk-drawer')];
53
49
  const headerClassNames = [getClassName('bpk-drawer__heading')];
54
50
  const contentClassNames = [getClassName('bpk-drawer__content')];
@@ -72,9 +68,12 @@ const BpkDrawerContent = ({
72
68
  exit: true,
73
69
  in: isDrawerShown,
74
70
  onExited: onCloseAnimationComplete,
75
- children: status => /*#__PURE__*/_jsxs("section", {
71
+ children: status =>
72
+ /*#__PURE__*/
73
+ // $FlowFixMe[cannot-spread-inexact] - inexact rest. See decisions/flowfixme.md
74
+ _jsxs("section", {
76
75
  id: id,
77
- tabIndex: -1,
76
+ tabIndex: "-1",
78
77
  role: "dialog",
79
78
  "aria-labelledby": headingId,
80
79
  className: [drawerClassNames.join(' '), getClassName(`bpk-drawer--${status}`)].join(' '),
@@ -103,4 +102,32 @@ const BpkDrawerContent = ({
103
102
  }, "dialog")
104
103
  });
105
104
  };
105
+ BpkDrawerContent.propTypes = {
106
+ children: PropTypes.node.isRequired,
107
+ dialogRef: PropTypes.func.isRequired,
108
+ onCloseAnimationComplete: PropTypes.func.isRequired,
109
+ onClose: PropTypes.func.isRequired,
110
+ id: PropTypes.string.isRequired,
111
+ title: PropTypes.string.isRequired,
112
+ className: PropTypes.string,
113
+ contentClassName: PropTypes.string,
114
+ closeLabel: PropTypes.string,
115
+ closeText: PropTypes.string,
116
+ isDrawerShown: PropTypes.bool,
117
+ hideTitle: PropTypes.bool,
118
+ closeOnScrimClick: PropTypes.bool,
119
+ isIphone: PropTypes.bool,
120
+ isIpad: PropTypes.bool
121
+ };
122
+ BpkDrawerContent.defaultProps = {
123
+ className: null,
124
+ contentClassName: null,
125
+ closeLabel: null,
126
+ closeText: null,
127
+ isDrawerShown: true,
128
+ hideTitle: false,
129
+ closeOnScrimClick: true,
130
+ isIphone: false,
131
+ isIpad: false
132
+ };
106
133
  export default BpkDrawerContent;
@@ -14,8 +14,5 @@
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */
18
-
19
- // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`.
20
- import { themeAttributes as linkAttributes } from "../../bpk-component-link";
17
+ */import { themeAttributes as linkAttributes } from "../../bpk-component-link";
21
18
  export default [...linkAttributes];
@@ -15,4 +15,4 @@
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */
18
- .bpk-floating-notification{position:absolute;right:0;bottom:2rem;left:0;display:flex;z-index:2;max-width:25rem;margin:auto;padding:1.5rem;align-items:center;transition:opacity 400ms ease-in-out,transform 400ms ease-in-out;border-radius:.75rem;background:#05203c;color:#fff;box-shadow:0px 12px 50px 0px rgba(37,32,31,.25),0px 4px 14px 0px rgba(37,32,31,.25)}@media(max-width: 32rem){.bpk-floating-notification{max-width:100%;padding:1rem}}.bpk-floating-notification--leave{transform:translateY(0);opacity:1}.bpk-floating-notification--leave-active{transform:translateY(2rem);opacity:0}.bpk-floating-notification--leave-done{opacity:0}.bpk-floating-notification--appear{transform:translateY(2rem);opacity:0}.bpk-floating-notification--appear-active{transform:translateY(0);opacity:1}.bpk-floating-notification__icon{margin-right:.5rem;margin-left:.25rem;flex-shrink:0;transform:translateY(0.25rem);text-align:bottom;fill:#fff}.bpk-floating-notification__text{margin-inline-end:.5rem}.bpk-floating-notification__cta{margin-inline-start:auto}
18
+ .bpk-floating-notification{position:fixed;right:0;bottom:2rem;left:0;display:flex;z-index:2;max-width:25rem;margin:auto;padding:1.5rem;align-items:center;transition:opacity 400ms ease-in-out,transform 400ms ease-in-out;border-radius:.75rem;background:#05203c;color:#fff;box-shadow:0px 12px 50px 0px rgba(37,32,31,.25),0px 4px 14px 0px rgba(37,32,31,.25)}@media(max-width: 32rem){.bpk-floating-notification{max-width:100%;padding:1rem}}.bpk-floating-notification--leave{transform:translateY(0);opacity:1}.bpk-floating-notification--leave-active{transform:translateY(2rem);opacity:0}.bpk-floating-notification--leave-done{opacity:0}.bpk-floating-notification--appear{transform:translateY(2rem);opacity:0}.bpk-floating-notification--appear-active{transform:translateY(0);opacity:1}.bpk-floating-notification__icon{margin-right:.5rem;margin-left:.25rem;flex-shrink:0;transform:translateY(0.25rem);text-align:bottom;fill:#fff}.bpk-floating-notification__text{margin-inline-end:.5rem}.bpk-floating-notification__cta{margin-inline-start:auto}
@@ -15,4 +15,4 @@
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */
18
- .bpk-image{opacity:1;position:relative;display:block;max-width:100%;transition:background-color 400ms ease-in-out;background-color:#e0e4e9;overflow:hidden;transition-delay:400ms}.bpk-image__img{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:100%;transition:opacity 400ms ease-in-out}.bpk-image__img--hidden{opacity:0}.bpk-image__spinner{position:absolute;top:50%;left:50%;display:block;align-items:center;transform:translate(-50%, -50%)}.bpk-image__spinner--hidden{transition:opacity 400ms ease-in-out;opacity:0}.bpk-image__spinner--shown{transition:opacity 400ms ease-in-out;opacity:1}.bpk-image--no-background{background-color:rgba(0,0,0,0)}.bpk-image--border-radius-sm{border-radius:.5rem}
18
+ .bpk-image{opacity:1;position:relative;display:block;max-width:100%;transition:background-color 400ms ease-in-out;background-color:#e0e4e9;overflow:hidden;transition-delay:400ms}.bpk-image__img{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:100%;transition:opacity 400ms ease-in-out}.bpk-image__img--hidden{opacity:0}.bpk-image__spinner{position:absolute;top:50%;left:50%;display:block;align-items:center;transform:translate(-50%, -50%)}.bpk-image__spinner--hidden{transition:opacity 400ms ease-in-out;opacity:0}.bpk-image__spinner--shown{transition:opacity 400ms ease-in-out;opacity:1}.bpk-image--no-background{background-color:rgba(0,0,0,0)}.bpk-image--border-radius-sm{border-radius:.75rem}
@@ -94,29 +94,25 @@ class BpkMobileScrollContainer extends Component {
94
94
  this.setScrollIndicatorClassName();
95
95
  }, 100);
96
96
  setScrollIndicatorClassName = () => {
97
- requestAnimationFrame(() => {
98
- const classNames = computeScrollIndicatorClassName(this.scrollerEl, this.props.leadingIndicatorClassName, this.props.trailingIndicatorClassName);
99
- if (!classNames) {
100
- return;
101
- }
102
- this.setState(() => ({
103
- scrollIndicatorClassName: classNames.join(' ')
104
- }));
105
- });
97
+ const classNames = computeScrollIndicatorClassName(this.scrollerEl, this.props.leadingIndicatorClassName, this.props.trailingIndicatorClassName);
98
+ if (!classNames) {
99
+ return;
100
+ }
101
+ this.setState(() => ({
102
+ scrollIndicatorClassName: classNames.join(' ')
103
+ }));
106
104
  };
107
105
  setScrollBarAwareHeight = () => {
108
106
  if (this.props.showScrollbar) {
109
107
  return;
110
108
  }
111
- requestAnimationFrame(() => {
112
- const computedHeight = computeScrollBarAwareHeight(this.scrollerEl, this.innerEl);
113
- if (!computedHeight) {
114
- return;
115
- }
116
- this.setState(() => ({
117
- computedHeight
118
- }));
119
- });
109
+ const computedHeight = computeScrollBarAwareHeight(this.scrollerEl, this.innerEl);
110
+ if (!computedHeight) {
111
+ return;
112
+ }
113
+ this.setState(() => ({
114
+ computedHeight
115
+ }));
120
116
  };
121
117
  render() {
122
118
  const classNames = [getClassName('bpk-mobile-scroll-container')];
@@ -7,7 +7,7 @@ export type Props = Partial<ModalDialogProps> & {
7
7
  isOpen: boolean;
8
8
  closeOnScrimClick?: boolean;
9
9
  closeOnEscPressed?: boolean;
10
- renderTarget?: (() => HTMLElement | null) | HTMLElement | null;
10
+ renderTarget?: null | HTMLElement | (() => null | HTMLElement);
11
11
  modalStyle?: ModalStyle;
12
12
  onClose?: (arg0?: TouchEvent | MouseEvent | KeyboardEvent, arg1?: {
13
13
  source: 'ESCAPE' | 'DOCUMENT_CLICK';
@@ -16,13 +16,11 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- import { useEffect } from 'react';
20
- import { FloatingPortal } from '@floating-ui/react';
21
- import { cssModules, isDeviceIphone } from "../../bpk-react-utils";
19
+ import { Portal, cssModules, isDeviceIphone } from "../../bpk-react-utils";
22
20
  import { withScrim } from "../../bpk-scrim-utils";
23
21
  import BpkModalInner, { MODAL_STYLING } from "./BpkModalInner";
24
22
  import STYLES from "./BpkModal.module.css";
25
- import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
23
+ import { jsx as _jsx } from "react/jsx-runtime";
26
24
  const getClassName = cssModules(STYLES);
27
25
  const ScrimBpkModalInner = withScrim(BpkModalInner);
28
26
  const BpkModal = ({
@@ -41,55 +39,42 @@ const BpkModal = ({
41
39
  modalStyle = MODAL_STYLING.default,
42
40
  onClose = () => null,
43
41
  padded = true,
44
- renderTarget = () => null,
42
+ renderTarget = null,
45
43
  showHeader = true,
46
44
  title = null,
47
45
  wide = false,
48
46
  ...rest
49
47
  }) => {
50
- useEffect(() => {
51
- const handleKeyDown = event => {
52
- if (event.key === 'Escape' && onClose) {
53
- onClose();
54
- }
55
- };
56
- if (isOpen) {
57
- window.addEventListener('keydown', handleKeyDown);
58
- }
59
- return () => {
60
- window.removeEventListener('keydown', handleKeyDown);
61
- };
62
- }, [isOpen, onClose]);
63
48
  const containerClass = [getClassName('bpk-modal__container')];
64
49
  if (fullScreen || isIphone) {
65
50
  containerClass.push(getClassName('bpk-modal__container--full-screen'));
66
51
  } else if (fullScreenOnMobile) {
67
52
  containerClass.push(getClassName('bpk-modal__container--full-screen-mobile'));
68
53
  }
69
- const renTarget = typeof renderTarget === 'function' ? renderTarget() : renderTarget;
70
- return /*#__PURE__*/_jsx(_Fragment, {
71
- children: isOpen && /*#__PURE__*/_jsx(FloatingPortal, {
72
- root: renTarget,
73
- children: /*#__PURE__*/_jsx(ScrimBpkModalInner, {
74
- onClose: onClose,
75
- fullScreenOnMobile: fullScreenOnMobile,
76
- fullScreen: fullScreen,
77
- closeOnScrimClick: closeOnScrimClick,
78
- containerClassName: containerClass.join(' '),
79
- isIphone: isIphone,
80
- title: title,
81
- className: className,
82
- contentClassName: contentClassName,
83
- closeLabel: closeLabel,
84
- closeText: closeText,
85
- wide: wide,
86
- showHeader: showHeader,
87
- padded: padded,
88
- accessoryView: accessoryView,
89
- dialogRef: dialogRef,
90
- modalStyle: modalStyle,
91
- ...rest
92
- })
54
+ return /*#__PURE__*/_jsx(Portal, {
55
+ isOpen: isOpen,
56
+ onClose: onClose,
57
+ renderTarget: renderTarget,
58
+ closeOnEscPressed: closeOnEscPressed,
59
+ children: /*#__PURE__*/_jsx(ScrimBpkModalInner, {
60
+ onClose: onClose,
61
+ fullScreenOnMobile: fullScreenOnMobile,
62
+ fullScreen: fullScreen,
63
+ closeOnScrimClick: closeOnScrimClick,
64
+ containerClassName: containerClass.join(' '),
65
+ isIphone: isIphone,
66
+ title: title,
67
+ className: className,
68
+ contentClassName: contentClassName,
69
+ closeLabel: closeLabel,
70
+ closeText: closeText,
71
+ wide: wide,
72
+ showHeader: showHeader,
73
+ padded: padded,
74
+ accessoryView: accessoryView,
75
+ dialogRef: dialogRef,
76
+ modalStyle: modalStyle,
77
+ ...rest
93
78
  })
94
79
  });
95
80
  };
@@ -23,5 +23,5 @@ type Props = CommonProps & {
23
23
  */
24
24
  compareValues: (arg0: any, arg1: any) => number;
25
25
  };
26
- declare const BpkConfigurableNudger: ({ buttonType, className, compareValues, decreaseButtonLabel, decrementValue, formatValue, id, increaseButtonLabel, incrementValue, inputClassName, max, min, onChange, value, ...rest }: Props) => JSX.Element;
26
+ declare const BpkConfigurableNudger: ({ buttonType, className, compareValues, decreaseButtonLabel, decrementValue, formatValue, id, increaseButtonLabel, incrementValue, inputClassName, max, min, name, onChange, value, ...rest }: Props) => JSX.Element;
27
27
  export default BpkConfigurableNudger;
@@ -16,11 +16,12 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
+ import { useRef } from 'react';
19
20
  import { BpkButtonV2, BUTTON_TYPES } from "../../bpk-component-button";
20
21
  import { withButtonAlignment } from "../../bpk-component-icon";
21
22
  import MinusIcon from "../../bpk-component-icon/sm/minus";
22
23
  import PlusIcon from "../../bpk-component-icon/sm/plus";
23
- import { cssModules } from "../../bpk-react-utils";
24
+ import { cssModules, setNativeValue } from "../../bpk-react-utils";
24
25
  import STYLES from "./BpkNudger.module.css";
25
26
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
27
  const getClassName = cssModules(STYLES);
@@ -39,6 +40,7 @@ const BpkConfigurableNudger = ({
39
40
  inputClassName = null,
40
41
  max,
41
42
  min,
43
+ name,
42
44
  onChange,
43
45
  value,
44
46
  ...rest
@@ -47,12 +49,19 @@ const BpkConfigurableNudger = ({
47
49
  const maxButtonDisabled = compareValues(value, max) >= 0;
48
50
  const minButtonDisabled = compareValues(value, min) <= 0;
49
51
  const inputStyles = getClassName('bpk-nudger__input', inputClassName && inputClassName, buttonType === 'secondaryOnDark' && 'bpk-nudger__input--secondary-on-dark');
52
+ const inputRef = useRef(null);
50
53
  return /*#__PURE__*/_jsxs("div", {
51
54
  className: classNames,
52
55
  children: [/*#__PURE__*/_jsx(BpkButtonV2, {
53
56
  type: BUTTON_TYPES[buttonType],
54
57
  iconOnly: true,
55
- onClick: () => onChange(decrementValue(value)),
58
+ onClick: () => {
59
+ const newValue = decrementValue(value);
60
+ onChange(newValue);
61
+ // We want to maintain native input events across our form components. Along with react updating
62
+ // the value attribute we can set it via native handlers and emit a "change" event.
63
+ inputRef.current && setNativeValue(inputRef.current, `${newValue}`);
64
+ },
56
65
  disabled: minButtonDisabled,
57
66
  title: decreaseButtonLabel,
58
67
  "aria-controls": id,
@@ -60,15 +69,23 @@ const BpkConfigurableNudger = ({
60
69
  }), /*#__PURE__*/_jsx("input", {
61
70
  type: "text",
62
71
  "aria-live": "polite",
63
- readOnly: true,
64
- value: formatValue(value),
72
+ defaultValue: formatValue(value),
65
73
  id: id,
74
+ ref: inputRef,
75
+ name: name || id,
76
+ onChange: event => onChange(parseInt(event?.target.value, 10)),
66
77
  className: inputStyles,
67
78
  ...rest
68
79
  }), /*#__PURE__*/_jsx(BpkButtonV2, {
69
80
  type: BUTTON_TYPES[buttonType],
70
81
  iconOnly: true,
71
- onClick: () => onChange(incrementValue(value)),
82
+ onClick: () => {
83
+ const newValue = incrementValue(value);
84
+ onChange(newValue);
85
+ // We want to maintain native input events across our form components. Along with react updating
86
+ // the value attribute we can set it via native handlers and emit a "change" event.
87
+ inputRef.current && setNativeValue(inputRef.current, `${newValue}`);
88
+ },
72
89
  disabled: maxButtonDisabled,
73
90
  title: increaseButtonLabel,
74
91
  "aria-controls": id,
@@ -9,6 +9,7 @@ export type CommonProps = {
9
9
  max: string | number;
10
10
  value: string | number;
11
11
  onChange: (arg0: any) => void | null;
12
+ name?: string | null;
12
13
  className?: string | null;
13
14
  /**
14
15
  * This is the label that will be read out when screen reader users tab to the increase button. Make sure you use a descriptive label.
@@ -6,9 +6,10 @@ import cssModules from './src/cssModules';
6
6
  import deprecated from './src/deprecated';
7
7
  import { isDeviceIphone, isDeviceIpad, isDeviceIos } from './src/deviceDetection';
8
8
  import isRTL from './src/isRTL';
9
+ import { setNativeValue } from './src/nativeEventHandler';
9
10
  import withDefaultProps from './src/withDefaultProps';
10
11
  import wrapDisplayName from './src/wrapDisplayName';
11
- export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper };
12
+ export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper, setNativeValue };
12
13
  declare const _default: {
13
14
  Portal: typeof Portal;
14
15
  TransitionInitialMount: ({ appearActiveClassName, appearClassName, children, transitionTimeout, }: {
@@ -38,5 +39,6 @@ declare const _default: {
38
39
  isDeviceIos: () => boolean;
39
40
  isRTL: () => boolean;
40
41
  BpkDialogWrapper: ({ children, closeOnEscPressed, closeOnScrimClick, dialogClassName, exiting, id, isOpen, onClose, timeout, transitionClassNames, ...ariaProps }: import("./src/BpkDialogWrapper/BpkDialogWrapper").Props) => JSX.Element | null;
42
+ setNativeValue: typeof setNativeValue;
41
43
  };
42
44
  export default _default;
@@ -24,9 +24,10 @@ import cssModules from "./src/cssModules";
24
24
  import deprecated from "./src/deprecated";
25
25
  import { isDeviceIphone, isDeviceIpad, isDeviceIos } from "./src/deviceDetection";
26
26
  import isRTL from "./src/isRTL";
27
+ import { setNativeValue } from "./src/nativeEventHandler";
27
28
  import withDefaultProps from "./src/withDefaultProps";
28
29
  import wrapDisplayName from "./src/wrapDisplayName";
29
- export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper };
30
+ export { Portal, TransitionInitialMount, cssModules, deprecated, withDefaultProps, wrapDisplayName, isDeviceIphone, isDeviceIpad, isDeviceIos, isRTL, BpkDialogWrapper, setNativeValue };
30
31
  export default {
31
32
  Portal,
32
33
  TransitionInitialMount,
@@ -38,5 +39,6 @@ export default {
38
39
  isDeviceIpad,
39
40
  isDeviceIos,
40
41
  isRTL,
41
- BpkDialogWrapper
42
+ BpkDialogWrapper,
43
+ setNativeValue
42
44
  };
@@ -20,10 +20,6 @@ type Props = {
20
20
  type State = {
21
21
  isVisible: boolean;
22
22
  };
23
- /**
24
- * @deprecated the portal is deprecated and will be removed in a future release. Please read [](https://github.com/Skyscanner/backpack/blob/main/packages/bpk-react-utils/docs/portal-migration.md) for further information
25
- * @returns{Component} used to render children into a new component tree. For use with Modals, popovers, tooltips.
26
- */
27
23
  declare class Portal extends Component<Props, State> {
28
24
  portalElement: null | HTMLDivElement;
29
25
  shouldClose: boolean;
@@ -25,10 +25,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
25
25
  const KEYCODES = {
26
26
  ESCAPE: 'Escape'
27
27
  };
28
- /**
29
- * @deprecated the portal is deprecated and will be removed in a future release. Please read [](https://github.com/Skyscanner/backpack/blob/main/packages/bpk-react-utils/docs/portal-migration.md) for further information
30
- * @returns{Component} used to render children into a new component tree. For use with Modals, popovers, tooltips.
31
- */
32
28
  class Portal extends Component {
33
29
  static defaultProps = {
34
30
  beforeClose: null,
@@ -0,0 +1,2 @@
1
+ declare function setNativeValue(element: HTMLInputElement, value: string): void;
2
+ export { setNativeValue };
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Backpack - Skyscanner's Design System
3
+ *
4
+ * Copyright 2016 Skyscanner Ltd
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ // There are cases where input element's values are modifed indirectly.
20
+ // This causes the elements to not emit events that would have been if they had been modified by the user directly.
21
+ // In order to maintain the expected native behaviour of the input element, It's possible to call this function during an
22
+ // "onEvent" handler and update the element value, together with updating react it's own state which isn't mapped to the elements value prop.
23
+ function setNativeValue(element, value) {
24
+ const inputProto = window.HTMLInputElement.prototype;
25
+ const descriptor = Object.getOwnPropertyDescriptor(inputProto, 'value');
26
+ const setValue = descriptor.set;
27
+ if (setValue) {
28
+ const event = new Event('change', {
29
+ bubbles: true
30
+ });
31
+ setValue.call(element, value);
32
+ element.dispatchEvent(event);
33
+ }
34
+ }
35
+
36
+ // eslint-disable-next-line import/prefer-default-export
37
+ export { setNativeValue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyscanner/backpack-web",
3
- "version": "34.13.1",
3
+ "version": "34.15.0",
4
4
  "description": "Backpack Design System web library",
5
5
  "repository": {
6
6
  "type": "git",
@@ -25,7 +25,7 @@
25
25
  "@floating-ui/react": "^0.26.12",
26
26
  "@popperjs/core": "^2.11.8",
27
27
  "@radix-ui/react-slider": "^1.1.2",
28
- "@react-google-maps/api": "^2.12.0",
28
+ "@react-google-maps/api": "^2.19.3",
29
29
  "@skyscanner/bpk-foundations-web": "^18.1.0",
30
30
  "@skyscanner/bpk-svgs": "^19.3.0",
31
31
  "a11y-focus-scope": "^1.1.3",
@@ -1,12 +0,0 @@
1
- import { renderHook } from '@testing-library/react-hooks';
2
- import { useScrollToInitialImage } from "./utils";
3
- describe('useScrollToInitialImage', () => {
4
- it('should scroll to initial image on mount', () => {
5
- const imagesRef = {
6
- current: [document.createElement('div'), document.createElement('div')]
7
- };
8
- const initialImageIndex = 0;
9
- renderHook(() => useScrollToInitialImage(initialImageIndex, imagesRef));
10
- expect(imagesRef.current[initialImageIndex].scrollIntoView).toHaveBeenCalledTimes(1);
11
- });
12
- });
@@ -1,43 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import { Component } from 'react';
3
- export type Props = {
4
- id: string;
5
- children: ReactNode | string;
6
- isOpen: boolean;
7
- onClose: () => void;
8
- title: string;
9
- /**
10
- * **Note:** In order to "hide" your application from screen readers whilst the drawer is open you need to let it know what
11
- * the root element for your application is by returning it's DOM node via the function passed to the
12
- * `getApplicationElement` prop (see the example above). The `pagewrap` element id is a convention we use internally at Skyscanner. In most cases it should "just work".
13
- */
14
- getApplicationElement: () => HTMLElement | null;
15
- renderTarget?: () => HTMLElement | HTMLElement | null;
16
- dialogRef?: (ref: HTMLElement | null | undefined) => void;
17
- className?: string | null;
18
- contentClassName?: string | null;
19
- closeLabel?: string | null;
20
- closeText?: string | null;
21
- hideTitle?: boolean;
22
- [rest: string]: any;
23
- };
24
- type State = {
25
- isDrawerShown: boolean;
26
- };
27
- declare class BpkDrawer extends Component<Props, State> {
28
- static defaultProps: {
29
- renderTarget: () => null;
30
- className: null;
31
- contentClassName: null;
32
- closeLabel: null;
33
- closeText: null;
34
- hideTitle: boolean;
35
- };
36
- constructor(props: Props);
37
- UNSAFE_componentWillReceiveProps(nextProps: Props): void;
38
- onCloseAnimationComplete: () => void;
39
- handleKeyDown: (event: KeyboardEvent) => void;
40
- hide: () => void;
41
- render(): JSX.Element;
42
- }
43
- export default BpkDrawer;
@@ -1,21 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- export type Props = {
3
- children: ReactNode;
4
- dialogRef: () => void;
5
- onCloseAnimationComplete: () => void;
6
- onClose: () => void;
7
- id: string;
8
- title: string;
9
- className?: string | null;
10
- contentClassName?: string | null;
11
- closeLabel?: string | null;
12
- closeText?: string | null;
13
- isDrawerShown?: boolean;
14
- hideTitle?: boolean;
15
- closeOnScrimClick?: boolean;
16
- isIphone?: boolean;
17
- isIpad?: boolean;
18
- [rest: string]: any;
19
- };
20
- declare const BpkDrawerContent: ({ children, className, closeLabel, closeOnScrimClick, closeText, contentClassName, dialogRef, hideTitle, id, isDrawerShown, isIpad, isIphone, onClose, onCloseAnimationComplete, title, ...rest }: Props) => JSX.Element;
21
- export default BpkDrawerContent;
@@ -1,2 +0,0 @@
1
- declare const _default: any[];
2
- export default _default;