@open-pioneer/search 0.11.0 → 0.12.0-dev.20250905090001

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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # @open-pioneer/search
2
2
 
3
+ ## 0.12.0-dev.20250905090001
4
+
5
+ ### Minor Changes
6
+
7
+ - 0485014: Introduce search API and `resetInput` method (see [PR](https://github.com/open-pioneer/trails-openlayers-base-packages/pull/493)).
8
+
9
+ The search API allows programmatic access to the search component.
10
+ Currently, it provides a method to reset the search input field.
11
+
12
+ - 2732052: Icons have been changed to unify the appearance of the components. Preferably, Lucide react-icons are used.
13
+
14
+ ### Patch Changes
15
+
16
+ - 10d2fe7: Update dependencies
17
+ - da6a410: Update dependencies
18
+ - Updated dependencies [29a10df]
19
+ - Updated dependencies [10d2fe7]
20
+ - Updated dependencies [2702df4]
21
+ - Updated dependencies [12561fe]
22
+ - Updated dependencies [5df900f]
23
+ - Updated dependencies [8986b3b]
24
+ - Updated dependencies [aeb9000]
25
+ - Updated dependencies [5df900f]
26
+ - Updated dependencies [f1f69f2]
27
+ - Updated dependencies [da6a410]
28
+ - @open-pioneer/map@0.12.0-dev.20250905090001
29
+
3
30
  ## 0.11.0
4
31
 
5
32
  ### Minor Changes
@@ -86,7 +113,6 @@
86
113
  ### Minor Changes
87
114
 
88
115
  - 2fa8020: Update trails core package dependencies.
89
-
90
116
  - Also updates Chakra UI to the latest 2.x version and Chakra React Select to version 5.
91
117
  - Removes any obsolete references to `@chakra-ui/system`.
92
118
  This dependency seems to be no longer required and may lead to duplicate packages in your dependency tree.
@@ -1,8 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { FiSearch } from 'react-icons/fi';
2
+ import { LuSearch, LuX } from 'react-icons/lu';
3
3
  import { Icon, chakra, IconButton } from '@chakra-ui/react';
4
4
  import { Tooltip } from '@open-pioneer/chakra-snippets/tooltip';
5
- import { LuX } from 'react-icons/lu';
6
5
  import { chakraComponents } from 'chakra-react-select';
7
6
  import classNames from 'classnames';
8
7
  import { useIntl } from './_virtual/_virtual-pioneer-module_react-hooks.js';
@@ -46,7 +45,7 @@ function ValueContainer({
46
45
  className: classNames(props.className, "search-value-container")
47
46
  };
48
47
  return /* @__PURE__ */ jsxs(chakraComponents.ValueContainer, { ...containerProps, children: [
49
- !!children && /* @__PURE__ */ jsx(Icon, { position: "absolute", left: "8px", boxSize: "1.25em", children: /* @__PURE__ */ jsx(FiSearch, {}) }),
48
+ !!children && /* @__PURE__ */ jsx(Icon, { position: "absolute", left: "8px", boxSize: "1.25em", children: /* @__PURE__ */ jsx(LuSearch, {}) }),
50
49
  children
51
50
  ] });
52
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CustomComponents.js","sources":["CustomComponents.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { FiSearch } from \"react-icons/fi\";\nimport { chakra, Icon, IconButton } from \"@chakra-ui/react\";\nimport { Tooltip } from \"@open-pioneer/chakra-snippets/tooltip\";\nimport { LuX } from \"react-icons/lu\";\nimport {\n ClearIndicatorProps,\n GroupProps,\n IndicatorsContainerProps,\n InputProps,\n MenuProps,\n NoticeProps,\n OptionProps,\n Props as SelectProps,\n SingleValueProps,\n ValueContainerProps,\n chakraComponents\n} from \"chakra-react-select\";\nimport classNames from \"classnames\";\nimport { useIntl } from \"open-pioneer:react-hooks\";\nimport { UIEvent, useMemo } from \"react\";\nimport { SearchGroupOption, SearchOption } from \"./Search\";\n\nexport function MenuComp(props: MenuProps<SearchOption, false, SearchGroupOption>) {\n const hasInput = props.selectProps.inputValue.length > 0;\n const menuProps: typeof props = {\n ...props,\n className: classNames(props.className, {\n \"search-invisible\": !hasInput\n })\n };\n\n return <chakraComponents.Menu {...menuProps}>{props.children}</chakraComponents.Menu>;\n}\n\nexport function GroupComp(props: GroupProps<SearchOption, false, SearchGroupOption>) {\n const ariaLabel = props.data.label;\n const innerProps = {\n ...props.innerProps,\n \"aria-label\": ariaLabel,\n role: \"group\"\n };\n return <chakraComponents.Group {...props} innerProps={innerProps}></chakraComponents.Group>;\n}\n\nexport function NoOptionsMessage(props: NoticeProps<SearchOption, false, SearchGroupOption>) {\n const intl = useIntl();\n const noMessageText = intl.formatMessage({ id: \"noOptionsText\" });\n\n return (\n <chakraComponents.NoOptionsMessage {...props}>\n <chakra.span className=\"search-no-match\">{noMessageText}</chakra.span>\n </chakraComponents.NoOptionsMessage>\n );\n}\n\nexport function LoadingMessage(props: NoticeProps<SearchOption, false, SearchGroupOption>) {\n const intl = useIntl();\n const loadingText = intl.formatMessage({ id: \"loadingText\" });\n\n return (\n <chakraComponents.LoadingMessage {...props}>\n <chakra.span className=\"search-loading-text\">{loadingText}</chakra.span>\n </chakraComponents.LoadingMessage>\n );\n}\n\nexport function ValueContainer({\n children,\n ...props\n}: ValueContainerProps<SearchOption, false, SearchGroupOption>) {\n const containerProps: typeof props = {\n ...props,\n className: classNames(props.className, \"search-value-container\")\n };\n return (\n <chakraComponents.ValueContainer {...containerProps}>\n {!!children && (\n <Icon position=\"absolute\" left=\"8px\" boxSize=\"1.25em\">\n <FiSearch />\n </Icon>\n )}\n {children}\n </chakraComponents.ValueContainer>\n );\n}\n\nexport function Input(props: InputProps<SearchOption, false, SearchGroupOption>) {\n const inputProps: typeof props = {\n ...props,\n isHidden: false\n };\n return <chakraComponents.Input {...inputProps} />;\n}\n\nexport function SingleValue(_props: SingleValueProps<SearchOption, false, SearchGroupOption>) {\n // Never render anything (we use the text input to show the selected result)\n return null;\n}\n\nexport function IndicatorsContainer(\n props: IndicatorsContainerProps<SearchOption, false, SearchGroupOption>\n) {\n return (\n <chakraComponents.IndicatorsContainer {...props}>\n {props.children}\n {!props.selectProps.isLoading && props.selectProps.inputValue && (\n <CustomClearIndicator\n selectProps={props.selectProps}\n clearValue={props.clearValue}\n />\n )}\n </chakraComponents.IndicatorsContainer>\n );\n}\n\nfunction CustomClearIndicator(props: {\n clearValue(): void;\n selectProps: SelectProps<SearchOption, false, SearchGroupOption>;\n}) {\n const intl = useIntl();\n const clearButtonLabel = intl.formatMessage({\n id: \"ariaLabel.clearButton\"\n });\n const clickHandler = (e: UIEvent) => {\n e.preventDefault();\n e.stopPropagation();\n props.clearValue();\n };\n\n return (\n <Tooltip content={clearButtonLabel}>\n <IconButton\n size=\"sm\"\n variant=\"ghost\"\n mr=\"1px\"\n aria-label={clearButtonLabel}\n onClick={clickHandler}\n // needed for correct touch handling; select control would otherwise preventDefault()\n onTouchEnd={clickHandler}\n // Stop select component from opening the menu.\n // It will otherwise flash briefly because of a mouse down listener in the select.\n onMouseDown={(e) => e.preventDefault()}\n >\n <Icon>\n <LuX />\n </Icon>\n </IconButton>\n </Tooltip>\n );\n}\n\nexport function ClearIndicator(\n _props: ClearIndicatorProps<SearchOption, false, SearchGroupOption>\n) {\n // Never render anything; we use our own clear indicator\n return null;\n}\n\nexport function HighlightOption(props: OptionProps<SearchOption, false, SearchGroupOption>) {\n const userInput = props.selectProps.inputValue;\n const label = props.data.label;\n const optionProps: typeof props = {\n ...props,\n className: classNames(props.className, \"search-option\")\n };\n\n const highlightedLabel = useMemo(() => {\n return (\n <chakra.div className=\"search-option-label\">\n {userInput.trim().length > 0 ? getHighlightedLabel(label, userInput) : label}\n </chakra.div>\n );\n }, [label, userInput]);\n\n return <chakraComponents.Option {...optionProps}>{highlightedLabel}</chakraComponents.Option>;\n}\n\nfunction getHighlightedLabel(label: string, userInput: string) {\n const matchIndex = label.toLowerCase().indexOf(userInput.toLowerCase());\n if (matchIndex >= 0) {\n return (\n <>\n {label.substring(0, matchIndex)}\n <chakra.span key=\"highlighted\" className=\"search-highlighted-match\">\n {label.substring(matchIndex, matchIndex + userInput.length)}\n </chakra.span>\n {label.substring(matchIndex + userInput.length)}\n </>\n );\n }\n return label;\n}\n"],"names":[],"mappings":";;;;;;;;;;AAwBO,SAAS,SAAS,KAA0D,EAAA;AAC/E,EAAA,MAAM,QAAW,GAAA,KAAA,CAAM,WAAY,CAAA,UAAA,CAAW,MAAS,GAAA,CAAA;AACvD,EAAA,MAAM,SAA0B,GAAA;AAAA,IAC5B,GAAG,KAAA;AAAA,IACH,SAAA,EAAW,UAAW,CAAA,KAAA,CAAM,SAAW,EAAA;AAAA,MACnC,oBAAoB,CAAC;AAAA,KACxB;AAAA,GACL;AAEA,EAAA,2BAAQ,gBAAiB,CAAA,IAAA,EAAjB,EAAuB,GAAG,SAAA,EAAY,gBAAM,QAAS,EAAA,CAAA;AACjE;AAEO,SAAS,UAAU,KAA2D,EAAA;AACjF,EAAM,MAAA,SAAA,GAAY,MAAM,IAAK,CAAA,KAAA;AAC7B,EAAA,MAAM,UAAa,GAAA;AAAA,IACf,GAAG,KAAM,CAAA,UAAA;AAAA,IACT,YAAc,EAAA,SAAA;AAAA,IACd,IAAM,EAAA;AAAA,GACV;AACA,EAAA,2BAAQ,gBAAiB,CAAA,KAAA,EAAjB,EAAwB,GAAG,OAAO,UAAwB,EAAA,CAAA;AACtE;AAEO,SAAS,iBAAiB,KAA4D,EAAA;AACzF,EAAA,MAAM,OAAO,OAAQ,EAAA;AACrB,EAAA,MAAM,gBAAgB,IAAK,CAAA,aAAA,CAAc,EAAE,EAAA,EAAI,iBAAiB,CAAA;AAEhE,EAAA,uBACK,GAAA,CAAA,gBAAA,CAAiB,gBAAjB,EAAA,EAAmC,GAAG,KAAA,EACnC,QAAC,kBAAA,GAAA,CAAA,MAAA,CAAO,IAAP,EAAA,EAAY,SAAU,EAAA,iBAAA,EAAmB,yBAAc,CAC5D,EAAA,CAAA;AAER;AAEO,SAAS,eAAe,KAA4D,EAAA;AACvF,EAAA,MAAM,OAAO,OAAQ,EAAA;AACrB,EAAA,MAAM,cAAc,IAAK,CAAA,aAAA,CAAc,EAAE,EAAA,EAAI,eAAe,CAAA;AAE5D,EAAA,uBACK,GAAA,CAAA,gBAAA,CAAiB,cAAjB,EAAA,EAAiC,GAAG,KAAA,EACjC,QAAC,kBAAA,GAAA,CAAA,MAAA,CAAO,IAAP,EAAA,EAAY,SAAU,EAAA,qBAAA,EAAuB,uBAAY,CAC9D,EAAA,CAAA;AAER;AAEO,SAAS,cAAe,CAAA;AAAA,EAC3B,QAAA;AAAA,EACA,GAAG;AACP,CAAgE,EAAA;AAC5D,EAAA,MAAM,cAA+B,GAAA;AAAA,IACjC,GAAG,KAAA;AAAA,IACH,SAAW,EAAA,UAAA,CAAW,KAAM,CAAA,SAAA,EAAW,wBAAwB;AAAA,GACnE;AACA,EAAA,uBACK,IAAA,CAAA,gBAAA,CAAiB,cAAjB,EAAA,EAAiC,GAAG,cAChC,EAAA,QAAA,EAAA;AAAA,IAAA,CAAC,CAAC,QAAA,oBACE,GAAA,CAAA,IAAA,EAAA,EAAK,QAAS,EAAA,UAAA,EAAW,IAAK,EAAA,KAAA,EAAM,OAAQ,EAAA,QAAA,EACzC,QAAC,kBAAA,GAAA,CAAA,QAAA,EAAA,EAAS,CACd,EAAA,CAAA;AAAA,IAEH;AAAA,GACL,EAAA,CAAA;AAER;AAEO,SAAS,MAAM,KAA2D,EAAA;AAC7E,EAAA,MAAM,UAA2B,GAAA;AAAA,IAC7B,GAAG,KAAA;AAAA,IACH,QAAU,EAAA;AAAA,GACd;AACA,EAAA,uBAAQ,GAAA,CAAA,gBAAA,CAAiB,KAAjB,EAAA,EAAwB,GAAG,UAAY,EAAA,CAAA;AACnD;AAEO,SAAS,YAAY,MAAkE,EAAA;AAE1F,EAAO,OAAA,IAAA;AACX;AAEO,SAAS,oBACZ,KACF,EAAA;AACE,EAAA,uBACK,IAAA,CAAA,gBAAA,CAAiB,mBAAjB,EAAA,EAAsC,GAAG,KACrC,EAAA,QAAA,EAAA;AAAA,IAAM,KAAA,CAAA,QAAA;AAAA,IACN,CAAC,KAAM,CAAA,WAAA,CAAY,SAAa,IAAA,KAAA,CAAM,YAAY,UAC/C,oBAAA,GAAA;AAAA,MAAC,oBAAA;AAAA,MAAA;AAAA,QACG,aAAa,KAAM,CAAA,WAAA;AAAA,QACnB,YAAY,KAAM,CAAA;AAAA;AAAA;AACtB,GAER,EAAA,CAAA;AAER;AAEA,SAAS,qBAAqB,KAG3B,EAAA;AACC,EAAA,MAAM,OAAO,OAAQ,EAAA;AACrB,EAAM,MAAA,gBAAA,GAAmB,KAAK,aAAc,CAAA;AAAA,IACxC,EAAI,EAAA;AAAA,GACP,CAAA;AACD,EAAM,MAAA,YAAA,GAAe,CAAC,CAAe,KAAA;AACjC,IAAA,CAAA,CAAE,cAAe,EAAA;AACjB,IAAA,CAAA,CAAE,eAAgB,EAAA;AAClB,IAAA,KAAA,CAAM,UAAW,EAAA;AAAA,GACrB;AAEA,EACI,uBAAA,GAAA,CAAC,OAAQ,EAAA,EAAA,OAAA,EAAS,gBACd,EAAA,QAAA,kBAAA,GAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACG,IAAK,EAAA,IAAA;AAAA,MACL,OAAQ,EAAA,OAAA;AAAA,MACR,EAAG,EAAA,KAAA;AAAA,MACH,YAAY,EAAA,gBAAA;AAAA,MACZ,OAAS,EAAA,YAAA;AAAA,MAET,UAAY,EAAA,YAAA;AAAA,MAGZ,WAAa,EAAA,CAAC,CAAM,KAAA,CAAA,CAAE,cAAe,EAAA;AAAA,MAErC,QAAC,kBAAA,GAAA,CAAA,IAAA,EAAA,EACG,QAAC,kBAAA,GAAA,CAAA,GAAA,EAAA,EAAI,CACT,EAAA;AAAA;AAAA,GAER,EAAA,CAAA;AAER;AAEO,SAAS,eACZ,MACF,EAAA;AAEE,EAAO,OAAA,IAAA;AACX;AAEO,SAAS,gBAAgB,KAA4D,EAAA;AACxF,EAAM,MAAA,SAAA,GAAY,MAAM,WAAY,CAAA,UAAA;AACpC,EAAM,MAAA,KAAA,GAAQ,MAAM,IAAK,CAAA,KAAA;AACzB,EAAA,MAAM,WAA4B,GAAA;AAAA,IAC9B,GAAG,KAAA;AAAA,IACH,SAAW,EAAA,UAAA,CAAW,KAAM,CAAA,SAAA,EAAW,eAAe;AAAA,GAC1D;AAEA,EAAM,MAAA,gBAAA,GAAmB,QAAQ,MAAM;AACnC,IAAA,uBACK,GAAA,CAAA,MAAA,CAAO,GAAP,EAAA,EAAW,WAAU,qBACjB,EAAA,QAAA,EAAA,SAAA,CAAU,IAAK,EAAA,CAAE,SAAS,CAAI,GAAA,mBAAA,CAAoB,KAAO,EAAA,SAAS,IAAI,KAC3E,EAAA,CAAA;AAAA,GAEL,EAAA,CAAC,KAAO,EAAA,SAAS,CAAC,CAAA;AAErB,EAAA,2BAAQ,gBAAiB,CAAA,MAAA,EAAjB,EAAyB,GAAG,aAAc,QAAiB,EAAA,gBAAA,EAAA,CAAA;AACvE;AAEA,SAAS,mBAAA,CAAoB,OAAe,SAAmB,EAAA;AAC3D,EAAA,MAAM,aAAa,KAAM,CAAA,WAAA,GAAc,OAAQ,CAAA,SAAA,CAAU,aAAa,CAAA;AACtE,EAAA,IAAI,cAAc,CAAG,EAAA;AACjB,IAAA,uBAES,IAAA,CAAA,QAAA,EAAA,EAAA,QAAA,EAAA;AAAA,MAAM,KAAA,CAAA,SAAA,CAAU,GAAG,UAAU,CAAA;AAAA,sBAC7B,GAAA,CAAA,MAAA,CAAO,IAAP,EAAA,EAA8B,SAAU,EAAA,0BAAA,EACpC,QAAM,EAAA,KAAA,CAAA,SAAA,CAAU,UAAY,EAAA,UAAA,GAAa,SAAU,CAAA,MAAM,KAD7C,aAEjB,CAAA;AAAA,MACC,KAAM,CAAA,SAAA,CAAU,UAAa,GAAA,SAAA,CAAU,MAAM;AAAA,KAClD,EAAA,CAAA;AAAA;AAGR,EAAO,OAAA,KAAA;AACX;;;;"}
1
+ {"version":3,"file":"CustomComponents.js","sources":["CustomComponents.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { LuSearch } from \"react-icons/lu\";\nimport { chakra, Icon, IconButton } from \"@chakra-ui/react\";\nimport { Tooltip } from \"@open-pioneer/chakra-snippets/tooltip\";\nimport { LuX } from \"react-icons/lu\";\nimport {\n ClearIndicatorProps,\n GroupProps,\n IndicatorsContainerProps,\n InputProps,\n MenuProps,\n NoticeProps,\n OptionProps,\n Props as SelectProps,\n SingleValueProps,\n ValueContainerProps,\n chakraComponents\n} from \"chakra-react-select\";\nimport classNames from \"classnames\";\nimport { useIntl } from \"open-pioneer:react-hooks\";\nimport { UIEvent, useMemo } from \"react\";\nimport { SearchGroupOption, SearchOption } from \"./Search\";\n\nexport function MenuComp(props: MenuProps<SearchOption, false, SearchGroupOption>) {\n const hasInput = props.selectProps.inputValue.length > 0;\n const menuProps: typeof props = {\n ...props,\n className: classNames(props.className, {\n \"search-invisible\": !hasInput\n })\n };\n\n return <chakraComponents.Menu {...menuProps}>{props.children}</chakraComponents.Menu>;\n}\n\nexport function GroupComp(props: GroupProps<SearchOption, false, SearchGroupOption>) {\n const ariaLabel = props.data.label;\n const innerProps = {\n ...props.innerProps,\n \"aria-label\": ariaLabel,\n role: \"group\"\n };\n return <chakraComponents.Group {...props} innerProps={innerProps}></chakraComponents.Group>;\n}\n\nexport function NoOptionsMessage(props: NoticeProps<SearchOption, false, SearchGroupOption>) {\n const intl = useIntl();\n const noMessageText = intl.formatMessage({ id: \"noOptionsText\" });\n\n return (\n <chakraComponents.NoOptionsMessage {...props}>\n <chakra.span className=\"search-no-match\">{noMessageText}</chakra.span>\n </chakraComponents.NoOptionsMessage>\n );\n}\n\nexport function LoadingMessage(props: NoticeProps<SearchOption, false, SearchGroupOption>) {\n const intl = useIntl();\n const loadingText = intl.formatMessage({ id: \"loadingText\" });\n\n return (\n <chakraComponents.LoadingMessage {...props}>\n <chakra.span className=\"search-loading-text\">{loadingText}</chakra.span>\n </chakraComponents.LoadingMessage>\n );\n}\n\nexport function ValueContainer({\n children,\n ...props\n}: ValueContainerProps<SearchOption, false, SearchGroupOption>) {\n const containerProps: typeof props = {\n ...props,\n className: classNames(props.className, \"search-value-container\")\n };\n return (\n <chakraComponents.ValueContainer {...containerProps}>\n {!!children && (\n <Icon position=\"absolute\" left=\"8px\" boxSize=\"1.25em\">\n <LuSearch />\n </Icon>\n )}\n {children}\n </chakraComponents.ValueContainer>\n );\n}\n\nexport function Input(props: InputProps<SearchOption, false, SearchGroupOption>) {\n const inputProps: typeof props = {\n ...props,\n isHidden: false\n };\n return <chakraComponents.Input {...inputProps} />;\n}\n\nexport function SingleValue(_props: SingleValueProps<SearchOption, false, SearchGroupOption>) {\n // Never render anything (we use the text input to show the selected result)\n return null;\n}\n\nexport function IndicatorsContainer(\n props: IndicatorsContainerProps<SearchOption, false, SearchGroupOption>\n) {\n return (\n <chakraComponents.IndicatorsContainer {...props}>\n {props.children}\n {!props.selectProps.isLoading && props.selectProps.inputValue && (\n <CustomClearIndicator\n selectProps={props.selectProps}\n clearValue={props.clearValue}\n />\n )}\n </chakraComponents.IndicatorsContainer>\n );\n}\n\nfunction CustomClearIndicator(props: {\n clearValue(): void;\n selectProps: SelectProps<SearchOption, false, SearchGroupOption>;\n}) {\n const intl = useIntl();\n const clearButtonLabel = intl.formatMessage({\n id: \"ariaLabel.clearButton\"\n });\n const clickHandler = (e: UIEvent) => {\n e.preventDefault();\n e.stopPropagation();\n props.clearValue();\n };\n\n return (\n <Tooltip content={clearButtonLabel}>\n <IconButton\n size=\"sm\"\n variant=\"ghost\"\n mr=\"1px\"\n aria-label={clearButtonLabel}\n onClick={clickHandler}\n // needed for correct touch handling; select control would otherwise preventDefault()\n onTouchEnd={clickHandler}\n // Stop select component from opening the menu.\n // It will otherwise flash briefly because of a mouse down listener in the select.\n onMouseDown={(e) => e.preventDefault()}\n >\n <Icon>\n <LuX />\n </Icon>\n </IconButton>\n </Tooltip>\n );\n}\n\nexport function ClearIndicator(\n _props: ClearIndicatorProps<SearchOption, false, SearchGroupOption>\n) {\n // Never render anything; we use our own clear indicator\n return null;\n}\n\nexport function HighlightOption(props: OptionProps<SearchOption, false, SearchGroupOption>) {\n const userInput = props.selectProps.inputValue;\n const label = props.data.label;\n const optionProps: typeof props = {\n ...props,\n className: classNames(props.className, \"search-option\")\n };\n\n const highlightedLabel = useMemo(() => {\n return (\n <chakra.div className=\"search-option-label\">\n {userInput.trim().length > 0 ? getHighlightedLabel(label, userInput) : label}\n </chakra.div>\n );\n }, [label, userInput]);\n\n return <chakraComponents.Option {...optionProps}>{highlightedLabel}</chakraComponents.Option>;\n}\n\nfunction getHighlightedLabel(label: string, userInput: string) {\n const matchIndex = label.toLowerCase().indexOf(userInput.toLowerCase());\n if (matchIndex >= 0) {\n return (\n <>\n {label.substring(0, matchIndex)}\n <chakra.span key=\"highlighted\" className=\"search-highlighted-match\">\n {label.substring(matchIndex, matchIndex + userInput.length)}\n </chakra.span>\n {label.substring(matchIndex + userInput.length)}\n </>\n );\n }\n return label;\n}\n"],"names":[],"mappings":"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"}
package/README.md CHANGED
@@ -37,14 +37,14 @@ To change the placeholder text of the search field, use the optional property `p
37
37
  <Search sources={searchsources} placeholder="Search for cities" />
38
38
  ```
39
39
 
40
- ### Listening to events
40
+ ### Listening to events and using the search API
41
41
 
42
42
  To listen to the events `onSelect` and `onClear`, provide optional callback functions to the component.
43
43
  In case of the `onSelect` event, you can access the selected search result (and its search source)
44
44
  from the parameter `SelectSearchEvent`.
45
45
 
46
46
  ```tsx
47
- import { Search, SearchSelectEvent } from "@open-pioneer/search";
47
+ import { Search, SearchClearEvent, SearchSelectEvent } from "@open-pioneer/search";
48
48
  // ...
49
49
  <Search
50
50
  map={map}
@@ -52,12 +52,54 @@ import { Search, SearchSelectEvent } from "@open-pioneer/search";
52
52
  onSelect={(event: SearchSelectEvent) => {
53
53
  // do something
54
54
  }}
55
- onClear={() => {
55
+ onClear={(event: SearchClearEvent) => {
56
56
  // do something
57
57
  }}
58
58
  />;
59
59
  ```
60
60
 
61
+ The search API allows programmatic access to the search component.
62
+
63
+ Currently, the search API provides a method to clear the search input field.
64
+ To receive the API, listen to the `onReady` event which provides the `SearchApi` as a parameter once the search component is ready to use:
65
+
66
+ ```tsx
67
+ import { Search } from "@open-pioneer/search";
68
+ import { Button } from "@chakra-ui/react";
69
+ import { Search, SearchApi, SearchClearEvent, SearchReadyEvent } from "@open-pioneer/search";
70
+ import { NotificationService, Notifier } from "@open-pioneer/notifier";
71
+ import { useRef } from "react";
72
+
73
+ const searchApiRef = useRef<SearchApi>(undefined);
74
+ const sources = [/* your sources */];
75
+
76
+ // the clear event contains information about the trigger of the clear action (user or API call)
77
+ function onSearchCleared(clearEvent: SearchClearEvent) {
78
+ console.log(clearEvent.trigger); // "api-reset" if triggered via API
79
+ }
80
+
81
+ // ...
82
+ <Search
83
+ sources={sources}
84
+ onClear={onSearchCleared}
85
+ onReady={(event: SearchReadyEvent) => {
86
+ // get the API object from the ready event and store it somewhere
87
+ searchApiRef.current = event.api;
88
+ }}
89
+ onDisposed={() => {
90
+ searchApiRef.current = undefined;
91
+ }}
92
+ />
93
+
94
+ <Button
95
+ onClick={() => {
96
+ searchApiRef.current?.resetInput(); // use the API to clear the search input
97
+ }}
98
+ >
99
+ reset search input
100
+ </Button>
101
+ ```
102
+
61
103
  ### Positioning the search bar
62
104
 
63
105
  The search bar is not placed at a certain location by default; it will simply fill its parent.
package/Search.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { MapModelProps } from "@open-pioneer/map";
2
2
  import { CommonComponentProps } from "@open-pioneer/react-utils";
3
3
  import { FC } from "react";
4
- import { SearchResult, SearchSource } from "./api";
4
+ import { SearchClearEvent, SearchDisposedEvent, SearchReadyEvent, SearchResult, SearchSelectEvent, SearchSource } from "./api";
5
5
  export interface SearchOption {
6
6
  /** Unique value for this option. */
7
7
  value: string;
@@ -18,15 +18,6 @@ export interface SearchGroupOption {
18
18
  /** Set of options that belong to this group. */
19
19
  options: SearchOption[];
20
20
  }
21
- /**
22
- * Event type emitted when the user selects an item.
23
- */
24
- export interface SearchSelectEvent {
25
- /** The source that returned the {@link result}. */
26
- source: SearchSource;
27
- /** The search result selected by the user. */
28
- result: SearchResult;
29
- }
30
21
  /**
31
22
  * Properties supported by the {@link Search} component.
32
23
  */
@@ -57,7 +48,16 @@ export interface SearchProps extends CommonComponentProps, MapModelProps {
57
48
  /**
58
49
  * This event handler will be called when the user clears the search input.
59
50
  */
60
- onClear?: () => void;
51
+ onClear?: (event: SearchClearEvent) => void;
52
+ /**
53
+ * Callback that is triggered once when the search is initialized.
54
+ * The search API can be accessed by the `api` property of the {@link SearchReadyEvent}.
55
+ */
56
+ onReady?: (event: SearchReadyEvent) => void;
57
+ /**
58
+ * Callback that is triggered once when the search is disposed and unmounted.
59
+ */
60
+ onDisposed?: (event: SearchDisposedEvent) => void;
61
61
  }
62
62
  /**
63
63
  * A component that allows the user to search a given set of {@link SearchSource | SearchSources}.
package/Search.js CHANGED
@@ -1,19 +1,28 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { Box, Portal, chakra, useToken } from '@chakra-ui/react';
3
3
  import { createLogger, isAbortError } from '@open-pioneer/core';
4
- import { useMapModel } from '@open-pioneer/map';
4
+ import { useMapModelValue } from '@open-pioneer/map';
5
5
  import { useCommonComponentProps, useEvent } from '@open-pioneer/react-utils';
6
6
  import { Select } from 'chakra-react-select';
7
7
  import { useIntl } from './_virtual/_virtual-pioneer-module_react-hooks.js';
8
8
  import { useRef, useState, useEffect, useReducer, useCallback, useMemo } from 'react';
9
9
  import { GroupComp, ClearIndicator, IndicatorsContainer, ValueContainer, LoadingMessage, NoOptionsMessage, HighlightOption, SingleValue, Input, MenuComp } from './CustomComponents.js';
10
10
  import { SearchController } from './SearchController.js';
11
+ import { SearchApiImpl } from './SearchApiImpl.js';
11
12
 
12
13
  const LOG = createLogger("search:Search");
13
14
  const Search = (props) => {
14
- const { sources, searchTypingDelay, maxResultsPerGroup, onSelect, onClear } = props;
15
+ const {
16
+ sources,
17
+ searchTypingDelay,
18
+ maxResultsPerGroup,
19
+ onSelect,
20
+ onClear,
21
+ onReady,
22
+ onDisposed
23
+ } = props;
15
24
  const { containerProps } = useCommonComponentProps("search", props);
16
- const { map } = useMapModel(props);
25
+ const map = useMapModelValue(props);
17
26
  const intl = useIntl();
18
27
  const controller = useController(sources, searchTypingDelay, maxResultsPerGroup, map);
19
28
  const { input, search: search2, selectedOption, onInputChanged, onResultConfirmed } = useSearchState(controller);
@@ -26,6 +35,14 @@ const Search = (props) => {
26
35
  onInputChanged(newValue);
27
36
  }
28
37
  });
38
+ const clearInput = useEvent((trigger) => {
39
+ onInputChanged("");
40
+ if (trigger === "user") {
41
+ selectRef.current?.blur();
42
+ selectRef.current?.focus();
43
+ }
44
+ onClear?.({ trigger });
45
+ });
29
46
  const handleSelectChange = useEvent(
30
47
  (value, actionMeta) => {
31
48
  switch (actionMeta.action) {
@@ -39,10 +56,7 @@ const Search = (props) => {
39
56
  }
40
57
  break;
41
58
  case "clear":
42
- onInputChanged("");
43
- selectRef.current?.blur();
44
- selectRef.current?.focus();
45
- onClear?.();
59
+ clearInput("user");
46
60
  break;
47
61
  default:
48
62
  LOG.debug(`Unhandled action type '${actionMeta.action}'.`);
@@ -50,6 +64,7 @@ const Search = (props) => {
50
64
  }
51
65
  }
52
66
  );
67
+ useSearchApi(onReady, onDisposed, clearInput);
53
68
  const selectRef = useRef(null);
54
69
  return /* @__PURE__ */ jsxs(Box, { ...containerProps, children: [
55
70
  /* @__PURE__ */ jsx(
@@ -176,9 +191,6 @@ function useChakraStyles() {
176
191
  function useController(sources, searchTypingDelay, maxResultsPerGroup, map) {
177
192
  const [controller, setController] = useState(void 0);
178
193
  useEffect(() => {
179
- if (!map) {
180
- return;
181
- }
182
194
  const controller2 = new SearchController(map, sources);
183
195
  setController(controller2);
184
196
  return () => {
@@ -302,6 +314,25 @@ function mapSuggestions(suggestions) {
302
314
  );
303
315
  return options;
304
316
  }
317
+ function useSearchApi(onReady, onDisposed, clearInput) {
318
+ const apiRef = useRef(null);
319
+ if (!apiRef.current) {
320
+ apiRef.current = new SearchApiImpl(clearInput);
321
+ }
322
+ const api = apiRef.current;
323
+ const readyTrigger = useEvent(() => {
324
+ onReady?.({
325
+ api
326
+ });
327
+ });
328
+ const disposeTrigger = useEvent(() => {
329
+ onDisposed?.({});
330
+ });
331
+ useEffect(() => {
332
+ readyTrigger();
333
+ return disposeTrigger;
334
+ }, [readyTrigger, disposeTrigger]);
335
+ }
305
336
 
306
337
  export { Search };
307
338
  //# sourceMappingURL=Search.js.map
package/Search.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Search.js","sources":["Search.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Box, chakra, Portal, useToken } from \"@chakra-ui/react\";\nimport { createLogger, isAbortError } from \"@open-pioneer/core\";\nimport { MapModel, MapModelProps, useMapModel } from \"@open-pioneer/map\";\nimport { CommonComponentProps, useCommonComponentProps, useEvent } from \"@open-pioneer/react-utils\";\nimport { PackageIntl } from \"@open-pioneer/runtime\";\nimport {\n ActionMeta,\n AriaLiveMessages,\n AriaOnChange,\n AriaOnFocus,\n ChakraStylesConfig,\n InputActionMeta,\n Select,\n SelectInstance,\n Props as SelectProps,\n SingleValue\n} from \"chakra-react-select\";\nimport { useIntl } from \"open-pioneer:react-hooks\";\nimport { FC, useCallback, useEffect, useMemo, useReducer, useRef, useState } from \"react\";\nimport {\n ClearIndicator,\n GroupComp,\n HighlightOption,\n IndicatorsContainer,\n Input,\n LoadingMessage,\n MenuComp,\n NoOptionsMessage,\n SingleValue as SingleValueComp,\n ValueContainer\n} from \"./CustomComponents\";\nimport { SearchController, SuggestionGroup } from \"./SearchController\";\nimport { SearchResult, SearchSource } from \"./api\";\n\nconst LOG = createLogger(\"search:Search\");\n\nexport interface SearchOption {\n /** Unique value for this option. */\n value: string;\n\n /** Display text shown in menu. */\n label: string;\n\n /** Search source that returned the suggestion. */\n source: SearchSource;\n\n /** The raw result from the search source. */\n result: SearchResult;\n}\n\nexport interface SearchGroupOption {\n /** Display text shown in menu. */\n label: string;\n\n /** Set of options that belong to this group. */\n options: SearchOption[];\n}\n\n/**\n * Event type emitted when the user selects an item.\n */\nexport interface SearchSelectEvent {\n /** The source that returned the {@link result}. */\n source: SearchSource;\n\n /** The search result selected by the user. */\n result: SearchResult;\n}\n\n/**\n * Properties supported by the {@link Search} component.\n */\nexport interface SearchProps extends CommonComponentProps, MapModelProps {\n /**\n * Data sources to be searched on.\n */\n sources: SearchSource[];\n\n /**\n * Typing delay (in milliseconds) before the async search query starts after the user types in the search term.\n * Defaults to `200`.\n */\n searchTypingDelay?: number;\n\n /**\n * The maximum number of results shown per group.\n * Defaults to `5`.\n */\n maxResultsPerGroup?: number;\n\n /**\n * The placeholder text shown in the search input field when it is empty.\n * Defaults to a generic (and localized) hint.\n */\n placeholder?: string;\n\n /**\n * This event handler will be called when the user selects a search result.\n */\n onSelect?: (event: SearchSelectEvent) => void;\n\n /**\n * This event handler will be called when the user clears the search input.\n */\n onClear?: () => void;\n}\n\n/**\n * A component that allows the user to search a given set of {@link SearchSource | SearchSources}.\n */\nexport const Search: FC<SearchProps> = (props) => {\n const { sources, searchTypingDelay, maxResultsPerGroup, onSelect, onClear } = props;\n const { containerProps } = useCommonComponentProps(\"search\", props);\n const { map } = useMapModel(props);\n const intl = useIntl();\n const controller = useController(sources, searchTypingDelay, maxResultsPerGroup, map);\n const { input, search, selectedOption, onInputChanged, onResultConfirmed } =\n useSearchState(controller);\n\n const chakraStyles = useChakraStyles();\n const ariaMessages = useAriaMessages(intl);\n const components = useCustomComponents();\n\n const portalDiv = useRef<HTMLDivElement>(null);\n\n const handleInputChange = useEvent((newValue: string, actionMeta: InputActionMeta) => {\n // Only update the input if the user actually typed something.\n // This keeps the input content if the user focuses another element or if the menu is closed.\n if (actionMeta.action === \"input-change\") {\n onInputChanged(newValue);\n }\n });\n\n const handleSelectChange = useEvent(\n (value: SingleValue<SearchOption>, actionMeta: ActionMeta<SearchOption>) => {\n switch (actionMeta.action) {\n case \"select-option\":\n if (value) {\n // Updates the input field with the option label\n onResultConfirmed(value);\n onSelect?.({\n source: value.source,\n result: value.result\n });\n }\n break;\n case \"clear\":\n // Updates the input field\n onInputChanged(\"\");\n\n // the next two lines are a workaround for the open bug in react-select regarding the\n // cursor not being shown after clearing, although the component is focussed:\n // https://github.com/JedWatson/react-select/issues/3871\n selectRef.current?.blur();\n selectRef.current?.focus();\n onClear?.();\n break;\n default:\n LOG.debug(`Unhandled action type '${actionMeta.action}'.`);\n break;\n }\n }\n );\n\n const selectRef = useRef<SelectInstance<SearchOption, false, SearchGroupOption>>(null);\n return (\n <Box {...containerProps}>\n <Select<SearchOption, false, SearchGroupOption>\n className=\"search-component\"\n classNamePrefix=\"react-select\"\n ref={selectRef}\n inputValue={input}\n onInputChange={handleInputChange}\n aria-label={intl.formatMessage({ id: \"ariaLabel.search\" })}\n ariaLiveMessages={ariaMessages}\n selectedOptionStyle=\"color\"\n selectedOptionColorPalette=\"colorPalette\"\n chakraStyles={chakraStyles}\n isClearable={true}\n placeholder={props.placeholder ?? intl.formatMessage({ id: \"searchPlaceholder\" })}\n closeMenuOnSelect={true}\n isLoading={search.kind === \"loading\"}\n options={search.kind === \"ready\" ? search.results : undefined}\n filterOption={() => true} // always show all options (don't filter based on input text)\n tabSelectsValue={false}\n components={components}\n onChange={handleSelectChange}\n value={selectedOption}\n menuPortalTarget={portalDiv.current}\n />\n <Portal>\n <chakra.div ref={portalDiv} className=\"search-component-menu\" />\n </Portal>\n </Box>\n );\n};\n\n/**\n * Provides custom aria messages for the select component.\n */\nfunction useAriaMessages(\n intl: PackageIntl\n): AriaLiveMessages<SearchOption, false, SearchGroupOption> {\n return useMemo(() => {\n /**\n * Method to create Aria-String for focus-Event\n */\n const onFocus: AriaOnFocus<SearchOption> = () => {\n //no aria string for focus-events because in some screen readers (NVDA) and browsers (Chrome) updating the aria string causes the instructions to be read out again each time a select option is focused\n return \"\";\n };\n\n /**\n * Method to create Aria-String for value-change-Event\n */\n const onChange: AriaOnChange<SearchOption, boolean> = () => {\n //no aria string for change-events because in some screen readers (NVDA) and browsers (Chrome) updating the aria string causes the instructions to be read out again each time a select option is focused\n return \"\";\n };\n\n /**\n * Method to create Aria-String for instruction\n */\n const guidance = () => {\n return `${intl.formatMessage({ id: \"ariaLabel.instructions\" })}`;\n };\n\n /**\n * Method to create Aria-String for result length\n */\n const onFilter = () => {\n return \"\";\n };\n\n return {\n onFocus,\n onChange,\n guidance,\n onFilter\n };\n }, [intl]);\n}\n\n/**\n * Customizes the inner components used by the select component.\n */\nfunction useCustomComponents(): SelectProps<SearchOption, false, SearchGroupOption>[\"components\"] {\n return useMemo(() => {\n return {\n Menu: MenuComp,\n Input: Input,\n SingleValue: SingleValueComp,\n Option: HighlightOption,\n NoOptionsMessage: NoOptionsMessage,\n LoadingMessage: LoadingMessage,\n ValueContainer: ValueContainer,\n IndicatorsContainer: IndicatorsContainer,\n ClearIndicator: ClearIndicator,\n Group: GroupComp\n };\n }, []);\n}\n\n/**\n * Customizes components styles within the select component.\n */\nfunction useChakraStyles() {\n const [groupHeadingBg, focussedItemBg, selectedItemBg] = useToken(\"colors\", [\n \"colorPalette.100\",\n \"colorPalette.50\",\n \"colorPalette.500\"\n ]);\n return useMemo(() => {\n const chakraStyles: ChakraStylesConfig<SearchOption, false, SearchGroupOption> = {\n control: (provided) => ({\n ...provided,\n paddingInline: 0\n }),\n inputContainer: (provided) => ({\n ...provided,\n gridTemplateAreas: \"'area area area'\",\n display: \"grid\"\n }),\n indicatorsContainer: (provided) => ({\n ...provided,\n // pointerEvents none can sneak in via chakra theme from <Select />\n pointerEvents: \"auto\"\n }),\n input: (provided) => ({\n ...provided,\n gridArea: \"area\"\n }),\n groupHeading: (provided) => ({\n ...provided,\n backgroundColor: groupHeadingBg,\n padding: \"8px 12px\",\n // make Header look like normal options:\n fontSize: \"inherit\",\n fontWeight: \"inherit\"\n }),\n option: (provided) => ({\n ...provided,\n backgroundColor: \"inherit\",\n _highlighted: {\n backgroundColor: focussedItemBg\n },\n _selected: {\n // Prevent white on white\n backgroundColor: selectedItemBg\n }\n }),\n dropdownIndicator: (provided) => ({\n ...provided,\n display: \"none\" // always hide\n })\n };\n return chakraStyles;\n }, [groupHeadingBg, focussedItemBg, selectedItemBg]);\n}\n\n/**\n * Creates a controller to search on the given sources.\n */\nfunction useController(\n sources: SearchSource[],\n searchTypingDelay: number | undefined,\n maxResultsPerGroup: number | undefined,\n map: MapModel | undefined\n) {\n const [controller, setController] = useState<SearchController | undefined>(undefined);\n useEffect(() => {\n if (!map) {\n return;\n }\n const controller = new SearchController(map, sources);\n setController(controller);\n return () => {\n controller.destroy();\n setController(undefined);\n };\n }, [map, sources]);\n\n useEffect(() => {\n if (controller) {\n controller.searchTypingDelay = searchTypingDelay;\n }\n }, [controller, searchTypingDelay]);\n useEffect(() => {\n if (controller) {\n controller.maxResultsPerSource = maxResultsPerGroup;\n }\n }, [controller, maxResultsPerGroup]);\n return controller;\n}\n\ntype SearchResultsReady = {\n kind: \"ready\";\n results: SearchGroupOption[];\n};\n\ntype SearchResultsLoading = {\n kind: \"loading\";\n};\n\ntype SearchResultsState = SearchResultsReady | SearchResultsLoading;\n\n/**\n * Keeps track of the current input text, active searches and their results.\n *\n * NOTE: it would be great to merge this state handling with the search controller\n * in a future revision.\n */\nfunction useSearchState(controller: SearchController | undefined) {\n interface FullSearchState {\n query: string;\n selectedOption: SearchOption | null;\n search: SearchResultsState;\n }\n\n type Action =\n | { kind: \"input\"; query: string }\n | { kind: \"select-option\"; option: SearchOption }\n | { kind: \"load-results\" }\n | { kind: \"accept-results\"; results: SearchGroupOption[] };\n\n const [state, dispatch] = useReducer(\n (current: FullSearchState, action: Action): FullSearchState => {\n switch (action.kind) {\n case \"input\":\n return {\n ...current,\n query: action.query,\n selectedOption: null\n };\n case \"select-option\":\n return {\n ...current,\n selectedOption: action.option,\n query: action.option.label\n };\n case \"load-results\":\n return {\n ...current,\n search: {\n kind: \"loading\"\n }\n };\n case \"accept-results\":\n return {\n ...current,\n search: {\n kind: \"ready\",\n results: action.results\n }\n };\n }\n },\n undefined,\n (): FullSearchState => ({\n query: \"\",\n selectedOption: null,\n search: {\n kind: \"ready\",\n results: []\n }\n })\n );\n\n // Stores the promise for the current search.\n // Any results from outdated searches are ignored.\n const currentSearch = useRef<Promise<unknown>>(undefined);\n const startSearch = useEvent((query: string) => {\n if (!controller) {\n currentSearch.current = undefined;\n dispatch({ kind: \"accept-results\", results: [] });\n return;\n }\n\n LOG.isDebug() && LOG.debug(`Starting new search for query ${JSON.stringify(query)}.`);\n dispatch({ kind: \"load-results\" });\n const promise = (currentSearch.current = search(controller, query).then((results) => {\n // Check if this job is still current\n if (currentSearch.current === promise) {\n dispatch({ kind: \"accept-results\", results });\n }\n }));\n });\n\n // Called when the user confirms a search result\n const onResultConfirmed = useCallback((option: SearchOption) => {\n // Do not start a new search when the user confirms a result\n dispatch({ kind: \"select-option\", option });\n }, []);\n\n // Called when a user types into the input field\n const onInputChanged = useCallback(\n (newValue: string) => {\n // Trigger a new search if the user changes the query by typing\n dispatch({ kind: \"input\", query: newValue });\n startSearch(newValue);\n },\n [startSearch]\n );\n\n return {\n input: state.query,\n search: state.search,\n selectedOption: state.selectedOption,\n onResultConfirmed,\n onInputChanged\n };\n}\n\nasync function search(controller: SearchController, query: string): Promise<SearchGroupOption[]> {\n let suggestions: SuggestionGroup[];\n try {\n suggestions = await controller.search(query);\n } catch (error) {\n if (!isAbortError(error)) {\n LOG.error(`Search failed`, error);\n }\n suggestions = [];\n }\n return mapSuggestions(suggestions);\n}\n\nfunction mapSuggestions(suggestions: SuggestionGroup[]): SearchGroupOption[] {\n const options = suggestions.map(\n (group, groupIndex): SearchGroupOption => ({\n label: group.label,\n options: group.results.map((suggestion): SearchOption => {\n return {\n value: `${groupIndex}-${suggestion.id}`,\n label: suggestion.label,\n source: group.source,\n result: suggestion\n };\n })\n })\n );\n return options;\n}\n"],"names":["search","SingleValueComp","controller"],"mappings":"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"}
1
+ {"version":3,"file":"Search.js","sources":["Search.tsx"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { Box, chakra, Portal, useToken } from \"@chakra-ui/react\";\nimport { createLogger, isAbortError } from \"@open-pioneer/core\";\nimport { MapModel, MapModelProps, useMapModelValue } from \"@open-pioneer/map\";\nimport { CommonComponentProps, useCommonComponentProps, useEvent } from \"@open-pioneer/react-utils\";\nimport { PackageIntl } from \"@open-pioneer/runtime\";\nimport {\n ActionMeta,\n AriaLiveMessages,\n AriaOnChange,\n AriaOnFocus,\n ChakraStylesConfig,\n InputActionMeta,\n Select,\n SelectInstance,\n Props as SelectProps,\n SingleValue\n} from \"chakra-react-select\";\nimport { useIntl } from \"open-pioneer:react-hooks\";\nimport { FC, useCallback, useEffect, useMemo, useReducer, useRef, useState } from \"react\";\nimport {\n ClearIndicator,\n GroupComp,\n HighlightOption,\n IndicatorsContainer,\n Input,\n LoadingMessage,\n MenuComp,\n NoOptionsMessage,\n SingleValue as SingleValueComp,\n ValueContainer\n} from \"./CustomComponents\";\nimport { SearchController, SuggestionGroup } from \"./SearchController\";\nimport {\n SearchClearEvent,\n SearchApi,\n SearchDisposedEvent,\n SearchReadyEvent,\n SearchResult,\n SearchSelectEvent,\n SearchSource,\n SearchClearTrigger\n} from \"./api\";\nimport { SearchApiImpl } from \"./SearchApiImpl\";\n\nconst LOG = createLogger(\"search:Search\");\n\nexport interface SearchOption {\n /** Unique value for this option. */\n value: string;\n\n /** Display text shown in menu. */\n label: string;\n\n /** Search source that returned the suggestion. */\n source: SearchSource;\n\n /** The raw result from the search source. */\n result: SearchResult;\n}\n\nexport interface SearchGroupOption {\n /** Display text shown in menu. */\n label: string;\n\n /** Set of options that belong to this group. */\n options: SearchOption[];\n}\n\n/**\n * Properties supported by the {@link Search} component.\n */\nexport interface SearchProps extends CommonComponentProps, MapModelProps {\n /**\n * Data sources to be searched on.\n */\n sources: SearchSource[];\n\n /**\n * Typing delay (in milliseconds) before the async search query starts after the user types in the search term.\n * Defaults to `200`.\n */\n searchTypingDelay?: number;\n\n /**\n * The maximum number of results shown per group.\n * Defaults to `5`.\n */\n maxResultsPerGroup?: number;\n\n /**\n * The placeholder text shown in the search input field when it is empty.\n * Defaults to a generic (and localized) hint.\n */\n placeholder?: string;\n\n /**\n * This event handler will be called when the user selects a search result.\n */\n onSelect?: (event: SearchSelectEvent) => void;\n\n /**\n * This event handler will be called when the user clears the search input.\n */\n onClear?: (event: SearchClearEvent) => void;\n\n /**\n * Callback that is triggered once when the search is initialized.\n * The search API can be accessed by the `api` property of the {@link SearchReadyEvent}.\n */\n onReady?: (event: SearchReadyEvent) => void;\n\n /**\n * Callback that is triggered once when the search is disposed and unmounted.\n */\n onDisposed?: (event: SearchDisposedEvent) => void;\n}\n\n/**\n * A component that allows the user to search a given set of {@link SearchSource | SearchSources}.\n */\nexport const Search: FC<SearchProps> = (props) => {\n const {\n sources,\n searchTypingDelay,\n maxResultsPerGroup,\n onSelect,\n onClear,\n onReady,\n onDisposed\n } = props;\n const { containerProps } = useCommonComponentProps(\"search\", props);\n const map = useMapModelValue(props);\n const intl = useIntl();\n const controller = useController(sources, searchTypingDelay, maxResultsPerGroup, map);\n const { input, search, selectedOption, onInputChanged, onResultConfirmed } =\n useSearchState(controller);\n\n const chakraStyles = useChakraStyles();\n const ariaMessages = useAriaMessages(intl);\n const components = useCustomComponents();\n\n const portalDiv = useRef<HTMLDivElement>(null);\n\n const handleInputChange = useEvent((newValue: string, actionMeta: InputActionMeta) => {\n // Only update the input if the user actually typed something.\n // This keeps the input content if the user focuses another element or if the menu is closed.\n if (actionMeta.action === \"input-change\") {\n onInputChanged(newValue);\n }\n });\n\n const clearInput = useEvent((trigger: SearchClearTrigger) => {\n // Updates the input field\n onInputChanged(\"\");\n\n // the next two lines are a workaround for the open bug in react-select regarding the\n // cursor not being shown after clearing, although the component is focussed:\n // https://github.com/JedWatson/react-select/issues/3871\n if (trigger === \"user\") {\n selectRef.current?.blur();\n selectRef.current?.focus();\n }\n\n onClear?.({ trigger: trigger });\n });\n\n const handleSelectChange = useEvent(\n (value: SingleValue<SearchOption>, actionMeta: ActionMeta<SearchOption>) => {\n switch (actionMeta.action) {\n case \"select-option\":\n if (value) {\n // Updates the input field with the option label\n onResultConfirmed(value);\n onSelect?.({\n source: value.source,\n result: value.result\n });\n }\n break;\n case \"clear\":\n clearInput(\"user\");\n break;\n default:\n LOG.debug(`Unhandled action type '${actionMeta.action}'.`);\n break;\n }\n }\n );\n\n useSearchApi(onReady, onDisposed, clearInput);\n\n const selectRef = useRef<SelectInstance<SearchOption, false, SearchGroupOption>>(null);\n return (\n <Box {...containerProps}>\n <Select<SearchOption, false, SearchGroupOption>\n className=\"search-component\"\n classNamePrefix=\"react-select\"\n ref={selectRef}\n inputValue={input}\n onInputChange={handleInputChange}\n aria-label={intl.formatMessage({ id: \"ariaLabel.search\" })}\n ariaLiveMessages={ariaMessages}\n selectedOptionStyle=\"color\"\n selectedOptionColorPalette=\"colorPalette\"\n chakraStyles={chakraStyles}\n isClearable={true}\n placeholder={props.placeholder ?? intl.formatMessage({ id: \"searchPlaceholder\" })}\n closeMenuOnSelect={true}\n isLoading={search.kind === \"loading\"}\n options={search.kind === \"ready\" ? search.results : undefined}\n filterOption={() => true} // always show all options (don't filter based on input text)\n tabSelectsValue={false}\n components={components}\n onChange={handleSelectChange}\n value={selectedOption}\n menuPortalTarget={portalDiv.current}\n />\n <Portal>\n <chakra.div ref={portalDiv} className=\"search-component-menu\" />\n </Portal>\n </Box>\n );\n};\n\n/**\n * Provides custom aria messages for the select component.\n */\nfunction useAriaMessages(\n intl: PackageIntl\n): AriaLiveMessages<SearchOption, false, SearchGroupOption> {\n return useMemo(() => {\n /**\n * Method to create Aria-String for focus-Event\n */\n const onFocus: AriaOnFocus<SearchOption> = () => {\n //no aria string for focus-events because in some screen readers (NVDA) and browsers (Chrome) updating the aria string causes the instructions to be read out again each time a select option is focused\n return \"\";\n };\n\n /**\n * Method to create Aria-String for value-change-Event\n */\n const onChange: AriaOnChange<SearchOption, boolean> = () => {\n //no aria string for change-events because in some screen readers (NVDA) and browsers (Chrome) updating the aria string causes the instructions to be read out again each time a select option is focused\n return \"\";\n };\n\n /**\n * Method to create Aria-String for instruction\n */\n const guidance = () => {\n return `${intl.formatMessage({ id: \"ariaLabel.instructions\" })}`;\n };\n\n /**\n * Method to create Aria-String for result length\n */\n const onFilter = () => {\n return \"\";\n };\n\n return {\n onFocus,\n onChange,\n guidance,\n onFilter\n };\n }, [intl]);\n}\n\n/**\n * Customizes the inner components used by the select component.\n */\nfunction useCustomComponents(): SelectProps<SearchOption, false, SearchGroupOption>[\"components\"] {\n return useMemo(() => {\n return {\n Menu: MenuComp,\n Input: Input,\n SingleValue: SingleValueComp,\n Option: HighlightOption,\n NoOptionsMessage: NoOptionsMessage,\n LoadingMessage: LoadingMessage,\n ValueContainer: ValueContainer,\n IndicatorsContainer: IndicatorsContainer,\n ClearIndicator: ClearIndicator,\n Group: GroupComp\n };\n }, []);\n}\n\n/**\n * Customizes components styles within the select component.\n */\nfunction useChakraStyles() {\n const [groupHeadingBg, focussedItemBg, selectedItemBg] = useToken(\"colors\", [\n \"colorPalette.100\",\n \"colorPalette.50\",\n \"colorPalette.500\"\n ]);\n return useMemo(() => {\n const chakraStyles: ChakraStylesConfig<SearchOption, false, SearchGroupOption> = {\n control: (provided) => ({\n ...provided,\n paddingInline: 0\n }),\n inputContainer: (provided) => ({\n ...provided,\n gridTemplateAreas: \"'area area area'\",\n display: \"grid\"\n }),\n indicatorsContainer: (provided) => ({\n ...provided,\n // pointerEvents none can sneak in via chakra theme from <Select />\n pointerEvents: \"auto\"\n }),\n input: (provided) => ({\n ...provided,\n gridArea: \"area\"\n }),\n groupHeading: (provided) => ({\n ...provided,\n backgroundColor: groupHeadingBg,\n padding: \"8px 12px\",\n // make Header look like normal options:\n fontSize: \"inherit\",\n fontWeight: \"inherit\"\n }),\n option: (provided) => ({\n ...provided,\n backgroundColor: \"inherit\",\n _highlighted: {\n backgroundColor: focussedItemBg\n },\n _selected: {\n // Prevent white on white\n backgroundColor: selectedItemBg\n }\n }),\n dropdownIndicator: (provided) => ({\n ...provided,\n display: \"none\" // always hide\n })\n };\n return chakraStyles;\n }, [groupHeadingBg, focussedItemBg, selectedItemBg]);\n}\n\n/**\n * Creates a controller to search on the given sources.\n */\nfunction useController(\n sources: SearchSource[],\n searchTypingDelay: number | undefined,\n maxResultsPerGroup: number | undefined,\n map: MapModel\n) {\n const [controller, setController] = useState<SearchController | undefined>(undefined);\n useEffect(() => {\n const controller = new SearchController(map, sources);\n setController(controller);\n return () => {\n controller.destroy();\n setController(undefined);\n };\n }, [map, sources]);\n\n useEffect(() => {\n if (controller) {\n controller.searchTypingDelay = searchTypingDelay;\n }\n }, [controller, searchTypingDelay]);\n useEffect(() => {\n if (controller) {\n controller.maxResultsPerSource = maxResultsPerGroup;\n }\n }, [controller, maxResultsPerGroup]);\n return controller;\n}\n\ntype SearchResultsReady = {\n kind: \"ready\";\n results: SearchGroupOption[];\n};\n\ntype SearchResultsLoading = {\n kind: \"loading\";\n};\n\ntype SearchResultsState = SearchResultsReady | SearchResultsLoading;\n\n/**\n * Keeps track of the current input text, active searches and their results.\n *\n * NOTE: it would be great to merge this state handling with the search controller\n * in a future revision.\n */\nfunction useSearchState(controller: SearchController | undefined) {\n interface FullSearchState {\n query: string;\n selectedOption: SearchOption | null;\n search: SearchResultsState;\n }\n\n type Action =\n | { kind: \"input\"; query: string }\n | { kind: \"select-option\"; option: SearchOption }\n | { kind: \"load-results\" }\n | { kind: \"accept-results\"; results: SearchGroupOption[] };\n\n const [state, dispatch] = useReducer(\n (current: FullSearchState, action: Action): FullSearchState => {\n switch (action.kind) {\n case \"input\":\n return {\n ...current,\n query: action.query,\n selectedOption: null\n };\n case \"select-option\":\n return {\n ...current,\n selectedOption: action.option,\n query: action.option.label\n };\n case \"load-results\":\n return {\n ...current,\n search: {\n kind: \"loading\"\n }\n };\n case \"accept-results\":\n return {\n ...current,\n search: {\n kind: \"ready\",\n results: action.results\n }\n };\n }\n },\n undefined,\n (): FullSearchState => ({\n query: \"\",\n selectedOption: null,\n search: {\n kind: \"ready\",\n results: []\n }\n })\n );\n\n // Stores the promise for the current search.\n // Any results from outdated searches are ignored.\n const currentSearch = useRef<Promise<unknown>>(undefined);\n const startSearch = useEvent((query: string) => {\n if (!controller) {\n currentSearch.current = undefined;\n dispatch({ kind: \"accept-results\", results: [] });\n return;\n }\n\n LOG.isDebug() && LOG.debug(`Starting new search for query ${JSON.stringify(query)}.`);\n dispatch({ kind: \"load-results\" });\n const promise = (currentSearch.current = search(controller, query).then((results) => {\n // Check if this job is still current\n if (currentSearch.current === promise) {\n dispatch({ kind: \"accept-results\", results });\n }\n }));\n });\n\n // Called when the user confirms a search result\n const onResultConfirmed = useCallback((option: SearchOption) => {\n // Do not start a new search when the user confirms a result\n dispatch({ kind: \"select-option\", option });\n }, []);\n\n // Called when a user types into the input field\n const onInputChanged = useCallback(\n (newValue: string) => {\n // Trigger a new search if the user changes the query by typing\n dispatch({ kind: \"input\", query: newValue });\n startSearch(newValue);\n },\n [startSearch]\n );\n\n return {\n input: state.query,\n search: state.search,\n selectedOption: state.selectedOption,\n onResultConfirmed,\n onInputChanged\n };\n}\n\nasync function search(controller: SearchController, query: string): Promise<SearchGroupOption[]> {\n let suggestions: SuggestionGroup[];\n try {\n suggestions = await controller.search(query);\n } catch (error) {\n if (!isAbortError(error)) {\n LOG.error(`Search failed`, error);\n }\n suggestions = [];\n }\n return mapSuggestions(suggestions);\n}\n\nfunction mapSuggestions(suggestions: SuggestionGroup[]): SearchGroupOption[] {\n const options = suggestions.map(\n (group, groupIndex): SearchGroupOption => ({\n label: group.label,\n options: group.results.map((suggestion): SearchOption => {\n return {\n value: `${groupIndex}-${suggestion.id}`,\n label: suggestion.label,\n source: group.source,\n result: suggestion\n };\n })\n })\n );\n return options;\n}\n\n// Note: `clearInput` must be stable because only the initial value is used to construct the API instance at this time.\nfunction useSearchApi(\n onReady: ((event: SearchReadyEvent) => void) | undefined,\n onDisposed: ((event: SearchDisposedEvent) => void) | undefined,\n clearInput: (trigger: SearchClearTrigger) => void\n) {\n const apiRef = useRef<SearchApi>(null);\n if (!apiRef.current) {\n apiRef.current = new SearchApiImpl(clearInput);\n }\n\n const api = apiRef.current;\n\n const readyTrigger = useEvent(() => {\n onReady?.({\n api\n });\n });\n\n const disposeTrigger = useEvent(() => {\n onDisposed?.({});\n });\n\n // Trigger ready / dispose on mount / unmount, but if the callbacks change.\n // useEvent() returns a stable function reference.\n useEffect(() => {\n readyTrigger();\n return disposeTrigger;\n }, [readyTrigger, disposeTrigger]);\n}\n"],"names":["search","SingleValueComp","controller"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { SearchApi, SearchClearTrigger } from "./api";
2
+ export declare class SearchApiImpl implements SearchApi {
3
+ #private;
4
+ constructor(clearInput: (trigger: SearchClearTrigger) => void);
5
+ resetInput(): void;
6
+ }
@@ -0,0 +1,12 @@
1
+ class SearchApiImpl {
2
+ #clearInput;
3
+ constructor(clearInput) {
4
+ this.#clearInput = clearInput;
5
+ }
6
+ resetInput() {
7
+ this.#clearInput("api-reset");
8
+ }
9
+ }
10
+
11
+ export { SearchApiImpl };
12
+ //# sourceMappingURL=SearchApiImpl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchApiImpl.js","sources":["SearchApiImpl.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { SearchApi, SearchClearTrigger } from \"./api\";\n\nexport class SearchApiImpl implements SearchApi {\n #clearInput: (trigger: SearchClearTrigger) => void;\n constructor(clearInput: (trigger: SearchClearTrigger) => void) {\n this.#clearInput = clearInput;\n }\n\n resetInput() {\n this.#clearInput(\"api-reset\");\n }\n}\n"],"names":[],"mappings":"AAIO,MAAM,aAAA,CAAmC;AAAA,EAC5C,WAAA;AAAA,EACA,YAAY,UAAA,EAAmD;AAC3D,IAAA,IAAA,CAAK,WAAA,GAAc,UAAA;AAAA,EACvB;AAAA,EAEA,UAAA,GAAa;AACT,IAAA,IAAA,CAAK,YAAY,WAAW,CAAA;AAAA,EAChC;AACJ;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"SearchController.js","sources":["SearchController.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger, isAbortError, throwAbortError } from \"@open-pioneer/core\";\nimport { SearchSource, SearchResult } from \"./api\";\nimport { MapModel } from \"@open-pioneer/map\";\n\nconst LOG = createLogger(\"search:SearchController\");\n\n/**\n * Group of suggestions returned from one source.\n */\nexport interface SuggestionGroup {\n label: string;\n source: SearchSource;\n results: SearchResult[];\n}\n\nconst DEFAULT_SEARCH_TYPING_DELAY = 200;\nconst DEFAULT_MAX_RESULTS_PER_SOURCE = 5;\n\nexport class SearchController {\n #mapModel: MapModel;\n\n /**\n * Search sources defined by the developer.\n */\n #sources: SearchSource[] = [];\n\n /**\n * Limits the number of results per source.\n */\n #maxResultsPerSource: number = DEFAULT_MAX_RESULTS_PER_SOURCE;\n\n /**\n * The timeout in millis.\n */\n #searchTypingDelay: number = DEFAULT_SEARCH_TYPING_DELAY;\n\n /**\n * Cancel or abort a previous request.\n */\n #abortController: AbortController | undefined;\n\n constructor(mapModel: MapModel, sources: SearchSource[]) {\n this.#mapModel = mapModel;\n this.#sources = sources;\n }\n\n destroy() {\n this.#abortController?.abort();\n this.#abortController = undefined;\n }\n\n async search(searchTerm: string): Promise<SuggestionGroup[]> {\n this.#abortController?.abort();\n this.#abortController = undefined;\n if (!searchTerm) {\n return [];\n }\n\n const abort = (this.#abortController = new AbortController());\n try {\n await waitForTimeOut(abort.signal, this.#searchTypingDelay);\n if (abort.signal.aborted) {\n LOG.debug(`search canceled with ${searchTerm}`);\n throwAbortError();\n }\n const settledSearches = await Promise.all(\n this.#sources.map((source) => this.#searchSource(source, searchTerm, abort.signal))\n );\n return settledSearches.filter((s): s is SuggestionGroup => s != null);\n } finally {\n if (this.#abortController === abort) {\n this.#abortController = undefined;\n }\n }\n }\n\n async #searchSource(\n source: SearchSource,\n searchTerm: string,\n signal: AbortSignal\n ): Promise<SuggestionGroup | undefined> {\n const label = source.label;\n const projection = this.#mapModel.projection;\n try {\n const maxResults = this.#maxResultsPerSource;\n let results = await source.search(searchTerm, {\n maxResults,\n signal,\n mapProjection: projection\n });\n if (results.length > maxResults) {\n results = results.slice(0, maxResults);\n }\n return { label, source, results };\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(`search for source ${label} failed`, e);\n }\n return undefined;\n }\n }\n\n get searchTypingDelay(): number {\n return this.#searchTypingDelay;\n }\n\n set searchTypingDelay(value: number | undefined) {\n this.#searchTypingDelay = value ?? DEFAULT_SEARCH_TYPING_DELAY;\n }\n\n get maxResultsPerSource(): number {\n return this.#maxResultsPerSource;\n }\n\n set maxResultsPerSource(value: number | undefined) {\n this.#maxResultsPerSource = value ?? DEFAULT_MAX_RESULTS_PER_SOURCE;\n }\n\n get sources() {\n return this.#sources;\n }\n}\n\n/**\n * wait for timeouts millis or until signal is aborted, whatever happens first\n */\nasync function waitForTimeOut(signal: AbortSignal, timeoutMillis: number) {\n if (signal.aborted) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n const done = () => {\n signal.removeEventListener(\"abort\", done);\n clearTimeout(timeoutId);\n resolve();\n };\n\n signal.addEventListener(\"abort\", done);\n const timeoutId = setTimeout(done, timeoutMillis);\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SearchController.js","sources":["SearchController.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2023-2025 Open Pioneer project (https://github.com/open-pioneer)\n// SPDX-License-Identifier: Apache-2.0\nimport { createLogger, isAbortError, throwAbortError } from \"@open-pioneer/core\";\nimport { SearchSource, SearchResult } from \"./api\";\nimport { MapModel } from \"@open-pioneer/map\";\n\nconst LOG = createLogger(\"search:SearchController\");\n\n/**\n * Group of suggestions returned from one source.\n */\nexport interface SuggestionGroup {\n label: string;\n source: SearchSource;\n results: SearchResult[];\n}\n\nconst DEFAULT_SEARCH_TYPING_DELAY = 200;\nconst DEFAULT_MAX_RESULTS_PER_SOURCE = 5;\n\nexport class SearchController {\n #mapModel: MapModel;\n\n /**\n * Search sources defined by the developer.\n */\n #sources: SearchSource[] = [];\n\n /**\n * Limits the number of results per source.\n */\n #maxResultsPerSource: number = DEFAULT_MAX_RESULTS_PER_SOURCE;\n\n /**\n * The timeout in millis.\n */\n #searchTypingDelay: number = DEFAULT_SEARCH_TYPING_DELAY;\n\n /**\n * Cancel or abort a previous request.\n */\n #abortController: AbortController | undefined;\n\n constructor(mapModel: MapModel, sources: SearchSource[]) {\n this.#mapModel = mapModel;\n this.#sources = sources;\n }\n\n destroy() {\n this.#abortController?.abort();\n this.#abortController = undefined;\n }\n\n async search(searchTerm: string): Promise<SuggestionGroup[]> {\n this.#abortController?.abort();\n this.#abortController = undefined;\n if (!searchTerm) {\n return [];\n }\n\n const abort = (this.#abortController = new AbortController());\n try {\n await waitForTimeOut(abort.signal, this.#searchTypingDelay);\n if (abort.signal.aborted) {\n LOG.debug(`search canceled with ${searchTerm}`);\n throwAbortError();\n }\n const settledSearches = await Promise.all(\n this.#sources.map((source) => this.#searchSource(source, searchTerm, abort.signal))\n );\n return settledSearches.filter((s): s is SuggestionGroup => s != null);\n } finally {\n if (this.#abortController === abort) {\n this.#abortController = undefined;\n }\n }\n }\n\n async #searchSource(\n source: SearchSource,\n searchTerm: string,\n signal: AbortSignal\n ): Promise<SuggestionGroup | undefined> {\n const label = source.label;\n const projection = this.#mapModel.projection;\n try {\n const maxResults = this.#maxResultsPerSource;\n let results = await source.search(searchTerm, {\n maxResults,\n signal,\n mapProjection: projection\n });\n if (results.length > maxResults) {\n results = results.slice(0, maxResults);\n }\n return { label, source, results };\n } catch (e) {\n if (!isAbortError(e)) {\n LOG.error(`search for source ${label} failed`, e);\n }\n return undefined;\n }\n }\n\n get searchTypingDelay(): number {\n return this.#searchTypingDelay;\n }\n\n set searchTypingDelay(value: number | undefined) {\n this.#searchTypingDelay = value ?? DEFAULT_SEARCH_TYPING_DELAY;\n }\n\n get maxResultsPerSource(): number {\n return this.#maxResultsPerSource;\n }\n\n set maxResultsPerSource(value: number | undefined) {\n this.#maxResultsPerSource = value ?? DEFAULT_MAX_RESULTS_PER_SOURCE;\n }\n\n get sources() {\n return this.#sources;\n }\n}\n\n/**\n * wait for timeouts millis or until signal is aborted, whatever happens first\n */\nasync function waitForTimeOut(signal: AbortSignal, timeoutMillis: number) {\n if (signal.aborted) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n const done = () => {\n signal.removeEventListener(\"abort\", done);\n clearTimeout(timeoutId);\n resolve();\n };\n\n signal.addEventListener(\"abort\", done);\n const timeoutId = setTimeout(done, timeoutMillis);\n });\n}\n"],"names":[],"mappings":"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"}
package/api.d.ts CHANGED
@@ -83,3 +83,47 @@ export interface SearchResult extends BaseFeature {
83
83
  */
84
84
  label: string;
85
85
  }
86
+ /**
87
+ * Event type emitted when the user selects an item.
88
+ */
89
+ export interface SearchSelectEvent {
90
+ /** The source that returned the {@link result}. */
91
+ source: SearchSource;
92
+ /** The search result selected by the user. */
93
+ result: SearchResult;
94
+ }
95
+ /**
96
+ * Event type emitted when the search is cleared.
97
+ */
98
+ export interface SearchClearEvent {
99
+ /** Specifies the trigger that caused the clear event.
100
+ * The clear can be triggered by the user or through the {@link resetInput} in the SearchAPI. */
101
+ trigger: SearchClearTrigger;
102
+ }
103
+ export type SearchClearTrigger = "user" | "api-reset";
104
+ /**
105
+ * API to control the Search component imperatively
106
+ */
107
+ export interface SearchApi {
108
+ /**
109
+ * Clears the search input field.
110
+ */
111
+ resetInput(): void;
112
+ }
113
+ /**
114
+ * Event that indicates that the Search component is initialized.
115
+ * The event carries a reference to the public {@link SearchApi}
116
+ */
117
+ export interface SearchReadyEvent {
118
+ /**
119
+ * Reference to the search API that allows manipulating the search.
120
+ */
121
+ api: SearchApi;
122
+ }
123
+ /**
124
+ * Event that indicates that the Search component has been disposed.
125
+ *
126
+ * Empty interface, might be extended in the future.
127
+ */
128
+ export interface SearchDisposedEvent {
129
+ }
package/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export { Search, type SearchProps, type SearchSelectEvent } from "./Search";
2
- export type { SearchSource, SearchResult, SearchOptions } from "./api";
1
+ export type { SearchApi, SearchClearEvent, SearchClearTrigger, SearchDisposedEvent, SearchOptions, SearchReadyEvent, SearchResult, SearchSelectEvent, SearchSource } from "./api";
2
+ export { Search, type SearchProps } from "./Search";
3
+ export { SearchApiImpl } from "./SearchApiImpl";
package/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { Search } from './Search.js';
2
+ export { SearchApiImpl } from './SearchApiImpl.js';
2
3
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@open-pioneer/search",
4
- "version": "0.11.0",
4
+ "version": "0.12.0-dev.20250905090001",
5
5
  "description": "This package provides a UI component to perform a search on given search sources.",
6
6
  "keywords": [
7
7
  "open-pioneer-trails"
@@ -14,17 +14,17 @@
14
14
  "directory": "src/packages/search"
15
15
  },
16
16
  "dependencies": {
17
- "@chakra-ui/react": "^3.19.2",
17
+ "@chakra-ui/react": "^3.24.2",
18
18
  "@open-pioneer/chakra-snippets": "^4.0.0",
19
19
  "@open-pioneer/core": "^4.0.0",
20
20
  "@open-pioneer/react-utils": "^4.0.0",
21
21
  "@open-pioneer/runtime": "^4.0.0",
22
22
  "chakra-react-select": "^6.1.0",
23
23
  "classnames": "^2.5.1",
24
- "ol": "^10.5.0",
25
- "react": "^19.1.0",
24
+ "ol": "^10.6.1",
25
+ "react": "^19.1.1",
26
26
  "react-icons": "^5.5.0",
27
- "@open-pioneer/map": "^0.11.0"
27
+ "@open-pioneer/map": "0.12.0-dev.20250905090001"
28
28
  },
29
29
  "exports": {
30
30
  "./package.json": "./package.json",