Simplify priority behaviour of PillsSelector, create LanguageChooser-element, update usages

This commit is contained in:
Pieter Vander Vennet 2022-10-29 03:03:51 +02:00
parent efb54782ca
commit 84eee064b2
7 changed files with 385 additions and 162 deletions

View file

@ -1,20 +1,20 @@
import Combine from "../Base/Combine"
import { FlowPanelFactory, FlowStep } from "../ImportFlow/FlowStep"
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import { InputElement } from "../Input/InputElement"
import { SvgToPdf, SvgToPdfOptions } from "../../Utils/svgToPdf"
import { FixedInputElement } from "../Input/FixedInputElement"
import { FixedUiElement } from "../Base/FixedUiElement"
import {FlowPanelFactory, FlowStep} from "../ImportFlow/FlowStep"
import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource"
import {InputElement} from "../Input/InputElement"
import {SvgToPdf, SvgToPdfOptions} from "../../Utils/svgToPdf"
import {FixedInputElement} from "../Input/FixedInputElement"
import {FixedUiElement} from "../Base/FixedUiElement"
import FileSelectorButton from "../Input/FileSelectorButton"
import InputElementMap from "../Input/InputElementMap"
import { RadioButton } from "../Input/RadioButton"
import { Utils } from "../../Utils"
import { VariableUiElement } from "../Base/VariableUIElement"
import {RadioButton} from "../Input/RadioButton"
import {Utils} from "../../Utils"
import {VariableUiElement} from "../Base/VariableUIElement"
import Loading from "../Base/Loading"
import BaseUIElement from "../BaseUIElement"
import Img from "../Base/Img"
import Title from "../Base/Title"
import { CheckBox } from "../Input/Checkboxes"
import {CheckBox} from "../Input/Checkboxes"
import Minimap from "../Base/Minimap"
import SearchAndGo from "./SearchAndGo"
import Toggle from "../Input/Toggle"
@ -25,9 +25,7 @@ import Toggleable from "../Base/Toggleable"
import Lazy from "../Base/Lazy"
import LinkToWeblate from "../Base/LinkToWeblate"
import Link from "../Base/Link"
import { SearchablePillsSelector } from "../Input/SearchableMappingsSelector"
import * as languages from "../../assets/language_translations.json"
import { Translation } from "../i18n/Translation"
import {AllLanguagesSelector} from "../Popup/AllLanguagesSelector";
class SelectTemplate extends Combine implements FlowStep<{ title: string; pages: string[] }> {
readonly IsValid: Store<boolean>
@ -203,23 +201,7 @@ class PreparePdf extends Combine implements FlowStep<{ svgToPdf: SvgToPdf; langu
constructor(title: string, pages: string[], options: SvgToPdfOptions) {
const svgToPdf = new SvgToPdf(title, pages, options)
const languageOptions = [
new FixedInputElement("Nederlands", "nl"),
new FixedInputElement("English", "en"),
]
const langs: string[] = Array.from(Object.keys(languages["default"] ?? languages))
console.log("Available languages are:", langs)
const languageSelector = new SearchablePillsSelector(
langs.map((l) => ({
show: new Translation(languages[l]),
value: l,
mainTerm: languages[l],
})),
{
mode: "select-many",
}
)
const languageSelector = new AllLanguagesSelector( )
const isPrepared = UIEventSource.FromPromiseWithErr(svgToPdf.Prepare())
super([

View file

@ -1,23 +1,25 @@
import { UIElement } from "../UIElement"
import { InputElement } from "./InputElement"
import {UIElement} from "../UIElement"
import {InputElement} from "./InputElement"
import BaseUIElement from "../BaseUIElement"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import {Store, UIEventSource} from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations"
import Locale from "../i18n/Locale"
import Combine from "../Base/Combine"
import { TextField } from "./TextField"
import {TextField} from "./TextField"
import Svg from "../../Svg"
import { VariableUiElement } from "../Base/VariableUIElement"
import {VariableUiElement} from "../Base/VariableUIElement"
/**
* A single 'pill' which can hide itself if the search criteria is not met
*/
class SelfHidingToggle extends UIElement implements InputElement<boolean> {
private readonly _shown: BaseUIElement
public readonly _selected: UIEventSource<boolean>
public readonly isShown: Store<boolean> = new UIEventSource<boolean>(true)
public readonly matchesSearchCriteria: Store<boolean>
public readonly forceSelected: UIEventSource<boolean>
private readonly _shown: BaseUIElement
private readonly _squared: boolean
public constructor(
shown: string | BaseUIElement,
mainTerm: Record<string, string>,
@ -26,7 +28,9 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
searchTerms?: Record<string, string[]>
selected?: UIEventSource<boolean>
forceSelected?: UIEventSource<boolean>
squared?: boolean
squared?: boolean,
/* Hide, if not selected*/
hide?: Store<boolean>
}
) {
super()
@ -49,24 +53,31 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
const selected = (this._selected = options?.selected ?? new UIEventSource<boolean>(false))
const forceSelected = (this.forceSelected =
options?.forceSelected ?? new UIEventSource<boolean>(false))
this.isShown = search.map(
(s) => {
if (s === undefined || s.length === 0) {
return true
}
this.matchesSearchCriteria = search.map(s => {
if (s === undefined || s.length === 0) {
return true
}
s = s?.trim()?.toLowerCase()
if (searchTerms[Locale.language.data]?.some((t) => t.indexOf(s) >= 0)) {
return true
}
if (searchTerms["*"]?.some((t) => t.indexOf(s) >= 0)) {
return true
}
return false
})
this.isShown = this.matchesSearchCriteria.map(
(matchesSearch) => {
if (selected.data && !forceSelected.data) {
return true
}
s = s?.trim()?.toLowerCase()
if (searchTerms[Locale.language.data]?.some((t) => t.indexOf(s) >= 0)) {
return true
if (options?.hide?.data) {
return false
}
if (searchTerms["*"]?.some((t) => t.indexOf(s) >= 0)) {
return true
}
return false
return matchesSearch
},
[selected, Locale.language]
[selected, Locale.language, options?.hide]
)
const self = this
@ -128,13 +139,12 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
* A searchfield can be used to filter the values
*/
export class SearchablePillsSelector<T> extends Combine implements InputElement<T[]> {
private readonly selectedElements: UIEventSource<T[]>
public readonly someMatchFound: Store<boolean>
private readonly selectedElements: UIEventSource<T[]>
/**
*
* @param values
* @param values: the values that can be selected
* @param options
*/
constructor(
@ -142,38 +152,57 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
show: BaseUIElement
value: T
mainTerm: Record<string, string>
searchTerms?: Record<string, string[]>
searchTerms?: Record<string, string[]>,
/* If there are more then 200 elements, should this element still be shown? */
hasPriority?: Store<boolean>
}[],
options?: {
/*
* If one single value can be selected (like a radio button) or if many values can be selected (like checkboxes)
*/
mode?: "select-one" | "select-many"
/**
* The values of the selected elements.
* Use this to tie input elements together
*/
selectedElements?: UIEventSource<T[]>
/**
* The search bar. Use this to seed the search value or to tie to another value
*/
searchValue?: UIEventSource<string>
/**
* What is shown if the search yielded no results.
* By default: a translated "no search results"
*/
onNoMatches?: BaseUIElement
/**
* An element that is shown if no search is entered
* Default behaviour is to show all options
*/
onNoSearchMade?: BaseUIElement
/**
* Shows this if there are many (>200) possible mappings
* Extra element to show if there are many (>200) possible mappings and when non-priority mappings are hidden
*
*/
onManyElements?: BaseUIElement
onManyElementsValue?: UIEventSource<T[]>
selectIfSingle?: false | boolean
searchAreaClass?: string
hideSearchBar?: false | boolean
}
) {
const search = new TextField({ value: options?.searchValue })
const search = new TextField({value: options?.searchValue})
const searchBar = options?.hideSearchBar
? undefined
: new Combine([
Svg.search_svg().SetClass("w-8 normal-background"),
search.SetClass("w-full"),
]).SetClass("flex items-center border-2 border-black m-2")
Svg.search_svg().SetClass("w-8 normal-background"),
search.SetClass("w-full"),
]).SetClass("flex items-center border-2 border-black m-2")
const searchValue = search.GetValue().map((s) => s?.trim()?.toLowerCase())
const selectedElements = options?.selectedElements ?? new UIEventSource<T[]>([])
const mode = options?.mode ?? "select-one"
const onEmpty = options?.onNoMatches ?? Translations.t.general.noMatchingMapping
const forceHide = new UIEventSource(false)
const mappedValues: {
show: SelfHidingToggle
mainTerm: Record<string, string>
@ -209,6 +238,7 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
searchTerms: v.searchTerms,
selected: vIsSelected,
squared: mode === "select-many",
hide: v.hasPriority === undefined ? forceHide : forceHide.map(fh => fh && !v.hasPriority?.data, [v.hasPriority])
})
return {
@ -217,62 +247,18 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
}
})
// The total number of elements that would be displayed based on the search criteria alone
let totalShown: Store<number>
if (options.selectIfSingle) {
let forcedSelection: { value: T; show: SelfHidingToggle } = undefined
totalShown = searchValue.map(
(_) => {
let totalShown = 0
let lastShownValue: { value: T; show: SelfHidingToggle }
for (const mv of mappedValues) {
const valueIsShown = mv.show.isShown.data
if (valueIsShown) {
totalShown++
lastShownValue = mv
}
}
if (totalShown == 1) {
if (selectedElements.data?.indexOf(lastShownValue.value) < 0) {
selectedElements.setData([lastShownValue.value])
lastShownValue.show.forceSelected.setData(true)
forcedSelection = lastShownValue
}
} else if (forcedSelection != undefined) {
forcedSelection?.show?.forceSelected?.setData(false)
forcedSelection = undefined
selectedElements.setData([])
}
return totalShown
},
mappedValues.map((mv) => mv.show.GetValue())
)
} else {
totalShown = searchValue.map(
(_) => mappedValues.filter((mv) => mv.show.isShown.data).length,
mappedValues.map((mv) => mv.show.GetValue())
)
}
const tooMuchElementsCutoff = 200
options?.onManyElementsValue?.map(
(value) => {
console.log("Installing toMuchElementsValue", value)
if (tooMuchElementsCutoff <= totalShown.data) {
selectedElements.setData(value)
selectedElements.ping()
}
},
[totalShown]
)
totalShown = searchValue.map((_) => mappedValues.filter((mv) => mv.show.matchesSearchCriteria.data).length)
const tooMuchElementsCutoff = 40
totalShown.addCallbackAndRunD(shown => forceHide.setData(tooMuchElementsCutoff < shown))
super([
searchBar,
new VariableUiElement(
Locale.language.map(
(lng) => {
if (totalShown.data >= 200) {
return options?.onManyElements ?? Translations.t.general.useSearch
}
if (
options?.onNoSearchMade !== undefined &&
(searchValue.data === undefined || searchValue.data.length === 0)
@ -284,9 +270,14 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
}
mappedValues.sort((a, b) => (a.mainTerm[lng] < b.mainTerm[lng] ? -1 : 1))
return new Combine(mappedValues.map((e) => e.show))
let pills = new Combine(mappedValues.map((e) => e.show))
.SetClass("flex flex-wrap w-full content-start")
.SetClass(options?.searchAreaClass ?? "")
if (totalShown.data >= tooMuchElementsCutoff) {
pills = new Combine([options?.onManyElements ?? Translations.t.general.useSearch, pills])
}
return pills
},
[totalShown, searchValue]
)

View file

@ -0,0 +1,45 @@
import {SearchablePillsSelector} from "../Input/SearchableMappingsSelector";
import {Store} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import * as all_languages from "../../assets/language_translations.json";
import {Translation} from "../i18n/Translation";
export class AllLanguagesSelector extends SearchablePillsSelector <string> {
constructor(options?: {
mode?: "select-many" | "select-one"
currentCountry?: Store<string>,
supportedLanguages?: Record<string, string> & { _meta?: { countries?: string[] } }
}) {
const possibleValues: {
show: BaseUIElement
value: string
mainTerm: Record<string, string>
searchTerms?: Record<string, string[]>,
hasPriority?: Store<boolean>
}[] = []
const langs = options?.supportedLanguages ?? all_languages["default"] ?? all_languages
for (const ln in langs) {
let languageInfo: Record<string, string> & { _meta?: { countries: string[] } } = all_languages[ln]
const countries = languageInfo._meta?.countries?.map(c => c.toLowerCase())
languageInfo = {...languageInfo}
delete languageInfo._meta
const term = {
show: new Translation(languageInfo),
value: ln,
mainTerm: languageInfo,
searchTerms: {"*": [ln]},
hasPriority: countries === undefined ? undefined : options?.currentCountry?.map(country => countries?.indexOf(country.toLowerCase()) >= 0)
}
possibleValues.push(term)
}
super(possibleValues,
{
mode: options?.mode ?? 'select-many'
});
}
}

View file

@ -10,6 +10,7 @@ import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import { Unit } from "../../Models/Unit"
import Lazy from "../Base/Lazy"
import { FixedUiElement } from "../Base/FixedUiElement"
import {EditButton} from "./SaveButton";
export default class EditableTagRendering extends Toggle {
constructor(
@ -70,16 +71,9 @@ export default class EditableTagRendering extends Toggle {
// We have a question and editing is enabled
const answerWithEditButton = new Combine([
answer,
new Toggle(
new Combine([Svg.pencil_ui()])
.SetClass("block relative h-10 w-10 p-2 float-right")
.SetStyle("border: 1px solid black; border-radius: 0.7em")
.onClick(() => {
editMode.setData(true)
}),
undefined,
state.osmConnection.isLoggedIn
),
new EditButton(state.osmConnection,() => {
editMode.setData(true)
}),
]).SetClass("flex justify-between w-full")
const question = new Lazy(

228
UI/Popup/LanguageElement.ts Normal file
View file

@ -0,0 +1,228 @@
import {SpecialVisualization} from "../SpecialVisualization";
import BaseUIElement from "../BaseUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState";
import {VariableUiElement} from "../Base/VariableUIElement";
import {OsmTags} from "../../Models/OsmFeature";
import * as all_languages from "../../assets/language_translations.json"
import {Translation} from "../i18n/Translation";
import Combine from "../Base/Combine";
import Title from "../Base/Title";
import Lazy from "../Base/Lazy";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import List from "../Base/List";
import {AllLanguagesSelector} from "./AllLanguagesSelector";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
import {And} from "../../Logic/Tags/And";
import {Tag} from "../../Logic/Tags/Tag";
import {EditButton, SaveButton} from "./SaveButton";
import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations";
import Toggle from "../Input/Toggle";
import {On} from "../../Models/ThemeConfig/Conversion/Conversion";
export class LanguageElement implements SpecialVisualization {
funcName: string = "language_chooser"
docs: string | BaseUIElement = "The language element allows to show and pick all known (modern) languages. The key can be set";
args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] =
[{
name: "key",
required: true,
doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked "
},
{
name: "question",
required: true,
doc: "What to ask if no questions are known"
},
{
name: "render_list_item",
doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
defaultValue: "{language()}"
},
{
name: "render_single_language",
doc: "What will be shown if the feature only supports a single language",
required: true
},
{
name: "render_all",
doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
defaultValue: "{list()}"
},
{
name: "no_known_languages",
doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead"
},
{
name: 'mode',
doc: "If one or many languages can be selected. Should be 'multi' or 'single'",
defaultValue: 'multi'
}
]
;
example: `
\`\`\`json
{"special":
"type": "language_chooser",
"key": "school:language",
"question": {"en": "What are the main (and administrative) languages spoken in this school?"},
"render_single_language": {"en": "{language()} is spoken on this school"},
"render_list_item": {"en": "{language()}"},
"render_all": {"en": "The following languages are spoken here:{list()}"}
"mode":"multi"
}
\`\`\`
`
constr(state: FeaturePipelineState, tagSource: UIEventSource<OsmTags>, argument: string[]): BaseUIElement {
let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] = argument
if (mode === undefined || mode.length == 0) {
mode = "multi"
}
if (item_render === undefined) {
item_render = "{language()}"
}
if (all_render === undefined || all_render.length == 0) {
all_render = "{list()}"
}
if (mode !== "single" && mode !== "multi") {
throw "Error while calling language_chooser: mode must be either 'single' or 'multi' but it is " + mode
}
if (single_render.indexOf("{language()") < 0 || item_render.indexOf("{language()") < 0) {
throw "Error while calling language_chooser: render_single_language and render_list_item must contain '{language()}'"
}
if (all_render.indexOf("{list()") < 0) {
throw "Error while calling language_chooser: render_all must contain '{list()}'"
}
const prefix = key + ":"
const foundLanguages = tagSource
.map(tags => {
const foundLanguages: string[] = []
for (const k in tags) {
const v = tags[k]
if (v !== "yes") {
continue
}
if (k.startsWith(prefix)) {
foundLanguages.push(k.substring(prefix.length))
}
}
return foundLanguages
})
const forceInputMode = new UIEventSource(false);
const inputEl = new Lazy(() => {
const selector = new AllLanguagesSelector(
{
mode: mode === "single" ? "select-one" : "select-many",
currentCountry: tagSource.map(tgs => tgs["_country"])
}
)
const cancelButton = Toggle.If(forceInputMode,
() => Translations.t.general.cancel
.Clone()
.SetClass("btn btn-secondary").onClick(() => forceInputMode.setData(false)))
const saveButton = new SaveButton(
selector.GetValue().map(lngs => lngs.length > 0 ? "true" : undefined),
state.osmConnection,
).onClick(() => {
const selectedLanguages = selector.GetValue().data
const currentLanguages = foundLanguages.data
const selection: Tag[] = selectedLanguages.map(ln => new Tag(prefix + ln, "yes"));
for (const currentLanguage of currentLanguages) {
if (selectedLanguages.indexOf(currentLanguage) >= 0) {
continue
}
// Erase language that is not spoken anymore
selection.push(new Tag(prefix + currentLanguage, ""))
}
if (state.featureSwitchIsTesting.data) {
for (const tag of selection) {
tagSource.data[tag.key] = tag.value
}
tagSource.ping()
} else {
(state?.changes)
.applyAction(
new ChangeTagAction(tagSource.data.id, new And(selection), tagSource.data, {
theme: state?.layoutToUse?.id ?? "unkown",
changeType: "answer",
})
)
.then((_) => {
console.log("Tagchanges applied")
})
}
forceInputMode.setData(false)
})
return new Combine([new Title(question), selector,
new Combine([cancelButton, saveButton]).SetClass("flex justify-end")
]).SetClass("flex flex-col question disable-links");
})
const editButton = new EditButton(state.osmConnection, () => forceInputMode.setData(true))
return new VariableUiElement(foundLanguages
.map(foundLanguages => {
if (forceInputMode.data) {
return inputEl
}
if (foundLanguages.length === 0) {
// No languages found - we show the question and the input element
if (on_no_known_languages !== undefined && on_no_known_languages.length > 0) {
return new Combine([on_no_known_languages, editButton]).SetClass("flex justify-end")
}
return inputEl
}
let rendered: BaseUIElement;
if (foundLanguages.length === 1) {
const ln = foundLanguages[0]
let mapping = new Map<string, BaseUIElement>();
mapping.set("language", new Translation(all_languages[ln]))
rendered = new SubstitutedTranslation(
new Translation({"*": single_render}, undefined),
tagSource, state, mapping
)
} else {
let mapping = new Map<string, BaseUIElement>();
const languagesList = new List(
foundLanguages.map(ln => {
let mappingLn = new Map<string, BaseUIElement>();
mappingLn.set("language", new Translation(all_languages[ln]))
return new SubstitutedTranslation(
new Translation({"*": item_render}, undefined),
tagSource, state, mappingLn
)
})
);
mapping.set("list", languagesList)
rendered = new SubstitutedTranslation(
new Translation({'*': all_render}, undefined), tagSource,
state, mapping
)
}
return new Combine([rendered, editButton]).SetClass("flex justify-between")
}, [forceInputMode]));
}
}

View file

@ -3,6 +3,21 @@ import Translations from "../i18n/Translations"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Toggle from "../Input/Toggle"
import BaseUIElement from "../BaseUIElement"
import Combine from "../Base/Combine";
import Svg from "../../Svg";
export class EditButton extends Toggle {
constructor(osmConnection: OsmConnection, onClick: () => void) {
super(
new Combine([Svg.pencil_ui()])
.SetClass("block relative h-10 w-10 p-2 float-right")
.SetStyle("border: 1px solid black; border-radius: 0.7em")
.onClick(onClick),
undefined,
osmConnection.isLoggedIn
)
}
}
export class SaveButton extends Toggle {
constructor(

View file

@ -105,7 +105,7 @@ export default class TagRenderingQuestion extends Combine {
TagUtils.FlattenAnd(inputElement.GetValue().data, tags.data)
)
if (selection) {
;(state?.changes)
(state?.changes)
.applyAction(
new ChangeTagAction(tags.data.id, selection, tags.data, {
theme: state?.layoutToUse?.id ?? "unkown",
@ -288,14 +288,16 @@ export default class TagRenderingQuestion extends Combine {
value: number
mainTerm: Record<string, string>
searchTerms?: Record<string, string[]>
original: Mapping
original: Mapping,
hasPriority?: Store<boolean>
}[] {
const values: {
show: BaseUIElement
value: number
mainTerm: Record<string, string>
searchTerms?: Record<string, string[]>
original: Mapping
original: Mapping,
hasPriority?: Store<boolean>
}[] = []
const addIcons = applicableMappings.some((m) => m.icon !== undefined)
for (let i = 0; i < applicableMappings.length; i++) {
@ -317,6 +319,7 @@ export default class TagRenderingQuestion extends Combine {
mainTerm: tr.translations,
searchTerms: mapping.searchTerms,
original: mapping,
hasPriority: tagsSource.map(tags => mapping.priorityIf?.matchesProperties(tags))
})
}
return values
@ -397,7 +400,7 @@ export default class TagRenderingQuestion extends Combine {
const values = TagRenderingQuestion.MappingToPillValue(
applicableMappings,
tagsSource,
state
state,
)
const searchValue: UIEventSource<string> =
@ -411,47 +414,12 @@ export default class TagRenderingQuestion extends Combine {
}
const mode = configuration.multiAnswer ? "select-many" : "select-one"
const tooMuchElementsValue = new UIEventSource<number[]>([])
let priorityPresets: BaseUIElement = undefined
const classes = "h-64 overflow-scroll"
if (applicableMappings.some((m) => m.priorityIf !== undefined)) {
const priorityValues = tagsSource.map((tags) =>
TagRenderingQuestion.MappingToPillValue(
applicableMappings,
tagsSource,
state
).filter((v) => v.original.priorityIf?.matchesProperties(tags))
)
priorityPresets = new VariableUiElement(
priorityValues.map((priority) => {
if (priority.length === 0) {
return Translations.t.general.useSearch
}
return new Combine([
Translations.t.general.useSearchForMore.Subs({
total: applicableMappings.length,
}),
new SearchablePillsSelector(priority, {
selectedElements: tooMuchElementsValue,
hideSearchBar: true,
mode,
}),
])
.SetClass("flex flex-col items-center ")
.SetClass(classes)
})
)
}
const presetSearch = new SearchablePillsSelector<number>(values, {
selectIfSingle: true,
mode,
searchValue,
onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"),
searchAreaClass: classes,
onManyElementsValue: tooMuchElementsValue,
onManyElements: priorityPresets,
searchAreaClass: classes
})
const fallbackTag = searchValue.map((s) => {
if (s === undefined || ff?.key === undefined) {