@scalar/components 0.31.1 → 0.32.1

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.
Files changed (54) hide show
  1. package/dist/components/ScalarCard/ScalarCardHeader.vue.d.ts.map +1 -1
  2. package/dist/components/ScalarCard/ScalarCardHeader.vue.js.map +1 -1
  3. package/dist/components/ScalarCard/ScalarCardHeader.vue.script.js +2 -2
  4. package/dist/components/ScalarCard/ScalarCardHeader.vue.script.js.map +1 -1
  5. package/dist/components/ScalarCombobox/ScalarCombobox.vue.d.ts +15 -1
  6. package/dist/components/ScalarCombobox/ScalarCombobox.vue.d.ts.map +1 -1
  7. package/dist/components/ScalarCombobox/ScalarCombobox.vue.js.map +1 -1
  8. package/dist/components/ScalarCombobox/ScalarCombobox.vue.script.js +20 -5
  9. package/dist/components/ScalarCombobox/ScalarCombobox.vue.script.js.map +1 -1
  10. package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.d.ts +15 -1
  11. package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.d.ts.map +1 -1
  12. package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.js.map +1 -1
  13. package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.script.js +22 -7
  14. package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.script.js.map +1 -1
  15. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.d.ts +16 -1
  16. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.d.ts.map +1 -1
  17. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.js.map +1 -1
  18. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.script.js +111 -45
  19. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.script.js.map +1 -1
  20. package/dist/components/ScalarCombobox/types.d.ts +5 -0
  21. package/dist/components/ScalarCombobox/types.d.ts.map +1 -1
  22. package/dist/components/ScalarCombobox/types.js.map +1 -1
  23. package/dist/components/ScalarDropdown/ScalarDropdown.vue.d.ts.map +1 -1
  24. package/dist/components/ScalarDropdown/ScalarDropdown.vue.js.map +1 -1
  25. package/dist/components/ScalarDropdown/ScalarDropdown.vue.script.js +2 -2
  26. package/dist/components/ScalarDropdown/ScalarDropdown.vue.script.js.map +1 -1
  27. package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.d.ts.map +1 -1
  28. package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.js.map +1 -1
  29. package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.script.js +4 -1
  30. package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.script.js.map +1 -1
  31. package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.d.ts.map +1 -1
  32. package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.js.map +1 -1
  33. package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.script.js +6 -2
  34. package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.script.js.map +1 -1
  35. package/dist/components/ScalarMarkdown/index.d.ts +1 -0
  36. package/dist/components/ScalarMarkdown/index.d.ts.map +1 -1
  37. package/dist/components/ScalarMarkdown/index.js +2 -1
  38. package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.d.ts +7 -0
  39. package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.d.ts.map +1 -0
  40. package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.js +46 -0
  41. package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.js.map +1 -0
  42. package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleButton.vue.js.map +1 -1
  43. package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleButton.vue.script.js +1 -1
  44. package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleButton.vue.script.js.map +1 -1
  45. package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleSpacer.vue.js.map +1 -1
  46. package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleSpacer.vue.script.js +1 -1
  47. package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleSpacer.vue.script.js.map +1 -1
  48. package/dist/components/ScalarVirtualText/ScalarVirtualTextSearch.vue.d.ts +2 -2
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +2 -1
  52. package/dist/style.css +34 -9
  53. package/dist/vue-styles.css +2 -1
  54. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCardHeader.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCard/ScalarCardHeader.vue"],"names":[],"mappings":"AA8DA;;;;;;;;;;GAUG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;cAsEJ,CAAC,KAAK,IAAgB,KAAK,GAAG;;cAC9B,CAAC,KAAK,IAAiB,KAAK,GAAG;EAQzC,CAAC;AACL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"ScalarCardHeader.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCard/ScalarCardHeader.vue"],"names":[],"mappings":"AA+DA;;;;;;;;;;GAUG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;cAqEJ,CAAC,KAAK,IAAgB,KAAK,GAAG;;cAC9B,CAAC,KAAK,IAAiB,KAAK,GAAG;EAQzC,CAAC;AACL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCardHeader.vue.js","names":[],"sources":["../../../src/components/ScalarCard/ScalarCardHeader.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar card header component\n *\n * Will automatically be used as the aria-labelledby for the parent ScalarCard component\n *\n * @example\n * <ScalarCard>\n * <ScalarCardHeader>Header</ScalarCardHeader>\n * ...\n * </ScalarCard>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { useId } from 'vue'\n\nimport ScalarCardSection from './ScalarCardSection.vue'\nimport { useCardHeading } from './useCardHeading'\n\n/**\n * Scalar Card Header - Header section for cards with automatic accessibility setup\n */\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n\n// Get a unique id for the heading\nconst id = useId()\n\n// Set the heading id for the card\nuseCardHeading(id)\n</script>\n<template>\n <ScalarCardSection\n v-bind=\"\n cx(\n 'scalar-card-header leading-[22px] font-medium py-[6.75px] px-3 shrink-0',\n )\n \">\n <div\n :id=\"id\"\n class=\"scalar-card-header-title min-w-0 flex-1 truncate\">\n <slot />\n </div>\n <div\n v-if=\"$slots.actions\"\n class=\"flex\">\n <slot name=\"actions\" />\n </div>\n </ScalarCardSection>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScalarCardHeader.vue.js","names":[],"sources":["../../../src/components/ScalarCard/ScalarCardHeader.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar card header component\n *\n * Will automatically be used as the aria-labelledby for the parent ScalarCard component\n *\n * @example\n * <ScalarCard>\n * <ScalarCardHeader>Header</ScalarCardHeader>\n * ...\n * </ScalarCard>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { useId } from 'vue'\n\nimport ScalarCardSection from './ScalarCardSection.vue'\nimport { useCardHeading } from './useCardHeading'\n\n/**\n * Scalar Card Header - Header section for cards with automatic accessibility setup\n */\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n\n// Get a unique id for the heading\nconst id = useId()\n\n// Set the heading id for the card\nuseCardHeading(id)\n</script>\n<template>\n <ScalarCardSection\n v-bind=\"\n cx(\n 'scalar-card-header flex-wrap gap-y-1 leading-[22px] font-medium py-[6.75px] px-3 shrink-0',\n )\n \">\n <!-- Sized to its content so the actions only drop to their own row when the title does not fit beside them -->\n <div\n :id=\"id\"\n class=\"scalar-card-header-title min-w-0 flex-auto\">\n <slot />\n </div>\n <div\n v-if=\"$slots.actions\"\n class=\"flex\">\n <slot name=\"actions\" />\n </div>\n </ScalarCardSection>\n</template>\n"],"mappings":""}
@@ -16,10 +16,10 @@ var ScalarCardHeader_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
16
16
  const id = useId();
17
17
  useCardHeading(id);
18
18
  return (_ctx, _cache) => {
19
- return openBlock(), createBlock(ScalarCardSection_default, normalizeProps(guardReactiveProps(unref(cx)("scalar-card-header leading-[22px] font-medium py-[6.75px] px-3 shrink-0"))), {
19
+ return openBlock(), createBlock(ScalarCardSection_default, normalizeProps(guardReactiveProps(unref(cx)("scalar-card-header flex-wrap gap-y-1 leading-[22px] font-medium py-[6.75px] px-3 shrink-0"))), {
20
20
  default: withCtx(() => [createElementVNode("div", {
21
21
  id: unref(id),
22
- class: "scalar-card-header-title min-w-0 flex-1 truncate"
22
+ class: "scalar-card-header-title min-w-0 flex-auto"
23
23
  }, [renderSlot(_ctx.$slots, "default")], 8, _hoisted_1), _ctx.$slots.actions ? (openBlock(), createElementBlock("div", _hoisted_2, [renderSlot(_ctx.$slots, "actions")])) : createCommentVNode("", true)]),
24
24
  _: 3
25
25
  }, 16);
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCardHeader.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCard/ScalarCardHeader.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar card header component\n *\n * Will automatically be used as the aria-labelledby for the parent ScalarCard component\n *\n * @example\n * <ScalarCard>\n * <ScalarCardHeader>Header</ScalarCardHeader>\n * ...\n * </ScalarCard>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { useId } from 'vue'\n\nimport ScalarCardSection from './ScalarCardSection.vue'\nimport { useCardHeading } from './useCardHeading'\n\n/**\n * Scalar Card Header - Header section for cards with automatic accessibility setup\n */\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n\n// Get a unique id for the heading\nconst id = useId()\n\n// Set the heading id for the card\nuseCardHeading(id)\n</script>\n<template>\n <ScalarCardSection\n v-bind=\"\n cx(\n 'scalar-card-header leading-[22px] font-medium py-[6.75px] px-3 shrink-0',\n )\n \">\n <div\n :id=\"id\"\n class=\"scalar-card-header-title min-w-0 flex-1 truncate\">\n <slot />\n </div>\n <div\n v-if=\"$slots.actions\"\n class=\"flex\">\n <slot name=\"actions\" />\n </div>\n </ScalarCardSection>\n</template>\n"],"mappings":";;;;;;;;;;;;;;EAyBA,MAAM,EAAE,OAAO,UAAU;EAGzB,MAAM,KAAK,MAAM;EAGjB,eAAe,EAAE;;uBAGf,YAgBoB,2BAAA,eAAA,mBAfH,MAAA,EAAA,CAAE,CAAA,yEAAA,CAAA,CAAA,GAAA;2BASX,CAJN,mBAIM,OAAA;KAHH,IAAI,MAAA,EAAA;KACL,OAAM;QACN,WAAQ,KAAA,QAAA,SAAA,CAAA,GAAA,GAAA,UAAA,GAGFA,KAAAA,OAAO,WAAA,UAAA,GADf,mBAIM,OAJN,YAIM,CADJ,WAAuB,KAAA,QAAA,SAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"ScalarCardHeader.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCard/ScalarCardHeader.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar card header component\n *\n * Will automatically be used as the aria-labelledby for the parent ScalarCard component\n *\n * @example\n * <ScalarCard>\n * <ScalarCardHeader>Header</ScalarCardHeader>\n * ...\n * </ScalarCard>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { useId } from 'vue'\n\nimport ScalarCardSection from './ScalarCardSection.vue'\nimport { useCardHeading } from './useCardHeading'\n\n/**\n * Scalar Card Header - Header section for cards with automatic accessibility setup\n */\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n\n// Get a unique id for the heading\nconst id = useId()\n\n// Set the heading id for the card\nuseCardHeading(id)\n</script>\n<template>\n <ScalarCardSection\n v-bind=\"\n cx(\n 'scalar-card-header flex-wrap gap-y-1 leading-[22px] font-medium py-[6.75px] px-3 shrink-0',\n )\n \">\n <!-- Sized to its content so the actions only drop to their own row when the title does not fit beside them -->\n <div\n :id=\"id\"\n class=\"scalar-card-header-title min-w-0 flex-auto\">\n <slot />\n </div>\n <div\n v-if=\"$slots.actions\"\n class=\"flex\">\n <slot name=\"actions\" />\n </div>\n </ScalarCardSection>\n</template>\n"],"mappings":";;;;;;;;;;;;;;EAyBA,MAAM,EAAE,OAAO,UAAU;EAGzB,MAAM,KAAK,MAAM;EAGjB,eAAe,EAAE;;uBAGf,YAiBoB,2BAAA,eAAA,mBAhBH,MAAA,EAAA,CAAE,CAAA,2FAAA,CAAA,CAAA,GAAA;2BAUX,CAJN,mBAIM,OAAA;KAHH,IAAI,MAAA,EAAA;KACL,OAAM;QACN,WAAQ,KAAA,QAAA,SAAA,CAAA,GAAA,GAAA,UAAA,GAGFA,KAAAA,OAAO,WAAA,UAAA,GADf,mBAIM,OAJN,YAIM,CADJ,WAAuB,KAAA,QAAA,SAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA"}
@@ -1,4 +1,5 @@
1
1
  import type { ScalarFloatingOptions } from '../ScalarFloating/index.js';
2
+ import type { LoadingState } from '../ScalarLoading/index.js';
2
3
  import type { ComboboxSlots, FilterFunction, Option, OptionGroup, OptionsOrGroups } from './types.js';
3
4
  /**
4
5
  * Scalar Combobox component
@@ -26,21 +27,34 @@ declare const __VLS_export: <O extends Option = Option, G extends OptionGroup<O>
26
27
  /** A function to filter the options based on a query,
27
28
  * if not provided, the options will be filtered by option label
28
29
  *
30
+ * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a
31
+ * server) using the `query` model, so results the server already matched are shown as given.
32
+ *
29
33
  * @see {@link FilterFunction} for more information
30
34
  */
31
35
  filterFn?: FilterFunction<O, G>;
36
+ /**
37
+ * The loading state of the options, see `useLoadingState`
38
+ *
39
+ * Shows a spinner at the end of the search input while active, or an X when the state is
40
+ * invalidated (e.g. a failed request).
41
+ */
42
+ loader?: LoadingState;
32
43
  } & ScalarFloatingOptions & {
33
44
  modelValue?: O;
45
+ /** The search query, bind it to search the options yourself */
46
+ query?: string;
34
47
  }) & {
35
48
  onAdd?: (() => any) | undefined;
36
49
  "onUpdate:modelValue"?: ((value: O | undefined) => any) | undefined;
50
+ "onUpdate:query"?: ((value: string) => any) | undefined;
37
51
  }> & (typeof globalThis extends {
38
52
  __VLS_PROPS_FALLBACK: infer P;
39
53
  } ? P : {});
40
54
  expose: (exposed: {}) => void;
41
55
  attrs: any;
42
56
  slots: ComboboxSlots<O, G>;
43
- emit: ((evt: "add") => void) & ((event: "update:modelValue", value: O | undefined) => void);
57
+ emit: ((evt: "add") => void) & (((event: "update:modelValue", value: O | undefined) => void) & ((event: "update:query", value: string) => void));
44
58
  }>) => import("vue").VNode & {
45
59
  __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
46
60
  };
@@ -1 +1 @@
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
+ {"version":3,"file":"ScalarCombobox.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarCombobox.vue"],"names":[],"mappings":"AAoIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAGpD,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;WA+MO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QA7MpD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB,+CAA+C;qBAClC,MAAM;QACnB,+DAA+D;oBACnD,MAAM;QAClB;;;;;;;WAOG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B;;;;;WAKG;iBACM,YAAY;;qBAsBZ,CAAC;QACd,+DAA+D;gBACrD,MAAM;;;;;KA8J2E,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,0IAAoC;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 /** 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":""}
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 type { LoadingState } from '../ScalarLoading'\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 * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a\n * server) using the `query` model, so results the server already matched are shown as given.\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /**\n * The loading state of the options, see `useLoadingState`\n *\n * Shows a spinner at the end of the search input while active, or an X when the state is\n * invalidated (e.g. a failed request).\n */\n loader?: LoadingState\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O>()\n\n/** The search query, bind it to search the options yourself */\nconst query = defineModel<string>('query', { default: '' })\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 v-model:query=\"query\"\n :close\n :filterFn\n :inputLabel\n :loader\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['search-end']\"\n #search-end=\"props\">\n <slot\n name=\"search-end\"\n v-bind=\"props\" />\n </template>\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":""}
@@ -9,6 +9,7 @@ var ScalarCombobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
9
9
  inputLabel: {},
10
10
  noResults: {},
11
11
  filterFn: { type: Function },
12
+ loader: {},
12
13
  placement: {},
13
14
  offset: { type: [
14
15
  Number,
@@ -21,12 +22,16 @@ var ScalarCombobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
21
22
  teleport: { type: [Boolean, String] }
22
23
  }, {
23
24
  "modelValue": {},
24
- "modelModifiers": {}
25
+ "modelModifiers": {},
26
+ "query": { default: "" },
27
+ "queryModifiers": {}
25
28
  }),
26
- emits: /*@__PURE__*/ mergeModels(["add"], ["update:modelValue"]),
29
+ emits: /*@__PURE__*/ mergeModels(["add"], ["update:modelValue", "update:query"]),
27
30
  setup(__props, { emit: __emit }) {
28
31
  const emit = __emit;
29
32
  const model = useModel(__props, "modelValue");
33
+ /** The search query, bind it to search the options yourself */
34
+ const query = useModel(__props, "query");
30
35
  return (_ctx, _cache) => {
31
36
  return openBlock(), createBlock(ScalarComboboxPopover_default, {
32
37
  middleware: __props.middleware,
@@ -38,9 +43,12 @@ var ScalarCombobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
38
43
  }, {
39
44
  default: withCtx(({ open }) => [renderSlot(_ctx.$slots, "default", { open })]),
40
45
  popover: withCtx(({ open, close }) => [createVNode(ScalarComboboxOptions_default, {
46
+ query: query.value,
47
+ "onUpdate:query": _cache[0] || (_cache[0] = ($event) => query.value = $event),
41
48
  close,
42
49
  filterFn: __props.filterFn,
43
50
  inputLabel: __props.inputLabel,
51
+ loader: __props.loader,
44
52
  modelValue: model.value ? [model.value] : [],
45
53
  noResults: __props.noResults,
46
54
  open,
@@ -49,25 +57,32 @@ var ScalarCombobox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ d
49
57
  onAdd: () => (close(), emit("add")),
50
58
  "onUpdate:modelValue": (v) => (close(), model.value = v[0])
51
59
  }, createSlots({ _: 2 }, [
60
+ _ctx.$slots["search-end"] ? {
61
+ name: "search-end",
62
+ fn: withCtx((props) => [renderSlot(_ctx.$slots, "search-end", normalizeProps(guardReactiveProps(props)))]),
63
+ key: "0"
64
+ } : void 0,
52
65
  _ctx.$slots.option ? {
53
66
  name: "option",
54
67
  fn: withCtx((props) => [renderSlot(_ctx.$slots, "option", normalizeProps(guardReactiveProps(props)))]),
55
- key: "0"
68
+ key: "1"
56
69
  } : void 0,
57
70
  _ctx.$slots.group ? {
58
71
  name: "group",
59
72
  fn: withCtx((props) => [renderSlot(_ctx.$slots, "group", normalizeProps(guardReactiveProps(props)))]),
60
- key: "1"
73
+ key: "2"
61
74
  } : void 0,
62
75
  _ctx.$slots.add ? {
63
76
  name: "add",
64
77
  fn: withCtx((props) => [renderSlot(_ctx.$slots, "add", normalizeProps(guardReactiveProps(props)))]),
65
- key: "2"
78
+ key: "3"
66
79
  } : void 0
67
80
  ]), 1032, [
81
+ "query",
68
82
  "close",
69
83
  "filterFn",
70
84
  "inputLabel",
85
+ "loader",
71
86
  "modelValue",
72
87
  "noResults",
73
88
  "open",
@@ -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 /** 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"}
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 type { LoadingState } from '../ScalarLoading'\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 * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a\n * server) using the `query` model, so results the server already matched are shown as given.\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /**\n * The loading state of the options, see `useLoadingState`\n *\n * Shows a spinner at the end of the search input while active, or an X when the state is\n * invalidated (e.g. a failed request).\n */\n loader?: LoadingState\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O>()\n\n/** The search query, bind it to search the options yourself */\nconst query = defineModel<string>('query', { default: '' })\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 v-model:query=\"query\"\n :close\n :filterFn\n :inputLabel\n :loader\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['search-end']\"\n #search-end=\"props\">\n <slot\n name=\"search-end\"\n v-bind=\"props\" />\n </template>\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8DA,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAc,SAAA,YAAC;;EAG7B,MAAM,QAAQ,SAAmB,SAAC,OAAwB;;uBAKxD,YAuDkB,+BAAA;IAtDf,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,SA2CE,EA3CE,MAAM,YAAK,CAC/B,YA0CkB,+BAAA;KAzCR,OAAO,MAAA;mEAAK,QAAA;KACnB;KACA,UAAA,QAAA;KACA,YAAA,QAAA;KACA,QAAA,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,OAAM,gBAAA;YACX;mBAAY,UAAK,CAClB,WAEmB,KAAA,QAAA,cAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA;;;KAGTA,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,4 +1,5 @@
1
1
  import type { ScalarFloatingOptions } from '../ScalarFloating/index.js';
2
+ import type { LoadingState } from '../ScalarLoading/index.js';
2
3
  import type { ComboboxSlots, FilterFunction, Option, OptionGroup, OptionsOrGroups } from './types.js';
3
4
  /**
4
5
  * Scalar Combobox Multiselect component
@@ -21,14 +22,27 @@ declare const __VLS_export: <O extends Option = Option, G extends OptionGroup<O>
21
22
  /** A function to filter the options based on a query,
22
23
  * if not provided, the options will be filtered by option label
23
24
  *
25
+ * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a
26
+ * server) using the `query` model, so results the server already matched are shown as given.
27
+ *
24
28
  * @see {@link FilterFunction} for more information
25
29
  */
26
30
  filterFn?: FilterFunction<O, G>;
31
+ /**
32
+ * The loading state of the options, see `useLoadingState`
33
+ *
34
+ * Shows a spinner at the end of the search input while active, or an X when the state is
35
+ * invalidated (e.g. a failed request).
36
+ */
37
+ loader?: LoadingState;
27
38
  } & ScalarFloatingOptions & {
28
39
  modelValue?: O[];
40
+ /** The search query, bind it to search the options yourself */
41
+ query?: string;
29
42
  }) & {
30
43
  onAdd?: (() => any) | undefined;
31
44
  "onUpdate:modelValue"?: ((value: O[]) => any) | undefined;
45
+ "onUpdate:query"?: ((value: string) => any) | undefined;
32
46
  }> & (typeof globalThis extends {
33
47
  __VLS_PROPS_FALLBACK: infer P;
34
48
  } ? P : {});
@@ -461,7 +475,7 @@ declare const __VLS_export: <O extends Option = Option, G extends OptionGroup<O>
461
475
  }>) => void;
462
476
  attrs: any;
463
477
  slots: ComboboxSlots<O, G>;
464
- emit: ((evt: "add") => void) & ((event: "update:modelValue", value: O[]) => void);
478
+ emit: ((evt: "add") => void) & (((event: "update:modelValue", value: O[]) => void) & ((event: "update:query", value: string) => void));
465
479
  }>) => import("vue").VNode & {
466
480
  __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
467
481
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxMultiselect.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarComboboxMultiselect.vue"],"names":[],"mappings":"AAgHA,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;;;;;;;;;GASG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY,GAAM,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAC3F,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;WAoLO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAlLpD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB;;;;WAIG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;;qBAyBtB,CAAC,EAAE;;;;KAgJ2E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,OAAO,KAAK,EAAE,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAAsB,KAAK,IAAI;WACxE,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":"ScalarComboboxMultiselect.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarComboboxMultiselect.vue"],"names":[],"mappings":"AAuIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAGpD,OAAO,KAAK,EAEV,aAAa,EACb,cAAc,EACd,MAAM,EACN,WAAW,EACX,eAAe,EAChB,MAAM,SAAS,CAAA;AAGhB;;;;;;;;;GASG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY,GAAM,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAC3F,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;WAqNO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAnNpD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB;;;;;;;WAOG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B;;;;;WAKG;iBACM,YAAY;;qBA6BZ,CAAC,EAAE;QAChB,+DAA+D;gBACrD,MAAM;;;;;KAiK2E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,OAAO,KAAK,EAAE,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAAsB,KAAK,IAAI;WACxE,GAAG;;UAEJ,gIAAoC;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":"ScalarComboboxMultiselect.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxMultiselect.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Multiselect component\n *\n * A searchable select input that supports selecting multiple options.\n *\n * @example\n * <ScalarComboboxMultiselect v-model=\"selected\" :options=\"options\">\n * <ScalarButton>Select items</ScalarButton>\n * </ScalarComboboxMultiselect>\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 { ref } from 'vue'\n\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[]>({ default: () => [] })\n\ndefineSlots<ComboboxSlots<O, G>>()\n\n/** Propagate up the popover ref */\nconst comboboxPopoverRef = ref<typeof ComboboxPopover | null>(null)\n\ndefineExpose({ comboboxPopoverRef })\n</script>\n<template>\n <ComboboxPopover\n ref=\"comboboxPopoverRef\"\n :middleware=\"middleware\"\n :offset=\"offset\"\n :placement=\"placement ?? 'bottom-start'\"\n :resize=\"resize\"\n :target=\"target\"\n :teleport=\"teleport\">\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open }\">\n <ComboboxOptions\n v-if=\"options?.length\"\n v-model=\"model\"\n :filterFn\n multiselect\n :open\n :options\n :placeholder\n @add=\"emit('add')\">\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":"ScalarComboboxMultiselect.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxMultiselect.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Multiselect component\n *\n * A searchable select input that supports selecting multiple options.\n *\n * @example\n * <ScalarComboboxMultiselect v-model=\"selected\" :options=\"options\">\n * <ScalarButton>Select items</ScalarButton>\n * </ScalarComboboxMultiselect>\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 { ref } from 'vue'\n\nimport type { ScalarFloatingOptions } from '../ScalarFloating'\nimport type { LoadingState } from '../ScalarLoading'\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 * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a\n * server) using the `query` model, so results the server already matched are shown as given.\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /**\n * The loading state of the options, see `useLoadingState`\n *\n * Shows a spinner at the end of the search input while active, or an X when the state is\n * invalidated (e.g. a failed request).\n */\n loader?: LoadingState\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\n/** The search query, bind it to search the options yourself */\nconst query = defineModel<string>('query', { default: '' })\n\ndefineSlots<ComboboxSlots<O, G>>()\n\n/** Propagate up the popover ref */\nconst comboboxPopoverRef = ref<typeof ComboboxPopover | null>(null)\n\ndefineExpose({ comboboxPopoverRef })\n</script>\n<template>\n <ComboboxPopover\n ref=\"comboboxPopoverRef\"\n :middleware=\"middleware\"\n :offset=\"offset\"\n :placement=\"placement ?? 'bottom-start'\"\n :resize=\"resize\"\n :target=\"target\"\n :teleport=\"teleport\">\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open }\">\n <ComboboxOptions\n v-if=\"options?.length || loader\"\n v-model=\"model\"\n v-model:query=\"query\"\n :filterFn\n :loader\n multiselect\n :open\n :options\n :placeholder\n @add=\"emit('add')\">\n <!-- Pass through the combobox slots -->\n <template\n v-if=\"$slots['search-end']\"\n #search-end=\"props\">\n <slot\n name=\"search-end\"\n v-bind=\"props\" />\n </template>\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":""}
@@ -7,6 +7,7 @@ var ScalarComboboxMultiselect_vue_vue_type_script_setup_true_lang_default = /*@_
7
7
  options: {},
8
8
  placeholder: {},
9
9
  filterFn: { type: Function },
10
+ loader: {},
10
11
  placement: {},
11
12
  offset: { type: [
12
13
  Number,
@@ -19,12 +20,16 @@ var ScalarComboboxMultiselect_vue_vue_type_script_setup_true_lang_default = /*@_
19
20
  teleport: { type: [Boolean, String] }
20
21
  }, {
21
22
  "modelValue": { default: () => [] },
22
- "modelModifiers": {}
23
+ "modelModifiers": {},
24
+ "query": { default: "" },
25
+ "queryModifiers": {}
23
26
  }),
24
- emits: /*@__PURE__*/ mergeModels(["add"], ["update:modelValue"]),
27
+ emits: /*@__PURE__*/ mergeModels(["add"], ["update:modelValue", "update:query"]),
25
28
  setup(__props, { expose: __expose, emit: __emit }) {
26
29
  const emit = __emit;
27
30
  const model = useModel(__props, "modelValue");
31
+ /** The search query, bind it to search the options yourself */
32
+ const query = useModel(__props, "query");
28
33
  /** Propagate up the popover ref */
29
34
  const comboboxPopoverRef = ref(null);
30
35
  __expose({ comboboxPopoverRef });
@@ -40,35 +45,45 @@ var ScalarComboboxMultiselect_vue_vue_type_script_setup_true_lang_default = /*@_
40
45
  teleport: __props.teleport
41
46
  }, {
42
47
  default: withCtx(({ open }) => [renderSlot(_ctx.$slots, "default", { open })]),
43
- popover: withCtx(({ open }) => [__props.options?.length ? (openBlock(), createBlock(ScalarComboboxOptions_default, {
48
+ popover: withCtx(({ open }) => [__props.options?.length || __props.loader ? (openBlock(), createBlock(ScalarComboboxOptions_default, {
44
49
  key: 0,
45
50
  modelValue: model.value,
46
51
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => model.value = $event),
52
+ query: query.value,
53
+ "onUpdate:query": _cache[1] || (_cache[1] = ($event) => query.value = $event),
47
54
  filterFn: __props.filterFn,
55
+ loader: __props.loader,
48
56
  multiselect: "",
49
57
  open,
50
58
  options: __props.options,
51
59
  placeholder: __props.placeholder,
52
- onAdd: _cache[1] || (_cache[1] = ($event) => emit("add"))
60
+ onAdd: _cache[2] || (_cache[2] = ($event) => emit("add"))
53
61
  }, createSlots({ _: 2 }, [
62
+ _ctx.$slots["search-end"] ? {
63
+ name: "search-end",
64
+ fn: withCtx((props) => [renderSlot(_ctx.$slots, "search-end", normalizeProps(guardReactiveProps(props)))]),
65
+ key: "0"
66
+ } : void 0,
54
67
  _ctx.$slots.option ? {
55
68
  name: "option",
56
69
  fn: withCtx((props) => [renderSlot(_ctx.$slots, "option", normalizeProps(guardReactiveProps(props)))]),
57
- key: "0"
70
+ key: "1"
58
71
  } : void 0,
59
72
  _ctx.$slots.group ? {
60
73
  name: "group",
61
74
  fn: withCtx((props) => [renderSlot(_ctx.$slots, "group", normalizeProps(guardReactiveProps(props)))]),
62
- key: "1"
75
+ key: "2"
63
76
  } : void 0,
64
77
  _ctx.$slots.add ? {
65
78
  name: "add",
66
79
  fn: withCtx((props) => [renderSlot(_ctx.$slots, "add", normalizeProps(guardReactiveProps(props)))]),
67
- key: "2"
80
+ key: "3"
68
81
  } : void 0
69
82
  ]), 1032, [
70
83
  "modelValue",
84
+ "query",
71
85
  "filterFn",
86
+ "loader",
72
87
  "open",
73
88
  "options",
74
89
  "placeholder"
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxMultiselect.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxMultiselect.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Multiselect component\n *\n * A searchable select input that supports selecting multiple options.\n *\n * @example\n * <ScalarComboboxMultiselect v-model=\"selected\" :options=\"options\">\n * <ScalarButton>Select items</ScalarButton>\n * </ScalarComboboxMultiselect>\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 { ref } from 'vue'\n\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[]>({ default: () => [] })\n\ndefineSlots<ComboboxSlots<O, G>>()\n\n/** Propagate up the popover ref */\nconst comboboxPopoverRef = ref<typeof ComboboxPopover | null>(null)\n\ndefineExpose({ comboboxPopoverRef })\n</script>\n<template>\n <ComboboxPopover\n ref=\"comboboxPopoverRef\"\n :middleware=\"middleware\"\n :offset=\"offset\"\n :placement=\"placement ?? 'bottom-start'\"\n :resize=\"resize\"\n :target=\"target\"\n :teleport=\"teleport\">\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open }\">\n <ComboboxOptions\n v-if=\"options?.length\"\n v-model=\"model\"\n :filterFn\n multiselect\n :open\n :options\n :placeholder\n @add=\"emit('add')\">\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":";;;;;;;;;;;;;;;;;;;;;;;;;EAgDA,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAgB,SAAA,YAAsB;;EAKpD,MAAM,qBAAqB,IAAmC,IAAI;EAElE,SAAa,EAAE,mBAAmB,CAAC;;uBAGjC,YA6CkB,+BAAA;aA5CZ;IAAJ,KAAI;IACH,YAAY,QAAA;IACZ,QAAQ,QAAA;IACR,WAAW,QAAA,aAAS;IACpB,QAAQ,QAAA;IACR,QAAQ,QAAA;IACR,UAAU,QAAA;;IACA,SAAO,SACF,EADM,WAAI,CACxB,WAAc,KAAA,QAAA,WAAA,EAAP,KAAI,CAAA,CAAA,CAAA;IAEF,SAAO,SAgCE,EAhCE,WAAI,CAEhB,QAAA,SAAS,UAAA,UAAA,GADjB,YA+BkB,+BAAA;;iBA7BP,MAAA;wEAAK,QAAA;KACb,UAAA,QAAA;KACD,aAAA;KACC;KACA,SAAA,QAAA;KACA,aAAA,QAAA;KACA,OAAG,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,KAAA;;KAGFA,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":"ScalarComboboxMultiselect.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxMultiselect.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Multiselect component\n *\n * A searchable select input that supports selecting multiple options.\n *\n * @example\n * <ScalarComboboxMultiselect v-model=\"selected\" :options=\"options\">\n * <ScalarButton>Select items</ScalarButton>\n * </ScalarComboboxMultiselect>\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 { ref } from 'vue'\n\nimport type { ScalarFloatingOptions } from '../ScalarFloating'\nimport type { LoadingState } from '../ScalarLoading'\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 * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a\n * server) using the `query` model, so results the server already matched are shown as given.\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /**\n * The loading state of the options, see `useLoadingState`\n *\n * Shows a spinner at the end of the search input while active, or an X when the state is\n * invalidated (e.g. a failed request).\n */\n loader?: LoadingState\n } & ScalarFloatingOptions\n>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\n/** The search query, bind it to search the options yourself */\nconst query = defineModel<string>('query', { default: '' })\n\ndefineSlots<ComboboxSlots<O, G>>()\n\n/** Propagate up the popover ref */\nconst comboboxPopoverRef = ref<typeof ComboboxPopover | null>(null)\n\ndefineExpose({ comboboxPopoverRef })\n</script>\n<template>\n <ComboboxPopover\n ref=\"comboboxPopoverRef\"\n :middleware=\"middleware\"\n :offset=\"offset\"\n :placement=\"placement ?? 'bottom-start'\"\n :resize=\"resize\"\n :target=\"target\"\n :teleport=\"teleport\">\n <template #default=\"{ open }\">\n <slot :open />\n </template>\n <template #popover=\"{ open }\">\n <ComboboxOptions\n v-if=\"options?.length || loader\"\n v-model=\"model\"\n v-model:query=\"query\"\n :filterFn\n :loader\n multiselect\n :open\n :options\n :placeholder\n @add=\"emit('add')\">\n <!-- Pass through the combobox slots -->\n <template\n v-if=\"$slots['search-end']\"\n #search-end=\"props\">\n <slot\n name=\"search-end\"\n v-bind=\"props\" />\n </template>\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2DA,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAgB,SAAA,YAAsB;;EAGpD,MAAM,QAAQ,SAAmB,SAAC,OAAwB;;EAK1D,MAAM,qBAAqB,IAAmC,IAAI;EAElE,SAAa,EAAE,mBAAmB,CAAC;;uBAGjC,YAsDkB,+BAAA;aArDZ;IAAJ,KAAI;IACH,YAAY,QAAA;IACZ,QAAQ,QAAA;IACR,WAAW,QAAA,aAAS;IACpB,QAAQ,QAAA;IACR,QAAQ,QAAA;IACR,UAAU,QAAA;;IACA,SAAO,SACF,EADM,WAAI,CACxB,WAAc,KAAA,QAAA,WAAA,EAAP,KAAI,CAAA,CAAA,CAAA;IAEF,SAAO,SAyCE,EAzCE,WAAI,CAEhB,QAAA,SAAS,UAAU,QAAA,UAAA,UAAA,GAD3B,YAwCkB,+BAAA;;iBAtCP,MAAA;wEAAK,QAAA;KACN,OAAO,MAAA;mEAAK,QAAA;KACnB,UAAA,QAAA;KACA,QAAA,QAAA;KACD,aAAA;KACC;KACA,SAAA,QAAA;KACA,aAAA,QAAA;KACA,OAAG,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,KAAA;;KAGFA,KAAAA,OAAM,gBAAA;YACX;mBAAY,UAAK,CAClB,WAEmB,KAAA,QAAA,cAAA,eAAA,mBAAT,KAAK,CAAA,CAAA,CAAA,CAAA;;;KAGTA,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,3 +1,4 @@
1
+ import { type LoadingState } from '../ScalarLoading/index.js';
1
2
  import { type ComboboxSlots, type FilterFunction, type Option, type OptionGroup, type OptionsOrGroups } from './types.js';
2
3
  /**
3
4
  * Scalar Combobox Options component
@@ -26,23 +27,37 @@ declare const __VLS_export: <O extends Option = Option, G extends OptionGroup<O>
26
27
  * A function to filter the options based on a query,
27
28
  * if not provided, the options will be filtered by option label
28
29
  *
30
+ * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a
31
+ * server) using the `query` model, so results the server already matched are shown as given.
32
+ *
29
33
  * @see {@link FilterFunction} for more information
30
34
  */
31
35
  filterFn?: FilterFunction<O, G>;
36
+ /**
37
+ * The loading state of the options, see `useLoadingState`
38
+ *
39
+ * Shows a spinner at the end of the search input while active, or an X when the state is
40
+ * invalidated (e.g. a failed request). While loading with no options yet, the list shows
41
+ * placeholder rows instead of the "no results" message and the spinner.
42
+ */
43
+ loader?: LoadingState;
32
44
  /** Whether the combobox is in multiselect mode, defaults to false */
33
45
  multiselect?: boolean;
34
46
  } & {
35
47
  modelValue?: O[];
48
+ /** The search query, exposed so callers can search the options themselves */
49
+ query?: string;
36
50
  }) & {
37
51
  onAdd?: (() => any) | undefined;
38
52
  "onUpdate:modelValue"?: ((value: O[]) => any) | undefined;
53
+ "onUpdate:query"?: ((value: string) => any) | undefined;
39
54
  }> & (typeof globalThis extends {
40
55
  __VLS_PROPS_FALLBACK: infer P;
41
56
  } ? P : {});
42
57
  expose: (exposed: {}) => void;
43
58
  attrs: any;
44
59
  slots: Omit<ComboboxSlots<O, G>, "default">;
45
- emit: ((evt: "add") => void) & ((event: "update:modelValue", value: O[]) => void);
60
+ emit: ((evt: "add") => void) & (((event: "update:modelValue", value: O[]) => void) & ((event: "update:query", value: string) => void));
46
61
  }>) => import("vue").VNode & {
47
62
  __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
48
63
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxOptions.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"names":[],"mappings":"AAyVA,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;WAyhBO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAvhBtD,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;;qBA6LV,CAAC,EAAE;;;;KAwU2E,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":"AA8bA,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,kBAAkB,CAAA;AAGnE,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;WAoqBO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QAlqBtD,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;;;;;;;;WAQG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B;;;;;;WAMG;iBACM,YAAY;QACrB,qEAAqE;sBACvD,OAAO;;qBA+PV,CAAC,EAAE;QAChB,6EAA6E;gBACnE,MAAM;;;;;KAoY2E,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,gIAAoC;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 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/**\n * Space toggles the active option in multiselect mode while the search box is empty.\n *\n * The options look like checkboxes, so keyboard users press Space to toggle them. Without this\n * handler Space types a leading space into the query, which filters out every option whose label\n * has no space and hides the whole list without any feedback (found by an accessibility audit).\n * Once a query has been typed, Space keeps inserting a character so multi-word labels stay\n * searchable. Key auto-repeat is ignored so holding Space after opening the popover with it does\n * not toggle the option repeatedly, and IME composition is left alone.\n */\nconst handleSpace = (event: KeyboardEvent): void => {\n if (!multiselect || query.value !== '' || event.isComposing) {\n return\n }\n // Suppress repeated spaces too, so holding the key does not start filtering.\n event.preventDefault()\n if (!event.repeat) {\n toggleSelected(activeRef.value)\n }\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 <!-- Inset to line up with the options below -->\n <div class=\"relative flex m-1 mb-0.75\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-1.75 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-expanded=\"Boolean(filtered.length || slots.add || noResults)\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded-md border-0 py-1.5 pl-7.25 pr-1.75 leading-none text-c-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.space=\"handleSpace\"\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":""}
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 { type LoadingState, ScalarLoading } from '../ScalarLoading'\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 loader,\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 * Pass `(_, options) => options` when the caller searches the options itself (e.g. on a\n * server) using the `query` model, so results the server already matched are shown as given.\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /**\n * The loading state of the options, see `useLoadingState`\n *\n * Shows a spinner at the end of the search input while active, or an X when the state is\n * invalidated (e.g. a failed request). While loading with no options yet, the list shows\n * placeholder rows instead of the \"no results\" message and the spinner.\n */\n loader?: LoadingState\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\n/** The search query, exposed so callers can search the options themselves */\nconst query = defineModel<string>('query', { default: '' })\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\n/**\n * The selected option when it is in the list, otherwise the top option.\n *\n * With async search the selected option is often not in the loaded results (e.g. a branch\n * past the first page), and pointing at a row that does not exist would let Enter select nothing.\n */\nconst getInitialActive = (): Option | undefined => {\n const selected = model.value[0]\n return selected && withAdd.value.some((option) => option.id === selected.id)\n ? selected\n : withAdd.value[0]\n}\n\nconst activeRef = ref<Option | undefined>(getInitialActive())\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 = getInitialActive()\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/**\n * Whether the query changed since the options last updated. Async results arrive after the\n * query changes, so the top hit of the new results should become active even when the\n * previously active option is still somewhere in the list.\n */\nlet queryChanged = false\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => {\n activeRef.value = withAdd.value[0]\n queryChanged = true\n },\n)\n\n/**\n * Async options arrive after the query has changed, so the active option can point at a row\n * that no longer exists or is no longer the top hit. Move it back to the top when that happens.\n */\nwatch(withAdd, (list) => {\n if (\n queryChanged ||\n !list.some((option) => option.id === activeRef.value?.id)\n ) {\n activeRef.value = list[0]\n }\n queryChanged = false\n})\n\n/** Whether the options are still loading */\nconst isLoading = computed<boolean>(() => Boolean(loader?.isLoading))\n\n/** Show placeholder rows while loading with nothing to show yet */\nconst showSkeleton = computed<boolean>(\n () => isLoading.value && !filtered.value.length,\n)\n\n/** Show the loader in the search input, unless the skeleton already says it is loading */\nconst showLoader = computed<boolean>(\n () => Boolean(loader?.isActive) && !showSkeleton.value,\n)\n\n/** Whether there is anything to show at the end of the search input */\nconst showSearchEnd = computed<boolean>(() =>\n Boolean(slots['search-end'] || showLoader.value),\n)\n\n/** Whether the listbox has anything to show */\nconst showList = computed<boolean>(() =>\n Boolean(\n filtered.value.length ||\n slots.add ||\n showSkeleton.value ||\n (noResults && !isLoading.value),\n ),\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 // Remove from selection list in multiselect mode\n if (multiselect && model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n return\n }\n\n // The active option can be stale while async options load, so never select a missing one\n const match = options.value.find((o) => o.id === option.id)\n if (!match) {\n return\n }\n\n // Add to the selection in multiselect mode, replace it in single select mode\n model.value = multiselect ? [...model.value, match] : [match]\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/**\n * Space toggles the active option in multiselect mode while the search box is empty.\n *\n * The options look like checkboxes, so keyboard users press Space to toggle them. Without this\n * handler Space types a leading space into the query, which filters out every option whose label\n * has no space and hides the whole list without any feedback (found by an accessibility audit).\n * Once a query has been typed, Space keeps inserting a character so multi-word labels stay\n * searchable. Key auto-repeat is ignored so holding Space after opening the popover with it does\n * not toggle the option repeatedly, and IME composition is left alone.\n */\nconst handleSpace = (event: KeyboardEvent): void => {\n if (!multiselect || query.value !== '' || event.isComposing) {\n return\n }\n // Suppress repeated spaces too, so holding the key does not start filtering.\n event.preventDefault()\n if (!event.repeat) {\n toggleSelected(activeRef.value)\n }\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 <!-- Inset to line up with the options below -->\n <div class=\"relative flex items-center m-1 mb-0.75\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-1.75 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-expanded=\"showList\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded-md border-0 py-1.5 pl-7.25 leading-none text-c-1\"\n :class=\"showSearchEnd ? 'pr-7.25' : 'pr-1.75'\"\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.space=\"handleSpace\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n <div\n v-if=\"showSearchEnd\"\n class=\"absolute right-1.75 top-1/2 flex -translate-y-1/2 items-center\">\n <slot\n :loader\n name=\"search-end\">\n <ScalarLoading\n v-if=\"showLoader\"\n :loader\n size=\"md\" />\n </slot>\n </div>\n </div>\n <ul\n v-show=\"showList\"\n :id=\"id\"\n :aria-busy=\"isLoading\"\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 <template v-if=\"showSkeleton\">\n <li\n v-for=\"idx in 3\"\n :key=\"idx\"\n aria-hidden=\"true\"\n class=\"flex px-2 py-1.5\"\n role=\"presentation\">\n <div\n class=\"h-lh rounded-md bg-b-3 animate-pulse\"\n :class=\"idx === 3 ? 'w-2/3' : 'w-full'\" />\n </li>\n </template>\n <li\n v-else-if=\"!filtered.length && !slots.add && noResults && !isLoading\"\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":""}