@simplybusiness/mobius 6.1.1 → 6.2.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.
package/dist/esm/index.js CHANGED
@@ -2482,7 +2482,8 @@ import { createContext } from "react";
2482
2482
  var DrawerContext = createContext({
2483
2483
  onClose: () => {
2484
2484
  },
2485
- closeLabel: void 0
2485
+ closeLabel: void 0,
2486
+ headerId: void 0
2486
2487
  });
2487
2488
 
2488
2489
  // src/components/Drawer/Drawer.tsx
@@ -2511,7 +2512,8 @@ var Drawer = forwardRef19((props, ref) => {
2511
2512
  CSSVariable: TRANSITION_CSS_VARIABLE
2512
2513
  }
2513
2514
  });
2514
- const hiddenId = `screen-reader-announce-${useId7()}`;
2515
+ const hiddenId = `dialog-screen-reader-announce-${useId7()}`;
2516
+ const headerId = `dialog-header-${useId7()}`;
2515
2517
  const dialogClasses = classNames24(
2516
2518
  "mobius",
2517
2519
  "mobius-drawer",
@@ -2527,9 +2529,10 @@ var Drawer = forwardRef19((props, ref) => {
2527
2529
  const contextValue = useMemo(
2528
2530
  () => ({
2529
2531
  onClose: close,
2530
- closeLabel
2532
+ closeLabel,
2533
+ headerId
2531
2534
  }),
2532
- [close, closeLabel]
2535
+ [close, closeLabel, headerId]
2533
2536
  );
2534
2537
  return /* @__PURE__ */ jsxs15(
2535
2538
  "dialog",
@@ -2539,6 +2542,7 @@ var Drawer = forwardRef19((props, ref) => {
2539
2542
  onCancel: close,
2540
2543
  className: dialogClasses,
2541
2544
  "aria-describedby": hiddenId,
2545
+ "aria-labelledby": headerId,
2542
2546
  children: [
2543
2547
  /* @__PURE__ */ jsx28(VisuallyHidden, { children: /* @__PURE__ */ jsx28("div", { id: hiddenId, children: announce }) }),
2544
2548
  /* @__PURE__ */ jsx28(DrawerContext.Provider, { value: contextValue, children })
@@ -2551,8 +2555,8 @@ Drawer.displayName = "Drawer";
2551
2555
  // src/components/Drawer/useDrawer.ts
2552
2556
  import { useContext } from "react";
2553
2557
  var useDrawer = () => {
2554
- const { onClose, closeLabel } = useContext(DrawerContext);
2555
- return { onClose, closeLabel };
2558
+ const { onClose, closeLabel, headerId } = useContext(DrawerContext);
2559
+ return { onClose, closeLabel, headerId };
2556
2560
  };
2557
2561
 
2558
2562
  // src/components/Drawer/Header.tsx
@@ -2561,9 +2565,9 @@ import { cross } from "@simplybusiness/icons";
2561
2565
  import { jsx as jsx29, jsxs as jsxs16 } from "react/jsx-runtime";
2562
2566
  var Header = forwardRef20(
2563
2567
  ({ children, ...otherProps }, ref) => {
2564
- const { onClose, closeLabel } = useDrawer();
2568
+ const { onClose, closeLabel, headerId } = useDrawer();
2565
2569
  return /* @__PURE__ */ jsxs16("header", { ref, ...otherProps, className: "mobius-drawer__header", children: [
2566
- children,
2570
+ /* @__PURE__ */ jsx29("h2", { id: headerId, children }),
2567
2571
  /* @__PURE__ */ jsxs16(
2568
2572
  Button,
2569
2573
  {
@@ -3191,6 +3195,7 @@ Modal2.displayName = "Modal";
3191
3195
 
3192
3196
  // src/components/NumberField/NumberField.tsx
3193
3197
  import classNames38 from "classnames/dedupe";
3198
+ import { useCallback as useCallback5, useEffect as useEffect21, useRef as useRef13 } from "react";
3194
3199
  import { forwardRef as forwardRef35 } from "react";
3195
3200
  import { jsx as jsx45 } from "react/jsx-runtime";
3196
3201
  var NumberField = forwardRef35((props, ref) => {
@@ -3203,6 +3208,14 @@ var NumberField = forwardRef35((props, ref) => {
3203
3208
  className,
3204
3209
  ...otherProps
3205
3210
  } = props;
3211
+ const focusedInputRef = useRef13(null);
3212
+ const wheelHandler = useCallback5((ev) => ev.preventDefault(), []);
3213
+ useEffect21(() => {
3214
+ return () => {
3215
+ focusedInputRef.current?.removeEventListener("wheel", wheelHandler);
3216
+ focusedInputRef.current = null;
3217
+ };
3218
+ }, [wheelHandler]);
3206
3219
  const containerClasses = classNames38("mobius-number-field", className, {
3207
3220
  "--hide-spin-buttons": hideSpinButtons
3208
3221
  });
@@ -3215,10 +3228,33 @@ var NumberField = forwardRef35((props, ref) => {
3215
3228
  event.preventDefault();
3216
3229
  }
3217
3230
  };
3231
+ const {
3232
+ onFocus: customOnFocus,
3233
+ onBlur: customOnBlur,
3234
+ ...rest
3235
+ } = otherProps;
3236
+ const forwardedProps = {
3237
+ ...rest,
3238
+ onFocus: (e) => {
3239
+ const el = e.currentTarget;
3240
+ focusedInputRef.current?.removeEventListener("wheel", wheelHandler);
3241
+ focusedInputRef.current = el;
3242
+ el.addEventListener("wheel", wheelHandler, { passive: false });
3243
+ customOnFocus?.(e);
3244
+ },
3245
+ onBlur: (e) => {
3246
+ const el = e.currentTarget;
3247
+ el.removeEventListener("wheel", wheelHandler);
3248
+ if (focusedInputRef.current === el) {
3249
+ focusedInputRef.current = null;
3250
+ }
3251
+ customOnBlur?.(e);
3252
+ }
3253
+ };
3218
3254
  return /* @__PURE__ */ jsx45(
3219
3255
  TextField,
3220
3256
  {
3221
- ...otherProps,
3257
+ ...forwardedProps,
3222
3258
  className: containerClasses,
3223
3259
  onBeforeInput: handleBeforeInput,
3224
3260
  type: "number",
@@ -3247,7 +3283,7 @@ Option2.displayName = "Option";
3247
3283
 
3248
3284
  // src/components/PasswordField/PasswordField.tsx
3249
3285
  import classNames39 from "classnames/dedupe";
3250
- import { forwardRef as forwardRef37, useState as useState18, useRef as useRef13 } from "react";
3286
+ import { forwardRef as forwardRef37, useState as useState18, useRef as useRef14 } from "react";
3251
3287
 
3252
3288
  // src/components/PasswordField/ShowHideButton.tsx
3253
3289
  import { jsx as jsx47 } from "react/jsx-runtime";
@@ -3276,7 +3312,7 @@ var PasswordField = forwardRef37(
3276
3312
  const [show, setShow] = useState18(false);
3277
3313
  const type = show ? "text" : "password";
3278
3314
  const classes = classNames39("mobius-password-field", className);
3279
- const localRef = useRef13(null);
3315
+ const localRef = useRef14(null);
3280
3316
  const handleShowHideButtonClick = () => {
3281
3317
  setShow((oldShow) => !oldShow);
3282
3318
  localRef.current?.focus();
@@ -3309,12 +3345,12 @@ import {
3309
3345
  } from "@floating-ui/react";
3310
3346
  import { cross as cross3 } from "@simplybusiness/icons";
3311
3347
  import classNames40 from "classnames";
3312
- import { cloneElement as cloneElement9, useRef as useRef14, useState as useState19 } from "react";
3348
+ import { cloneElement as cloneElement9, useRef as useRef15, useState as useState19 } from "react";
3313
3349
  import { Fragment as Fragment3, jsx as jsx49, jsxs as jsxs22 } from "react/jsx-runtime";
3314
3350
  var OFFSET_FROM_CONTENT_DEFAULT = 10;
3315
3351
  var Popover = (props) => {
3316
3352
  const { trigger, children, onOpen, onClose, className } = props;
3317
- const arrowRef = useRef14(null);
3353
+ const arrowRef = useRef15(null);
3318
3354
  const [isOpen, setIsOpen] = useState19(false);
3319
3355
  const { refs, floatingStyles, context } = useFloating({
3320
3356
  open: isOpen,
@@ -3618,9 +3654,8 @@ import {
3618
3654
  cloneElement as cloneElement10,
3619
3655
  forwardRef as forwardRef40,
3620
3656
  isValidElement as isValidElement6,
3621
- useEffect as useEffect21,
3657
+ useEffect as useEffect22,
3622
3658
  useId as useId10,
3623
- useRef as useRef15,
3624
3659
  useState as useState20
3625
3660
  } from "react";
3626
3661
  import { jsx as jsx52, jsxs as jsxs25 } from "react/jsx-runtime";
@@ -3652,15 +3687,11 @@ var RadioGroup = forwardRef40((props, ref) => {
3652
3687
  } = props;
3653
3688
  const defaultSelected = getDefaultVal(children, value || defaultValue);
3654
3689
  const [selected, setSelected] = useState20(defaultSelected);
3655
- const selectedRef = useRef15(null);
3656
- useEffect21(() => {
3690
+ useEffect22(() => {
3657
3691
  if (value !== void 0) {
3658
3692
  setSelected(value);
3659
3693
  }
3660
3694
  }, [value]);
3661
- useEffect21(() => {
3662
- selectedRef.current?.focus();
3663
- });
3664
3695
  const validationClasses = useValidationClasses({
3665
3696
  validationState,
3666
3697
  isInvalid
@@ -3711,7 +3742,6 @@ var RadioGroup = forwardRef40((props, ref) => {
3711
3742
  label && /* @__PURE__ */ jsx52(Label, { htmlFor: name, id: labelId, className: labelClasses, children: label }),
3712
3743
  /* @__PURE__ */ jsx52("div", { className: radioWrapperClasses, children: Children8.map(children, (child) => {
3713
3744
  if (isValidElement6(child)) {
3714
- const props2 = child.props;
3715
3745
  return cloneElement10(
3716
3746
  child,
3717
3747
  {
@@ -3723,8 +3753,7 @@ var RadioGroup = forwardRef40((props, ref) => {
3723
3753
  setSelected,
3724
3754
  isRequired,
3725
3755
  "aria-describedby": describedBy,
3726
- onChange,
3727
- ref: props2.value === selected ? selectedRef : void 0
3756
+ onChange
3728
3757
  }
3729
3758
  );
3730
3759
  }
@@ -3860,7 +3889,7 @@ import classNames47 from "classnames/dedupe";
3860
3889
  import { useRef as useRef16, useState as useState21 } from "react";
3861
3890
 
3862
3891
  // src/components/Slider/helpers.ts
3863
- import { useCallback as useCallback5, useMemo as useMemo4 } from "react";
3892
+ import { useCallback as useCallback6, useMemo as useMemo4 } from "react";
3864
3893
  function numberFormatter(value, formatOptions, locale = navigator.languages?.[0] || "en-GB") {
3865
3894
  if (!formatOptions) {
3866
3895
  return value?.toString() || "";
@@ -3984,7 +4013,7 @@ SVG.displayName = "SVG";
3984
4013
  import {
3985
4014
  forwardRef as forwardRef43,
3986
4015
  useState as useState22,
3987
- useEffect as useEffect22
4016
+ useEffect as useEffect23
3988
4017
  } from "react";
3989
4018
  import classNames49 from "classnames/dedupe";
3990
4019
  import { jsx as jsx58, jsxs as jsxs29 } from "react/jsx-runtime";
@@ -3997,7 +4026,7 @@ var Switch = forwardRef43((props, ref) => {
3997
4026
  ...otherProps
3998
4027
  } = props;
3999
4028
  const [enabled, setEnabled] = useState22(checked);
4000
- useEffect22(() => {
4029
+ useEffect23(() => {
4001
4030
  setEnabled(checked);
4002
4031
  }, [checked]);
4003
4032
  const classes = classNames49(
@@ -4233,7 +4262,7 @@ Title.displayName = "Title";
4233
4262
 
4234
4263
  // src/components/Trust/Trust.tsx
4235
4264
  import classNames60 from "classnames/dedupe";
4236
- import { forwardRef as forwardRef54, useEffect as useEffect23, useRef as useRef17, useState as useState23 } from "react";
4265
+ import { forwardRef as forwardRef54, useEffect as useEffect24, useRef as useRef17, useState as useState23 } from "react";
4237
4266
 
4238
4267
  // src/components/Trust/constants.ts
4239
4268
  var REQUIRED_TRUSTPILOT_CLASS_NAME = "trustpilot-widget";
@@ -4330,13 +4359,13 @@ var Trust = forwardRef54((props, ref) => {
4330
4359
  },
4331
4360
  className
4332
4361
  );
4333
- useEffect23(() => {
4362
+ useEffect24(() => {
4334
4363
  const hasTrustpilotLoaded = trustRef.current && window?.Trustpilot && window?.Trustpilot.loadFromElement;
4335
4364
  if (isMounted && hasTrustpilotLoaded) {
4336
4365
  window.Trustpilot.loadFromElement(trustRef.current, true);
4337
4366
  }
4338
4367
  }, [isMounted]);
4339
- useEffect23(() => {
4368
+ useEffect24(() => {
4340
4369
  setIsMounted(true);
4341
4370
  }, []);
4342
4371
  if (!isMounted) return /* @__PURE__ */ jsx69("div", { style: styles });
@@ -4362,7 +4391,7 @@ var Trust = forwardRef54((props, ref) => {
4362
4391
  });
4363
4392
 
4364
4393
  // src/components/ExpandableText/ExpandableText.tsx
4365
- import { useState as useState24, useEffect as useEffect24, useRef as useRef18, forwardRef as forwardRef55, useId as useId12 } from "react";
4394
+ import { useState as useState24, useEffect as useEffect25, useRef as useRef18, forwardRef as forwardRef55, useId as useId12 } from "react";
4366
4395
  import classNames61 from "classnames/dedupe";
4367
4396
  import { jsx as jsx70, jsxs as jsxs32 } from "react/jsx-runtime";
4368
4397
  var ExpandableText = forwardRef55((props, ref) => {
@@ -4385,7 +4414,7 @@ var ExpandableText = forwardRef55((props, ref) => {
4385
4414
  const baseId = useId12();
4386
4415
  const expandButtonId = `expandable-text-expand-${baseId}`;
4387
4416
  const shouldCollapse = breakpoint ? down(breakpoint) : true;
4388
- useEffect24(() => {
4417
+ useEffect25(() => {
4389
4418
  if (!shouldCollapse || !textRef.current) {
4390
4419
  setIsCollapsed(false);
4391
4420
  return;
@@ -4446,6 +4475,67 @@ var ExpandableText = forwardRef55((props, ref) => {
4446
4475
  );
4447
4476
  });
4448
4477
  ExpandableText.displayName = "ExpandableText";
4478
+
4479
+ // src/components/MaskedField/MaskedField.tsx
4480
+ import { forwardRef as forwardRef56 } from "react";
4481
+ import { useIMask } from "react-imask";
4482
+ import { jsx as jsx71 } from "react/jsx-runtime";
4483
+ var MaskedField = forwardRef56((props, ref) => {
4484
+ const {
4485
+ mask,
4486
+ value,
4487
+ defaultValue,
4488
+ useMaskedValue = false,
4489
+ onChange,
4490
+ "aria-describedby": ariaDescribedBy,
4491
+ "aria-label": ariaLabel,
4492
+ ...textFieldProps
4493
+ } = props;
4494
+ const { ref: maskRef, value: maskedValue } = useIMask(mask, {
4495
+ defaultValue,
4496
+ onAccept: (value2, maskRef2) => {
4497
+ if (onChange) {
4498
+ const valueToEmit = useMaskedValue ? value2 : maskRef2.unmaskedValue;
4499
+ const syntheticEvent = {
4500
+ target: {
4501
+ value: valueToEmit,
4502
+ name: textFieldProps.name
4503
+ },
4504
+ currentTarget: {
4505
+ value: valueToEmit,
4506
+ name: textFieldProps.name
4507
+ }
4508
+ };
4509
+ onChange(syntheticEvent);
4510
+ }
4511
+ }
4512
+ });
4513
+ const inputRef = (node) => {
4514
+ if (maskRef) {
4515
+ maskRef.current = node;
4516
+ }
4517
+ if (ref) {
4518
+ if (typeof ref === "function") {
4519
+ ref(node);
4520
+ } else {
4521
+ ref.current = node;
4522
+ }
4523
+ }
4524
+ };
4525
+ return /* @__PURE__ */ jsx71(
4526
+ TextField,
4527
+ {
4528
+ ...textFieldProps,
4529
+ ref: inputRef,
4530
+ value: maskedValue,
4531
+ onChange: () => {
4532
+ },
4533
+ "aria-describedby": ariaDescribedBy,
4534
+ "aria-label": ariaLabel
4535
+ }
4536
+ );
4537
+ });
4538
+ MaskedField.displayName = "MaskedField";
4449
4539
  export {
4450
4540
  Accordion,
4451
4541
  AddressLookup,
@@ -4479,6 +4569,7 @@ export {
4479
4569
  Logo,
4480
4570
  LoqateAddressLookupService,
4481
4571
  MIN_MAX_ERROR,
4572
+ MaskedField,
4482
4573
  Modal2 as Modal,
4483
4574
  NumberField,
4484
4575
  Option2 as Option,
@@ -18,4 +18,5 @@ export interface DrawerProps {
18
18
  export type DrawerContextProps = {
19
19
  onClose: (event?: SyntheticEvent<HTMLElement, Event>) => void;
20
20
  closeLabel: string | undefined;
21
+ headerId?: string;
21
22
  };
@@ -1,4 +1,5 @@
1
1
  export declare const useDrawer: () => {
2
2
  onClose: (event?: import("react").SyntheticEvent<HTMLElement, Event>) => void;
3
3
  closeLabel: string | undefined;
4
+ headerId: string | undefined;
4
5
  };
@@ -0,0 +1,14 @@
1
+ import type { Ref, RefAttributes } from "react";
2
+ import type { ReactMaskOpts } from "react-imask";
3
+ import type { DOMProps } from "../../types/dom";
4
+ import type { ForwardedRefComponent } from "../../types/components";
5
+ import { type TextFieldProps } from "../TextField";
6
+ export type MaskedFieldElementType = HTMLInputElement;
7
+ export interface MaskedFieldProps extends Omit<TextFieldProps, "type">, DOMProps, RefAttributes<MaskedFieldElementType> {
8
+ /** The mask configuration to apply */
9
+ mask: ReactMaskOpts;
10
+ /** Whether to return the masked (formatted) value in onChange. Defaults to false (unmasked value) */
11
+ useMaskedValue?: boolean;
12
+ }
13
+ export type MaskedFieldRef = Ref<MaskedFieldElementType>;
14
+ export declare const MaskedField: ForwardedRefComponent<MaskedFieldProps, MaskedFieldElementType>;
@@ -0,0 +1,7 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { MaskedField } from ".";
3
+ type StoryType = StoryObj<typeof MaskedField>;
4
+ declare const meta: Meta<typeof MaskedField>;
5
+ export declare const PhoneNumber: StoryType;
6
+ export declare const CommaSeparatedNumber: StoryType;
7
+ export default meta;
@@ -0,0 +1 @@
1
+ export * from "./MaskedField";
@@ -1,5 +1,5 @@
1
1
  import { type Ref, type RefAttributes } from "react";
2
- import type { ForwardedRefComponent } from "../../types/components";
2
+ import type { ForwardedRefComponent } from "../../types";
3
3
  import { type TextFieldElementType, type TextFieldProps } from "../TextField";
4
4
  export type NumberFieldElementType = TextFieldElementType;
5
5
  export interface NumberFieldProps extends Omit<TextFieldProps, "type" | "defaultValue" | "inputElementType">, RefAttributes<NumberFieldElementType> {
@@ -45,3 +45,4 @@ export * from "./Title";
45
45
  export * from "./Trust";
46
46
  export * from "./ExpandableText";
47
47
  export * from "./VisuallyHidden";
48
+ export * from "./MaskedField";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplybusiness/mobius",
3
3
  "license": "UNLICENSED",
4
- "version": "6.1.1",
4
+ "version": "6.2.0",
5
5
  "description": "Core library of Mobius react components",
6
6
  "repository": {
7
7
  "type": "git",
@@ -83,11 +83,12 @@
83
83
  },
84
84
  "dependencies": {
85
85
  "@floating-ui/react": "^0.27.12",
86
- "@simplybusiness/icons": "^4.32.0",
86
+ "@simplybusiness/icons": "^4.33.0",
87
87
  "classnames": "^2.5.1",
88
88
  "dialog-polyfill": "^0.5.6",
89
89
  "lodash.debounce": "^4.0.8",
90
- "react-accessible-dropdown-menu-hook": "^4.0.0"
90
+ "react-accessible-dropdown-menu-hook": "^4.0.0",
91
+ "react-imask": "^7.6.1"
91
92
  },
92
93
  "lint-staged": {
93
94
  "*.{js,ts,jsx,tsx}": "eslint --fix"
@@ -37,7 +37,8 @@ const Drawer = forwardRef((props: DrawerProps, ref: DialogRef) => {
37
37
  CSSVariable: TRANSITION_CSS_VARIABLE,
38
38
  },
39
39
  });
40
- const hiddenId = `screen-reader-announce-${useId()}`;
40
+ const hiddenId = `dialog-screen-reader-announce-${useId()}`;
41
+ const headerId = `dialog-header-${useId()}`;
41
42
 
42
43
  const dialogClasses = classNames(
43
44
  "mobius",
@@ -57,8 +58,9 @@ const Drawer = forwardRef((props: DrawerProps, ref: DialogRef) => {
57
58
  () => ({
58
59
  onClose: close,
59
60
  closeLabel,
61
+ headerId,
60
62
  }),
61
- [close, closeLabel],
63
+ [close, closeLabel, headerId],
62
64
  );
63
65
 
64
66
  return (
@@ -68,6 +70,7 @@ const Drawer = forwardRef((props: DrawerProps, ref: DialogRef) => {
68
70
  onCancel={close}
69
71
  className={dialogClasses}
70
72
  aria-describedby={hiddenId}
73
+ aria-labelledby={headerId}
71
74
  >
72
75
  <VisuallyHidden>
73
76
  <div id={hiddenId}>{announce}</div>
@@ -4,4 +4,5 @@ import type { DrawerContextProps } from "./types";
4
4
  export const DrawerContext = createContext<DrawerContextProps>({
5
5
  onClose: () => {},
6
6
  closeLabel: undefined,
7
+ headerId: undefined,
7
8
  });
@@ -17,11 +17,11 @@ export interface HeaderProps
17
17
 
18
18
  const Header = forwardRef(
19
19
  ({ children, ...otherProps }: HeaderProps, ref: HeaderRef) => {
20
- const { onClose, closeLabel } = useDrawer();
20
+ const { onClose, closeLabel, headerId } = useDrawer();
21
21
 
22
22
  return (
23
23
  <header ref={ref} {...otherProps} className="mobius-drawer__header">
24
- {children}
24
+ <h2 id={headerId}>{children}</h2>
25
25
  <Button
26
26
  aria-label="Close"
27
27
  variant="basic"
@@ -20,4 +20,5 @@ export interface DrawerProps {
20
20
  export type DrawerContextProps = {
21
21
  onClose: (event?: SyntheticEvent<HTMLElement, Event>) => void;
22
22
  closeLabel: string | undefined;
23
+ headerId?: string;
23
24
  };
@@ -2,7 +2,7 @@ import { useContext } from "react";
2
2
  import { DrawerContext } from "./DrawerContext";
3
3
 
4
4
  export const useDrawer = () => {
5
- const { onClose, closeLabel } = useContext(DrawerContext);
5
+ const { onClose, closeLabel, headerId } = useContext(DrawerContext);
6
6
 
7
- return { onClose, closeLabel };
7
+ return { onClose, closeLabel, headerId };
8
8
  };