@scalar/components 0.30.2 → 0.30.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.d.ts +8 -0
  2. package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.d.ts.map +1 -1
  3. package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.js.map +1 -1
  4. package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.script.js +13 -10
  5. package/dist/components/ScalarCodeBlock/ScalarCodeBlock.vue.script.js.map +1 -1
  6. package/dist/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue.d.ts.map +1 -1
  7. package/dist/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue.js.map +1 -1
  8. package/dist/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue.script.js +5 -4
  9. package/dist/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue.script.js.map +1 -1
  10. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.d.ts.map +1 -1
  11. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.js.map +1 -1
  12. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.script.js +2 -0
  13. package/dist/components/ScalarCombobox/ScalarComboboxOptions.vue.script.js.map +1 -1
  14. package/dist/components/ScalarSidebar/ScalarSidebarFooter.vue.d.ts.map +1 -1
  15. package/dist/components/ScalarSidebar/ScalarSidebarFooter.vue.js.map +1 -1
  16. package/dist/components/ScalarSidebar/ScalarSidebarFooter.vue.script.js +8 -3
  17. package/dist/components/ScalarSidebar/ScalarSidebarFooter.vue.script.js.map +1 -1
  18. package/dist/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue.d.ts +4 -0
  19. package/dist/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue.d.ts.map +1 -1
  20. package/dist/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue.js.map +1 -1
  21. package/dist/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue.script.js +10 -3
  22. package/dist/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue.script.js.map +1 -1
  23. package/dist/style.css +18 -4
  24. package/package.json +4 -4
@@ -11,6 +11,8 @@ import type { StandardLanguageKey } from './types';
11
11
  declare const _default: typeof __VLS_export;
12
12
  export default _default;
13
13
  declare const __VLS_export: import("vue").DefineComponent<({
14
+ /** Accessible name of the keyboard-scrollable code region. */
15
+ label?: string;
14
16
  content?: string | object;
15
17
  prettyPrintedContent?: string;
16
18
  lang?: StandardLanguageKey | string;
@@ -21,6 +23,8 @@ declare const __VLS_export: import("vue").DefineComponent<({
21
23
  /** Raw unformatted object or string content */
22
24
  content: string | object;
23
25
  }) | ({
26
+ /** Accessible name of the keyboard-scrollable code region. */
27
+ label?: string;
24
28
  content?: string | object;
25
29
  prettyPrintedContent?: string;
26
30
  lang?: StandardLanguageKey | string;
@@ -35,6 +39,8 @@ declare const __VLS_export: import("vue").DefineComponent<({
35
39
  */
36
40
  prettyPrintedContent: string;
37
41
  }), {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<({
42
+ /** Accessible name of the keyboard-scrollable code region. */
43
+ label?: string;
38
44
  content?: string | object;
39
45
  prettyPrintedContent?: string;
40
46
  lang?: StandardLanguageKey | string;
@@ -45,6 +51,8 @@ declare const __VLS_export: import("vue").DefineComponent<({
45
51
  /** Raw unformatted object or string content */
46
52
  content: string | object;
47
53
  }) | ({
54
+ /** Accessible name of the keyboard-scrollable code region. */
55
+ label?: string;
48
56
  content?: string | object;
49
57
  prettyPrintedContent?: string;
50
58
  lang?: StandardLanguageKey | string;
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCodeBlock.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"names":[],"mappings":"AAmLA,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;;IAWzB,+CAA+C;aACtC,MAAM,GAAG,MAAM;;cAjBtB,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;;IAezB;;;;OAIG;0BACmB,MAAM;;cAzB1B,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;;IAWzB,+CAA+C;aACtC,MAAM,GAAG,MAAM;;cAjBtB,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;;IAezB;;;;OAIG;0BACmB,MAAM;kGAyLlC,CAAC"}
1
+ {"version":3,"file":"ScalarCodeBlock.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlock.vue"],"names":[],"mappings":"AAwLA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAGlD;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,8DAA8D;YACtD,MAAM;cACJ,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;;IAWzB,+CAA+C;aACtC,MAAM,GAAG,MAAM;;IAnBhC,8DAA8D;YACtD,MAAM;cACJ,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;;IAezB;;;;OAIG;0BACmB,MAAM;;IA3BpC,8DAA8D;YACtD,MAAM;cACJ,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;;IAWzB,+CAA+C;aACtC,MAAM,GAAG,MAAM;;IAnBhC,8DAA8D;YACtD,MAAM;cACJ,MAAM,GAAG,MAAM;2BACF,MAAM;WACtB,mBAAmB,GAAG,MAAM;kBACrB,OAAO;sBACH,MAAM,GAAG,MAAM,EAAE;WAC5B,QAAQ,GAAG,OAAO,GAAG,KAAK;;IAezB;;;;OAIG;0BACmB,MAAM;kGA4LlC,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\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 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 <!-- Inherits the corners so the inset focus ring follows a rounded code block -->\n <div\n tabindex=\"0\"\n class=\"custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1\">\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 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 :showLang=\"!isOneLine\"\n :lang=\"lang\"\n :aria-controls=\"id\">\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 /** Accessible name of the keyboard-scrollable code region. */\n label?: string\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\n/**\n * Uses highlight.js for syntax highlighting\n *\n * Requires at least one of content or prettyPrintedContent\n */\nconst {\n label = 'Code sample',\n lang = 'plaintext',\n lineNumbers = false,\n copy = 'hover',\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 <!-- Inherits the corners so the inset focus ring follows a rounded code block -->\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=\"region\"\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 :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":""}
@@ -5,15 +5,13 @@ import { computed, createBlock, createCommentVNode, createElementBlock, createEl
5
5
  import { standardLanguages, syntaxHighlight } from "@scalar/code-highlight";
6
6
  import { prettyPrintJson } from "@scalar/helpers/json/pretty-print-json";
7
7
  //#region src/components/ScalarCodeBlock/ScalarCodeBlock.vue?vue&type=script&setup=true&lang.ts
8
- var _hoisted_1 = {
9
- tabindex: "0",
10
- class: "custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1"
11
- };
8
+ var _hoisted_1 = ["aria-label"];
12
9
  var _hoisted_2 = ["id", "innerHTML"];
13
10
  var ScalarCodeBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
14
11
  inheritAttrs: false,
15
12
  __name: "ScalarCodeBlock",
16
13
  props: {
14
+ label: { default: "Code sample" },
17
15
  content: {},
18
16
  prettyPrintedContent: {},
19
17
  lang: { default: "plaintext" },
@@ -55,26 +53,31 @@ var ScalarCodeBlock_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
55
53
  const reserveCopySpace = computed(() => isOneLine.value && showCopy.value);
56
54
  const { cx } = useBindCx();
57
55
  return (_ctx, _cache) => {
58
- return openBlock(), createElementBlock("div", normalizeProps(guardReactiveProps(unref(cx)("scalar-code-block group/code-block flex flex-col", "relative bg-b-1 min-h-0 min-w-0 focus-visible:outline"))), [createElementVNode("div", _hoisted_1, [createElementVNode("pre", {
56
+ return openBlock(), createElementBlock("div", normalizeProps(guardReactiveProps(unref(cx)("scalar-code-block group/code-block flex flex-col", "relative bg-b-1 min-h-0 min-w-0 focus-visible:outline"))), [createElementVNode("div", {
57
+ "aria-label": __props.label,
58
+ class: "custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1",
59
+ role: "region",
60
+ tabindex: "0"
61
+ }, [createElementVNode("pre", {
59
62
  id: unref(id),
60
63
  class: normalizeClass(["m-0 bg-transparent text-nowrap whitespace-pre w-fit", { "pr-6": reserveCopySpace.value }]),
61
64
  innerHTML: highlightedCode.value
62
- }, null, 10, _hoisted_2)]), showCopy.value ? (openBlock(), createBlock(unref(ScalarCodeBlockCopy_default), {
65
+ }, null, 10, _hoisted_2)], 8, _hoisted_1), showCopy.value ? (openBlock(), createBlock(unref(ScalarCodeBlockCopy_default), {
63
66
  key: 0,
67
+ "aria-controls": unref(id),
64
68
  class: normalizeClass(["scalar-code-copy absolute", [isOneLine.value ? "top-1/2 -translate-y-1/2 m-0 right-1" : "top-2.5 right-2.5", { "opacity-100": __props.copy === "always" }]]),
65
69
  content: prettyContent.value,
66
- showLang: !isOneLine.value,
67
70
  lang: __props.lang,
68
- "aria-controls": unref(id)
71
+ showLang: !isOneLine.value
69
72
  }, {
70
73
  backdrop: withCtx(() => [createVNode(ScalarCopyBackdrop_default, { class: normalizeClass(["scalar-code-copy-backdrop", [isOneLine.value ? "-inset-y-0.75 -right-1 group-hocus-within/code-block:-left-0.5 left-3" : "-right-1.5 -top-1"]]) }, null, 8, ["class"])]),
71
74
  _: 1
72
75
  }, 8, [
76
+ "aria-controls",
73
77
  "class",
74
78
  "content",
75
- "showLang",
76
79
  "lang",
77
- "aria-controls"
80
+ "showLang"
78
81
  ])) : createCommentVNode("", true)], 16);
79
82
  };
80
83
  }
@@ -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\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 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 <!-- Inherits the corners so the inset focus ring follows a rounded code block -->\n <div\n tabindex=\"0\"\n class=\"custom-scroll overflow-x-auto p-2 -outline-offset-2 rounded-[inherit] min-h-0 min-w-0 flex-1\">\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 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 :showLang=\"!isOneLine\"\n :lang=\"lang\"\n :aria-controls=\"id\">\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8DA,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,mBAwCM,OAAA,eAAA,mBAvCW,MAAA,EAAA,CAAE,CAAA,oDAAA,uDAAA,CAAA,CAAA,GAAA,CAOjB,mBAQM,OARN,YAQM,CALJ,mBAI6B,OAAA;IAH1B,IAAI,MAAA,EAAA;IACL,OAAK,eAAA,CAAC,uDAAqD,EAAA,QACzC,iBAAA,MAAgB,CAAA,CAAA;IAClC,WAAQ,gBAAA;+BAGJ,SAAA,SAAA,UAAA,GADR,YAsBsB,MAAA,2BAAA,GAAA;;IApBpB,OAAK,eAAA,CAAC,6BAA2B,CACf,UAAA,QAAA,yCAAA,qBAAA,EAAA,eAAuH,QAAA,SAAI,SAAA,CAAA,CAAA,CAAA;IAM5I,SAAS,cAAA;IACT,UAAQ,CAAG,UAAA;IACX,MAAM,QAAA;IACN,iBAAe,MAAA,EAAA;;IACL,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
+ {"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 /** Accessible name of the keyboard-scrollable code region. */\n label?: string\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\n/**\n * Uses highlight.js for syntax highlighting\n *\n * Requires at least one of content or prettyPrintedContent\n */\nconst {\n label = 'Code sample',\n lang = 'plaintext',\n lineNumbers = false,\n copy = 'hover',\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 <!-- Inherits the corners so the inset focus ring follows a rounded code block -->\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=\"region\"\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 :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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiEA,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,mBA0CM,OAAA,eAAA,mBAzCW,MAAA,EAAA,CAAE,CAAA,oDAAA,uDAAA,CAAA,CAAA,GAAA,CAOjB,mBAUM,OAAA;IATH,cAAY,QAAA;IACb,OAAM;IACN,MAAK;IACL,UAAS;OACT,mBAI6B,OAAA;IAH1B,IAAI,MAAA,EAAA;IACL,OAAK,eAAA,CAAC,uDAAqD,EAAA,QACzC,iBAAA,MAAgB,CAAA,CAAA;IAClC,WAAQ,gBAAA;8CAGJ,SAAA,SAAA,UAAA,GADR,YAsBsB,MAAA,2BAAA,GAAA;;IApBnB,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,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 +1 @@
1
- {"version":3,"file":"ScalarCodeBlockCopy.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue"],"names":[],"mappings":"AA0FA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAGlD;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,yCAAyC;eAC9B,OAAO;IAClB,mCAAmC;aAC1B,MAAM,GAAG,MAAM;IACxB,iCAAiC;WAC1B,mBAAmB,GAAG,MAAM;;aA0B1B,OAAO;;;;IA/BhB,yCAAyC;eAC9B,OAAO;IAClB,mCAAmC;aAC1B,MAAM,GAAG,MAAM;IACxB,iCAAiC;WAC1B,mBAAmB,GAAG,MAAM;;aA0B1B,OAAO;;;;eAsGH,CAAC,KAAK,IAAiB,KAAK,GAAG;EAU1C,CAAC;AACL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"ScalarCodeBlockCopy.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue"],"names":[],"mappings":"AA2FA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAGlD;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,yCAAyC;eAC9B,OAAO;IAClB,mCAAmC;aAC1B,MAAM,GAAG,MAAM;IACxB,iCAAiC;WAC1B,mBAAmB,GAAG,MAAM;;aA0B1B,OAAO;;;;IA/BhB,yCAAyC;eAC9B,OAAO;IAClB,mCAAmC;aAC1B,MAAM,GAAG,MAAM;IACxB,iCAAiC;WAC1B,mBAAmB,GAAG,MAAM;;aA0B1B,OAAO;;;;eAwGH,CAAC,KAAK,IAAiB,KAAK,GAAG;EAU1C,CAAC;AACL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCodeBlockCopy.vue.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block Copy button\n *\n * Displays a copy-to-clipboard button with an optional language label.\n * Used internally by ScalarCodeBlock.\n *\n * @example\n * <ScalarCodeBlockCopy content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script setup lang=\"ts\">\nimport { ScalarCopy } from '@/components/ScalarCopy'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed } from 'vue'\n\nimport { LANGUAGE_LABELS } from './constants'\nimport type { StandardLanguageKey } from './types'\n\nconst { content } = defineProps<{\n /** Whether to show the language label */\n showLang?: boolean\n /** Content to copy to clipboard */\n content: string | object\n /** Language of the code block */\n lang?: StandardLanguageKey | string\n}>()\n\nconst copied = defineModel<boolean>('copied', { default: false })\n\n/** Handles the copy button click */\nconst contentToCopy = computed<string>(() => {\n return typeof content === 'string'\n ? content\n : JSON.stringify(content, null, 2)\n})\n\n/** Type guard to check if the language is a standard language */\nconst isStandardLanguage = (lang: string): lang is StandardLanguageKey =>\n lang in LANGUAGE_LABELS\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n\n<template>\n <ScalarCopy\n :content=\"contentToCopy\"\n showLabel\n v-model:copied=\"copied\"\n placement=\"left\"\n v-bind=\"{\n ...cx(\n copied\n ? 'opacity-100'\n : 'opacity-0 group-hocus-within/code-block:opacity-100',\n ),\n }\">\n <template\n v-if=\"lang\"\n #copy>\n <span class=\"hidden group-hocus-within/code-block:inline\">\n <span\n v-if=\"showLang\"\n class=\"group-hocus/copy-button:sr-only\"\n :class=\"{ capitalize: !isStandardLanguage(lang) }\">\n {{ isStandardLanguage(lang) ? LANGUAGE_LABELS[lang] : lang }}\n </span>\n <span\n :class=\"{\n 'group-hocus/copy-button:not-sr-only sr-only': showLang,\n }\"\n >Copy</span\n >\n </span>\n </template>\n <template #backdrop>\n <slot name=\"backdrop\" />\n </template>\n </ScalarCopy>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScalarCodeBlockCopy.vue.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block Copy button\n *\n * Displays a copy-to-clipboard button with an optional language label.\n * Used internally by ScalarCodeBlock.\n *\n * @example\n * <ScalarCodeBlockCopy content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script setup lang=\"ts\">\nimport { ScalarCopy } from '@/components/ScalarCopy'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed } from 'vue'\n\nimport { LANGUAGE_LABELS } from './constants'\nimport type { StandardLanguageKey } from './types'\n\nconst { content } = defineProps<{\n /** Whether to show the language label */\n showLang?: boolean\n /** Content to copy to clipboard */\n content: string | object\n /** Language of the code block */\n lang?: StandardLanguageKey | string\n}>()\n\nconst copied = defineModel<boolean>('copied', { default: false })\n\n/** Handles the copy button click */\nconst contentToCopy = computed<string>(() => {\n return typeof content === 'string'\n ? content\n : JSON.stringify(content, null, 2)\n})\n\n/** Type guard to check if the language is a standard language */\nconst isStandardLanguage = (lang: string): lang is StandardLanguageKey =>\n lang in LANGUAGE_LABELS\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n\n<template>\n <ScalarCopy\n v-model:copied=\"copied\"\n aria-label=\"Copy code sample\"\n :content=\"contentToCopy\"\n placement=\"left\"\n showLabel\n v-bind=\"{\n ...cx(\n copied\n ? 'opacity-100'\n : 'opacity-0 group-hocus-within/code-block:opacity-100',\n ),\n }\">\n <template\n v-if=\"lang\"\n #copy>\n <span class=\"hidden group-hocus-within/code-block:inline\">\n <span\n v-if=\"showLang\"\n class=\"group-hocus/copy-button:sr-only\"\n :class=\"{ capitalize: !isStandardLanguage(lang) }\">\n {{ isStandardLanguage(lang) ? LANGUAGE_LABELS[lang] : lang }}\n </span>\n <span\n :class=\"{\n 'group-hocus/copy-button:not-sr-only sr-only': showLang,\n }\"\n >Copy</span\n >\n </span>\n </template>\n <template #backdrop>\n <slot name=\"backdrop\" />\n </template>\n </ScalarCopy>\n</template>\n"],"mappings":""}
@@ -30,11 +30,12 @@ var ScalarCodeBlockCopy_vue_vue_type_script_setup_true_lang_default = /*@__PURE_
30
30
  const { cx } = useBindCx();
31
31
  return (_ctx, _cache) => {
32
32
  return openBlock(), createBlock(unref(ScalarCopy_default), mergeProps({
33
- content: contentToCopy.value,
34
- showLabel: "",
35
33
  copied: copied.value,
36
34
  "onUpdate:copied": _cache[0] || (_cache[0] = ($event) => copied.value = $event),
37
- placement: "left"
35
+ "aria-label": "Copy code sample",
36
+ content: contentToCopy.value,
37
+ placement: "left",
38
+ showLabel: ""
38
39
  }, { ...unref(cx)(copied.value ? "opacity-100" : "opacity-0 group-hocus-within/code-block:opacity-100") }), createSlots({
39
40
  backdrop: withCtx(() => [renderSlot(_ctx.$slots, "backdrop")]),
40
41
  _: 2
@@ -45,7 +46,7 @@ var ScalarCodeBlockCopy_vue_vue_type_script_setup_true_lang_default = /*@__PURE_
45
46
  class: normalizeClass(["group-hocus/copy-button:sr-only", { capitalize: !isStandardLanguage(__props.lang) }])
46
47
  }, toDisplayString(isStandardLanguage(__props.lang) ? unref(LANGUAGE_LABELS)[__props.lang] : __props.lang), 3)) : createCommentVNode("", true), createElementVNode("span", { class: normalizeClass({ "group-hocus/copy-button:not-sr-only sr-only": __props.showLang }) }, "Copy", 2)])]),
47
48
  key: "0"
48
- } : void 0]), 1040, ["content", "copied"]);
49
+ } : void 0]), 1040, ["copied", "content"]);
49
50
  };
50
51
  }
51
52
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarCodeBlockCopy.vue.script.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block Copy button\n *\n * Displays a copy-to-clipboard button with an optional language label.\n * Used internally by ScalarCodeBlock.\n *\n * @example\n * <ScalarCodeBlockCopy content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script setup lang=\"ts\">\nimport { ScalarCopy } from '@/components/ScalarCopy'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed } from 'vue'\n\nimport { LANGUAGE_LABELS } from './constants'\nimport type { StandardLanguageKey } from './types'\n\nconst { content } = defineProps<{\n /** Whether to show the language label */\n showLang?: boolean\n /** Content to copy to clipboard */\n content: string | object\n /** Language of the code block */\n lang?: StandardLanguageKey | string\n}>()\n\nconst copied = defineModel<boolean>('copied', { default: false })\n\n/** Handles the copy button click */\nconst contentToCopy = computed<string>(() => {\n return typeof content === 'string'\n ? content\n : JSON.stringify(content, null, 2)\n})\n\n/** Type guard to check if the language is a standard language */\nconst isStandardLanguage = (lang: string): lang is StandardLanguageKey =>\n lang in LANGUAGE_LABELS\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n\n<template>\n <ScalarCopy\n :content=\"contentToCopy\"\n showLabel\n v-model:copied=\"copied\"\n placement=\"left\"\n v-bind=\"{\n ...cx(\n copied\n ? 'opacity-100'\n : 'opacity-0 group-hocus-within/code-block:opacity-100',\n ),\n }\">\n <template\n v-if=\"lang\"\n #copy>\n <span class=\"hidden group-hocus-within/code-block:inline\">\n <span\n v-if=\"showLang\"\n class=\"group-hocus/copy-button:sr-only\"\n :class=\"{ capitalize: !isStandardLanguage(lang) }\">\n {{ isStandardLanguage(lang) ? LANGUAGE_LABELS[lang] : lang }}\n </span>\n <span\n :class=\"{\n 'group-hocus/copy-button:not-sr-only sr-only': showLang,\n }\"\n >Copy</span\n >\n </span>\n </template>\n <template #backdrop>\n <slot name=\"backdrop\" />\n </template>\n </ScalarCopy>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;EA6BA,MAAM,SAAS,SAAoB,SAAC,QAA4B;;EAGhE,MAAM,gBAAgB,eAAuB;GAC3C,OAAO,OAAO,QAAA,YAAY,WACtB,QAAA,UACA,KAAK,UAAU,QAAA,SAAS,MAAM,CAAC;EACrC,CAAC;;EAGD,MAAM,sBAAsB,SAC1B,QAAQ;EAGV,MAAM,EAAE,OAAO,UAAU;;uBAIvB,YAiCa,MAAA,kBAAA,GAjCb,WAiCa;IAhCV,SAAS,cAAA;IACV,WAAA;IACQ,QAAQ,OAAA;oEAAM,QAAA;IACtB,WAAU;WACS,MAAA,EAAA,CAAE,CAAU,OAAA,QAAA,gBAAA,qDAAA,EAAA,CAAA,GAAA,YAAA;IAyBpB,UAAQ,cACO,CAAxB,WAAwB,KAAA,QAAA,UAAA,CAAA,CAAA;;OAlBlB,QAAA,OAAA;UACL;sBAcM,CAbP,mBAaO,QAbP,YAaO,CAXG,QAAA,YAAA,UAAA,GADR,mBAKO,QAAA;;KAHL,OAAK,eAAA,CAAC,mCAAiC,EAAA,YAAA,CAChB,mBAAmB,QAAA,IAAI,EAAA,CAAA,CAAA;uBAC3C,mBAAmB,QAAA,IAAI,IAAI,MAAA,eAAA,CAAe,CAAC,QAAA,QAAQ,QAAA,IAAI,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAE5D,mBAKC,QAAA,EAJE,OAAK,eAAA,EAAA,+CAA+D,QAAA,SAAA,CAAA,EAAA,GAGpE,QAAI,CAAA,CAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"ScalarCodeBlockCopy.vue.script.js","names":[],"sources":["../../../src/components/ScalarCodeBlock/ScalarCodeBlockCopy.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Code Block Copy button\n *\n * Displays a copy-to-clipboard button with an optional language label.\n * Used internally by ScalarCodeBlock.\n *\n * @example\n * <ScalarCodeBlockCopy content=\"const x = 1\" lang=\"javascript\" />\n */\nexport default {}\n</script>\n<script setup lang=\"ts\">\nimport { ScalarCopy } from '@/components/ScalarCopy'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed } from 'vue'\n\nimport { LANGUAGE_LABELS } from './constants'\nimport type { StandardLanguageKey } from './types'\n\nconst { content } = defineProps<{\n /** Whether to show the language label */\n showLang?: boolean\n /** Content to copy to clipboard */\n content: string | object\n /** Language of the code block */\n lang?: StandardLanguageKey | string\n}>()\n\nconst copied = defineModel<boolean>('copied', { default: false })\n\n/** Handles the copy button click */\nconst contentToCopy = computed<string>(() => {\n return typeof content === 'string'\n ? content\n : JSON.stringify(content, null, 2)\n})\n\n/** Type guard to check if the language is a standard language */\nconst isStandardLanguage = (lang: string): lang is StandardLanguageKey =>\n lang in LANGUAGE_LABELS\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n\n<template>\n <ScalarCopy\n v-model:copied=\"copied\"\n aria-label=\"Copy code sample\"\n :content=\"contentToCopy\"\n placement=\"left\"\n showLabel\n v-bind=\"{\n ...cx(\n copied\n ? 'opacity-100'\n : 'opacity-0 group-hocus-within/code-block:opacity-100',\n ),\n }\">\n <template\n v-if=\"lang\"\n #copy>\n <span class=\"hidden group-hocus-within/code-block:inline\">\n <span\n v-if=\"showLang\"\n class=\"group-hocus/copy-button:sr-only\"\n :class=\"{ capitalize: !isStandardLanguage(lang) }\">\n {{ isStandardLanguage(lang) ? LANGUAGE_LABELS[lang] : lang }}\n </span>\n <span\n :class=\"{\n 'group-hocus/copy-button:not-sr-only sr-only': showLang,\n }\"\n >Copy</span\n >\n </span>\n </template>\n <template #backdrop>\n <slot name=\"backdrop\" />\n </template>\n </ScalarCopy>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;EA6BA,MAAM,SAAS,SAAoB,SAAC,QAA4B;;EAGhE,MAAM,gBAAgB,eAAuB;GAC3C,OAAO,OAAO,QAAA,YAAY,WACtB,QAAA,UACA,KAAK,UAAU,QAAA,SAAS,MAAM,CAAC;EACrC,CAAC;;EAGD,MAAM,sBAAsB,SAC1B,QAAQ;EAGV,MAAM,EAAE,OAAO,UAAU;;uBAIvB,YAkCa,MAAA,kBAAA,GAlCb,WAkCa;IAjCH,QAAQ,OAAA;oEAAM,QAAA;IACtB,cAAW;IACV,SAAS,cAAA;IACV,WAAU;IACV,WAAA;WACmB,MAAA,EAAA,CAAE,CAAU,OAAA,QAAA,gBAAA,qDAAA,EAAA,CAAA,GAAA,YAAA;IAyBpB,UAAQ,cACO,CAAxB,WAAwB,KAAA,QAAA,UAAA,CAAA,CAAA;;OAlBlB,QAAA,OAAA;UACL;sBAcM,CAbP,mBAaO,QAbP,YAaO,CAXG,QAAA,YAAA,UAAA,GADR,mBAKO,QAAA;;KAHL,OAAK,eAAA,CAAC,mCAAiC,EAAA,YAAA,CAChB,mBAAmB,QAAA,IAAI,EAAA,CAAA,CAAA;uBAC3C,mBAAmB,QAAA,IAAI,IAAI,MAAA,eAAA,CAAe,CAAC,QAAA,QAAQ,QAAA,IAAI,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAE5D,mBAKC,QAAA,EAJE,OAAK,eAAA,EAAA,+CAA+D,QAAA,SAAA,CAAA,EAAA,GAGpE,QAAI,CAAA,CAAA,CAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxOptions.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"names":[],"mappings":"AAiUA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,eAAe,EAErB,MAAM,SAAS,CAAA;AAGhB;;;;;;;;GAQG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY,GAAK,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,SAAS,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAC1F,aAAa,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,EAC9D,YAAY,mBAAmB,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,EAC3G,gBAAgB,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,EAClE;WAggBO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QA9ftD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB,+CAA+C;qBAClC,MAAM;QACnB,+DAA+D;oBACnD,MAAM;QAClB,+DAA+D;gBACvD,MAAM,IAAI;QAClB;;;;;WAKG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,qEAAqE;sBACvD,OAAO;;qBAwKV,CAAC,EAAE;;;;KAoU2E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;UAEJ,2EAAoC;EAEvC,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"ScalarComboboxOptions.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"names":[],"mappings":"AAkUA,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;WAigBO,OAAO,KAAK,EAAE,WAAW,GAAG,mBAAmB,CAAC;QA/ftD,6CAA6C;iBACpC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9B,sDAAsD;sBACxC,MAAM;QACpB,+CAA+C;qBAClC,MAAM;QACnB,+DAA+D;oBACnD,MAAM;QAClB,+DAA+D;gBACvD,MAAM,IAAI;QAClB;;;;;WAKG;mBACQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,qEAAqE;sBACvD,OAAO;;qBAwKV,CAAC,EAAE;;;;KAqU2E,CAAC,GAAG,CAAC,OAAO,UAAU,SAAS;QAAE,oBAAoB,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,EAAE,CAAC;YAC5J,CAAC,OAAO,EAAE,EAAE,KAAK,IAAI;WACtB,GAAG;;UAEJ,2EAAoC;EAEvC,KACQ,OAAO,KAAK,EAAE,KAAK,GAAG;IAAE,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC,CAAA;CAAI,CAAC;AACzF,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAG,CAAC,GAAG,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxOptions.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n inputLabel,\n noResults,\n close,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** Close the popover without changing the current selection */\n close?: () => void\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.esc.prevent=\"close?.()\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add || noResults\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <li\n v-if=\"!filtered.length && !slots.add && noResults\"\n class=\"text-c-3 px-2.5 py-2\"\n role=\"status\">\n {{ noResults }}\n </li>\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScalarComboboxOptions.vue.js","names":[],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n inputLabel,\n noResults,\n close,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** Close the popover without changing the current selection */\n close?: () => void\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n :aria-expanded=\"Boolean(filtered.length || slots.add || noResults)\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.esc.prevent=\"close?.()\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add || noResults\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <li\n v-if=\"!filtered.length && !slots.add && noResults\"\n class=\"text-c-3 px-2.5 py-2\"\n role=\"status\">\n {{ noResults }}\n </li>\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":""}
@@ -9,6 +9,7 @@ import { ScalarIconMagnifyingGlass, ScalarIconPlus } from "@scalar/icons";
9
9
  var _hoisted_1 = { class: "relative flex" };
10
10
  var _hoisted_2 = [
11
11
  "aria-activedescendant",
12
+ "aria-expanded",
12
13
  "aria-label",
13
14
  "placeholder"
14
15
  ];
@@ -111,6 +112,7 @@ var ScalarComboboxOptions_vue_vue_type_script_setup_true_lang_default = /*@__PUR
111
112
  "aria-activedescendant": activeRef.value ? getOptionId(activeRef.value) : void 0,
112
113
  "aria-autocomplete": "list",
113
114
  "aria-controls": id,
115
+ "aria-expanded": Boolean(filtered.value.length || slots.add || __props.noResults),
114
116
  "aria-label": __props.inputLabel,
115
117
  class: "min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1",
116
118
  "data-1p-ignore": "",
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarComboboxOptions.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n inputLabel,\n noResults,\n close,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** Close the popover without changing the current selection */\n close?: () => void\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.esc.prevent=\"close?.()\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add || noResults\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <li\n v-if=\"!filtered.length && !slots.add && noResults\"\n class=\"text-c-3 px-2.5 py-2\"\n role=\"status\">\n {{ noResults }}\n </li>\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ScalarComboboxOptions.vue.script.js","names":["$slots"],"sources":["../../../src/components/ScalarCombobox/ScalarComboboxOptions.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Combobox Options component\n *\n * Renders the searchable option list for a combobox, including\n * filtering, keyboard navigation, and an optional \"add new\" action.\n *\n * @example\n * <ScalarComboboxOptions v-model=\"selected\" :options=\"options\" />\n */\nexport default {}\n</script>\n\n<!-- prettier-ignore-attribute generic -->\n<script\n setup\n lang=\"ts\"\n generic=\"O extends Option = Option, G extends OptionGroup<O> = OptionGroup<O>\">\nimport { filterByOptionLabel } from '@/components/ScalarCombobox/filter-by-option-label'\nimport { ScalarIconMagnifyingGlass, ScalarIconPlus } from '@scalar/icons'\nimport { computed, onMounted, ref, useId, watch } from 'vue'\n\nimport { ScalarListboxCheckbox } from '../ScalarListbox'\nimport ComboboxOption from './ScalarComboboxOption.vue'\nimport ComboboxOptionGroup from './ScalarComboboxOptionGroup.vue'\nimport {\n type ComboboxEmits,\n type ComboboxSlots,\n type FilterFunction,\n type Option,\n type OptionGroup,\n type OptionsOrGroups,\n isGroups,\n} from './types'\n\nconst {\n options: optionsOrGroups,\n placeholder,\n inputLabel,\n noResults,\n close,\n filterFn = filterByOptionLabel,\n multiselect,\n} = defineProps<{\n /** The options to display in the combobox */\n options: OptionsOrGroups<O, G>\n /** The placeholder text to display in the combobox */\n placeholder?: string\n /** An accessible label for the search input */\n inputLabel?: string\n /** The message to display when filtering returns no options */\n noResults?: string\n /** Close the popover without changing the current selection */\n close?: () => void\n /**\n * A function to filter the options based on a query,\n * if not provided, the options will be filtered by option label\n *\n * @see {@link FilterFunction} for more information\n */\n filterFn?: FilterFunction<O, G>\n /** Whether the combobox is in multiselect mode, defaults to false */\n multiselect?: boolean\n}>()\n\nconst emit = defineEmits<ComboboxEmits>()\n\nconst model = defineModel<O[]>({ default: () => [] })\n\nconst slots = defineSlots<Omit<ComboboxSlots<O, G>, 'default'>>()\n\ndefineOptions({ inheritAttrs: false })\n\n/** A unique ID for the combobox */\nconst id = `scalar-combobox-items-${useId()}`\n\n/** A static option entry for the \"Add a new option\" option */\nconst addOption: Option = { id: `${useId()}-add`, label: 'Add a new option' }\n\n/** Generate a unique ID for an option */\nfunction getOptionId(option: Option) {\n return `${id}-${option.id}`\n}\n\n/** A flat list of all options */\nconst options = computed<O[]>(() =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups.flatMap((group) => group.options)\n : optionsOrGroups,\n)\n\n/** An list of all groups */\nconst groups = computed<G[]>(\n () =>\n isGroups(optionsOrGroups)\n ? optionsOrGroups // G extends OptionGroup<O>\n : /*\n We know G is an unextended OptionGroup<O> here because of the\n structure of the component props so we can cast it to G\n */\n [{ label: '', options: optionsOrGroups } as G], // G is OptionGroup<O>\n)\n\nconst query = ref<string>('')\nconst activeRef = ref<Option | undefined>(model.value?.[0] ?? options.value[0])\n\n// Clear the query on open and close\nonMounted(() => {\n // Clear the query\n query.value = ''\n\n // Set the active option to the selected option or the first option\n activeRef.value = model.value?.[0] ?? options.value[0]\n\n // Scroll to the selected option\n const selected = model.value[0]\n if (selected) {\n setTimeout(() => {\n const value = model.value[0]\n if (!value) {\n return\n }\n\n document\n ?.getElementById(getOptionId(value))\n ?.scrollIntoView({ block: 'nearest' })\n }, 10)\n }\n})\n\n// Set the top option as active when searching\nwatch(\n () => query.value,\n () => (activeRef.value = withAdd.value[0]),\n)\n\n/** The filtered list of options */\nconst filtered = computed<O[]>(() =>\n filterFn(query.value, options.value, groups.value),\n)\n\n/** The list of filtered options with the \"Add a new option\" option */\nconst withAdd = computed<Option[]>(() =>\n slots.add ? [...filtered.value, addOption] : filtered.value,\n)\n\nfunction toggleSelected(option: Option | undefined) {\n if (!option) {\n return\n }\n\n if (option.id === addOption.id) {\n addNew()\n return\n }\n\n if (multiselect) {\n // Remove from selection list\n if (model.value.some((o) => o.id === option.id)) {\n model.value = model.value.filter((o) => o.id !== option.id)\n }\n // Add to selection list\n else {\n model.value = [\n ...model.value,\n options.value.find((o) => o.id === option.id)!,\n ]\n }\n } else {\n // Set selection for single select mode\n model.value = [options.value.find((o) => o.id === option.id)!]\n }\n}\n\nfunction moveActive(dir: 1 | -1) {\n const list = withAdd.value\n\n // Find active index\n const activeIdx = list.findIndex(\n (option) => option.id === activeRef.value?.id,\n )\n\n // Calculate next index and exit if it's out of bounds\n const nextIdx = activeIdx + dir\n\n if (nextIdx < 0 || nextIdx > list.length - 1) {\n return\n }\n\n activeRef.value = list[nextIdx]! // We know it's in bounds from the check above\n\n if (!activeRef.value) {\n return\n }\n\n // Scroll to the active option\n document?.getElementById(getOptionId(activeRef.value))?.scrollIntoView({\n behavior: 'smooth',\n block: 'nearest',\n })\n}\n\nfunction addNew() {\n emit('add')\n query.value = ''\n}\n\n// Manual autofocus for the input\nconst input = ref<HTMLInputElement | null>(null)\n\n// This must be a setTimeout to ensure there is no scroll jump. nextTick does not work here.\nonMounted(() => setTimeout(() => input.value?.focus(), 0))\n</script>\n<template>\n <div class=\"relative flex\">\n <ScalarIconMagnifyingGlass\n class=\"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-c-3 size-4\" />\n <input\n ref=\"input\"\n v-model=\"query\"\n :aria-activedescendant=\"activeRef ? getOptionId(activeRef) : undefined\"\n aria-autocomplete=\"list\"\n :aria-controls=\"id\"\n :aria-expanded=\"Boolean(filtered.length || slots.add || noResults)\"\n :aria-label=\"inputLabel\"\n class=\"min-w-0 flex-1 rounded border-0 py-2.5 pl-8 pr-3 leading-none text-c-1 -outline-offset-1\"\n data-1p-ignore\n :placeholder\n role=\"combobox\"\n tabindex=\"0\"\n type=\"text\"\n @keydown.down.prevent=\"moveActive(1)\"\n @keydown.enter.prevent=\"activeRef && toggleSelected(activeRef)\"\n @keydown.esc.prevent=\"close?.()\"\n @keydown.up.prevent=\"moveActive(-1)\" />\n </div>\n <ul\n v-show=\"filtered.length || slots.add || noResults\"\n :id=\"id\"\n :aria-multiselectable=\"multiselect\"\n class=\"border-t p-0.75 custom-scroll overscroll-contain flex-1 min-h-0\"\n role=\"listbox\"\n tabindex=\"-1\">\n <li\n v-if=\"!filtered.length && !slots.add && noResults\"\n class=\"text-c-3 px-2.5 py-2\"\n role=\"status\">\n {{ noResults }}\n </li>\n <ComboboxOptionGroup\n v-for=\"(group, i) in groups\"\n :id=\"`${id}-group-${i}`\"\n :key=\"i\"\n :hidden=\"\n // Only show the group label if there are some results\n !group.options.some((o) => filtered.some((f) => f.id === o.id)) ||\n // And it has a label\n !group.label\n \">\n <template #label>\n <slot\n v-if=\"$slots.group\"\n :group\n name=\"group\" />\n <template v-else>\n {{ group.label }}\n </template>\n </template>\n <template\n v-for=\"option in filtered\"\n :key=\"option.id\">\n <ComboboxOption\n v-if=\"group.options.some((o) => o.id === option.id)\"\n :id=\"getOptionId(option)\"\n v-slot=\"{ active, selected }\"\n :active=\"activeRef?.id === option.id\"\n :selected=\"model.some((o) => o.id === option.id)\"\n @click=\"toggleSelected(option)\"\n @mousedown.prevent\n @mouseenter=\"activeRef = option\">\n <slot\n v-if=\"$slots.option\"\n :active\n name=\"option\"\n :option\n :selected />\n <template v-else>\n <ScalarListboxCheckbox\n :multiselect\n :selected=\"model.some((o) => o.id === option.id)\" />\n <span class=\"inline-block min-w-0 flex-1 truncate text-c-1\">\n {{ option.label }}\n </span>\n </template>\n </ComboboxOption>\n </template>\n </ComboboxOptionGroup>\n <ComboboxOption\n v-if=\"slots.add\"\n :id=\"getOptionId(addOption)\"\n v-slot=\"{ active }\"\n :active=\"activeRef?.id === addOption.id\"\n @click=\"addNew\"\n @mousedown.prevent\n @mouseenter=\"activeRef = addOption\">\n <ScalarIconPlus class=\"size-4 p-px\" />\n <slot\n :active\n name=\"add\" />\n </ComboboxOption>\n </ul>\n</template>\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarSidebarFooter.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarSidebar/ScalarSidebarFooter.vue"],"names":[],"mappings":"AAiEA;;;;;;;;;;;;;;;;GAgBG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,8CAA8C;gBAClC,OAAO;IACnB,0CAA0C;oBAC1B,OAAO;IACvB,qCAAqC;eAC1B,OAAO;EAmEhB,CAAC;AACL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
1
+ {"version":3,"file":"ScalarSidebarFooter.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarSidebar/ScalarSidebarFooter.vue"],"names":[],"mappings":"AAuEA;;;;;;;;;;;;;;;;GAgBG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,8CAA8C;gBAClC,OAAO;IACnB,0CAA0C;oBAC1B,OAAO;IACvB,qCAAqC;eAC1B,OAAO;EAwEhB,CAAC;AACL,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KACV,CAAA;CACD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarSidebarFooter.vue.js","names":[],"sources":["../../../src/components/ScalarSidebar/ScalarSidebarFooter.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Sidebar Footer component\n *\n * A footer for the sidebar, be default it contains a\n * \"Powered by Scalar\" link and a {@link ScalarColorModeToggle} toggle.\n *\n * @example\n * <ScalarSidebarFooter>\n * <!-- Footer content at the top of the footer -->\n * <template #description>\n * <!-- Replaces the Powered by Scalar link -->\n * </template>\n * <template #toggle>\n * <!-- Replaces the color mode toggle -->\n * </template>\n * </ScalarSidebarFooter>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport { ScalarColorModeToggle } from '../ScalarColorModeToggle'\n\ndefineSlots<{\n /** Footer content at the top of the footer */\n default?(): unknown\n /** Replaces the Powered by Scalar link */\n description?(): unknown\n /** Replaces the color mode toggle */\n toggle?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n<template>\n <div v-bind=\"cx('flex flex-col gap-3 px-3 pb-3 border-sidebar-border')\">\n <slot />\n <div class=\"flex items-center\">\n <div class=\"flex-1 min-w-0 flex items-center text-sm text-sidebar-c-2\">\n <slot name=\"description\">\n <a\n class=\"no-underline hover:underline\"\n href=\"https://www.scalar.com\"\n target=\"_blank\">\n Powered by Scalar\n </a>\n </slot>\n </div>\n <slot name=\"toggle\">\n <ScalarColorModeToggle />\n </slot>\n </div>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScalarSidebarFooter.vue.js","names":[],"sources":["../../../src/components/ScalarSidebar/ScalarSidebarFooter.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Sidebar Footer component\n *\n * A footer for the sidebar, be default it contains a\n * \"Powered by Scalar\" link and a {@link ScalarColorModeToggle} toggle.\n *\n * @example\n * <ScalarSidebarFooter>\n * <!-- Footer content at the top of the footer -->\n * <template #description>\n * <!-- Replaces the Powered by Scalar link -->\n * </template>\n * <template #toggle>\n * <!-- Replaces the color mode toggle -->\n * </template>\n * </ScalarSidebarFooter>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { makePoweredByUrl } from '@scalar/helpers/url/make-powered-by-url'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport { ScalarColorModeToggle } from '../ScalarColorModeToggle'\n\ndefineSlots<{\n /** Footer content at the top of the footer */\n default?(): unknown\n /** Replaces the Powered by Scalar link */\n description?(): unknown\n /** Replaces the color mode toggle */\n toggle?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n\n/** The footer does not know the integration, so the link goes out without a campaign */\nconst poweredByUrl = makePoweredByUrl()\n</script>\n<template>\n <div v-bind=\"cx('flex flex-col gap-3 px-3 pb-3 border-sidebar-border')\">\n <slot />\n <div class=\"flex items-center\">\n <div class=\"flex-1 min-w-0 flex items-center text-sm text-sidebar-c-2\">\n <slot name=\"description\">\n <a\n class=\"no-underline hover:underline\"\n :href=\"poweredByUrl\"\n rel=\"noopener\"\n target=\"_blank\">\n Powered by Scalar\n </a>\n </slot>\n </div>\n <slot name=\"toggle\">\n <ScalarColorModeToggle />\n </slot>\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -1,20 +1,25 @@
1
1
  import ScalarColorModeToggle_default from "../ScalarColorModeToggle/ScalarColorModeToggle.vue.js";
2
2
  import { useBindCx } from "@scalar/use-hooks/useBindCx";
3
3
  import { createElementBlock, createElementVNode, createVNode, defineComponent, guardReactiveProps, normalizeProps, openBlock, renderSlot, unref } from "vue";
4
+ import { makePoweredByUrl } from "@scalar/helpers/url/make-powered-by-url";
4
5
  //#region src/components/ScalarSidebar/ScalarSidebarFooter.vue?vue&type=script&setup=true&lang.ts
5
6
  var _hoisted_1 = { class: "flex items-center" };
6
7
  var _hoisted_2 = { class: "flex-1 min-w-0 flex items-center text-sm text-sidebar-c-2" };
8
+ var _hoisted_3 = ["href"];
7
9
  var ScalarSidebarFooter_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
8
10
  inheritAttrs: false,
9
11
  __name: "ScalarSidebarFooter",
10
12
  setup(__props) {
11
13
  const { cx } = useBindCx();
14
+ /** The footer does not know the integration, so the link goes out without a campaign */
15
+ const poweredByUrl = makePoweredByUrl();
12
16
  return (_ctx, _cache) => {
13
- return openBlock(), createElementBlock("div", normalizeProps(guardReactiveProps(unref(cx)("flex flex-col gap-3 px-3 pb-3 border-sidebar-border"))), [renderSlot(_ctx.$slots, "default"), createElementVNode("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "description", {}, () => [_cache[0] || (_cache[0] = createElementVNode("a", {
17
+ return openBlock(), createElementBlock("div", normalizeProps(guardReactiveProps(unref(cx)("flex flex-col gap-3 px-3 pb-3 border-sidebar-border"))), [renderSlot(_ctx.$slots, "default"), createElementVNode("div", _hoisted_1, [createElementVNode("div", _hoisted_2, [renderSlot(_ctx.$slots, "description", {}, () => [createElementVNode("a", {
14
18
  class: "no-underline hover:underline",
15
- href: "https://www.scalar.com",
19
+ href: unref(poweredByUrl),
20
+ rel: "noopener",
16
21
  target: "_blank"
17
- }, " Powered by Scalar ", -1))])]), renderSlot(_ctx.$slots, "toggle", {}, () => [createVNode(unref(ScalarColorModeToggle_default))])])], 16);
22
+ }, " Powered by Scalar ", 8, _hoisted_3)])]), renderSlot(_ctx.$slots, "toggle", {}, () => [createVNode(unref(ScalarColorModeToggle_default))])])], 16);
18
23
  };
19
24
  }
20
25
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarSidebarFooter.vue.script.js","names":[],"sources":["../../../src/components/ScalarSidebar/ScalarSidebarFooter.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Sidebar Footer component\n *\n * A footer for the sidebar, be default it contains a\n * \"Powered by Scalar\" link and a {@link ScalarColorModeToggle} toggle.\n *\n * @example\n * <ScalarSidebarFooter>\n * <!-- Footer content at the top of the footer -->\n * <template #description>\n * <!-- Replaces the Powered by Scalar link -->\n * </template>\n * <template #toggle>\n * <!-- Replaces the color mode toggle -->\n * </template>\n * </ScalarSidebarFooter>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport { ScalarColorModeToggle } from '../ScalarColorModeToggle'\n\ndefineSlots<{\n /** Footer content at the top of the footer */\n default?(): unknown\n /** Replaces the Powered by Scalar link */\n description?(): unknown\n /** Replaces the color mode toggle */\n toggle?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n</script>\n<template>\n <div v-bind=\"cx('flex flex-col gap-3 px-3 pb-3 border-sidebar-border')\">\n <slot />\n <div class=\"flex items-center\">\n <div class=\"flex-1 min-w-0 flex items-center text-sm text-sidebar-c-2\">\n <slot name=\"description\">\n <a\n class=\"no-underline hover:underline\"\n href=\"https://www.scalar.com\"\n target=\"_blank\">\n Powered by Scalar\n </a>\n </slot>\n </div>\n <slot name=\"toggle\">\n <ScalarColorModeToggle />\n </slot>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;EAmCA,MAAM,EAAE,OAAO,UAAU;;uBAGvB,mBAiBM,OAAA,eAAA,mBAjBO,MAAA,EAAA,CAAE,CAAA,qDAAA,CAAA,CAAA,GAAA,CACb,WAAQ,KAAA,QAAA,SAAA,GACR,mBAcM,OAdN,YAcM,CAbJ,mBASM,OATN,YASM,CARJ,WAOO,KAAA,QAAA,eAAA,CAAA,SAAA,CAAA,OAAA,OAAA,OAAA,KANL,mBAKI,KAAA;IAJF,OAAM;IACN,MAAK;IACL,QAAO;MAAS,uBAElB,EAAA,EAAA,CAAA,CAAA,CAAA,GAGJ,WAEO,KAAA,QAAA,UAAA,CAAA,SAAA,CADL,YAAyB,MAAA,6BAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA"}
1
+ {"version":3,"file":"ScalarSidebarFooter.vue.script.js","names":[],"sources":["../../../src/components/ScalarSidebar/ScalarSidebarFooter.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Sidebar Footer component\n *\n * A footer for the sidebar, be default it contains a\n * \"Powered by Scalar\" link and a {@link ScalarColorModeToggle} toggle.\n *\n * @example\n * <ScalarSidebarFooter>\n * <!-- Footer content at the top of the footer -->\n * <template #description>\n * <!-- Replaces the Powered by Scalar link -->\n * </template>\n * <template #toggle>\n * <!-- Replaces the color mode toggle -->\n * </template>\n * </ScalarSidebarFooter>\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { makePoweredByUrl } from '@scalar/helpers/url/make-powered-by-url'\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport { ScalarColorModeToggle } from '../ScalarColorModeToggle'\n\ndefineSlots<{\n /** Footer content at the top of the footer */\n default?(): unknown\n /** Replaces the Powered by Scalar link */\n description?(): unknown\n /** Replaces the color mode toggle */\n toggle?(): unknown\n}>()\n\ndefineOptions({ inheritAttrs: false })\nconst { cx } = useBindCx()\n\n/** The footer does not know the integration, so the link goes out without a campaign */\nconst poweredByUrl = makePoweredByUrl()\n</script>\n<template>\n <div v-bind=\"cx('flex flex-col gap-3 px-3 pb-3 border-sidebar-border')\">\n <slot />\n <div class=\"flex items-center\">\n <div class=\"flex-1 min-w-0 flex items-center text-sm text-sidebar-c-2\">\n <slot name=\"description\">\n <a\n class=\"no-underline hover:underline\"\n :href=\"poweredByUrl\"\n rel=\"noopener\"\n target=\"_blank\">\n Powered by Scalar\n </a>\n </slot>\n </div>\n <slot name=\"toggle\">\n <ScalarColorModeToggle />\n </slot>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;EAoCA,MAAM,EAAE,OAAO,UAAU;;EAGzB,MAAM,eAAe,iBAAiB;;uBAGpC,mBAkBM,OAAA,eAAA,mBAlBO,MAAA,EAAA,CAAE,CAAA,qDAAA,CAAA,CAAA,GAAA,CACb,WAAQ,KAAA,QAAA,SAAA,GACR,mBAeM,OAfN,YAeM,CAdJ,mBAUM,OAVN,YAUM,CATJ,WAQO,KAAA,QAAA,eAAA,CAAA,SAAA,CAPL,mBAMI,KAAA;IALF,OAAM;IACL,MAAM,MAAA,YAAA;IACP,KAAI;IACJ,QAAO;MAAS,uBAElB,GAAA,UAAA,CAAA,CAAA,CAAA,CAAA,GAGJ,WAEO,KAAA,QAAA,UAAA,CAAA,SAAA,CADL,YAAyB,MAAA,6BAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,GAAA,EAAA"}
@@ -14,6 +14,8 @@ export default _default;
14
14
  declare const __VLS_export: import("vue").DefineComponent<{
15
15
  /** Text content to display */
16
16
  content: string;
17
+ /** Accessible name of the keyboard-scrollable code region. */
18
+ label?: string;
17
19
  /** Language label for the copy button */
18
20
  lang?: StandardLanguageKey | string;
19
21
  /** Copy button visibility: 'always', 'hover', or false */
@@ -23,6 +25,8 @@ declare const __VLS_export: import("vue").DefineComponent<{
23
25
  }, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{
24
26
  /** Text content to display */
25
27
  content: string;
28
+ /** Accessible name of the keyboard-scrollable code region. */
29
+ label?: string;
26
30
  /** Language label for the copy button */
27
31
  lang?: StandardLanguageKey | string;
28
32
  /** Copy button visibility: 'always', 'hover', or false */
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarVirtualCodeBlock.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue"],"names":[],"mappings":"AAyEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAKnE;;;;;;;;;GASG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,8BAA8B;aACrB,MAAM;IACf,yCAAyC;WAClC,mBAAmB,GAAG,MAAM;IACnC,0DAA0D;WACnD,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC,oCAAoC;iBACvB,MAAM;;IAPnB,8BAA8B;aACrB,MAAM;IACf,yCAAyC;WAClC,mBAAmB,GAAG,MAAM;IACnC,0DAA0D;WACnD,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC,oCAAoC;iBACvB,MAAM;iGAsGjB,CAAC"}
1
+ {"version":3,"file":"ScalarVirtualCodeBlock.vue.d.ts","sourceRoot":"","sources":["../../../src/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue"],"names":[],"mappings":"AA8EA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAKnE;;;;;;;;;GASG;wBACkB,OAAO,YAAY;AAAxC,wBAAyC;AAGzC,QAAA,MAAM,YAAY;IAEhB,8BAA8B;aACrB,MAAM;IACf,8DAA8D;YACtD,MAAM;IACd,yCAAyC;WAClC,mBAAmB,GAAG,MAAM;IACnC,0DAA0D;WACnD,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC,oCAAoC;iBACvB,MAAM;;IATnB,8BAA8B;aACrB,MAAM;IACf,8DAA8D;YACtD,MAAM;IACd,yCAAyC;WAClC,mBAAmB,GAAG,MAAM;IACnC,0DAA0D;WACnD,QAAQ,GAAG,OAAO,GAAG,KAAK;IACjC,oCAAoC;iBACvB,MAAM;iGA2GjB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarVirtualCodeBlock.vue.js","names":[],"sources":["../../../src/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Virtual Code Block component\n *\n * Renders large blocks of code using virtualized scrolling with a copy button.\n * Used as a fallback for ScalarCodeBlock when content exceeds the performance\n * threshold for syntax highlighting.\n *\n * @example\n * <ScalarVirtualCodeBlock content=\"large json string\" lang=\"json\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport ScalarCodeBlockCopy from '../ScalarCodeBlock/ScalarCodeBlockCopy.vue'\nimport type { StandardLanguageKey } from '../ScalarCodeBlock/types'\nimport ScalarCopyBackdrop from '../ScalarCopy/ScalarCopyBackdrop.vue'\nimport ScalarVirtualText from '../ScalarVirtualText/ScalarVirtualText.vue'\n\nconst {\n content,\n lang = 'plaintext',\n copy = 'hover',\n lineHeight = 20,\n} = defineProps<{\n /** Text content to display */\n content: string\n /** Language label for the copy button */\n lang?: StandardLanguageKey | string\n /** Copy button visibility: 'always', 'hover', or false */\n copy?: 'always' | 'hover' | false\n /** Height of each line in pixels */\n lineHeight?: number\n}>()\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',\n )\n \">\n <ScalarVirtualText\n containerClass=\"custom-scroll overflow-auto flex flex-1 max-h-screen\"\n contentClass=\"language-plaintext whitespace-pre font-code text-base p-2\"\n :lineHeight=\"lineHeight\"\n :text=\"content\" />\n <ScalarCodeBlockCopy\n v-if=\"copy\"\n class=\"scalar-code-copy absolute top-2.5 right-2.5\"\n :class=\"[{ 'opacity-100': copy === 'always' }]\"\n :content=\"content\"\n :showLang=\"true\"\n :lang=\"lang\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop -right-1.5 -top-1\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScalarVirtualCodeBlock.vue.js","names":[],"sources":["../../../src/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Virtual Code Block component\n *\n * Renders large blocks of code using virtualized scrolling with a copy button.\n * Used as a fallback for ScalarCodeBlock when content exceeds the performance\n * threshold for syntax highlighting.\n *\n * @example\n * <ScalarVirtualCodeBlock content=\"large json string\" lang=\"json\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport ScalarCodeBlockCopy from '../ScalarCodeBlock/ScalarCodeBlockCopy.vue'\nimport type { StandardLanguageKey } from '../ScalarCodeBlock/types'\nimport ScalarCopyBackdrop from '../ScalarCopy/ScalarCopyBackdrop.vue'\nimport ScalarVirtualText from '../ScalarVirtualText/ScalarVirtualText.vue'\n\nconst {\n content,\n label = 'Code sample',\n lang = 'plaintext',\n copy = 'hover',\n lineHeight = 20,\n} = defineProps<{\n /** Text content to display */\n content: string\n /** Accessible name of the keyboard-scrollable code region. */\n label?: string\n /** Language label for the copy button */\n lang?: StandardLanguageKey | string\n /** Copy button visibility: 'always', 'hover', or false */\n copy?: 'always' | 'hover' | false\n /** Height of each line in pixels */\n lineHeight?: number\n}>()\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',\n )\n \">\n <ScalarVirtualText\n :aria-label=\"label\"\n containerClass=\"custom-scroll overflow-auto flex flex-1 max-h-screen\"\n contentClass=\"language-plaintext whitespace-pre font-code text-base p-2\"\n :lineHeight=\"lineHeight\"\n role=\"region\"\n :text=\"content\" />\n <ScalarCodeBlockCopy\n v-if=\"copy\"\n class=\"scalar-code-copy absolute top-2.5 right-2.5\"\n :class=\"[{ 'opacity-100': copy === 'always' }]\"\n :content=\"content\"\n :lang=\"lang\"\n :showLang=\"true\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop -right-1.5 -top-1\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n"],"mappings":""}
@@ -8,6 +8,7 @@ var ScalarVirtualCodeBlock_vue_vue_type_script_setup_true_lang_default = /*@__PU
8
8
  __name: "ScalarVirtualCodeBlock",
9
9
  props: {
10
10
  content: {},
11
+ label: { default: "Code sample" },
11
12
  lang: { default: "plaintext" },
12
13
  copy: {
13
14
  type: [String, Boolean],
@@ -19,16 +20,22 @@ var ScalarVirtualCodeBlock_vue_vue_type_script_setup_true_lang_default = /*@__PU
19
20
  const { cx } = useBindCx();
20
21
  return (_ctx, _cache) => {
21
22
  return openBlock(), createElementBlock("div", normalizeProps(guardReactiveProps(unref(cx)("scalar-code-block group/code-block flex flex-col", "relative bg-b-1 min-h-0 min-w-0"))), [createVNode(ScalarVirtualText_default, {
23
+ "aria-label": __props.label,
22
24
  containerClass: "custom-scroll overflow-auto flex flex-1 max-h-screen",
23
25
  contentClass: "language-plaintext whitespace-pre font-code text-base p-2",
24
26
  lineHeight: __props.lineHeight,
27
+ role: "region",
25
28
  text: __props.content
26
- }, null, 8, ["lineHeight", "text"]), __props.copy ? (openBlock(), createBlock(ScalarCodeBlockCopy_default, {
29
+ }, null, 8, [
30
+ "aria-label",
31
+ "lineHeight",
32
+ "text"
33
+ ]), __props.copy ? (openBlock(), createBlock(ScalarCodeBlockCopy_default, {
27
34
  key: 0,
28
35
  class: normalizeClass(["scalar-code-copy absolute top-2.5 right-2.5", [{ "opacity-100": __props.copy === "always" }]]),
29
36
  content: __props.content,
30
- showLang: true,
31
- lang: __props.lang
37
+ lang: __props.lang,
38
+ showLang: true
32
39
  }, {
33
40
  backdrop: withCtx(() => [createVNode(ScalarCopyBackdrop_default, { class: "scalar-code-copy-backdrop -right-1.5 -top-1" })]),
34
41
  _: 1
@@ -1 +1 @@
1
- {"version":3,"file":"ScalarVirtualCodeBlock.vue.script.js","names":[],"sources":["../../../src/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Virtual Code Block component\n *\n * Renders large blocks of code using virtualized scrolling with a copy button.\n * Used as a fallback for ScalarCodeBlock when content exceeds the performance\n * threshold for syntax highlighting.\n *\n * @example\n * <ScalarVirtualCodeBlock content=\"large json string\" lang=\"json\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport ScalarCodeBlockCopy from '../ScalarCodeBlock/ScalarCodeBlockCopy.vue'\nimport type { StandardLanguageKey } from '../ScalarCodeBlock/types'\nimport ScalarCopyBackdrop from '../ScalarCopy/ScalarCopyBackdrop.vue'\nimport ScalarVirtualText from '../ScalarVirtualText/ScalarVirtualText.vue'\n\nconst {\n content,\n lang = 'plaintext',\n copy = 'hover',\n lineHeight = 20,\n} = defineProps<{\n /** Text content to display */\n content: string\n /** Language label for the copy button */\n lang?: StandardLanguageKey | string\n /** Copy button visibility: 'always', 'hover', or false */\n copy?: 'always' | 'hover' | false\n /** Height of each line in pixels */\n lineHeight?: number\n}>()\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',\n )\n \">\n <ScalarVirtualText\n containerClass=\"custom-scroll overflow-auto flex flex-1 max-h-screen\"\n contentClass=\"language-plaintext whitespace-pre font-code text-base p-2\"\n :lineHeight=\"lineHeight\"\n :text=\"content\" />\n <ScalarCodeBlockCopy\n v-if=\"copy\"\n class=\"scalar-code-copy absolute top-2.5 right-2.5\"\n :class=\"[{ 'opacity-100': copy === 'always' }]\"\n :content=\"content\"\n :showLang=\"true\"\n :lang=\"lang\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop -right-1.5 -top-1\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAsCA,MAAM,EAAE,OAAO,UAAU;;uBAGvB,mBAwBM,OAAA,eAAA,mBAvBW,MAAA,EAAA,CAAE,CAAA,oDAAA,iCAAA,CAAA,CAAA,GAAA,CAMjB,YAIoB,2BAAA;IAHlB,gBAAe;IACf,cAAa;IACZ,YAAY,QAAA;IACZ,MAAM,QAAA;wCAED,QAAA,QAAA,UAAA,GADR,YAWsB,6BAAA;;IATpB,OAAK,eAAA,CAAC,+CAA6C,CAAA,EAAA,eACzB,QAAA,SAAI,SAAA,CAAA,CAAA,CAAA;IAC7B,SAAS,QAAA;IACT,UAAU;IACV,MAAM,QAAA;;IACI,UAAQ,cAEuC,CADxD,YACwD,4BAAA,EAAtD,OAAM,8CAA6C,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"ScalarVirtualCodeBlock.vue.script.js","names":[],"sources":["../../../src/components/ScalarVirtualCodeBlock/ScalarVirtualCodeBlock.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Scalar Virtual Code Block component\n *\n * Renders large blocks of code using virtualized scrolling with a copy button.\n * Used as a fallback for ScalarCodeBlock when content exceeds the performance\n * threshold for syntax highlighting.\n *\n * @example\n * <ScalarVirtualCodeBlock content=\"large json string\" lang=\"json\" />\n */\nexport default {}\n</script>\n<script lang=\"ts\" setup>\nimport { useBindCx } from '@scalar/use-hooks/useBindCx'\n\nimport ScalarCodeBlockCopy from '../ScalarCodeBlock/ScalarCodeBlockCopy.vue'\nimport type { StandardLanguageKey } from '../ScalarCodeBlock/types'\nimport ScalarCopyBackdrop from '../ScalarCopy/ScalarCopyBackdrop.vue'\nimport ScalarVirtualText from '../ScalarVirtualText/ScalarVirtualText.vue'\n\nconst {\n content,\n label = 'Code sample',\n lang = 'plaintext',\n copy = 'hover',\n lineHeight = 20,\n} = defineProps<{\n /** Text content to display */\n content: string\n /** Accessible name of the keyboard-scrollable code region. */\n label?: string\n /** Language label for the copy button */\n lang?: StandardLanguageKey | string\n /** Copy button visibility: 'always', 'hover', or false */\n copy?: 'always' | 'hover' | false\n /** Height of each line in pixels */\n lineHeight?: number\n}>()\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',\n )\n \">\n <ScalarVirtualText\n :aria-label=\"label\"\n containerClass=\"custom-scroll overflow-auto flex flex-1 max-h-screen\"\n contentClass=\"language-plaintext whitespace-pre font-code text-base p-2\"\n :lineHeight=\"lineHeight\"\n role=\"region\"\n :text=\"content\" />\n <ScalarCodeBlockCopy\n v-if=\"copy\"\n class=\"scalar-code-copy absolute top-2.5 right-2.5\"\n :class=\"[{ 'opacity-100': copy === 'always' }]\"\n :content=\"content\"\n :lang=\"lang\"\n :showLang=\"true\">\n <template #backdrop>\n <ScalarCopyBackdrop\n class=\"scalar-code-copy-backdrop -right-1.5 -top-1\" />\n </template>\n </ScalarCodeBlockCopy>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;EAyCA,MAAM,EAAE,OAAO,UAAU;;uBAGvB,mBA0BM,OAAA,eAAA,mBAzBW,MAAA,EAAA,CAAE,CAAA,oDAAA,iCAAA,CAAA,CAAA,GAAA,CAMjB,YAMoB,2BAAA;IALjB,cAAY,QAAA;IACb,gBAAe;IACf,cAAa;IACZ,YAAY,QAAA;IACb,MAAK;IACJ,MAAM,QAAA;;;;;OAED,QAAA,QAAA,UAAA,GADR,YAWsB,6BAAA;;IATpB,OAAK,eAAA,CAAC,+CAA6C,CAAA,EAAA,eACzB,QAAA,SAAI,SAAA,CAAA,CAAA,CAAA;IAC7B,SAAS,QAAA;IACT,MAAM,QAAA;IACN,UAAU;;IACA,UAAQ,cAEuC,CADxD,YACwD,4BAAA,EAAtD,OAAM,8CAA6C,CAAA,CAAA,CAAA"}
package/dist/style.css CHANGED
@@ -213,7 +213,7 @@
213
213
  --scalar-background-3: #e7e7e7;
214
214
  --scalar-background-accent: #8ab4f81f;
215
215
  --scalar-color-1: #1b1b1b;
216
- --scalar-color-2: #757575;
216
+ --scalar-color-2: #707070;
217
217
  --scalar-color-3: #8e8e8e;
218
218
  --scalar-color-accent: #09f;
219
219
  --scalar-border-color: #dfdfdf;
@@ -261,7 +261,7 @@
261
261
  --scalar-color-green: #069061;
262
262
  --scalar-color-red: #ef0006;
263
263
  --scalar-color-yellow: #edbe20;
264
- --scalar-color-blue: #0082d0;
264
+ --scalar-color-blue: #0072bd;
265
265
  --scalar-color-orange: #ff5800;
266
266
  --scalar-color-purple: #5203d1;
267
267
  --scalar-color-query: #007b80;
@@ -379,7 +379,7 @@
379
379
  --scalar-color-green: color(display-p3 .023529 .564706 .380392);
380
380
  --scalar-color-red: color(display-p3 .937255 0 .023529);
381
381
  --scalar-color-yellow: color(display-p3 .929412 .745098 .12549);
382
- --scalar-color-blue: color(display-p3 0 .509804 .815686);
382
+ --scalar-color-blue: color(display-p3 0 .447059 .741176);
383
383
  --scalar-color-orange: color(display-p3 1 .4 .02);
384
384
  --scalar-color-purple: color(display-p3 .321569 .011765 .819608);
385
385
  }
@@ -411,9 +411,19 @@
411
411
  :where(.scalar-app) {
412
412
  font-family: var(--scalar-font);
413
413
  color: var(--scalar-color-1);
414
+ --scalar-focus-color: var(--scalar-color-accent);
415
+ line-height: 1.15;
416
+ }
417
+
418
+ @supports (color: color-mix(in lab, red, red)) {
419
+ :where(.scalar-app) {
420
+ --scalar-focus-color: color-mix(in srgb, var(--scalar-color-accent), var(--scalar-color-1) 20%);
421
+ }
422
+ }
423
+
424
+ :where(.scalar-app) {
414
425
  -webkit-text-size-adjust: 100%;
415
426
  tab-size: 4;
416
- line-height: 1.15;
417
427
  }
418
428
 
419
429
  :where(.scalar-app) *, :where(.scalar-app) :before, :where(.scalar-app) :after {
@@ -504,6 +514,10 @@
504
514
  outline-offset: -1px;
505
515
  }
506
516
 
517
+ :where(.scalar-app) a:focus-visible, :where(.scalar-app) button:focus-visible, :where(.scalar-app) [role="button"]:focus-visible {
518
+ outline-color: var(--scalar-focus-color);
519
+ }
520
+
507
521
  :where(.scalar-app) button, :where(.scalar-app) [role="button"] {
508
522
  cursor: pointer;
509
523
  }
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "url": "git+https://github.com/scalar/scalar.git",
11
11
  "directory": "packages/components"
12
12
  },
13
- "version": "0.30.2",
13
+ "version": "0.30.4",
14
14
  "engines": {
15
15
  "node": ">=22"
16
16
  },
@@ -217,10 +217,10 @@
217
217
  "@headlessui/tailwindcss": "^0.2.2",
218
218
  "@headlessui/vue": "1.7.23",
219
219
  "@scalar/code-highlight": "0.4.7",
220
- "@scalar/helpers": "0.14.0",
220
+ "@scalar/helpers": "0.16.0",
221
221
  "@scalar/icons": "0.7.6",
222
- "@scalar/themes": "0.18.0",
223
- "@scalar/use-hooks": "0.4.14",
222
+ "@scalar/themes": "0.18.1",
223
+ "@scalar/use-hooks": "0.4.16",
224
224
  "@vueuse/core": "13.9.0",
225
225
  "cva": "1.0.0-beta.4",
226
226
  "radix-vue": "^1.9.17",