@impulse-ui-native/select 8.2.0 → 8.3.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/README.md +3 -1
- package/dist/components/multi-select.js +10 -1
- package/dist/components/multi-select.js.map +1 -1
- package/dist/components/option.js +2 -1
- package/dist/components/option.js.map +1 -1
- package/dist/components/select-flyout-option.d.mts +9 -0
- package/dist/components/select-flyout-option.js +23 -0
- package/dist/components/select-flyout-option.js.map +1 -0
- package/dist/components/select-options.d.mts +9 -0
- package/dist/components/select-options.js +54 -0
- package/dist/components/select-options.js.map +1 -0
- package/dist/components/select.flyout.js +22 -48
- package/dist/components/select.flyout.js.map +1 -1
- package/dist/components/select.js +10 -1
- package/dist/components/select.js.map +1 -1
- package/dist/types/select-options.types.d.mts +12 -0
- package/dist/types/select-options.types.js +1 -0
- package/dist/types/select-options.types.js.map +1 -0
- package/dist/types/select.types.d.mts +3 -0
- package/package.json +12 -13
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Single- and multi-value select controls that present their options in a themed f
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
pnpm add @impulse-ui-native/select
|
|
8
|
+
pnpm add @impulse-ui-native/select react-native-gesture-handler react-native-reanimated react-native-safe-area-context react-native-worklets
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Complete the Gesture Handler, Reanimated, Worklets, and Safe Area Context setup required by your React Native or Expo project. The simplest application setup is to use `ThemeProvider`, wrap the app with `GestureHandlerRootView` and `SafeAreaProvider`, and mount one `PortalsHost` inside a `PortalProvider` at the application root.
|
|
@@ -54,3 +54,5 @@ export function DepartmentField() {
|
|
|
54
54
|
```
|
|
55
55
|
|
|
56
56
|
Both controls support controlled `value` and uncontrolled `defaultValue` usage. Values are limited to strings, numbers, `null`, and `undefined`; multi-select stores an array of those values.
|
|
57
|
+
|
|
58
|
+
On each opening, Select reveals its selected option. MultiSelect reveals the first selected option in list order. Options are single-line rows with a fixed `theme.components.select.option.height` (56 by default). The same height is supplied to native `getItemLayout`, so native `initialScrollIndex` handles positioning. Long labels truncate at the end. Selection changes while open do not change the opening target. Set `initialScrollToSelected={false}` to retain the normal starting position.
|
|
@@ -21,7 +21,15 @@ function SelectedTagComponent(props) {
|
|
|
21
21
|
}
|
|
22
22
|
const SelectedTag = memo(SelectedTagComponent);
|
|
23
23
|
function MultiSelectComponent(props) {
|
|
24
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
options,
|
|
26
|
+
value,
|
|
27
|
+
defaultValue,
|
|
28
|
+
onChange,
|
|
29
|
+
placeholder,
|
|
30
|
+
initialScrollToSelected,
|
|
31
|
+
...rest
|
|
32
|
+
} = props;
|
|
25
33
|
const tokens = useComponentsTokens();
|
|
26
34
|
const selectTokens = tokens.select;
|
|
27
35
|
const id = useId();
|
|
@@ -61,6 +69,7 @@ function MultiSelectComponent(props) {
|
|
|
61
69
|
title: "Options",
|
|
62
70
|
open: isOpen,
|
|
63
71
|
options,
|
|
72
|
+
initialScrollToSelected,
|
|
64
73
|
isSelected,
|
|
65
74
|
onCloseFinished: close,
|
|
66
75
|
onSelect: select
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/multi-select.tsx"],"sourcesContent":["import { memo, ReactElement, useId } from \"react\";\n\nimport { useEventCallback, useIsOpen } from \"@impulse-ui-native/core\";\nimport { Control, Tag, View } from \"@impulse-ui-native/primitives\";\nimport { useComponentsTokens } from \"@impulse-ui-native/theme\";\n\nimport { useMultiSelect } from \"../hooks/use-multi-select.hook\";\nimport { MultiSelectProps, PrimitiveValue } from \"../types\";\nimport { SelectControl } from \"./select-control\";\nimport { SelectFlyout } from \"./select.flyout\";\n\ninterface SelectedTagProps<Value extends PrimitiveValue> {\n value: Value;\n label: string;\n size: MultiSelectProps<Value>[\"size\"];\n onSelect: (value: Value) => void;\n}\n\nfunction SelectedTagComponent<Value extends PrimitiveValue>(\n props: SelectedTagProps<Value>,\n) {\n const handleClose = useEventCallback(() => props.onSelect(props.value));\n\n return (\n <Tag\n variant=\"filled\"\n size={props.size}\n closable\n onClose={handleClose}\n label={props.label}\n />\n );\n}\n\nconst SelectedTag = memo(SelectedTagComponent) as <\n Value extends PrimitiveValue,\n>(\n props: SelectedTagProps<Value>,\n) => ReactElement | null;\n\nfunction MultiSelectComponent<Value extends PrimitiveValue>(\n props: MultiSelectProps<Value>,\n) {\n const {
|
|
1
|
+
{"version":3,"sources":["../../src/components/multi-select.tsx"],"sourcesContent":["import { memo, ReactElement, useId } from \"react\";\n\nimport { useEventCallback, useIsOpen } from \"@impulse-ui-native/core\";\nimport { Control, Tag, View } from \"@impulse-ui-native/primitives\";\nimport { useComponentsTokens } from \"@impulse-ui-native/theme\";\n\nimport { useMultiSelect } from \"../hooks/use-multi-select.hook\";\nimport { MultiSelectProps, PrimitiveValue } from \"../types\";\nimport { SelectControl } from \"./select-control\";\nimport { SelectFlyout } from \"./select.flyout\";\n\ninterface SelectedTagProps<Value extends PrimitiveValue> {\n value: Value;\n label: string;\n size: MultiSelectProps<Value>[\"size\"];\n onSelect: (value: Value) => void;\n}\n\nfunction SelectedTagComponent<Value extends PrimitiveValue>(\n props: SelectedTagProps<Value>,\n) {\n const handleClose = useEventCallback(() => props.onSelect(props.value));\n\n return (\n <Tag\n variant=\"filled\"\n size={props.size}\n closable\n onClose={handleClose}\n label={props.label}\n />\n );\n}\n\nconst SelectedTag = memo(SelectedTagComponent) as <\n Value extends PrimitiveValue,\n>(\n props: SelectedTagProps<Value>,\n) => ReactElement | null;\n\nfunction MultiSelectComponent<Value extends PrimitiveValue>(\n props: MultiSelectProps<Value>,\n) {\n const {\n options,\n value,\n defaultValue,\n onChange,\n placeholder,\n initialScrollToSelected,\n ...rest\n } = props;\n\n const tokens = useComponentsTokens();\n\n const selectTokens = tokens.select;\n\n const id = useId();\n\n const { isOpen, open, close } = useIsOpen();\n\n const { selected, hasSelected, select, isSelected, getLabel } =\n useMultiSelect({\n options,\n value,\n defaultValue,\n onChange,\n });\n\n return (\n <SelectControl {...rest} onPress={open}>\n {!hasSelected ? (\n <Control.Placeholder>{placeholder}</Control.Placeholder>\n ) : null}\n {hasSelected ? (\n <View\n flex={1}\n flexDirection=\"row\"\n gap={selectTokens.multiValueGap}\n marginHorizontal={selectTokens.multiValueMarginHorizontal}\n overflow=\"hidden\"\n >\n {selected\n ? selected.map((value) => (\n <SelectedTag\n size={props.size}\n key={value}\n label={String(getLabel(value))}\n value={value}\n onSelect={select}\n />\n ))\n : null}\n </View>\n ) : null}\n <SelectFlyout\n id={id}\n title=\"Options\"\n open={isOpen}\n options={options}\n initialScrollToSelected={initialScrollToSelected}\n isSelected={isSelected}\n onCloseFinished={close}\n onSelect={select}\n />\n </SelectControl>\n );\n}\n\nexport const MultiSelect = memo(MultiSelectComponent) as <\n Value extends PrimitiveValue,\n>(\n props: MultiSelectProps<Value>,\n) => ReactElement | null;\n"],"mappings":"AAwBI,cA8CA,YA9CA;AAxBJ,SAAS,MAAoB,aAAa;AAE1C,SAAS,kBAAkB,iBAAiB;AAC5C,SAAS,SAAS,KAAK,YAAY;AACnC,SAAS,2BAA2B;AAEpC,SAAS,sBAAsB;AAE/B,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAS7B,SAAS,qBACP,OACA;AACA,QAAM,cAAc,iBAAiB,MAAM,MAAM,SAAS,MAAM,KAAK,CAAC;AAEtE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAM,MAAM;AAAA,MACZ,UAAQ;AAAA,MACR,SAAS;AAAA,MACT,OAAO,MAAM;AAAA;AAAA,EACf;AAEJ;AAEA,MAAM,cAAc,KAAK,oBAAoB;AAM7C,SAAS,qBACP,OACA;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,SAAS,oBAAoB;AAEnC,QAAM,eAAe,OAAO;AAE5B,QAAM,KAAK,MAAM;AAEjB,QAAM,EAAE,QAAQ,MAAM,MAAM,IAAI,UAAU;AAE1C,QAAM,EAAE,UAAU,aAAa,QAAQ,YAAY,SAAS,IAC1D,eAAe;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAEH,SACE,qBAAC,iBAAe,GAAG,MAAM,SAAS,MAC/B;AAAA,KAAC,cACA,oBAAC,QAAQ,aAAR,EAAqB,uBAAY,IAChC;AAAA,IACH,cACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,eAAc;AAAA,QACd,KAAK,aAAa;AAAA,QAClB,kBAAkB,aAAa;AAAA,QAC/B,UAAS;AAAA,QAER,qBACG,SAAS,IAAI,CAACA,WACZ;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,MAAM;AAAA,YAEZ,OAAO,OAAO,SAASA,MAAK,CAAC;AAAA,YAC7B,OAAOA;AAAA,YACP,UAAU;AAAA;AAAA,UAHLA;AAAA,QAIP,CACD,IACD;AAAA;AAAA,IACN,IACE;AAAA,IACJ;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,QACjB,UAAU;AAAA;AAAA,IACZ;AAAA,KACF;AAEJ;AAEO,MAAM,cAAc,KAAK,oBAAoB;","names":["value"]}
|
|
@@ -17,12 +17,13 @@ const Option = memo(function Option2(props) {
|
|
|
17
17
|
return /* @__PURE__ */ jsx(Pressable, { ...rest, children: /* @__PURE__ */ jsxs(
|
|
18
18
|
View,
|
|
19
19
|
{
|
|
20
|
+
height: optionTokens.height,
|
|
20
21
|
padding: optionTokens.padding,
|
|
21
22
|
flexDirection: "row",
|
|
22
23
|
alignItems: "center",
|
|
23
24
|
justifyContent: "space-between",
|
|
24
25
|
children: [
|
|
25
|
-
/* @__PURE__ */ jsx(Typography.Body, { children: label }),
|
|
26
|
+
/* @__PURE__ */ jsx(Typography.Body, { flex: 1, numberOfLines: 1, ellipsizeMode: "tail", children: label }),
|
|
26
27
|
/* @__PURE__ */ jsx(
|
|
27
28
|
Icon,
|
|
28
29
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/option.tsx"],"sourcesContent":["import { memo } from \"react\";\n\nimport { Icon } from \"@impulse-ui-native/icon\";\nimport { CheckIcon } from \"@impulse-ui-native/icon/icons/check\";\nimport { Pressable, Typography, View } from \"@impulse-ui-native/primitives\";\nimport {\n getSelectionItemStateTokens,\n useComponentsTokens,\n} from \"@impulse-ui-native/theme\";\n\nimport { OptionProps } from \"../types\";\n\nexport const Option = memo(function Option(props: OptionProps) {\n const { label, selected, ...rest } = props;\n\n const tokens = useComponentsTokens();\n\n const optionTokens = tokens.select.option;\n const appearanceTokens = getSelectionItemStateTokens(optionTokens.states, {\n selected,\n });\n\n return (\n <Pressable {...rest}>\n <View\n padding={optionTokens.padding}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"space-between\"\n >\n <Typography.Body
|
|
1
|
+
{"version":3,"sources":["../../src/components/option.tsx"],"sourcesContent":["import { memo } from \"react\";\n\nimport { Icon } from \"@impulse-ui-native/icon\";\nimport { CheckIcon } from \"@impulse-ui-native/icon/icons/check\";\nimport { Pressable, Typography, View } from \"@impulse-ui-native/primitives\";\nimport {\n getSelectionItemStateTokens,\n useComponentsTokens,\n} from \"@impulse-ui-native/theme\";\n\nimport { OptionProps } from \"../types\";\n\nexport const Option = memo(function Option(props: OptionProps) {\n const { label, selected, ...rest } = props;\n\n const tokens = useComponentsTokens();\n\n const optionTokens = tokens.select.option;\n const appearanceTokens = getSelectionItemStateTokens(optionTokens.states, {\n selected,\n });\n\n return (\n <Pressable {...rest}>\n <View\n height={optionTokens.height}\n padding={optionTokens.padding}\n flexDirection=\"row\"\n alignItems=\"center\"\n justifyContent=\"space-between\"\n >\n <Typography.Body flex={1} numberOfLines={1} ellipsizeMode=\"tail\">\n {label}\n </Typography.Body>\n\n <Icon\n icon={CheckIcon}\n color={appearanceTokens.iconColor}\n opacity={appearanceTokens.iconOpacity}\n />\n </View>\n </Pressable>\n );\n});\n"],"mappings":"AAwBM,SAOE,KAPF;AAxBN,SAAS,YAAY;AAErB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,WAAW,YAAY,YAAY;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAIA,MAAM,SAAS,KAAK,SAASA,QAAO,OAAoB;AAC7D,QAAM,EAAE,OAAO,UAAU,GAAG,KAAK,IAAI;AAErC,QAAM,SAAS,oBAAoB;AAEnC,QAAM,eAAe,OAAO,OAAO;AACnC,QAAM,mBAAmB,4BAA4B,aAAa,QAAQ;AAAA,IACxE;AAAA,EACF,CAAC;AAED,SACE,oBAAC,aAAW,GAAG,MACb;AAAA,IAAC;AAAA;AAAA,MACC,QAAQ,aAAa;AAAA,MACrB,SAAS,aAAa;AAAA,MACtB,eAAc;AAAA,MACd,YAAW;AAAA,MACX,gBAAe;AAAA,MAEf;AAAA,4BAAC,WAAW,MAAX,EAAgB,MAAM,GAAG,eAAe,GAAG,eAAc,QACvD,iBACH;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,OAAO,iBAAiB;AAAA,YACxB,SAAS,iBAAiB;AAAA;AAAA,QAC5B;AAAA;AAAA;AAAA,EACF,GACF;AAEJ,CAAC;","names":["Option"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { SelectFlyoutOptionProps } from '../types/select-options.types.mjs';
|
|
3
|
+
import { PrimitiveValue } from '../types/select.types.mjs';
|
|
4
|
+
import '@impulse-ui-native/flyout';
|
|
5
|
+
import '@impulse-ui-native/primitives';
|
|
6
|
+
|
|
7
|
+
declare const SelectFlyoutOption: <Value extends PrimitiveValue>(props: SelectFlyoutOptionProps<Value>) => ReactElement | null;
|
|
8
|
+
|
|
9
|
+
export { SelectFlyoutOption };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from "react";
|
|
3
|
+
import { useEventCallback } from "@impulse-ui-native/core";
|
|
4
|
+
import { Option } from "./option";
|
|
5
|
+
const SelectFlyoutOptionComponent = function SelectFlyoutOption(props) {
|
|
6
|
+
const handlePress = useEventCallback(
|
|
7
|
+
() => props.onSelect(props.option.value)
|
|
8
|
+
);
|
|
9
|
+
return /* @__PURE__ */ jsx(
|
|
10
|
+
Option,
|
|
11
|
+
{
|
|
12
|
+
label: props.option.label,
|
|
13
|
+
onPress: handlePress,
|
|
14
|
+
disabled: props.option.disabled,
|
|
15
|
+
selected: props.selected
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
};
|
|
19
|
+
const SelectFlyoutOption2 = memo(SelectFlyoutOptionComponent);
|
|
20
|
+
export {
|
|
21
|
+
SelectFlyoutOption2 as SelectFlyoutOption
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=select-flyout-option.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/select-flyout-option.tsx"],"sourcesContent":["import { memo, ReactElement } from \"react\";\n\nimport { useEventCallback } from \"@impulse-ui-native/core\";\n\nimport type { SelectFlyoutOptionProps } from \"../types/select-options.types\";\nimport { PrimitiveValue } from \"../types\";\nimport { Option } from \"./option\";\n\nconst SelectFlyoutOptionComponent = function SelectFlyoutOption<\n Value extends PrimitiveValue,\n>(props: SelectFlyoutOptionProps<Value>) {\n const handlePress = useEventCallback(() =>\n props.onSelect(props.option.value),\n );\n\n return (\n <Option\n label={props.option.label}\n onPress={handlePress}\n disabled={props.option.disabled}\n selected={props.selected}\n />\n );\n};\n\nexport const SelectFlyoutOption = memo(SelectFlyoutOptionComponent) as <\n Value extends PrimitiveValue,\n>(\n props: SelectFlyoutOptionProps<Value>,\n) => ReactElement | null;\n"],"mappings":"AAgBI;AAhBJ,SAAS,YAA0B;AAEnC,SAAS,wBAAwB;AAIjC,SAAS,cAAc;AAEvB,MAAM,8BAA8B,SAAS,mBAE3C,OAAuC;AACvC,QAAM,cAAc;AAAA,IAAiB,MACnC,MAAM,SAAS,MAAM,OAAO,KAAK;AAAA,EACnC;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,MAAM,OAAO;AAAA,MACpB,SAAS;AAAA,MACT,UAAU,MAAM,OAAO;AAAA,MACvB,UAAU,MAAM;AAAA;AAAA,EAClB;AAEJ;AAEO,MAAMA,sBAAqB,KAAK,2BAA2B;","names":["SelectFlyoutOption"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { SelectOptionsProps } from '../types/select-options.types.mjs';
|
|
3
|
+
import { PrimitiveValue } from '../types/select.types.mjs';
|
|
4
|
+
import '@impulse-ui-native/flyout';
|
|
5
|
+
import '@impulse-ui-native/primitives';
|
|
6
|
+
|
|
7
|
+
declare const SelectOptions: <Value extends PrimitiveValue>(props: SelectOptionsProps<Value>) => ReactElement | null;
|
|
8
|
+
|
|
9
|
+
export { SelectOptions };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useCallback, useRef } from "react";
|
|
3
|
+
import {
|
|
4
|
+
FlatList,
|
|
5
|
+
useFlatListInitialScroll
|
|
6
|
+
} from "@impulse-ui-native/flat-list";
|
|
7
|
+
import { useComponentsTokens } from "@impulse-ui-native/theme";
|
|
8
|
+
import { getOptionId } from "../utils";
|
|
9
|
+
import { SelectFlyoutOption } from "./select-flyout-option";
|
|
10
|
+
const SelectOptionsComponent = function SelectOptions(props) {
|
|
11
|
+
const { options, isSelected, onSelect, initialScrollToSelected } = props;
|
|
12
|
+
const initialSelection = useRef(isSelected);
|
|
13
|
+
const tokens = useComponentsTokens();
|
|
14
|
+
const selectedIndex = options.findIndex(
|
|
15
|
+
(option) => option.value !== null && option.value !== void 0 && initialSelection.current(option.value)
|
|
16
|
+
);
|
|
17
|
+
const initialScroll = useFlatListInitialScroll({
|
|
18
|
+
itemSize: tokens.select.option.height,
|
|
19
|
+
initialScrollIndex: initialScrollToSelected && selectedIndex >= 0 ? selectedIndex : void 0
|
|
20
|
+
});
|
|
21
|
+
const renderItem = useCallback(
|
|
22
|
+
({ item }) => /* @__PURE__ */ jsx(
|
|
23
|
+
SelectFlyoutOption,
|
|
24
|
+
{
|
|
25
|
+
option: item,
|
|
26
|
+
selected: isSelected(item.value),
|
|
27
|
+
onSelect
|
|
28
|
+
}
|
|
29
|
+
),
|
|
30
|
+
[isSelected, onSelect]
|
|
31
|
+
);
|
|
32
|
+
return /* @__PURE__ */ jsx(
|
|
33
|
+
FlatList,
|
|
34
|
+
{
|
|
35
|
+
...initialScroll,
|
|
36
|
+
data: options,
|
|
37
|
+
renderItem,
|
|
38
|
+
keyExtractor: getOptionId,
|
|
39
|
+
showsHorizontalScrollIndicator: false,
|
|
40
|
+
showsVerticalScrollIndicator: false,
|
|
41
|
+
keyboardShouldPersistTaps: "handled",
|
|
42
|
+
removeClippedSubviews: true,
|
|
43
|
+
windowSize: 5,
|
|
44
|
+
initialNumToRender: 12,
|
|
45
|
+
maxToRenderPerBatch: 12,
|
|
46
|
+
updateCellsBatchingPeriod: 32
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
const SelectOptions2 = memo(SelectOptionsComponent);
|
|
51
|
+
export {
|
|
52
|
+
SelectOptions2 as SelectOptions
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=select-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/select-options.tsx"],"sourcesContent":["import type { ListRenderItemInfo } from \"react-native\";\nimport { memo, ReactElement, useCallback, useRef } from \"react\";\n\nimport {\n FlatList,\n useFlatListInitialScroll,\n} from \"@impulse-ui-native/flat-list\";\nimport { useComponentsTokens } from \"@impulse-ui-native/theme\";\n\nimport type { SelectOptionsProps } from \"../types/select-options.types\";\nimport { PrimitiveValue, SelectOption } from \"../types\";\nimport { getOptionId } from \"../utils\";\nimport { SelectFlyoutOption } from \"./select-flyout-option\";\n\nconst SelectOptionsComponent = function SelectOptions<\n Value extends PrimitiveValue,\n>(props: SelectOptionsProps<Value>) {\n const { options, isSelected, onSelect, initialScrollToSelected } = props;\n\n const initialSelection = useRef(isSelected);\n\n const tokens = useComponentsTokens();\n\n // Capture selection at opening, including when options arrive later.\n const selectedIndex = options.findIndex(\n (option) =>\n option.value !== null &&\n option.value !== undefined &&\n initialSelection.current(option.value),\n );\n const initialScroll = useFlatListInitialScroll<SelectOption<Value>>({\n itemSize: tokens.select.option.height,\n initialScrollIndex:\n initialScrollToSelected && selectedIndex >= 0 ? selectedIndex : undefined,\n });\n\n const renderItem = useCallback(\n ({ item }: ListRenderItemInfo<SelectOption<Value>>) => (\n <SelectFlyoutOption\n option={item}\n selected={isSelected(item.value)}\n onSelect={onSelect}\n />\n ),\n [isSelected, onSelect],\n );\n\n return (\n <FlatList\n {...initialScroll}\n data={options}\n renderItem={renderItem}\n keyExtractor={getOptionId}\n showsHorizontalScrollIndicator={false}\n showsVerticalScrollIndicator={false}\n keyboardShouldPersistTaps=\"handled\"\n removeClippedSubviews\n windowSize={5}\n initialNumToRender={12}\n maxToRenderPerBatch={12}\n updateCellsBatchingPeriod={32}\n />\n );\n};\n\nexport const SelectOptions = memo(SelectOptionsComponent) as <\n Value extends PrimitiveValue,\n>(\n props: SelectOptionsProps<Value>,\n) => ReactElement | null;\n"],"mappings":"AAsCM;AArCN,SAAS,MAAoB,aAAa,cAAc;AAExD;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,2BAA2B;AAIpC,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAEnC,MAAM,yBAAyB,SAAS,cAEtC,OAAkC;AAClC,QAAM,EAAE,SAAS,YAAY,UAAU,wBAAwB,IAAI;AAEnE,QAAM,mBAAmB,OAAO,UAAU;AAE1C,QAAM,SAAS,oBAAoB;AAGnC,QAAM,gBAAgB,QAAQ;AAAA,IAC5B,CAAC,WACC,OAAO,UAAU,QACjB,OAAO,UAAU,UACjB,iBAAiB,QAAQ,OAAO,KAAK;AAAA,EACzC;AACA,QAAM,gBAAgB,yBAA8C;AAAA,IAClE,UAAU,OAAO,OAAO,OAAO;AAAA,IAC/B,oBACE,2BAA2B,iBAAiB,IAAI,gBAAgB;AAAA,EACpE,CAAC;AAED,QAAM,aAAa;AAAA,IACjB,CAAC,EAAE,KAAK,MACN;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ;AAAA,QACR,UAAU,WAAW,KAAK,KAAK;AAAA,QAC/B;AAAA;AAAA,IACF;AAAA,IAEF,CAAC,YAAY,QAAQ;AAAA,EACvB;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,MAAM;AAAA,MACN;AAAA,MACA,cAAc;AAAA,MACd,gCAAgC;AAAA,MAChC,8BAA8B;AAAA,MAC9B,2BAA0B;AAAA,MAC1B,uBAAqB;AAAA,MACrB,YAAY;AAAA,MACZ,oBAAoB;AAAA,MACpB,qBAAqB;AAAA,MACrB,2BAA2B;AAAA;AAAA,EAC7B;AAEJ;AAEO,MAAMA,iBAAgB,KAAK,sBAAsB;","names":["SelectOptions"]}
|
|
@@ -1,60 +1,34 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { memo,
|
|
3
|
-
import { FlatList } from "react-native";
|
|
2
|
+
import { memo, useState } from "react";
|
|
4
3
|
import { useEventCallback } from "@impulse-ui-native/core";
|
|
5
4
|
import { Flyout } from "@impulse-ui-native/flyout";
|
|
6
5
|
import { Portal } from "@impulse-ui-native/portal";
|
|
7
6
|
import { View } from "@impulse-ui-native/primitives";
|
|
8
7
|
import { useComponentsTokens } from "@impulse-ui-native/theme";
|
|
9
|
-
import {
|
|
10
|
-
import { Option } from "./option";
|
|
11
|
-
function SelectFlyoutOptionComponent(props) {
|
|
12
|
-
const handlePress = useEventCallback(
|
|
13
|
-
() => props.onSelect(props.option.value)
|
|
14
|
-
);
|
|
15
|
-
return /* @__PURE__ */ jsx(
|
|
16
|
-
Option,
|
|
17
|
-
{
|
|
18
|
-
label: props.option.label,
|
|
19
|
-
onPress: handlePress,
|
|
20
|
-
disabled: props.option.disabled,
|
|
21
|
-
selected: props.selected
|
|
22
|
-
}
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
const SelectFlyoutOption = memo(SelectFlyoutOptionComponent);
|
|
8
|
+
import { SelectOptions } from "./select-options";
|
|
26
9
|
const SelectFlyoutComponent = function SelectFlyoutContent(props) {
|
|
27
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
options,
|
|
12
|
+
isSelected,
|
|
13
|
+
onSelect,
|
|
14
|
+
initialScrollToSelected = true,
|
|
15
|
+
...rest
|
|
16
|
+
} = props;
|
|
17
|
+
const [openingKey, setOpeningKey] = useState(0);
|
|
28
18
|
const tokens = useComponentsTokens();
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
option: item,
|
|
36
|
-
selected: isSelected(item.value),
|
|
37
|
-
onSelect
|
|
38
|
-
}
|
|
39
|
-
);
|
|
40
|
-
},
|
|
41
|
-
[isSelected, onSelect]
|
|
42
|
-
);
|
|
43
|
-
return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Flyout, { ...rest, children: /* @__PURE__ */ jsx(View, { height: selectTokens.flyoutHeight, children: /* @__PURE__ */ jsx(
|
|
44
|
-
FlatList,
|
|
19
|
+
const handleOpen = useEventCallback((id) => {
|
|
20
|
+
setOpeningKey((key) => key + 1);
|
|
21
|
+
rest.onOpen?.(id);
|
|
22
|
+
});
|
|
23
|
+
return /* @__PURE__ */ jsx(Portal, { children: /* @__PURE__ */ jsx(Flyout, { ...rest, onOpen: handleOpen, children: /* @__PURE__ */ jsx(View, { height: tokens.select.flyoutHeight, children: /* @__PURE__ */ jsx(
|
|
24
|
+
SelectOptions,
|
|
45
25
|
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
removeClippedSubviews: true,
|
|
53
|
-
windowSize: 5,
|
|
54
|
-
initialNumToRender: 12,
|
|
55
|
-
maxToRenderPerBatch: 12,
|
|
56
|
-
updateCellsBatchingPeriod: 32
|
|
57
|
-
}
|
|
26
|
+
options,
|
|
27
|
+
isSelected,
|
|
28
|
+
onSelect,
|
|
29
|
+
initialScrollToSelected: Boolean(rest.open) && initialScrollToSelected
|
|
30
|
+
},
|
|
31
|
+
openingKey
|
|
58
32
|
) }) }) });
|
|
59
33
|
};
|
|
60
34
|
const SelectFlyout = memo(SelectFlyoutComponent);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/select.flyout.tsx"],"sourcesContent":["import { memo, ReactElement,
|
|
1
|
+
{"version":3,"sources":["../../src/components/select.flyout.tsx"],"sourcesContent":["import { memo, ReactElement, useState } from \"react\";\n\nimport { useEventCallback } from \"@impulse-ui-native/core\";\nimport { Flyout } from \"@impulse-ui-native/flyout\";\nimport { Portal } from \"@impulse-ui-native/portal\";\nimport { View } from \"@impulse-ui-native/primitives\";\nimport { useComponentsTokens } from \"@impulse-ui-native/theme\";\n\nimport { PrimitiveValue, SelectFlyoutProps } from \"../types\";\nimport { SelectOptions } from \"./select-options\";\n\nconst SelectFlyoutComponent = function SelectFlyoutContent<\n Value extends PrimitiveValue,\n>(props: SelectFlyoutProps<Value>) {\n const {\n options,\n isSelected,\n onSelect,\n initialScrollToSelected = true,\n ...rest\n } = props;\n\n const [openingKey, setOpeningKey] = useState(0);\n\n const tokens = useComponentsTokens();\n\n const handleOpen = useEventCallback((id: string) => {\n setOpeningKey((key) => key + 1);\n rest.onOpen?.(id);\n });\n\n return (\n <Portal>\n <Flyout {...rest} onOpen={handleOpen}>\n <View height={tokens.select.flyoutHeight}>\n <SelectOptions\n key={openingKey}\n options={options}\n isSelected={isSelected}\n onSelect={onSelect}\n initialScrollToSelected={\n Boolean(rest.open) && initialScrollToSelected\n }\n />\n </View>\n </Flyout>\n </Portal>\n );\n};\n\nexport const SelectFlyout = memo(SelectFlyoutComponent) as <\n Value extends PrimitiveValue,\n>(\n props: SelectFlyoutProps<Value>,\n) => ReactElement | null;\n"],"mappings":"AAmCU;AAnCV,SAAS,MAAoB,gBAAgB;AAE7C,SAAS,wBAAwB;AACjC,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,2BAA2B;AAGpC,SAAS,qBAAqB;AAE9B,MAAM,wBAAwB,SAAS,oBAErC,OAAiC;AACjC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,0BAA0B;AAAA,IAC1B,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,CAAC;AAE9C,QAAM,SAAS,oBAAoB;AAEnC,QAAM,aAAa,iBAAiB,CAAC,OAAe;AAClD,kBAAc,CAAC,QAAQ,MAAM,CAAC;AAC9B,SAAK,SAAS,EAAE;AAAA,EAClB,CAAC;AAED,SACE,oBAAC,UACC,8BAAC,UAAQ,GAAG,MAAM,QAAQ,YACxB,8BAAC,QAAK,QAAQ,OAAO,OAAO,cAC1B;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA,MACA;AAAA,MACA,yBACE,QAAQ,KAAK,IAAI,KAAK;AAAA;AAAA,IALnB;AAAA,EAOP,GACF,GACF,GACF;AAEJ;AAEO,MAAM,eAAe,KAAK,qBAAqB;","names":[]}
|
|
@@ -6,7 +6,15 @@ import { useSelect } from "../hooks/use-select.hook";
|
|
|
6
6
|
import { SelectControl } from "./select-control";
|
|
7
7
|
import { SelectFlyout } from "./select.flyout";
|
|
8
8
|
function SelectComponent(props) {
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
options,
|
|
11
|
+
value,
|
|
12
|
+
defaultValue,
|
|
13
|
+
onChange,
|
|
14
|
+
placeholder,
|
|
15
|
+
initialScrollToSelected,
|
|
16
|
+
...rest
|
|
17
|
+
} = props;
|
|
10
18
|
const id = useId();
|
|
11
19
|
const { isOpen, open, close } = useIsOpen();
|
|
12
20
|
const { selected, hasSelected, select, isSelected, getLabel } = useSelect({
|
|
@@ -32,6 +40,7 @@ function SelectComponent(props) {
|
|
|
32
40
|
title: "Options",
|
|
33
41
|
open: isOpen,
|
|
34
42
|
options,
|
|
43
|
+
initialScrollToSelected,
|
|
35
44
|
isSelected,
|
|
36
45
|
onCloseFinished: close,
|
|
37
46
|
onSelect: handleOnSelect
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/select.tsx"],"sourcesContent":["import { memo, ReactElement, useCallback, useId } from \"react\";\n\nimport { useIsOpen } from \"@impulse-ui-native/core\";\nimport { Control } from \"@impulse-ui-native/primitives\";\n\nimport { useSelect } from \"../hooks/use-select.hook\";\nimport { PrimitiveValue, SelectProps } from \"../types\";\nimport { SelectControl } from \"./select-control\";\nimport { SelectFlyout } from \"./select.flyout\";\n\nfunction SelectComponent<Value extends PrimitiveValue>(\n props: SelectProps<Value>,\n) {\n const {
|
|
1
|
+
{"version":3,"sources":["../../src/components/select.tsx"],"sourcesContent":["import { memo, ReactElement, useCallback, useId } from \"react\";\n\nimport { useIsOpen } from \"@impulse-ui-native/core\";\nimport { Control } from \"@impulse-ui-native/primitives\";\n\nimport { useSelect } from \"../hooks/use-select.hook\";\nimport { PrimitiveValue, SelectProps } from \"../types\";\nimport { SelectControl } from \"./select-control\";\nimport { SelectFlyout } from \"./select.flyout\";\n\nfunction SelectComponent<Value extends PrimitiveValue>(\n props: SelectProps<Value>,\n) {\n const {\n options,\n value,\n defaultValue,\n onChange,\n placeholder,\n initialScrollToSelected,\n ...rest\n } = props;\n\n const id = useId();\n\n const { isOpen, open, close } = useIsOpen();\n\n const { selected, hasSelected, select, isSelected, getLabel } = useSelect({\n options,\n value,\n defaultValue,\n onChange,\n });\n\n const handleOnSelect = useCallback(\n (value: Value) => {\n select(value);\n close();\n },\n [select, close],\n );\n\n return (\n <SelectControl {...rest} onPress={open}>\n {!hasSelected ? (\n <Control.Placeholder>{placeholder}</Control.Placeholder>\n ) : null}\n {selected !== null && selected !== undefined ? (\n <Control.Value>{getLabel(selected)}</Control.Value>\n ) : null}\n <SelectFlyout\n id={id}\n title=\"Options\"\n open={isOpen}\n options={options}\n initialScrollToSelected={initialScrollToSelected}\n isSelected={isSelected}\n onCloseFinished={close}\n onSelect={handleOnSelect}\n />\n </SelectControl>\n );\n}\n\nexport const Select = memo(SelectComponent) as <Value extends PrimitiveValue>(\n props: SelectProps<Value>,\n) => ReactElement | null;\n"],"mappings":"AA2CI,SAEI,KAFJ;AA3CJ,SAAS,MAAoB,aAAa,aAAa;AAEvD,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AAExB,SAAS,iBAAiB;AAE1B,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAE7B,SAAS,gBACP,OACA;AACA,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,KAAK,MAAM;AAEjB,QAAM,EAAE,QAAQ,MAAM,MAAM,IAAI,UAAU;AAE1C,QAAM,EAAE,UAAU,aAAa,QAAQ,YAAY,SAAS,IAAI,UAAU;AAAA,IACxE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB;AAAA,IACrB,CAACA,WAAiB;AAChB,aAAOA,MAAK;AACZ,YAAM;AAAA,IACR;AAAA,IACA,CAAC,QAAQ,KAAK;AAAA,EAChB;AAEA,SACE,qBAAC,iBAAe,GAAG,MAAM,SAAS,MAC/B;AAAA,KAAC,cACA,oBAAC,QAAQ,aAAR,EAAqB,uBAAY,IAChC;AAAA,IACH,aAAa,QAAQ,aAAa,SACjC,oBAAC,QAAQ,OAAR,EAAe,mBAAS,QAAQ,GAAE,IACjC;AAAA,IACJ;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,QACjB,UAAU;AAAA;AAAA,IACZ;AAAA,KACF;AAEJ;AAEO,MAAM,SAAS,KAAK,eAAe;","names":["value"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PrimitiveValue, SelectOption, SelectFlyoutProps } from './select.types.mjs';
|
|
2
|
+
import '@impulse-ui-native/flyout';
|
|
3
|
+
import '@impulse-ui-native/primitives';
|
|
4
|
+
|
|
5
|
+
type SelectOptionsProps<Value extends PrimitiveValue> = Pick<SelectFlyoutProps<Value>, "options" | "isSelected" | "onSelect" | "initialScrollToSelected">;
|
|
6
|
+
interface SelectFlyoutOptionProps<Value extends PrimitiveValue> {
|
|
7
|
+
option: SelectOption<Value>;
|
|
8
|
+
selected: boolean;
|
|
9
|
+
onSelect: (value: Value) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type { SelectFlyoutOptionProps, SelectOptionsProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=select-options.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -10,6 +10,8 @@ type SelectOption<Value> = {
|
|
|
10
10
|
};
|
|
11
11
|
interface CommonSelectProps<Value> extends Partial<ControlComponentProps>, ControlComponentCommonProps {
|
|
12
12
|
loading?: boolean;
|
|
13
|
+
/** Reveal the selection on each opening. Defaults to true. */
|
|
14
|
+
initialScrollToSelected?: boolean;
|
|
13
15
|
onChange?: (value: Value | undefined) => void;
|
|
14
16
|
}
|
|
15
17
|
interface SelectControlProps<Value> extends CommonSelectProps<Value> {
|
|
@@ -27,6 +29,7 @@ interface MultiSelectProps<Value extends PrimitiveValue> extends Partial<Control
|
|
|
27
29
|
}
|
|
28
30
|
interface SelectFlyoutProps<Value extends PrimitiveValue> extends FlyoutProps {
|
|
29
31
|
options: SelectOption<Value>[];
|
|
32
|
+
initialScrollToSelected?: boolean;
|
|
30
33
|
isSelected: (value: Value) => boolean;
|
|
31
34
|
onSelect: (value: Value) => void;
|
|
32
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@impulse-ui-native/select",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0",
|
|
4
4
|
"description": "Select and option picker components for ImpulseUI Native applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -28,30 +28,29 @@
|
|
|
28
28
|
],
|
|
29
29
|
"sideEffects": false,
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@impulse-ui-native/theme": "8.
|
|
32
|
-
"@impulse-ui-native/icon": "8.
|
|
33
|
-
"@impulse-ui-native/input": "8.
|
|
34
|
-
"@impulse-ui-native/
|
|
35
|
-
"@impulse-ui-native/
|
|
36
|
-
"@impulse-ui-native/flyout": "8.
|
|
37
|
-
"@impulse-ui-native/portal": "8.
|
|
31
|
+
"@impulse-ui-native/theme": "8.3.0",
|
|
32
|
+
"@impulse-ui-native/icon": "8.3.0",
|
|
33
|
+
"@impulse-ui-native/input": "8.3.0",
|
|
34
|
+
"@impulse-ui-native/primitives": "8.3.0",
|
|
35
|
+
"@impulse-ui-native/core": "8.3.0",
|
|
36
|
+
"@impulse-ui-native/flyout": "8.3.0",
|
|
37
|
+
"@impulse-ui-native/portal": "8.3.0",
|
|
38
|
+
"@impulse-ui-native/flat-list": "8.3.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@shopify/flash-list": "2.0.2",
|
|
41
41
|
"@types/react": "~19.2.14",
|
|
42
42
|
"eslint": "^9.38.0",
|
|
43
43
|
"prettier": "^3.6.2",
|
|
44
44
|
"react": "19.2.3",
|
|
45
45
|
"react-native": "0.86.2",
|
|
46
46
|
"typescript": "~6.0.3",
|
|
47
|
+
"@impulse-ui-native/eslint-config": "1.0.0",
|
|
47
48
|
"@impulse-ui-native/prettier-config": "1.0.0",
|
|
48
|
-
"@impulse-ui-native/tsconfig": "1.0.0"
|
|
49
|
-
"@impulse-ui-native/eslint-config": "1.0.0"
|
|
49
|
+
"@impulse-ui-native/tsconfig": "1.0.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": ">=18",
|
|
53
|
-
"react-native": ">=0.73"
|
|
54
|
-
"@shopify/flash-list": ">=2"
|
|
53
|
+
"react-native": ">=0.73"
|
|
55
54
|
},
|
|
56
55
|
"publishConfig": {
|
|
57
56
|
"access": "public"
|