Skip to content

Commit 44423cf

Browse files
Separate text extraction from format.
They serve different purposes, so keep them separate for clarity. In addition, this simplifies the function signature and avoids the typing nightmare of conditional return types (that isn't actually properly supported today by Typescript). As part of this change, standadise the handling of 'key' so that custom 'format', 'disable', and 'extractText' functions receive the keyed value when 'key' is also specified. This feels most intuitive. For those that need the full object, they can always not pass 'key' and manage explicitly.
1 parent 46c46fa commit 44423cf

1 file changed

Lines changed: 70 additions & 82 deletions

File tree

src/create-options.tsx

Lines changed: 70 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -21,68 +21,57 @@ type CreateOptionsCreateableFunction = (
2121
options: CreateOptionsOption[],
2222
) => CreateSelectValue | CreateSelectValue[];
2323

24-
type CreateOptionsFormatFunction = <Type extends "value" | "label" | "text">(
24+
type CreateOptionsFormatFunction = (
2525
value: CreateSelectValue,
26-
type: Type,
26+
type: "value" | "label",
2727
meta: { highlight?: JSXElement; prefix?: string },
28-
) => Type extends "text" ? string : JSXElement;
29-
30-
const hasToString = (value: any): value is { toString: () => string } =>
31-
typeof value.toString === "function";
32-
33-
const defaultFormat: CreateOptionsFormatFunction = (value, type, meta) => {
34-
switch (type) {
35-
case "value":
36-
return value;
37-
case "text":
38-
if (!hasToString(value)) {
39-
throw new Error(
40-
"Value must have a 'toString' method for text extraction.",
41-
);
42-
}
43-
return value.toString();
44-
case "label":
45-
return (
46-
<>
47-
{meta?.prefix}
48-
{meta?.highlight ?? value}
49-
</>
50-
);
51-
}
52-
};
53-
54-
type CreateOptionsConfig = (
55-
| {
56-
key?: string;
57-
format?: never;
58-
}
59-
| {
60-
format?: CreateOptionsFormatFunction;
61-
key?: never;
62-
}
63-
) & {
28+
) => JSXElement;
29+
30+
const defaultFormat: CreateOptionsFormatFunction = (value, type, meta) =>
31+
type === "label" ? (
32+
<>
33+
{meta.prefix}
34+
{meta.highlight ?? value}
35+
</>
36+
) : (
37+
value
38+
);
39+
40+
interface CreateOptionsConfig {
41+
key?: string;
42+
format?: CreateOptionsFormatFunction;
6443
filterable?: boolean | CreateOptionsFilterableFunction;
6544
createable?: boolean | CreateOptionsCreateableFunction;
45+
extractText?: (value: CreateSelectValue) => string;
6646
disable?: (value: CreateSelectValue) => boolean;
67-
};
47+
}
6848

6949
const createOptions = (
7050
values: CreateSelectValue[] | ((inputValue: string) => CreateSelectValue[]),
7151
userConfig?: CreateOptionsConfig,
7252
) => {
7353
const config: CreateOptionsConfig &
74-
Required<Pick<CreateOptionsConfig, "filterable" | "disable">> =
75-
Object.assign(
76-
{
77-
filterable: true,
78-
disable: () => false,
79-
},
80-
userConfig || {},
81-
);
54+
Required<
55+
Pick<CreateOptionsConfig, "extractText" | "filterable" | "disable">
56+
> = Object.assign(
57+
{
58+
extractText: (value: CreateSelectValue) =>
59+
value.toString ? value.toString() : value,
60+
filterable: true,
61+
disable: () => false,
62+
},
63+
userConfig || {},
64+
);
8265

83-
if (config.key && config.format) {
66+
if (
67+
config.key &&
68+
userConfig &&
69+
(userConfig.format || userConfig.disable || userConfig.extractText)
70+
) {
8471
console.warn(
85-
"The 'key' option is ignored when 'format' option is specified.",
72+
"When 'key' option is specified, custom 'format', 'disable' and",
73+
"'extractText' functions will receive the keyed value rather than the",
74+
"full object.",
8675
);
8776
}
8877

@@ -98,26 +87,32 @@ const createOptions = (
9887
);
9988
}
10089

101-
const formatter: CreateOptionsFormatFunction = (
102-
value,
103-
type,
104-
meta: { highlight?: JSXElement; prefix?: string },
105-
) => {
90+
const resolveValue = (value: CreateSelectValue) =>
91+
config.key ? value[config.key] : value;
92+
93+
const extractText = (value: CreateSelectValue) =>
94+
config.extractText(resolveValue(value));
95+
96+
const format: CreateOptionsFormatFunction = (value, type, meta) => {
97+
const resolvedValue = resolveValue(value);
10698
return config.format
107-
? config.format(value, type, meta)
108-
: defaultFormat(config.key ? value[config.key] : value, type, meta);
99+
? config.format(resolvedValue, type, meta)
100+
: defaultFormat(resolvedValue, type, meta);
109101
};
110102

103+
const disable = (value: CreateSelectValue) =>
104+
config.disable(resolveValue(value));
105+
111106
const options = (inputValue: string) => {
112107
const initialValues =
113108
typeof values === "function" ? values(inputValue) : values;
114109

115110
let createdOptions: CreateOptionsOption[] = initialValues.map((value) => {
116111
return {
117-
value: value,
118-
label: formatter(value, "label", {}),
119-
text: formatter(value, "text", {}),
120-
disabled: config.disable(value),
112+
value,
113+
label: format(value, "label", {}),
114+
text: extractText(value),
115+
disabled: disable(value),
121116
};
122117
});
123118

@@ -128,7 +123,7 @@ const createOptions = (
128123
createdOptions = fuzzySort(inputValue, createdOptions, "text").map(
129124
(result) => ({
130125
...result.item,
131-
label: formatter(result.item.value, "label", {
126+
label: format(result.item.value, "label", {
132127
highlight: fuzzyHighlight(result),
133128
}),
134129
}),
@@ -162,10 +157,9 @@ const createOptions = (
162157
);
163158
}
164159
} else if (!exists) {
165-
valueOrValues =
166-
config.key && !config.format
167-
? { [config.key]: trimmedValue }
168-
: trimmedValue;
160+
valueOrValues = config.key
161+
? { [config.key]: trimmedValue }
162+
: trimmedValue;
169163
}
170164

171165
if (valueOrValues !== undefined) {
@@ -177,8 +171,8 @@ const createOptions = (
177171
for (const value of values) {
178172
optionsToAdd.push({
179173
value: value,
180-
label: formatter(value, "label", { prefix: "Create " }),
181-
text: formatter(value, "text", {}),
174+
label: format(value, "label", { prefix: "Create " }),
175+
text: extractText(value),
182176
disabled: false,
183177
});
184178
}
@@ -191,23 +185,17 @@ const createOptions = (
191185
return createdOptions;
192186
};
193187

194-
const optionToValue = (option: CreateOptionsOption) => option.value;
195-
196-
const format = (
197-
item: CreateOptionsOption | CreateSelectValue,
198-
type: "option" | "value",
199-
) =>
200-
type === "option"
201-
? (item as CreateOptionsOption).label
202-
: formatter(item, "value", {});
203-
204-
const isOptionDisabled = (option: CreateOptionsOption) => option.disabled;
205-
206188
return {
207189
options,
208-
optionToValue,
209-
isOptionDisabled,
210-
format,
190+
optionToValue: (option: CreateOptionsOption) => option.value,
191+
isOptionDisabled: (option: CreateOptionsOption) => option.disabled,
192+
format: (
193+
item: CreateOptionsOption | CreateSelectValue,
194+
type: "option" | "value",
195+
) =>
196+
type === "option"
197+
? (item as CreateOptionsOption).label
198+
: format(item as CreateSelectValue, "value", {}),
211199
};
212200
};
213201

0 commit comments

Comments
 (0)