// src/create-select.tsx
import {
  createEffect,
  createMemo,
  createSignal,
  mergeProps,
  on
} from "solid-js";
var createSelect = (props) => {
  const config = mergeProps(
    {
      multiple: false,
      disabled: false,
      optionToValue: (option) => option,
      isOptionDisabled: (option) => false
    },
    props
  );
  const parseValue = (value2) => {
    if (config.multiple && Array.isArray(value2)) {
      return value2;
    } else if (!config.multiple && !Array.isArray(value2)) {
      return value2 !== null ? [value2] : [];
    } else {
      throw new Error(
        `Incompatible value type for ${config.multiple ? "multple" : "single"} select.`
      );
    }
  };
  const [_value, _setValue] = createSignal(
    config.initialValue !== void 0 ? parseValue(config.initialValue) : []
  );
  const value = () => config.multiple ? _value() : _value()[0] || null;
  const setValue = (value2) => _setValue(parseValue(value2));
  const clearValue = () => _setValue([]);
  const hasValue = () => !!(config.multiple ? value().length : value());
  createEffect(on(_value, () => config.onChange?.(value()), { defer: true }));
  const [inputValue, setInputValue] = createSignal("");
  const clearInputValue = () => setInputValue("");
  const hasInputValue = () => !!inputValue().length;
  createEffect(
    on(inputValue, (inputValue2) => config.onInput?.(inputValue2), {
      defer: true
    })
  );
  createEffect(
    on(
      inputValue,
      (inputValue2) => {
        if (inputValue2 && !isOpen()) {
          setIsOpen(true);
        }
      },
      { defer: true }
    )
  );
  const options = typeof config.options === "function" ? createMemo(
    () => config.options(inputValue()),
    config.options(inputValue())
  ) : () => config.options;
  const optionsCount = () => options().length;
  const pickOption = (option) => {
    if (config.isOptionDisabled(option)) return;
    const value2 = config.optionToValue(option);
    if (config.multiple) {
      setValue([..._value(), value2]);
    } else {
      setValue(value2);
      setIsActive(false);
    }
    setIsOpen(false);
  };
  const [isActive, setIsActive] = createSignal(false);
  const [isOpen, setIsOpen] = createSignal(false);
  const toggleOpen = () => setIsOpen(!isOpen());
  const [focusedOptionIndex, setFocusedOptionIndex] = createSignal(-1);
  const focusedOption = () => options()[focusedOptionIndex()];
  const isOptionFocused = (option) => option === focusedOption();
  const focusOption = (direction) => {
    if (!optionsCount()) setFocusedOptionIndex(-1);
    const max = optionsCount() - 1;
    const delta = direction === "next" ? 1 : -1;
    let index = focusedOptionIndex() + delta;
    if (index > max) {
      index = 0;
    }
    if (index < 0) {
      index = max;
    }
    setFocusedOptionIndex(index);
  };
  const focusPreviousOption = () => focusOption("previous");
  const focusNextOption = () => focusOption("next");
  createEffect(
    on(
      options,
      (options2) => {
        if (isOpen()) setFocusedOptionIndex(Math.min(0, options2.length - 1));
      },
      { defer: true }
    )
  );
  createEffect(
    on(
      () => config.disabled,
      (isDisabled) => {
        if (isDisabled && isOpen()) {
          setIsOpen(false);
        }
      }
    )
  );
  createEffect(
    on(
      isOpen,
      (isOpen2) => {
        if (isOpen2) {
          if (focusedOptionIndex() === -1) focusNextOption();
          setIsActive(true);
        } else {
          if (focusedOptionIndex() > -1) setFocusedOptionIndex(-1);
          setInputValue("");
        }
      },
      { defer: true }
    )
  );
  createEffect(
    on(
      focusedOptionIndex,
      (focusedOptionIndex2) => {
        if (focusedOptionIndex2 > -1 && !isOpen()) {
          setIsOpen(true);
        }
      },
      { defer: true }
    )
  );
  const onFocusIn = () => setIsActive(true);
  const onFocusOut = () => {
    setIsActive(false);
    setIsOpen(false);
  };
  const onMouseDown = (event) => event.preventDefault();
  const onClick = (event) => {
    if (!config.disabled && !hasInputValue()) toggleOpen();
  };
  const onInput = (event) => {
    setInputValue(event.target.value);
  };
  const onKeyDown = (event) => {
    switch (event.key) {
      case "ArrowDown":
        focusNextOption();
        break;
      case "ArrowUp":
        focusPreviousOption();
        break;
      case "Enter":
        if (isOpen() && focusedOption()) {
          pickOption(focusedOption());
          break;
        }
        return;
      case "Escape":
        if (isOpen()) {
          setIsOpen(false);
          break;
        }
        return;
      case "Delete":
      case "Backspace":
        if (inputValue()) {
          return;
        }
        if (config.multiple) {
          const currentValue = value();
          setValue([...currentValue.slice(0, -1)]);
        } else {
          clearValue();
        }
        break;
      case " ":
        if (inputValue()) {
          return;
        }
        if (!isOpen()) {
          setIsOpen(true);
        } else {
          if (focusedOption()) {
            pickOption(focusedOption());
          }
        }
        break;
      case "Tab":
        if (focusedOption() && isOpen()) {
          pickOption(focusedOption());
          break;
        }
        return;
      default:
        return;
    }
    event.preventDefault();
    event.stopPropagation();
  };
  return {
    options,
    value,
    setValue,
    hasValue,
    clearValue,
    inputValue,
    setInputValue,
    hasInputValue,
    clearInputValue,
    isOpen,
    setIsOpen,
    toggleOpen,
    isActive,
    setIsActive,
    get multiple() {
      return config.multiple;
    },
    get disabled() {
      return config.disabled;
    },
    pickOption,
    isOptionFocused,
    isOptionDisabled: config.isOptionDisabled,
    onFocusIn,
    onFocusOut,
    onMouseDown,
    onClick,
    onInput,
    onKeyDown
  };
};

// src/fuzzy.tsx
var SCORING = {
  NO_MATCH: 0,
  MATCH: 1,
  WORD_START: 2,
  START: 3
};
var fuzzySearch = (value, target) => {
  let score = SCORING.NO_MATCH;
  let matches = [];
  if (value.length <= target.length) {
    const valueChars = Array.from(value.toLocaleLowerCase());
    const targetChars = Array.from(target.toLocaleLowerCase());
    let delta = SCORING.START;
    outer: for (let valueIndex = 0, targetIndex = 0; valueIndex < valueChars.length; valueIndex++) {
      while (targetIndex < targetChars.length) {
        if (targetChars[targetIndex] === valueChars[valueIndex]) {
          matches[targetIndex] = true;
          if (delta === SCORING.MATCH && targetChars[targetIndex - 1] === " " && targetChars[targetIndex] !== " ") {
            delta = SCORING.WORD_START;
          }
          score += delta;
          delta++;
          targetIndex++;
          continue outer;
        } else {
          delta = SCORING.MATCH;
          targetIndex++;
        }
      }
      score = SCORING.NO_MATCH;
      matches.length = 0;
    }
  }
  return {
    target,
    score,
    matches
  };
};
var fuzzyHighlight = (searchResult, highlighter = (match) => <mark>{match}</mark>) => {
  const target = searchResult.target;
  const matches = searchResult.matches;
  const separator = "\0";
  const highlighted = [];
  let open = false;
  for (let index = 0; index < target.length; index++) {
    const char = target[index];
    const matched = matches[index];
    if (!open && matched) {
      highlighted.push(separator);
      open = true;
    } else if (open && !matched) {
      highlighted.push(separator);
      open = false;
    }
    highlighted.push(char);
  }
  if (open) {
    highlighted.push(separator);
    open = false;
  }
  return <>
      {highlighted.join("").split(separator).map((part, index) => index % 2 ? highlighter(part) : part)}
    </>;
};
var fuzzySort = (value, items, key) => {
  const sorted = [];
  for (let index = 0; index < items.length; index++) {
    const item = items[index];
    const target = key ? typeof key === "function" ? key(item) : item[key] : item;
    const result = fuzzySearch(value, target);
    if (result.score) {
      sorted.push({ ...result, item, index });
    }
  }
  sorted.sort((a, b) => {
    let delta = b.score - a.score;
    if (delta === 0) {
      delta = a.index - b.index;
    }
    return delta;
  });
  return sorted;
};

// src/create-options.tsx
var defaultFormat = (value, type, meta) => type === "label" ? <>
      {meta.prefix}
      {meta.highlight ?? value}
    </> : value;
var createOptions = (values, userConfig) => {
  const config = Object.assign(
    {
      extractText: (value) => value.toString ? value.toString() : value,
      filterable: true,
      disable: () => false
    },
    userConfig || {}
  );
  if (config.key && userConfig && (userConfig.format || userConfig.disable || userConfig.extractText)) {
  }
  if (typeof config.createable === "function" && config.createable.length === 1) {
  }
  const resolveValue = (value) => config.key ? value[config.key] : value;
  const extractText = (value) => config.extractText(resolveValue(value));
  const format = (value, type, meta) => {
    const resolvedValue = resolveValue(value);
    return config.format ? config.format(resolvedValue, type, meta) : defaultFormat(resolvedValue, type, meta);
  };
  const disable = (value) => config.disable(resolveValue(value));
  const options = (inputValue) => {
    const trimmedValue = inputValue.trim();
    const initialValues = typeof values === "function" ? values(inputValue) : values;
    let createdOptions = initialValues.map((value) => {
      return {
        value,
        label: format(value, "label", {}),
        text: extractText(value),
        disabled: disable(value)
      };
    });
    if (config.filterable && trimmedValue) {
      if (typeof config.filterable === "function") {
        createdOptions = config.filterable(trimmedValue, createdOptions);
      } else {
        createdOptions = fuzzySort(trimmedValue, createdOptions, "text").map(
          (result) => ({
            ...result.item,
            label: format(result.item.value, "label", {
              highlight: fuzzyHighlight(result)
            })
          })
        );
      }
    }
    if (config.createable !== void 0) {
      const exists = createdOptions.some(
        (option) => areEqualIgnoringCase(trimmedValue, option.text)
      );
      if (trimmedValue) {
        let valueOrValues;
        if (typeof config.createable === "function") {
          if (config.createable.length === 1) {
            if (!exists) {
              valueOrValues = config.createable(
                trimmedValue,
                exists,
                createdOptions
              );
            }
          } else {
            valueOrValues = config.createable(
              trimmedValue,
              exists,
              createdOptions
            );
          }
        } else if (!exists) {
          valueOrValues = config.key ? { [config.key]: trimmedValue } : trimmedValue;
        }
        if (valueOrValues !== void 0) {
          const values2 = Array.isArray(valueOrValues) ? valueOrValues : [valueOrValues];
          const optionsToAdd = [];
          for (const value of values2) {
            optionsToAdd.push({
              value,
              label: format(value, "label", { prefix: "Create " }),
              text: extractText(value),
              disabled: false
            });
          }
          createdOptions = [...createdOptions, ...optionsToAdd];
        }
      }
    }
    return createdOptions;
  };
  return {
    options,
    optionToValue: (option) => option.value,
    isOptionDisabled: (option) => option.disabled,
    format: (item, type) => type === "option" ? item.label : format(item, "value", {})
  };
};
var areEqualIgnoringCase = (firstString, secondString) => firstString.localeCompare(secondString, void 0, {
  sensitivity: "base"
}) === 0;

// src/create-async-options.tsx
import { createSignal as createSignal2, createResource, getOwner, onCleanup } from "solid-js";
var createAsyncOptions = (fetcher, timeout = 250) => {
  const [inputValue, setInputValue] = createSignal2("");
  const throttledFetcher = throttle(fetcher, timeout);
  const [asyncOptions] = createResource(inputValue, throttledFetcher, {
    initialValue: []
  });
  return {
    get options() {
      return asyncOptions();
    },
    get loading() {
      return asyncOptions.loading;
    },
    onInput: setInputValue,
    readonly: false
  };
};
var throttle = (callback, threshold) => {
  let activePromise = null, timeoutId, lastArgs;
  const wait = () => new Promise((resolve) => timeoutId = setTimeout(resolve, threshold));
  const throttled = (...args) => {
    lastArgs = args;
    if (activePromise) return activePromise;
    activePromise = wait().then(() => {
      activePromise = null;
      return callback(...lastArgs);
    });
    return activePromise;
  };
  const clear = () => {
    clearTimeout(timeoutId);
    activePromise = null;
  };
  if (getOwner()) onCleanup(clear);
  return Object.assign(throttled, { clear });
};

// src/select.tsx
import {
  Show,
  For,
  splitProps,
  mergeProps as mergeProps2,
  createEffect as createEffect2,
  on as on2,
  createContext,
  useContext
} from "solid-js";
var SelectContext = createContext();
var useSelect = () => {
  const context = useContext(SelectContext);
  if (!context) throw new Error("No SelectContext found in ancestry.");
  return context;
};
var Select = (props) => {
  const [selectProps, local] = splitProps(
    mergeProps2(
      {
        format: ((data, type) => data),
        placeholder: "Select...",
        readonly: typeof props.options !== "function",
        loading: false,
        loadingPlaceholder: "Loading...",
        emptyPlaceholder: "No options"
      },
      props
    ),
    [
      "options",
      "optionToValue",
      "isOptionDisabled",
      "multiple",
      "disabled",
      "onInput",
      "onChange"
    ]
  );
  const select = createSelect(selectProps);
  createEffect2(
    on2(
      () => local.initialValue,
      (value) => value !== void 0 && select.setValue(value)
    )
  );
  return <SelectContext.Provider value={select}>
      <Container class={local.class}>
        <Control
    id={local.id}
    name={local.name}
    format={local.format}
    placeholder={local.placeholder}
    autofocus={local.autofocus}
    readonly={local.readonly}
    ref={props.ref}
  />
        <List
    loading={local.loading}
    loadingPlaceholder={local.loadingPlaceholder}
    emptyPlaceholder={local.emptyPlaceholder}
    format={local.format}
  />
      </Container>
    </SelectContext.Provider>;
};
var Container = (props) => {
  const select = useSelect();
  return <div
    class={`solid-select-container ${props.class !== void 0 ? props.class : ""}`}
    data-disabled={select.disabled}
    onFocusIn={select.onFocusIn}
    onFocusOut={select.onFocusOut}
    onMouseDown={(event) => {
      select.onMouseDown(event);
      event.currentTarget.getElementsByTagName("input")[0].focus();
    }}
  >
      {props.children}
    </div>;
};
var Control = (props) => {
  const select = useSelect();
  const removeValue = (index) => {
    const value = select.value();
    select.setValue([...value.slice(0, index), ...value.slice(index + 1)]);
  };
  return <div
    class="solid-select-control"
    data-multiple={select.multiple}
    data-has-value={select.hasValue()}
    data-disabled={select.disabled}
    onClick={select.onClick}
  >
      <Show when={!select.hasValue() && !select.hasInputValue()}>
        <Placeholder>{props.placeholder}</Placeholder>
      </Show>
      <Show
    when={select.hasValue() && !select.multiple && !select.hasInputValue()}
  >
        <SingleValue>{props.format(select.value(), "value")}</SingleValue>
      </Show>
      <Show when={select.hasValue() && select.multiple}>
        <For each={select.value()}>
          {(value, index) => <MultiValue onRemove={() => removeValue(index())}>
              {props.format(value, "value")}
            </MultiValue>}
        </For>
      </Show>
      <Input
    id={props.id}
    name={props.name}
    autofocus={props.autofocus}
    readonly={props.readonly}
    ref={props.ref}
  />
    </div>;
};
var Placeholder = (props) => {
  return <div class="solid-select-placeholder">{props.children}</div>;
};
var SingleValue = (props) => {
  return <div class="solid-select-single-value">{props.children}</div>;
};
var MultiValue = (props) => {
  const select = useSelect();
  return <div class="solid-select-multi-value">
      <span>{props.children}</span>
      <button
    type="button"
    class="solid-select-multi-value-remove"
    onClick={(event) => {
      event.stopPropagation();
      props.onRemove();
    }}
  >
        ⨯
      </button>
    </div>;
};
var Input = (props) => {
  const select = useSelect();
  return <input
    ref={props.ref}
    id={props.id}
    name={props.name}
    class="solid-select-input"
    data-multiple={select.multiple}
    data-is-active={select.isActive()}
    type="text"
    tabIndex={0}
    autocomplete="off"
    autocapitalize="none"
    autocorrect="off"
    autofocus={props.autofocus}
    readonly={props.readonly}
    disabled={select.disabled}
    size={1}
    value={select.inputValue()}
    onInput={select.onInput}
    onKeyDown={(event) => {
      select.onKeyDown(event);
      if (!event.defaultPrevented) {
        if (event.key === "Escape") {
          event.preventDefault();
          event.stopPropagation();
          event.target.blur();
        }
      }
    }}
    onMouseDown={(event) => {
      event.stopPropagation();
    }}
  />;
};
var List = (props) => {
  const select = useSelect();
  return <Show when={select.isOpen()}>
      <div class="solid-select-list">
        <Show
    when={!props.loading}
    fallback={<div class="solid-select-list-placeholder">
              {props.loadingPlaceholder}
            </div>}
  >
          <For
    each={select.options()}
    fallback={<div class="solid-select-list-placeholder">
                {props.emptyPlaceholder}
              </div>}
  >
            {(option) => <Option option={option}>{props.format(option, "option")}</Option>}
          </For>
        </Show>
      </div>
    </Show>;
};
var Option = (props) => {
  const select = useSelect();
  const scrollIntoViewOnFocus = (element) => {
    createEffect2(() => {
      if (select.isOptionFocused(props.option)) {
        element.scrollIntoView({ block: "nearest" });
      }
    });
  };
  return <div
    ref={scrollIntoViewOnFocus}
    data-disabled={select.isOptionDisabled(props.option)}
    data-focused={select.isOptionFocused(props.option)}
    class="solid-select-option"
    onClick={() => select.pickOption(props.option)}
  >
      {props.children}
    </div>;
};
export {
  Container,
  Control,
  Input,
  List,
  MultiValue,
  Option,
  Placeholder,
  Select,
  SelectContext,
  SingleValue,
  createAsyncOptions,
  createOptions,
  createSelect,
  defaultFormat,
  fuzzyHighlight,
  fuzzySearch,
  fuzzySort,
  useSelect
};
