= Number(required) ? JSON.stringify(selected) : null} tabindex="-1" aria-hidden="true" aria-label="ignore this, used only to prevent form submission if select is required but empty" class="form-control" bind:this={form_input} on:invalid={() => { invalid = true let msg if (maxSelect && maxSelect > 1 && Number(required) > 1) { msg = `Please select between ${required} and ${maxSelect} options` } else if (Number(required) > 1) { msg = `Please select at least ${required} options` } else { msg = `Please select an option` } form_input?.setCustomValidity(msg) }} />
    {#each selected as option, idx (duplicates ? [key(option), idx] : key(option))}
  • 1} on:dragstart={dragstart(idx)} on:drop|preventDefault={drop(idx)} on:dragenter={() => (drag_idx = idx)} on:dragover|preventDefault class:active={drag_idx === idx} style="{get_style(option, `selected`)} {liSelectedStyle}" > {#if parseLabelsAsHtml} {@html get_label(option)} {:else} {get_label(option)} {/if} {#if !disabled && (minSelect === null || selected.length > minSelect)} {/if}
  • {/each} open_dropdown(e)} on:keydown|stopPropagation={(e) => handle_keydown(e)} on:focus={(e) => open_dropdown(e)} on:input={(e) => highlight_matching_options(e)} {id} {disabled} {autocomplete} {inputmode} {pattern} placeholder={selected.length === 0 ? placeholder : undefined} aria-invalid={invalid ? 'true' : undefined} on:blur on:change on:click on:keydown on:keyup on:mousedown on:mouseenter on:mouseleave on:touchcancel on:touchend on:touchmove on:touchstart />
{#if loading} spinner {/if} {#if disabled} disable {:else if selected.length > 0} {#if maxSelect !== 1 && selected.length > 1 && !disableRemoveAll} {/if} {/if} {#if allowUserOptions || (searchText && noMatchingOptionsMsg) || options?.length > 0}
{@const option = matchingOptions[index]} {@const { label, disabled = null, title = null, selectedTitle = null, disabledTitle = defaultDisabledTitle } = option instanceof Object ? option : { label: option }}
{ if (!disabled) add(option, event) }} title={disabled ? disabledTitle : (is_selected(label) && selectedTitle) || title} class={twMerge('hover:bg-blue-100 hover:dark:bg-gray-900 cursor-pointer !px-2 py-1')} on:mouseover={() => { if (!disabled) activeIndex = index }} on:focus={() => { if (!disabled) activeIndex = index }} on:mouseout={() => (activeIndex = null)} on:blur={() => (activeIndex = null)} role="option" aria-selected="false" style={get_style(option, `option`)} > {#if parseLabelsAsHtml} {@html get_label(option)} {:else} {get_label(option)} {/if}
{#if searchText} {@const text_input_is_duplicate = selected.map(get_label).includes(searchText)} {@const is_dupe = !duplicates && text_input_is_duplicate && `dupe`} {@const can_create = Boolean(allowUserOptions && createOptionMsg) && `create`} {@const no_match = Boolean(matchingOptions?.length == 0 && noMatchingOptionsMsg) && `no-match`} {@const msgType = is_dupe || can_create || no_match} {#if msgType} {@const msg = { dupe: duplicateOptionMsg, create: createOptionMsg, 'no-match': noMatchingOptionsMsg }[msgType]}
{ if (allowUserOptions) add(searchText, event) }} title={createOptionMsg} class:active={option_msg_is_active} on:mouseover={() => (option_msg_is_active = true)} on:focus={() => (option_msg_is_active = true)} on:mouseout={() => (option_msg_is_active = false)} on:blur={() => (option_msg_is_active = false)} role="option" aria-selected="false" class="user-msg p-1" style:cursor={{ dupe: `not-allowed`, create: `pointer`, 'no-match': `default` }[msgType]} > {msg}
{/if} {/if}
{/if}