@simplybusiness/mobius 9.1.2 → 9.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/CHANGELOG.md +10 -0
- package/dist/cjs/index.js +12 -8
- package/dist/cjs/index.js.map +3 -3
- package/dist/cjs/meta.json +12 -12
- package/dist/esm/{MaskedField-FRCJBGMX.js → MaskedField-U2CL5FHC.js} +2 -2
- package/dist/esm/{chunk-YUDNS4SZ.js → chunk-XGQ4SX2S.js} +47 -43
- package/dist/esm/chunk-XGQ4SX2S.js.map +7 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/meta.json +20 -20
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/components/Text/Text.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Combobox/useComboboxOptions.test.ts +32 -0
- package/src/components/Combobox/useComboboxOptions.ts +14 -7
- package/src/components/Text/Text.css +9 -0
- package/src/components/Text/Text.stories.tsx +7 -0
- package/src/components/Text/Text.tsx +2 -1
- package/dist/esm/chunk-YUDNS4SZ.js.map +0 -7
- /package/dist/esm/{MaskedField-FRCJBGMX.js.map → MaskedField-U2CL5FHC.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode, RefAttributes } from "react";
|
|
2
2
|
import type { DOMProps } from "../../types/dom";
|
|
3
3
|
export type TextElementType = HTMLHeadingElement | HTMLParagraphElement;
|
|
4
|
-
export type TextVariantType = "h1" | "h2" | "h3" | "h4" | "body" | "small" | "legal";
|
|
4
|
+
export type TextVariantType = "h1" | "h2" | "h3" | "h4" | "body" | "small" | "legal" | "title";
|
|
5
5
|
export type ElementType = "h1" | "h2" | "h3" | "h4" | "p" | "span";
|
|
6
6
|
export interface TextProps extends DOMProps, RefAttributes<TextElementType> {
|
|
7
7
|
/** HTML element for the text */
|
package/package.json
CHANGED
|
@@ -95,6 +95,38 @@ describe("useComboboxOptions", () => {
|
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
describe("async options stability", () => {
|
|
99
|
+
it("should not re-fetch when asyncOptions reference changes but inputValue stays the same", async () => {
|
|
100
|
+
const asyncOptionsV1 = vi
|
|
101
|
+
.fn()
|
|
102
|
+
.mockResolvedValue([{ label: "Result", value: "1" }]);
|
|
103
|
+
const asyncOptionsV2 = vi
|
|
104
|
+
.fn()
|
|
105
|
+
.mockResolvedValue([{ label: "Result", value: "1" }]);
|
|
106
|
+
|
|
107
|
+
const { rerender } = renderHook(
|
|
108
|
+
({ asyncOptions }) =>
|
|
109
|
+
useComboboxOptions({
|
|
110
|
+
asyncOptions,
|
|
111
|
+
inputValue: "hello",
|
|
112
|
+
}),
|
|
113
|
+
{ initialProps: { asyncOptions: asyncOptionsV1 } },
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
await act(() => Promise.resolve());
|
|
117
|
+
|
|
118
|
+
expect(asyncOptionsV1).toHaveBeenCalledTimes(1);
|
|
119
|
+
|
|
120
|
+
// Simulate parent re-render providing a new asyncOptions reference
|
|
121
|
+
rerender({ asyncOptions: asyncOptionsV2 });
|
|
122
|
+
|
|
123
|
+
await act(() => Promise.resolve());
|
|
124
|
+
|
|
125
|
+
// Should NOT have triggered a new fetch since inputValue didn't change
|
|
126
|
+
expect(asyncOptionsV2).not.toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
98
130
|
describe("async options handling", () => {
|
|
99
131
|
it("should handle async options", async () => {
|
|
100
132
|
const asyncOptions = vi.fn().mockResolvedValue([
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useDebouncedValue } from "@simplybusiness/mobius-hooks";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
3
|
import type { ComboboxOption, ComboboxOptions, ComboboxProps } from "./types";
|
|
3
4
|
import { filterOptions } from "./utils";
|
|
4
|
-
import { useDebouncedValue } from "@simplybusiness/mobius-hooks";
|
|
5
5
|
|
|
6
6
|
export type UseComboboxOptionsProps<T extends ComboboxOption> = Pick<
|
|
7
7
|
ComboboxProps<T>,
|
|
@@ -32,6 +32,13 @@ export function useComboboxOptions<T extends ComboboxOption>({
|
|
|
32
32
|
const [isLoading, setIsLoading] = useState(false);
|
|
33
33
|
const [error, setError] = useState<Error | null>(null);
|
|
34
34
|
|
|
35
|
+
// Keep refs to latest callbacks so the fetch effect doesn't re-run when
|
|
36
|
+
// their references change (e.g. due to un-memoised props in parent)
|
|
37
|
+
const asyncOptionsRef = useRef(asyncOptions);
|
|
38
|
+
asyncOptionsRef.current = asyncOptions;
|
|
39
|
+
const onSearchedRef = useRef(onSearched);
|
|
40
|
+
onSearchedRef.current = onSearched;
|
|
41
|
+
|
|
35
42
|
useEffect(() => {
|
|
36
43
|
const controller = new AbortController();
|
|
37
44
|
const { signal } = controller;
|
|
@@ -40,14 +47,16 @@ export function useComboboxOptions<T extends ComboboxOption>({
|
|
|
40
47
|
setIsLoading(true);
|
|
41
48
|
setError(null);
|
|
42
49
|
try {
|
|
43
|
-
if (
|
|
50
|
+
if (asyncOptionsRef.current) {
|
|
44
51
|
if (debouncedInputValue.length < minSearchLength) {
|
|
45
52
|
setFilteredOptions(undefined);
|
|
46
53
|
return;
|
|
47
54
|
}
|
|
48
|
-
const result = await
|
|
55
|
+
const result = await asyncOptionsRef.current(debouncedInputValue, {
|
|
56
|
+
signal,
|
|
57
|
+
});
|
|
49
58
|
setFilteredOptions(result);
|
|
50
|
-
|
|
59
|
+
onSearchedRef.current?.(debouncedInputValue);
|
|
51
60
|
} else if (options) {
|
|
52
61
|
setFilteredOptions(filterOptions(options, debouncedInputValue));
|
|
53
62
|
} else {
|
|
@@ -76,11 +85,9 @@ export function useComboboxOptions<T extends ComboboxOption>({
|
|
|
76
85
|
}, [
|
|
77
86
|
debouncedInputValue,
|
|
78
87
|
options,
|
|
79
|
-
asyncOptions,
|
|
80
88
|
delay,
|
|
81
89
|
minSearchLength,
|
|
82
90
|
skipNextDebounceRef,
|
|
83
|
-
onSearched,
|
|
84
91
|
]);
|
|
85
92
|
|
|
86
93
|
function updateFilteredOptions(newOptions: Promise<ComboboxOptions<T>>) {
|
|
@@ -88,6 +88,15 @@
|
|
|
88
88
|
color: var(--color-text-medium);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/* Title variant */
|
|
92
|
+
&:where(.--is-title) {
|
|
93
|
+
color: var(--color-text);
|
|
94
|
+
font-size: var(--font-size-small-title);
|
|
95
|
+
font-weight: var(--font-weight-bold);
|
|
96
|
+
line-height: var(--line-height-tight);
|
|
97
|
+
margin: var(--size-md) 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
91
100
|
/* Compact text */
|
|
92
101
|
&:where(.--has-line-height-tight) {
|
|
93
102
|
line-height: var(--line-height-tight);
|
|
@@ -111,6 +111,13 @@ export const VariantLegal: StoryType = {
|
|
|
111
111
|
},
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
+
export const VariantTitle: StoryType = {
|
|
115
|
+
render: (args: TextProps) => <Text {...args}>Variant title</Text>,
|
|
116
|
+
args: {
|
|
117
|
+
variant: "title",
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
114
121
|
export const Themed: StoryType = {
|
|
115
122
|
render: (args: TextProps) => <Text {...args}>Sample Text</Text>,
|
|
116
123
|
args: {
|
|
@@ -10,7 +10,8 @@ export type TextVariantType =
|
|
|
10
10
|
| "h4"
|
|
11
11
|
| "body"
|
|
12
12
|
| "small"
|
|
13
|
-
| "legal"
|
|
13
|
+
| "legal"
|
|
14
|
+
| "title";
|
|
14
15
|
export type ElementType = "h1" | "h2" | "h3" | "h4" | "p" | "span";
|
|
15
16
|
export interface TextProps extends DOMProps, RefAttributes<TextElementType> {
|
|
16
17
|
/** HTML element for the text */
|