@scalar/components 0.29.3 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,6 +19,10 @@ declare const __VLS_export: <O extends Option = Option, G extends OptionGroup<O>
19
19
  options: OptionsOrGroups<O, G>;
20
20
  /** The placeholder text to display in the combobox */
21
21
  placeholder?: string;
22
+ /** An accessible label for the search input */
23
+ inputLabel?: string;
24
+ /** The message to display when filtering returns no options */
25
+ noResults?: string;
22
26
  /** A function to filter the options based on a query,
23
27
  * if not provided, the options will be filtered by option label
24
28
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCombobox.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarCombobox.vue"],"names":[],"mappings":"AAsGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAG9D,OAAO,KAAK,EAEV,aAAa,EACb,cAAc,EACd,MAAM,EACN,WAAW,EACX,eAAe,EAChB,MAAM,SAAS,CAAA;AAGhB;;;;;;;;;;GAUG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAC1F,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WAkKO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAhKpD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB;;;;WAIG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;;qBAgBtB,CAAC;;;;KAuI6E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;UAEJ,qFAAoC;EAEvC,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"ScalarCombobox.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarCombobox.vue"],"names":[],"mappings":"AA6GA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAG9D,OAAO,KAAK,EAEV,aAAa,EACb,cAAc,EACd,MAAM,EACN,WAAW,EACX,eAAe,EAChB,MAAM,SAAS,CAAA;AAGhB;;;;;;;;;;GAUG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAC1F,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WA4KO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QA1KpD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB,+CAA+C;qBAClC,MAAM;QACnB,+DAA+D;oBACnD,MAAM;QAClB;;;;WAIG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;;qBAgBtB,CAAC;;;;KA6I6E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;UAEJ,qFAAoC;EAEvC,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCombobox.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarCombobox.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox component\n *\n * A searchable select input with support for filtering, option groups,\n * and custom option rendering.\n *\n * @example\n * <ScalarCombobox v-model=\"selected\" :options=\"options\">\n * <ScalarButton>{{ selected?.label ?? 'Select' }}</ScalarButton>\n * </ScalarCombobox>\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport type { ScalarFloatingOptions } from '../ScalarFloating'\nimport ComboboxOptions from './ScalarComboboxOptions.vue'\nimport ComboboxPopover from './ScalarComboboxPopover.vue'\nimport type {\n ComboboxEmits,\n ComboboxSlots,\n FilterFunction,\n Option,\n OptionGroup,\n OptionsOrGroups,\n} from './types'\n\ndefineProps<\n {\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O>()\n\ndefineSlots<ComboboxSlots<O, G>>()\n</script>\n<template>\n <ComboboxPopover\n :middleware\n :offset\n :placement=\"placement ?? 'bottom-start'\"\n :resize\n :target\n :teleport>\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open, close }\">\n <ComboboxOptions\n :filterFn\n :modelValue=\"model ? [model] : []\"\n :open\n :options\n :placeholder\n @add=\"() => (close(), emit('add'))\"\n @update:modelValue=\"(v) => (close(), (model = v[0]))\">\n <!-- Pass through the combobox slots -->\n <template\n v-if=\"$slots.option\"\n #option=\"props\">\n <slot\n name=\"option\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.group\"\n #group=\"props\">\n <slot\n name=\"group\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.add\"\n #add=\"props\">\n <slot\n name=\"add\"\n v-bind=\"props\" />\n </template>\n </ComboboxOptions>\n </template>\n </ComboboxPopover>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScalarCombobox.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarCombobox.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox component\n *\n * A searchable select input with support for filtering, option groups,\n * and custom option rendering.\n *\n * @example\n * <ScalarCombobox v-model=\"selected\" :options=\"options\">\n * <ScalarButton>{{ selected?.label ?? 'Select' }}</ScalarButton>\n * </ScalarCombobox>\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport type { ScalarFloatingOptions } from '../ScalarFloating'\nimport ComboboxOptions from './ScalarComboboxOptions.vue'\nimport ComboboxPopover from './ScalarComboboxPopover.vue'\nimport type {\n ComboboxEmits,\n ComboboxSlots,\n FilterFunction,\n Option,\n OptionGroup,\n OptionsOrGroups,\n} from './types'\n\ndefineProps<\n {\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O>()\n\ndefineSlots<ComboboxSlots<O, G>>()\n</script>\n<template>\n <ComboboxPopover\n :middleware\n :offset\n :placement=\"placement ?? 'bottom-start'\"\n :resize\n :target\n :teleport>\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open, close }\">\n <ComboboxOptions\n :close\n :filterFn\n :inputLabel\n :modelValue=\"model ? [model] : []\"\n :noResults\n :open\n :options\n :placeholder\n @add=\"() => (close(), emit('add'))\"\n @update:modelValue=\"(v) => (close(), (model = v[0]))\">\n <!-- Pass through the combobox slots -->\n <template\n v-if=\"$slots.option\"\n #option=\"props\">\n <slot\n name=\"option\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.group\"\n #group=\"props\">\n <slot\n name=\"group\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.add\"\n #add=\"props\">\n <slot\n name=\"add\"\n v-bind=\"props\" />\n </template>\n </ComboboxOptions>\n </template>\n </ComboboxPopover>\n</template>\n"],"mappings":""}
@@ -6,6 +6,8 @@ var ScalarCombobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
6
6
  props: /*@__PURE__*/ mergeModels({
7
7
  options: {},
8
8
  placeholder: {},
9
+ inputLabel: {},
10
+ noResults: {},
9
11
  filterFn: { type: Function },
10
12
  placement: {},
11
13
  offset: { type: [
@@ -36,8 +38,11 @@ var ScalarCombobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
36
38
  }, {
37
39
  default: withCtx(({ open }) => [renderSlot(_ctx.$slots, "default", { open })]),
38
40
  popover: withCtx(({ open, close }) => [createVNode(ScalarComboboxOptions_default, {
41
+ close,
39
42
  filterFn: __props.filterFn,
43
+ inputLabel: __props.inputLabel,
40
44
  modelValue: model.value ? [model.value] : [],
45
+ noResults: __props.noResults,
41
46
  open,
42
47
  options: __props.options,
43
48
  placeholder: __props.placeholder,
@@ -60,8 +65,11 @@ var ScalarCombobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
60
65
  key: "2"
61
66
  } : void 0
62
67
  ]), 1032, [
68
+ "close",
63
69
  "filterFn",
70
+ "inputLabel",
64
71
  "modelValue",
72
+ "noResults",
65
73
  "open",
66
74
  "options",
67
75
  "placeholder",
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCombobox.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarCombobox.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox component\n *\n * A searchable select input with support for filtering, option groups,\n * and custom option rendering.\n *\n * @example\n * <ScalarCombobox v-model=\"selected\" :options=\"options\">\n * <ScalarButton>{{ selected?.label ?? 'Select' }}</ScalarButton>\n * </ScalarCombobox>\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport type { ScalarFloatingOptions } from '../ScalarFloating'\nimport ComboboxOptions from './ScalarComboboxOptions.vue'\nimport ComboboxPopover from './ScalarComboboxPopover.vue'\nimport type {\n ComboboxEmits,\n ComboboxSlots,\n FilterFunction,\n Option,\n OptionGroup,\n OptionsOrGroups,\n} from './types'\n\ndefineProps<\n {\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O>()\n\ndefineSlots<ComboboxSlots<O, G>>()\n</script>\n<template>\n <ComboboxPopover\n :middleware\n :offset\n :placement=\"placement ?? 'bottom-start'\"\n :resize\n :target\n :teleport>\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open, close }\">\n <ComboboxOptions\n :filterFn\n :modelValue=\"model ? [model] : []\"\n :open\n :options\n :placeholder\n @add=\"() => (close(), emit('add'))\"\n @update:modelValue=\"(v) => (close(), (model = v[0]))\">\n <!-- Pass through the combobox slots -->\n <template\n v-if=\"$slots.option\"\n #option=\"props\">\n <slot\n name=\"option\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.group\"\n #group=\"props\">\n <slot\n name=\"group\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.add\"\n #add=\"props\">\n <slot\n name=\"add\"\n v-bind=\"props\" />\n </template>\n </ComboboxOptions>\n </template>\n </ComboboxPopover>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;EA+CA,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAc,SAAA,YAAC;;uBAK3B,YA2CkB,+BAAA;IA1Cf,YAAA,QAAA;IACA,QAAA,QAAA;IACA,WAAW,QAAA,aAAS;IACpB,QAAA,QAAA;IACA,QAAA,QAAA;IACA,UAAA,QAAA;;IACU,SAAO,SACF,EADM,WAAI,CACxB,WAAc,KAAA,QAAA,WAAA,EAAP,KAAI,CAAA,CAAA,CAAA;IAEF,SAAO,SA+BE,EA/BE,MAAM,YAAK,CAC/B,YA8BkB,+BAAA;KA7Bf,UAAA,QAAA;KACA,YAAY,MAAA,QAAK,CAAI,MAAA,KAAK,IAAA,CAAA;KAC1B;KACA,SAAA,QAAA;KACA,aAAA,QAAA;KACA,cAAY,MAAK,GAAI,KAAI,KAAA;KACzB,wBAAoB,OAAO,MAAK,GAAK,MAAA,QAAQ,EAAC;;KAGvCA,KAAAA,OAAO,SAAA;YACZ;mBAAQ,UAAK,CACd,WAEmB,KAAA,QAAA,UAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA;;;KAGTA,KAAAA,OAAO,QAAA;YACZ;mBAAO,UAAK,CACb,WAEmB,KAAA,QAAA,SAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA;;;KAGTA,KAAAA,OAAO,MAAA;YACZ;mBAAK,UAAK,CACX,WAEmB,KAAA,QAAA,OAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"ScalarCombobox.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarCombobox.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox component\n *\n * A searchable select input with support for filtering, option groups,\n * and custom option rendering.\n *\n * @example\n * <ScalarCombobox v-model=\"selected\" :options=\"options\">\n * <ScalarButton>{{ selected?.label ?? 'Select' }}</ScalarButton>\n * </ScalarCombobox>\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport type { ScalarFloatingOptions } from '../ScalarFloating'\nimport ComboboxOptions from './ScalarComboboxOptions.vue'\nimport ComboboxPopover from './ScalarComboboxPopover.vue'\nimport type {\n ComboboxEmits,\n ComboboxSlots,\n FilterFunction,\n Option,\n OptionGroup,\n OptionsOrGroups,\n} from './types'\n\ndefineProps<\n {\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O>()\n\ndefineSlots<ComboboxSlots<O, G>>()\n</script>\n<template>\n <ComboboxPopover\n :middleware\n :offset\n :placement=\"placement ?? 'bottom-start'\"\n :resize\n :target\n :teleport>\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open, close }\">\n <ComboboxOptions\n :close\n :filterFn\n :inputLabel\n :modelValue=\"model ? [model] : []\"\n :noResults\n :open\n :options\n :placeholder\n @add=\"() => (close(), emit('add'))\"\n @update:modelValue=\"(v) => (close(), (model = v[0]))\">\n <!-- Pass through the combobox slots -->\n <template\n v-if=\"$slots.option\"\n #option=\"props\">\n <slot\n name=\"option\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.group\"\n #group=\"props\">\n <slot\n name=\"group\"\n v-bind=\"props\" />\n </template>\n <template\n v-if=\"$slots.add\"\n #add=\"props\">\n <slot\n name=\"add\"\n v-bind=\"props\" />\n </template>\n </ComboboxOptions>\n </template>\n </ComboboxPopover>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EAmDA,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAc,SAAA,YAAC;;uBAK3B,YA8CkB,+BAAA;IA7Cf,YAAA,QAAA;IACA,QAAA,QAAA;IACA,WAAW,QAAA,aAAS;IACpB,QAAA,QAAA;IACA,QAAA,QAAA;IACA,UAAA,QAAA;;IACU,SAAO,SACF,EADM,WAAI,CACxB,WAAc,KAAA,QAAA,WAAA,EAAP,KAAI,CAAA,CAAA,CAAA;IAEF,SAAO,SAkCE,EAlCE,MAAM,YAAK,CAC/B,YAiCkB,+BAAA;KAhCf;KACA,UAAA,QAAA;KACA,YAAA,QAAA;KACA,YAAY,MAAA,QAAK,CAAI,MAAA,KAAK,IAAA,CAAA;KAC1B,WAAA,QAAA;KACA;KACA,SAAA,QAAA;KACA,aAAA,QAAA;KACA,cAAY,MAAK,GAAI,KAAI,KAAA;KACzB,wBAAoB,OAAO,MAAK,GAAK,MAAA,QAAQ,EAAC;;KAGvCA,KAAAA,OAAO,SAAA;YACZ;mBAAQ,UAAK,CACd,WAEmB,KAAA,QAAA,UAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA;;;KAGTA,KAAAA,OAAO,QAAA;YACZ;mBAAO,UAAK,CACb,WAEmB,KAAA,QAAA,SAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA;;;KAGTA,KAAAA,OAAO,MAAA;YACZ;mBAAK,UAAK,CACX,WAEmB,KAAA,QAAA,OAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA"}
@@ -16,6 +16,12 @@ declare const __VLS_export: <O extends Option = Option, G extends OptionGroup<O>
16
16
  options: OptionsOrGroups<O, G>;
17
17
  /** The placeholder text to display in the combobox */
18
18
  placeholder?: string;
19
+ /** An accessible label for the search input */
20
+ inputLabel?: string;
21
+ /** The message to display when filtering returns no options */
22
+ noResults?: string;
23
+ /** Close the popover without changing the current selection */
24
+ close?: () => void;
19
25
  /**
20
26
  * A function to filter the options based on a query,
21
27
  * if not provided, the options will be filtered by option label
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxOptions.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"names":[],"mappings":"AAgTA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,eAAe,EAErB,MAAM,SAAS,CAAA;AAGhB;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAC1F,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WAueO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAretD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB;;;;;WAKG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,qEAAqE;sBACvD,OAAO;;qBAqKV,CAAC,EAAE;;;;KAoT2E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;UAEJ,2EAAoC;EAEvC,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"ScalarComboboxOptions.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"names":[],"mappings":"AAiUA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,eAAe,EAErB,MAAM,SAAS,CAAA;AAGhB;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAC1F,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WAggBO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QA9ftD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB,+CAA+C;qBAClC,MAAM;QACnB,+DAA+D;oBACnD,MAAM;QAClB,+DAA+D;gBACvD,MAAM,IAAI;QAClB;;;;;WAKG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,qEAAqE;sBACvD,OAAO;;qBAwKV,CAAC,EAAE;;;;KAoU2E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;UAEJ,2EAAoC;EAEvC,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxOptions.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScalarComboboxOptions.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n inputLabel,\n noResults,\n close,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** Close the popover without changing the current selection */\n close?: () => void\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.esc.prevent=\"close?.()\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add || noResults\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <li\n v-if=\"!filtered.length && !slots.add && noResults\"\n class=\"text-c-3 px-2.5 py-2\"\n role=\"status\">\n {{ noResults }}\n </li>\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":""}
@@ -7,15 +7,27 @@ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock
7
7
  import { ScalarIconMagnifyingGlass, ScalarIconPlus } from "@scalar/icons";
8
8
  //#region src/components/ScalarCombobox/ScalarComboboxOptions.vue?vue&type=script&setup=true&lang.ts
9
9
  var _hoisted_1 = { class: "relative flex" };
10
- var _hoisted_2 = ["aria-activedescendant", "placeholder"];
10
+ var _hoisted_2 = [
11
+ "aria-activedescendant",
12
+ "aria-label",
13
+ "placeholder"
14
+ ];
11
15
  var _hoisted_3 = ["aria-multiselectable"];
12
- var _hoisted_4 = { class: "inline-block min-w-0 flex-1 truncate text-c-1" };
16
+ var _hoisted_4 = {
17
+ key: 0,
18
+ class: "text-c-3 px-2.5 py-2",
19
+ role: "status"
20
+ };
21
+ var _hoisted_5 = { class: "inline-block min-w-0 flex-1 truncate text-c-1" };
13
22
  var ScalarComboboxOptions_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
14
23
  inheritAttrs: false,
15
24
  __name: "ScalarComboboxOptions",
16
25
  props: /*@__PURE__*/ mergeModels({
17
26
  options: {},
18
27
  placeholder: {},
28
+ inputLabel: {},
29
+ noResults: {},
30
+ close: { type: Function },
19
31
  filterFn: {
20
32
  type: Function,
21
33
  default: filterByOptionLabel
@@ -99,6 +111,7 @@ var ScalarComboboxOptions_vue_vue_type_script_setup_true_lang_default = /*@__PUR
99
111
  "aria-activedescendant": activeRef.value ? getOptionId(activeRef.value) : void 0,
100
112
  "aria-autocomplete": "list",
101
113
  "aria-controls": id,
114
+ "aria-label": __props.inputLabel,
102
115
  class: "min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1",
103
116
  "data-1p-ignore": "",
104
117
  placeholder: __props.placeholder,
@@ -108,7 +121,8 @@ var ScalarComboboxOptions_vue_vue_type_script_setup_true_lang_default = /*@__PUR
108
121
  onKeydown: [
109
122
  _cache[1] || (_cache[1] = withKeys(withModifiers(($event) => moveActive(1), ["prevent"]), ["down"])),
110
123
  _cache[2] || (_cache[2] = withKeys(withModifiers(($event) => activeRef.value && toggleSelected(activeRef.value), ["prevent"]), ["enter"])),
111
- _cache[3] || (_cache[3] = withKeys(withModifiers(($event) => moveActive(-1), ["prevent"]), ["up"]))
124
+ _cache[3] || (_cache[3] = withKeys(withModifiers(($event) => __props.close?.(), ["prevent"]), ["esc"])),
125
+ _cache[4] || (_cache[4] = withKeys(withModifiers(($event) => moveActive(-1), ["prevent"]), ["up"]))
112
126
  ]
113
127
  }, null, 40, _hoisted_2), [[vModelText, query.value]])]), withDirectives(createElementVNode("ul", {
114
128
  id,
@@ -116,53 +130,57 @@ var ScalarComboboxOptions_vue_vue_type_script_setup_true_lang_default = /*@__PUR
116
130
  class: "border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0",
117
131
  role: "listbox",
118
132
  tabindex: "-1"
119
- }, [(openBlock(true), createElementBlock(Fragment, null, renderList(groups.value, (group, i) => {
120
- return openBlock(), createBlock(ScalarComboboxOptionGroup_default, {
121
- id: `${id}-group-${i}`,
122
- key: i,
123
- hidden: !group.options.some((o) => filtered.value.some((f) => f.id === o.id)) || !group.label
133
+ }, [
134
+ !filtered.value.length && !slots.add && __props.noResults ? (openBlock(), createElementBlock("li", _hoisted_4, toDisplayString(__props.noResults), 1)) : createCommentVNode("", true),
135
+ (openBlock(true), createElementBlock(Fragment, null, renderList(groups.value, (group, i) => {
136
+ return openBlock(), createBlock(ScalarComboboxOptionGroup_default, {
137
+ id: `${id}-group-${i}`,
138
+ key: i,
139
+ hidden: !group.options.some((o) => filtered.value.some((f) => f.id === o.id)) || !group.label
140
+ }, {
141
+ label: withCtx(() => [_ctx.$slots.group ? renderSlot(_ctx.$slots, "group", { group }, void 0, void 0, 0) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(group.label), 1)], 64))]),
142
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(filtered.value, (option) => {
143
+ return openBlock(), createElementBlock(Fragment, { key: option.id }, [group.options.some((o) => o.id === option.id) ? (openBlock(), createBlock(ScalarComboboxOption_default, {
144
+ key: 0,
145
+ id: getOptionId(option),
146
+ active: activeRef.value?.id === option.id,
147
+ selected: model.value.some((o) => o.id === option.id),
148
+ onClick: ($event) => toggleSelected(option),
149
+ onMousedown: _cache[5] || (_cache[5] = withModifiers(() => {}, ["prevent"])),
150
+ onMouseenter: ($event) => activeRef.value = option
151
+ }, {
152
+ default: withCtx(({ active, selected }) => [_ctx.$slots.option ? renderSlot(_ctx.$slots, "option", {
153
+ active,
154
+ option,
155
+ selected
156
+ }, void 0, void 0, 0) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createVNode(unref(ScalarListboxCheckbox_default), {
157
+ multiselect: __props.multiselect,
158
+ selected: model.value.some((o) => o.id === option.id)
159
+ }, null, 8, ["multiselect", "selected"]), createElementVNode("span", _hoisted_5, toDisplayString(option.label), 1)], 64))]),
160
+ _: 2
161
+ }, 1032, [
162
+ "id",
163
+ "active",
164
+ "selected",
165
+ "onClick",
166
+ "onMouseenter"
167
+ ])) : createCommentVNode("", true)], 64);
168
+ }), 128))]),
169
+ _: 2
170
+ }, 1032, ["id", "hidden"]);
171
+ }), 128)),
172
+ slots.add ? (openBlock(), createBlock(ScalarComboboxOption_default, {
173
+ key: 1,
174
+ id: getOptionId(addOption),
175
+ active: activeRef.value?.id === addOption.id,
176
+ onClick: addNew,
177
+ onMousedown: _cache[6] || (_cache[6] = withModifiers(() => {}, ["prevent"])),
178
+ onMouseenter: _cache[7] || (_cache[7] = ($event) => activeRef.value = addOption)
124
179
  }, {
125
- label: withCtx(() => [_ctx.$slots.group ? renderSlot(_ctx.$slots, "group", { group }, void 0, void 0, 0) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(group.label), 1)], 64))]),
126
- default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(filtered.value, (option) => {
127
- return openBlock(), createElementBlock(Fragment, { key: option.id }, [group.options.some((o) => o.id === option.id) ? (openBlock(), createBlock(ScalarComboboxOption_default, {
128
- key: 0,
129
- id: getOptionId(option),
130
- active: activeRef.value?.id === option.id,
131
- selected: model.value.some((o) => o.id === option.id),
132
- onClick: ($event) => toggleSelected(option),
133
- onMousedown: _cache[4] || (_cache[4] = withModifiers(() => {}, ["prevent"])),
134
- onMouseenter: ($event) => activeRef.value = option
135
- }, {
136
- default: withCtx(({ active, selected }) => [_ctx.$slots.option ? renderSlot(_ctx.$slots, "option", {
137
- active,
138
- option,
139
- selected
140
- }, void 0, void 0, 0) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createVNode(unref(ScalarListboxCheckbox_default), {
141
- multiselect: __props.multiselect,
142
- selected: model.value.some((o) => o.id === option.id)
143
- }, null, 8, ["multiselect", "selected"]), createElementVNode("span", _hoisted_4, toDisplayString(option.label), 1)], 64))]),
144
- _: 2
145
- }, 1032, [
146
- "id",
147
- "active",
148
- "selected",
149
- "onClick",
150
- "onMouseenter"
151
- ])) : createCommentVNode("", true)], 64);
152
- }), 128))]),
153
- _: 2
154
- }, 1032, ["id", "hidden"]);
155
- }), 128)), slots.add ? (openBlock(), createBlock(ScalarComboboxOption_default, {
156
- key: 0,
157
- id: getOptionId(addOption),
158
- active: activeRef.value?.id === addOption.id,
159
- onClick: addNew,
160
- onMousedown: _cache[5] || (_cache[5] = withModifiers(() => {}, ["prevent"])),
161
- onMouseenter: _cache[6] || (_cache[6] = ($event) => activeRef.value = addOption)
162
- }, {
163
- default: withCtx(({ active }) => [createVNode(unref(ScalarIconPlus), { class: "size-4 p-px" }), renderSlot(_ctx.$slots, "add", { active })]),
164
- _: 3
165
- }, 8, ["id", "active"])) : createCommentVNode("", true)], 8, _hoisted_3), [[vShow, filtered.value.length || slots.add]])], 64);
180
+ default: withCtx(({ active }) => [createVNode(unref(ScalarIconPlus), { class: "size-4 p-px" }), renderSlot(_ctx.$slots, "add", { active })]),
181
+ _: 3
182
+ }, 8, ["id", "active"])) : createCommentVNode("", true)
183
+ ], 8, _hoisted_3), [[vShow, filtered.value.length || slots.add || __props.noResults]])], 64);
166
184
  };
167
185
  }
168
186
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxOptions.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ScalarComboboxOptions.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n inputLabel,\n noResults,\n close,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** Close the popover without changing the current selection */\n close?: () => void\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.esc.prevent=\"close?.()\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add || noResults\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <li\n v-if=\"!filtered.length && !slots.add && noResults\"\n class=\"text-c-3 px-2.5 py-2\"\n role=\"status\">\n {{ noResults }}\n </li>\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":"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"}
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "url": "git+https://github.com/scalar/scalar.git",
11
11
  "directory": "packages/components"
12
12
  },
13
- "version": "0.29.3",
13
+ "version": "0.30.0",
14
14
  "engines": {
15
15
  "node": ">=22"
16
16
  },