@scalar/components 0.31.0 → 0.32.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.
- package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.d.ts.map +1 -1
- package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.js.map +1 -1
- package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.script.js +1 -1
- package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.script.js.map +1 -1
- package/dist/components/ScalarCombobox/ScalarCombobox.vue.d.ts +15 -1
- package/dist/components/ScalarCombobox/ScalarCombobox.vue.d.ts.map +1 -1
- package/dist/components/ScalarCombobox/ScalarCombobox.vue.js.map +1 -1
- package/dist/components/ScalarCombobox/ScalarCombobox.vue.script.js +20 -5
- package/dist/components/ScalarCombobox/ScalarCombobox.vue.script.js.map +1 -1
- package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.d.ts +15 -1
- package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.d.ts.map +1 -1
- package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.js.map +1 -1
- package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.script.js +22 -7
- package/dist/components/ScalarCombobox/ScalarComboboxMultiselect.vue.script.js.map +1 -1
- package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.d.ts +16 -1
- package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.d.ts.map +1 -1
- package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.js.map +1 -1
- package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.script.js +111 -45
- package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.script.js.map +1 -1
- package/dist/components/ScalarCombobox/types.d.ts +5 -0
- package/dist/components/ScalarCombobox/types.d.ts.map +1 -1
- package/dist/components/ScalarCombobox/types.js.map +1 -1
- package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.d.ts.map +1 -1
- package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.js.map +1 -1
- package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.script.js +4 -1
- package/dist/components/ScalarMarkdown/ScalarMarkdown.vue.script.js.map +1 -1
- package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.d.ts.map +1 -1
- package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.js.map +1 -1
- package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.script.js +6 -2
- package/dist/components/ScalarMarkdown/ScalarMarkdownSummary.vue.script.js.map +1 -1
- package/dist/components/ScalarMarkdown/index.d.ts +1 -0
- package/dist/components/ScalarMarkdown/index.d.ts.map +1 -1
- package/dist/components/ScalarMarkdown/index.js +2 -1
- package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.d.ts +7 -0
- package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.d.ts.map +1 -0
- package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.js +46 -0
- package/dist/components/ScalarMarkdown/useMarkdownRenderHooks.js.map +1 -0
- package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleButton.vue.js.map +1 -1
- package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleButton.vue.script.js +1 -1
- package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleButton.vue.script.js.map +1 -1
- package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleSpacer.vue.js.map +1 -1
- package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleSpacer.vue.script.js +1 -1
- package/dist/components/ScalarSidebar/ScalarSidebarGroupToggleSpacer.vue.script.js.map +1 -1
- package/dist/components/ScalarTooltip/ScalarTooltip.vue.d.ts.map +1 -1
- package/dist/components/ScalarTooltip/ScalarTooltip.vue.js.map +1 -1
- package/dist/components/ScalarTooltip/ScalarTooltip.vue.script.js.map +1 -1
- package/dist/components/ScalarTooltip/useTooltip.d.ts.map +1 -1
- package/dist/components/ScalarTooltip/useTooltip.js +7 -2
- package/dist/components/ScalarTooltip/useTooltip.js.map +1 -1
- package/dist/components/ScalarVirtualText/ScalarVirtualTextSearch.vue.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/style.css +27 -17
- package/dist/vue-styles.css +3 -9
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScalarCodeBlock.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"names":[],"mappings":"AAuMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAGlD;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;cAEN,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAWV,+CAA+C;aACtC,MAAM,GAAG,MAAM;;cA5BtB,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAeV;;;;OAIG;0BACmB,MAAM;;cApC1B,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAWV,+CAA+C;aACtC,MAAM,GAAG,MAAM;;cA5BtB,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAeV;;;;OAIG;0BACmB,MAAM;
|
|
1
|
+
{"version":3,"file":"ScalarCodeBlock.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"names":[],"mappings":"AAuMA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAGlD;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;cAEN,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAWV,+CAA+C;aACtC,MAAM,GAAG,MAAM;;cA5BtB,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAeV;;;;OAIG;0BACmB,MAAM;;cApC1B,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAWV,+CAA+C;aACtC,MAAM,GAAG,MAAM;;cA5BtB,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC;;;;OAIG;YACK,MAAM;IACd;;;OAGG;gBACS,MAAM;;IAeV;;;;OAIG;0BACmB,MAAM;kGAiMlC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScalarCodeBlock.vue.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block component\n *\n * Renders syntax-highlighted code using highlight.js.\n * Supports line numbers, credential masking, and an optional copy button.\n *\n * @example\n * <ScalarCodeBlock content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport ScalarCopyBackdrop from '@/components/ScalarCopy/ScalarCopyBackdrop.vue'\nimport { standardLanguages, syntaxHighlight } from '@scalar/code-highlight'\nimport { prettyPrintJson } from '@scalar/helpers/json/pretty-print-json'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed, useId } from 'vue'\n\nimport { ScalarCodeBlockCopy } from '../ScalarCodeBlock'\nimport type { StandardLanguageKey } from './types'\n\ntype BaseProps = {\n content?: string | object\n prettyPrintedContent?: string\n lang?: StandardLanguageKey | string\n lineNumbers?: boolean\n hideCredentials?: string | string[]\n copy?: 'always' | 'hover' | false\n /**\n * Accessible name for the code scroller. Long code scrolls inside a focusable region so\n * keyboard users can reach it, and a focusable region needs a name or screen readers announce\n * nothing when focus lands on it.\n */\n label?: string\n /**\n * Accessible name for the copy button. The visible label only reads \"Copy\" (and the language)\n * while the block is hovered, so the name has to say what is copied on its own.\n */\n copyLabel?: string\n}\n\n/**\n * Uses highlight.js for syntax highlighting\n *\n * Requires at least one of content or prettyPrintedContent\n */\nconst {\n lang = 'plaintext',\n lineNumbers = false,\n copy = 'hover',\n label = 'Code sample',\n copyLabel,\n content,\n prettyPrintedContent,\n hideCredentials,\n} = defineProps<\n BaseProps &\n (\n | {\n /** Raw unformatted object or string content */\n content: string | object\n }\n | {\n /**\n * Pre-pretty printed content string for better performance\n *\n * Avoids unnecessary costly re-serialization of large content\n */\n prettyPrintedContent: string\n }\n )\n>()\n\n/** Base id for the code block */\nconst id = useId()\n\n/** Formatted the content into an indented json string */\nconst prettyContent = computed(\n () => prettyPrintedContent || prettyPrintJson(content ?? ''),\n)\n\nconst highlightedCode = computed(() => {\n const html = syntaxHighlight(prettyContent.value, {\n lang: lang.trim(),\n languages: standardLanguages,\n lineNumbers: lineNumbers,\n maskCredentials: hideCredentials,\n })\n\n // Need to remove the wrapping <pre> element so we can use v-html without another wrapper\n return html.slice(5, -6)\n})\n\n/** Determine if the content is a single line */\nconst isOneLine = computed(() => !prettyContent.value.includes('\\n'))\n\nconst isContentValid = computed(() => {\n return (\n prettyContent.value !== null &&\n prettyContent.value !== 'null' &&\n prettyContent.value !== '404 Not Found'\n )\n})\n\n/** Whether the copy button is rendered on top of the code */\nconst showCopy = computed(() => copy && isContentValid.value)\n\n/**\n * A one-liner has the copy button floating over the end of the line, so the line needs room to\n * scroll clear of the button. The padding goes on the `w-fit` <pre> because browsers disagree about\n * whether trailing padding on an `overflow-x` container is scrollable.\n */\nconst reserveCopySpace = computed(() => isOneLine.value && showCopy.value)\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n<template>\n <div\n v-bind=\"\n cx(\n 'scalar-code-block group/code-block flex flex-col',\n 'relative bg-b-1 min-h-0 min-w-0 focus-visible:outline',\n )\n \">\n <!--\n Inherits the corners so the inset focus ring follows a rounded code block.\n Always a tab stop so code that scrolls stays reachable by keyboard (WCAG 2.1.1). A named\n group rather than a region, so every code sample does not add a landmark.\n -->\n <div\n :aria-label=\"label\"\n class=\"custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1\"\n role=\"group\"\n tabindex=\"0\">\n <pre\n :id=\"id\"\n class=\"m-0 bg-transparent text-nowrap whitespace-pre w-fit\"\n :class=\"{ 'pr-6': reserveCopySpace }\"\n v-html=\"highlightedCode\" />\n </div>\n <ScalarCodeBlockCopy\n v-if=\"showCopy\"\n :aria-controls=\"id\"\n class=\"scalar-code-copy absolute\"\n :class=\"[\n isOneLine\n ? 'top-1/2 -translate-y-1/2 m-0 right-1'\n : 'top-2.5 right-2.5',\n { 'opacity-100': copy === 'always' },\n ]\"\n :content=\"prettyContent\"\n :copyLabel=\"copyLabel\"\n :lang=\"lang\"\n :showLang=\"!isOneLine\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop\"\n :class=\"[\n isOneLine\n ? '-inset-y-0.75 -right-1 group-hocus-within/code-block:-left-0.5 left-3'\n : '-right-1.5 -top-1',\n ]\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n<style>\n@reference '../../style.css';\n@import '@scalar/code-highlight/css/code.css';\n\n/** Disable font ligatures so code renders with literal characters */\n.scalar-code-block pre {\n font-variant-ligatures: none;\n}\n\n/** Make the copy button label backdrop match the background */\n.scalar-code-block.bg-b-1 .scalar-code-copy-backdrop {\n @apply bg-b-1;\n}\n.scalar-code-block.bg-b-2 .scalar-code-copy-backdrop {\n @apply bg-b-2;\n}\n/** Make the copy button one shade darker than the background */\n.scalar-code-block.bg-b-2 .scalar-code-copy {\n @apply bg-b-3;\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"ScalarCodeBlock.vue.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block component\n *\n * Renders syntax-highlighted code using highlight.js.\n * Supports line numbers, credential masking, and an optional copy button.\n *\n * @example\n * <ScalarCodeBlock content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport ScalarCopyBackdrop from '@/components/ScalarCopy/ScalarCopyBackdrop.vue'\nimport { standardLanguages, syntaxHighlight } from '@scalar/code-highlight'\nimport { prettyPrintJson } from '@scalar/helpers/json/pretty-print-json'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed, useId } from 'vue'\n\nimport { ScalarCodeBlockCopy } from '../ScalarCodeBlock'\nimport type { StandardLanguageKey } from './types'\n\ntype BaseProps = {\n content?: string | object\n prettyPrintedContent?: string\n lang?: StandardLanguageKey | string\n lineNumbers?: boolean\n hideCredentials?: string | string[]\n copy?: 'always' | 'hover' | false\n /**\n * Accessible name for the code scroller. Long code scrolls inside a focusable region so\n * keyboard users can reach it, and a focusable region needs a name or screen readers announce\n * nothing when focus lands on it.\n */\n label?: string\n /**\n * Accessible name for the copy button. The visible label only reads \"Copy\" (and the language)\n * while the block is hovered, so the name has to say what is copied on its own.\n */\n copyLabel?: string\n}\n\n/**\n * Uses highlight.js for syntax highlighting\n *\n * Requires at least one of content or prettyPrintedContent\n */\nconst {\n lang = 'plaintext',\n lineNumbers = false,\n copy = 'hover',\n label = 'Code sample',\n copyLabel,\n content,\n prettyPrintedContent,\n hideCredentials,\n} = defineProps<\n BaseProps &\n (\n | {\n /** Raw unformatted object or string content */\n content: string | object\n }\n | {\n /**\n * Pre-pretty printed content string for better performance\n *\n * Avoids unnecessary costly re-serialization of large content\n */\n prettyPrintedContent: string\n }\n )\n>()\n\n/** Base id for the code block */\nconst id = useId()\n\n/** Formatted the content into an indented json string */\nconst prettyContent = computed(\n () => prettyPrintedContent || prettyPrintJson(content ?? ''),\n)\n\nconst highlightedCode = computed(() => {\n const html = syntaxHighlight(prettyContent.value, {\n lang: lang.trim(),\n languages: standardLanguages,\n lineNumbers: lineNumbers,\n maskCredentials: hideCredentials,\n })\n\n // Need to remove the wrapping <pre> element so we can use v-html without another wrapper\n return html.slice(5, -6)\n})\n\n/** Determine if the content is a single line */\nconst isOneLine = computed(() => !prettyContent.value.includes('\\n'))\n\nconst isContentValid = computed(() => {\n return (\n prettyContent.value !== null &&\n prettyContent.value !== 'null' &&\n prettyContent.value !== '404 Not Found'\n )\n})\n\n/** Whether the copy button is rendered on top of the code */\nconst showCopy = computed(() => copy && isContentValid.value)\n\n/**\n * A one-liner has the copy button floating over the end of the line, so the line needs room to\n * scroll clear of the button. The padding goes on the `w-fit` <pre> because browsers disagree about\n * whether trailing padding on an `overflow-x` container is scrollable.\n */\nconst reserveCopySpace = computed(() => isOneLine.value && showCopy.value)\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n<template>\n <div\n v-bind=\"\n cx(\n 'scalar-code-block group/code-block flex flex-col',\n 'relative bg-b-1 min-h-0 min-w-0 focus-visible:outline',\n )\n \">\n <!--\n Inherits the corners so the inset focus ring follows a rounded code block.\n Always a tab stop so code that scrolls stays reachable by keyboard (WCAG 2.1.1). A named\n group rather than a region, so every code sample does not add a landmark.\n -->\n <div\n :aria-label=\"label\"\n class=\"custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1\"\n role=\"group\"\n tabindex=\"0\">\n <pre\n :id=\"id\"\n class=\"m-0 p-0 border-0 bg-transparent text-nowrap whitespace-pre w-fit\"\n :class=\"{ 'pr-6': reserveCopySpace }\"\n v-html=\"highlightedCode\" />\n </div>\n <ScalarCodeBlockCopy\n v-if=\"showCopy\"\n :aria-controls=\"id\"\n class=\"scalar-code-copy absolute\"\n :class=\"[\n isOneLine\n ? 'top-1/2 -translate-y-1/2 m-0 right-1'\n : 'top-2.5 right-2.5',\n { 'opacity-100': copy === 'always' },\n ]\"\n :content=\"prettyContent\"\n :copyLabel=\"copyLabel\"\n :lang=\"lang\"\n :showLang=\"!isOneLine\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop\"\n :class=\"[\n isOneLine\n ? '-inset-y-0.75 -right-1 group-hocus-within/code-block:-left-0.5 left-3'\n : '-right-1.5 -top-1',\n ]\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n<style>\n@reference '../../style.css';\n@import '@scalar/code-highlight/css/code.css';\n\n/** Disable font ligatures so code renders with literal characters */\n.scalar-code-block pre {\n font-variant-ligatures: none;\n}\n\n/** Make the copy button label backdrop match the background */\n.scalar-code-block.bg-b-1 .scalar-code-copy-backdrop {\n @apply bg-b-1;\n}\n.scalar-code-block.bg-b-2 .scalar-code-copy-backdrop {\n @apply bg-b-2;\n}\n/** Make the copy button one shade darker than the background */\n.scalar-code-block.bg-b-2 .scalar-code-copy {\n @apply bg-b-3;\n}\n</style>\n"],"mappings":""}
|
|
@@ -61,7 +61,7 @@ var ScalarCodeBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
61
61
|
tabindex: "0"
|
|
62
62
|
}, [createElementVNode("pre", {
|
|
63
63
|
id: unref(id),
|
|
64
|
-
class: normalizeClass(["m-0 bg-transparent text-nowrap whitespace-pre w-fit", { "pr-6": reserveCopySpace.value }]),
|
|
64
|
+
class: normalizeClass(["m-0 p-0 border-0 bg-transparent text-nowrap whitespace-pre w-fit", { "pr-6": reserveCopySpace.value }]),
|
|
65
65
|
innerHTML: highlightedCode.value
|
|
66
66
|
}, null, 10, _hoisted_2)], 8, _hoisted_1), showCopy.value ? (openBlock(), createBlock(unref(ScalarCodeBlockCopy_default), {
|
|
67
67
|
key: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScalarCodeBlock.vue.script.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block component\n *\n * Renders syntax-highlighted code using highlight.js.\n * Supports line numbers, credential masking, and an optional copy button.\n *\n * @example\n * <ScalarCodeBlock content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport ScalarCopyBackdrop from '@/components/ScalarCopy/ScalarCopyBackdrop.vue'\nimport { standardLanguages, syntaxHighlight } from '@scalar/code-highlight'\nimport { prettyPrintJson } from '@scalar/helpers/json/pretty-print-json'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed, useId } from 'vue'\n\nimport { ScalarCodeBlockCopy } from '../ScalarCodeBlock'\nimport type { StandardLanguageKey } from './types'\n\ntype BaseProps = {\n content?: string | object\n prettyPrintedContent?: string\n lang?: StandardLanguageKey | string\n lineNumbers?: boolean\n hideCredentials?: string | string[]\n copy?: 'always' | 'hover' | false\n /**\n * Accessible name for the code scroller. Long code scrolls inside a focusable region so\n * keyboard users can reach it, and a focusable region needs a name or screen readers announce\n * nothing when focus lands on it.\n */\n label?: string\n /**\n * Accessible name for the copy button. The visible label only reads \"Copy\" (and the language)\n * while the block is hovered, so the name has to say what is copied on its own.\n */\n copyLabel?: string\n}\n\n/**\n * Uses highlight.js for syntax highlighting\n *\n * Requires at least one of content or prettyPrintedContent\n */\nconst {\n lang = 'plaintext',\n lineNumbers = false,\n copy = 'hover',\n label = 'Code sample',\n copyLabel,\n content,\n prettyPrintedContent,\n hideCredentials,\n} = defineProps<\n BaseProps &\n (\n | {\n /** Raw unformatted object or string content */\n content: string | object\n }\n | {\n /**\n * Pre-pretty printed content string for better performance\n *\n * Avoids unnecessary costly re-serialization of large content\n */\n prettyPrintedContent: string\n }\n )\n>()\n\n/** Base id for the code block */\nconst id = useId()\n\n/** Formatted the content into an indented json string */\nconst prettyContent = computed(\n () => prettyPrintedContent || prettyPrintJson(content ?? ''),\n)\n\nconst highlightedCode = computed(() => {\n const html = syntaxHighlight(prettyContent.value, {\n lang: lang.trim(),\n languages: standardLanguages,\n lineNumbers: lineNumbers,\n maskCredentials: hideCredentials,\n })\n\n // Need to remove the wrapping <pre> element so we can use v-html without another wrapper\n return html.slice(5, -6)\n})\n\n/** Determine if the content is a single line */\nconst isOneLine = computed(() => !prettyContent.value.includes('\\n'))\n\nconst isContentValid = computed(() => {\n return (\n prettyContent.value !== null &&\n prettyContent.value !== 'null' &&\n prettyContent.value !== '404 Not Found'\n )\n})\n\n/** Whether the copy button is rendered on top of the code */\nconst showCopy = computed(() => copy && isContentValid.value)\n\n/**\n * A one-liner has the copy button floating over the end of the line, so the line needs room to\n * scroll clear of the button. The padding goes on the `w-fit` <pre> because browsers disagree about\n * whether trailing padding on an `overflow-x` container is scrollable.\n */\nconst reserveCopySpace = computed(() => isOneLine.value && showCopy.value)\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n<template>\n <div\n v-bind=\"\n cx(\n 'scalar-code-block group/code-block flex flex-col',\n 'relative bg-b-1 min-h-0 min-w-0 focus-visible:outline',\n )\n \">\n <!--\n Inherits the corners so the inset focus ring follows a rounded code block.\n Always a tab stop so code that scrolls stays reachable by keyboard (WCAG 2.1.1). A named\n group rather than a region, so every code sample does not add a landmark.\n -->\n <div\n :aria-label=\"label\"\n class=\"custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1\"\n role=\"group\"\n tabindex=\"0\">\n <pre\n :id=\"id\"\n class=\"m-0 bg-transparent text-nowrap whitespace-pre w-fit\"\n :class=\"{ 'pr-6': reserveCopySpace }\"\n v-html=\"highlightedCode\" />\n </div>\n <ScalarCodeBlockCopy\n v-if=\"showCopy\"\n :aria-controls=\"id\"\n class=\"scalar-code-copy absolute\"\n :class=\"[\n isOneLine\n ? 'top-1/2 -translate-y-1/2 m-0 right-1'\n : 'top-2.5 right-2.5',\n { 'opacity-100': copy === 'always' },\n ]\"\n :content=\"prettyContent\"\n :copyLabel=\"copyLabel\"\n :lang=\"lang\"\n :showLang=\"!isOneLine\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop\"\n :class=\"[\n isOneLine\n ? '-inset-y-0.75 -right-1 group-hocus-within/code-block:-left-0.5 left-3'\n : '-right-1.5 -top-1',\n ]\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n<style>\n@reference '../../style.css';\n@import '@scalar/code-highlight/css/code.css';\n\n/** Disable font ligatures so code renders with literal characters */\n.scalar-code-block pre {\n font-variant-ligatures: none;\n}\n\n/** Make the copy button label backdrop match the background */\n.scalar-code-block.bg-b-1 .scalar-code-copy-backdrop {\n @apply bg-b-1;\n}\n.scalar-code-block.bg-b-2 .scalar-code-copy-backdrop {\n @apply bg-b-2;\n}\n/** Make the copy button one shade darker than the background */\n.scalar-code-block.bg-b-2 .scalar-code-copy {\n @apply bg-b-3;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2EA,MAAM,KAAK,MAAM;;EAGjB,MAAM,gBAAgB,eACd,QAAA,wBAAwB,gBAAgB,QAAA,WAAW,EAAE,CAC7D;EAEA,MAAM,kBAAkB,eAAe;GASrC,OARa,gBAAgB,cAAc,OAAO;IAChD,MAAM,QAAA,KAAK,KAAK;IAChB,WAAW;IACX,aAAa,QAAA;IACb,iBAAiB,QAAA;GACnB,CAGO,CAAA,CAAK,MAAM,GAAG,EAAE;EACzB,CAAC;;EAGD,MAAM,YAAY,eAAe,CAAC,cAAc,MAAM,SAAS,IAAI,CAAC;EAEpE,MAAM,iBAAiB,eAAe;GACpC,OACE,cAAc,UAAU,QACxB,cAAc,UAAU,UACxB,cAAc,UAAU;EAE5B,CAAC;;EAGD,MAAM,WAAW,eAAe,QAAA,QAAQ,eAAe,KAAK;;;;;;EAO5D,MAAM,mBAAmB,eAAe,UAAU,SAAS,SAAS,KAAK;EAGzE,MAAM,EAAE,OAAO,UAAU;;uBAGvB,mBA+CM,OAAA,eAAA,mBA9CW,MAAA,EAAA,CAAE,CAAA,oDAAA,uDAAA,CAAA,CAAA,GAAA,CAWjB,mBAUM,OAAA;IATH,cAAY,QAAA;IACb,OAAM;IACN,MAAK;IACL,UAAS;OACT,mBAI6B,OAAA;IAH1B,IAAI,MAAA,EAAA;IACL,OAAK,eAAA,CAAC,
|
|
1
|
+
{"version":3,"file":"ScalarCodeBlock.vue.script.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block component\n *\n * Renders syntax-highlighted code using highlight.js.\n * Supports line numbers, credential masking, and an optional copy button.\n *\n * @example\n * <ScalarCodeBlock content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport ScalarCopyBackdrop from '@/components/ScalarCopy/ScalarCopyBackdrop.vue'\nimport { standardLanguages, syntaxHighlight } from '@scalar/code-highlight'\nimport { prettyPrintJson } from '@scalar/helpers/json/pretty-print-json'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed, useId } from 'vue'\n\nimport { ScalarCodeBlockCopy } from '../ScalarCodeBlock'\nimport type { StandardLanguageKey } from './types'\n\ntype BaseProps = {\n content?: string | object\n prettyPrintedContent?: string\n lang?: StandardLanguageKey | string\n lineNumbers?: boolean\n hideCredentials?: string | string[]\n copy?: 'always' | 'hover' | false\n /**\n * Accessible name for the code scroller. Long code scrolls inside a focusable region so\n * keyboard users can reach it, and a focusable region needs a name or screen readers announce\n * nothing when focus lands on it.\n */\n label?: string\n /**\n * Accessible name for the copy button. The visible label only reads \"Copy\" (and the language)\n * while the block is hovered, so the name has to say what is copied on its own.\n */\n copyLabel?: string\n}\n\n/**\n * Uses highlight.js for syntax highlighting\n *\n * Requires at least one of content or prettyPrintedContent\n */\nconst {\n lang = 'plaintext',\n lineNumbers = false,\n copy = 'hover',\n label = 'Code sample',\n copyLabel,\n content,\n prettyPrintedContent,\n hideCredentials,\n} = defineProps<\n BaseProps &\n (\n | {\n /** Raw unformatted object or string content */\n content: string | object\n }\n | {\n /**\n * Pre-pretty printed content string for better performance\n *\n * Avoids unnecessary costly re-serialization of large content\n */\n prettyPrintedContent: string\n }\n )\n>()\n\n/** Base id for the code block */\nconst id = useId()\n\n/** Formatted the content into an indented json string */\nconst prettyContent = computed(\n () => prettyPrintedContent || prettyPrintJson(content ?? ''),\n)\n\nconst highlightedCode = computed(() => {\n const html = syntaxHighlight(prettyContent.value, {\n lang: lang.trim(),\n languages: standardLanguages,\n lineNumbers: lineNumbers,\n maskCredentials: hideCredentials,\n })\n\n // Need to remove the wrapping <pre> element so we can use v-html without another wrapper\n return html.slice(5, -6)\n})\n\n/** Determine if the content is a single line */\nconst isOneLine = computed(() => !prettyContent.value.includes('\\n'))\n\nconst isContentValid = computed(() => {\n return (\n prettyContent.value !== null &&\n prettyContent.value !== 'null' &&\n prettyContent.value !== '404 Not Found'\n )\n})\n\n/** Whether the copy button is rendered on top of the code */\nconst showCopy = computed(() => copy && isContentValid.value)\n\n/**\n * A one-liner has the copy button floating over the end of the line, so the line needs room to\n * scroll clear of the button. The padding goes on the `w-fit` <pre> because browsers disagree about\n * whether trailing padding on an `overflow-x` container is scrollable.\n */\nconst reserveCopySpace = computed(() => isOneLine.value && showCopy.value)\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n<template>\n <div\n v-bind=\"\n cx(\n 'scalar-code-block group/code-block flex flex-col',\n 'relative bg-b-1 min-h-0 min-w-0 focus-visible:outline',\n )\n \">\n <!--\n Inherits the corners so the inset focus ring follows a rounded code block.\n Always a tab stop so code that scrolls stays reachable by keyboard (WCAG 2.1.1). A named\n group rather than a region, so every code sample does not add a landmark.\n -->\n <div\n :aria-label=\"label\"\n class=\"custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1\"\n role=\"group\"\n tabindex=\"0\">\n <pre\n :id=\"id\"\n class=\"m-0 p-0 border-0 bg-transparent text-nowrap whitespace-pre w-fit\"\n :class=\"{ 'pr-6': reserveCopySpace }\"\n v-html=\"highlightedCode\" />\n </div>\n <ScalarCodeBlockCopy\n v-if=\"showCopy\"\n :aria-controls=\"id\"\n class=\"scalar-code-copy absolute\"\n :class=\"[\n isOneLine\n ? 'top-1/2 -translate-y-1/2 m-0 right-1'\n : 'top-2.5 right-2.5',\n { 'opacity-100': copy === 'always' },\n ]\"\n :content=\"prettyContent\"\n :copyLabel=\"copyLabel\"\n :lang=\"lang\"\n :showLang=\"!isOneLine\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop\"\n :class=\"[\n isOneLine\n ? '-inset-y-0.75 -right-1 group-hocus-within/code-block:-left-0.5 left-3'\n : '-right-1.5 -top-1',\n ]\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n<style>\n@reference '../../style.css';\n@import '@scalar/code-highlight/css/code.css';\n\n/** Disable font ligatures so code renders with literal characters */\n.scalar-code-block pre {\n font-variant-ligatures: none;\n}\n\n/** Make the copy button label backdrop match the background */\n.scalar-code-block.bg-b-1 .scalar-code-copy-backdrop {\n @apply bg-b-1;\n}\n.scalar-code-block.bg-b-2 .scalar-code-copy-backdrop {\n @apply bg-b-2;\n}\n/** Make the copy button one shade darker than the background */\n.scalar-code-block.bg-b-2 .scalar-code-copy {\n @apply bg-b-3;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2EA,MAAM,KAAK,MAAM;;EAGjB,MAAM,gBAAgB,eACd,QAAA,wBAAwB,gBAAgB,QAAA,WAAW,EAAE,CAC7D;EAEA,MAAM,kBAAkB,eAAe;GASrC,OARa,gBAAgB,cAAc,OAAO;IAChD,MAAM,QAAA,KAAK,KAAK;IAChB,WAAW;IACX,aAAa,QAAA;IACb,iBAAiB,QAAA;GACnB,CAGO,CAAA,CAAK,MAAM,GAAG,EAAE;EACzB,CAAC;;EAGD,MAAM,YAAY,eAAe,CAAC,cAAc,MAAM,SAAS,IAAI,CAAC;EAEpE,MAAM,iBAAiB,eAAe;GACpC,OACE,cAAc,UAAU,QACxB,cAAc,UAAU,UACxB,cAAc,UAAU;EAE5B,CAAC;;EAGD,MAAM,WAAW,eAAe,QAAA,QAAQ,eAAe,KAAK;;;;;;EAO5D,MAAM,mBAAmB,eAAe,UAAU,SAAS,SAAS,KAAK;EAGzE,MAAM,EAAE,OAAO,UAAU;;uBAGvB,mBA+CM,OAAA,eAAA,mBA9CW,MAAA,EAAA,CAAE,CAAA,oDAAA,uDAAA,CAAA,CAAA,GAAA,CAWjB,mBAUM,OAAA;IATH,cAAY,QAAA;IACb,OAAM;IACN,MAAK;IACL,UAAS;OACT,mBAI6B,OAAA;IAH1B,IAAI,MAAA,EAAA;IACL,OAAK,eAAA,CAAC,oEAAkE,EAAA,QACtD,iBAAA,MAAgB,CAAA,CAAA;IAClC,WAAQ,gBAAA;8CAGJ,SAAA,SAAA,UAAA,GADR,YAuBsB,MAAA,2BAAA,GAAA;;IArBnB,iBAAe,MAAA,EAAA;IAChB,OAAK,eAAA,CAAC,6BAA2B,CACf,UAAA,QAAA,yCAAA,qBAAA,EAAA,eAAuH,QAAA,SAAI,SAAA,CAAA,CAAA,CAAA;IAM5I,SAAS,cAAA;IACT,WAAW,QAAA;IACX,MAAM,QAAA;IACN,UAAQ,CAAG,UAAA;;IACD,UAAQ,cAOV,CANP,YAMO,4BAAA,EALL,OAAK,eAAA,CAAC,6BAA2B,CACX,UAAA,QAAA,0EAAA,mBAAA,CAAA,CAAA,EAAA,GAAA,MAAA,GAAA,CAAA,OAAA,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 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":"
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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":"
|
|
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":"
|
|
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[
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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":"
|
|
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":"
|
|
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":""}
|