@skyscanner/backpack-web 34.13.0 → 34.14.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.
@@ -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,
@@ -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')];
@@ -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.
@@ -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-segmented-control-group{display:flex;flex-wrap:nowrap;overflow:hidden;border-radius:.5rem}.bpk-segmented-control-group-shadow{box-shadow:0px 1px 3px 0px rgba(37,32,31,.3)}.bpk-segmented-control{min-height:2rem;padding:.5rem 1rem;flex:1;border:none;text-overflow:ellipsis;cursor:pointer;overflow:hidden;font-size:.875rem;line-height:1.25rem;font-weight:700}.bpk-segmented-control--canvas-default{background-color:#eff3f8;color:#161616}.bpk-segmented-control--canvas-default-selected{background-color:#05203c;color:#fff}.bpk-segmented-control--canvas-contrast{background-color:#fff;color:#161616}.bpk-segmented-control--canvas-contrast-selected{background-color:#05203c;color:#fff}.bpk-segmented-control--surface-default{background-color:#eff3f8;color:#161616}.bpk-segmented-control--surface-default-selected{background-color:#05203c;color:#fff}.bpk-segmented-control--surface-contrast{background-color:rgba(255,255,255,.1);color:#fff}.bpk-segmented-control--surface-contrast-selected{background-color:#024daf;color:#fff}.bpk-segmented-control:not(:first-of-type,[class*=selected]){border-inline-start:.0625rem solid #c1c7cf}.bpk-segmented-control--surface-contrast:not(:first-of-type,[class*=selected]){border-inline-start:.0625rem solid rgba(255,255,255,.5)}.bpk-segmented-control[class*=rightOfOption]{border-inline-start:none}.bpk-segmented-control:first-child{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.bpk-segmented-control:last-child{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.bpk-segmented-control:focus{border:.125rem solid #0062e3;outline:none}
18
+ .bpk-segmented-control-group{display:flex;flex-wrap:nowrap;overflow:hidden;border-radius:.5rem}.bpk-segmented-control-group-shadow{box-shadow:0px 1px 3px 0px rgba(37,32,31,.3)}.bpk-segmented-control{min-height:2rem;padding:.5rem 1rem;flex:1;border:none;text-overflow:ellipsis;cursor:pointer;overflow:hidden;font-size:.875rem;line-height:1.25rem;font-weight:700}.bpk-segmented-control--canvas-default{background-color:#eff3f8;color:#161616}.bpk-segmented-control--canvas-default-selected{background-color:#05203c;color:#fff}.bpk-segmented-control--canvas-contrast{background-color:#fff;color:#161616}.bpk-segmented-control--canvas-contrast-selected{background-color:#05203c;color:#fff}.bpk-segmented-control--surface-default{background-color:#eff3f8;color:#161616}.bpk-segmented-control--surface-default-selected{background-color:#05203c;color:#fff}.bpk-segmented-control--surface-contrast{background-color:rgba(255,255,255,.1);color:#fff}.bpk-segmented-control--surface-contrast-selected{background-color:#024daf;color:#fff}.bpk-segmented-control:not(:first-of-type,[class*=selected]){border-inline-start:.0625rem solid #c1c7cf}.bpk-segmented-control--surface-contrast:not(:first-of-type,[class*=selected]){border-inline-start:.0625rem solid rgba(255,255,255,.5)}.bpk-segmented-control[class*=rightOfOption]{border-inline-start:none}.bpk-segmented-control:first-child{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}html[dir=rtl] .bpk-segmented-control:first-child{border-top-left-radius:0;border-top-right-radius:.5rem;border-bottom-left-radius:0;border-bottom-right-radius:.5rem}.bpk-segmented-control:last-child{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}html[dir=rtl] .bpk-segmented-control:last-child{border-top-left-radius:.5rem;border-top-right-radius:0;border-bottom-left-radius:.5rem;border-bottom-right-radius:0}.bpk-segmented-control:focus{border:.125rem solid #0062e3;outline:none}
@@ -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
  };
@@ -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.0",
3
+ "version": "34.14.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
- });