@scalar/api-reference 1.59.1 → 1.59.3

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 (133) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/blocks/scalar-info-block/components/InfoLinks.vue.d.ts.map +1 -1
  3. package/dist/blocks/scalar-info-block/components/InfoLinks.vue.js.map +1 -1
  4. package/dist/blocks/scalar-info-block/components/InfoLinks.vue.script.js +12 -7
  5. package/dist/blocks/scalar-info-block/components/InfoLinks.vue.script.js.map +1 -1
  6. package/dist/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue.d.ts +13 -0
  7. package/dist/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue.d.ts.map +1 -1
  8. package/dist/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue.js +1 -1
  9. package/dist/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue.js.map +1 -1
  10. package/dist/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue.script.js +32 -8
  11. package/dist/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue.script.js.map +1 -1
  12. package/dist/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.d.ts +16 -7
  13. package/dist/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.d.ts.map +1 -1
  14. package/dist/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.js +33 -7
  15. package/dist/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.js.map +1 -1
  16. package/dist/browser/chunks/AgentScalarChatInterface-Dv0uBVU9.js +27 -0
  17. package/dist/browser/chunks/ScalarTextInput.vue-ZmlutrTU.js +27 -0
  18. package/dist/browser/chunks/basic-shape-diamond.svg-BTEOT1FL.js +1 -1
  19. package/dist/browser/chunks/basic-shape-hexagon.svg-BSPzaCgF.js +1 -1
  20. package/dist/browser/chunks/basic-shape-primary-circle-ellipse-round.svg-DKmZ8UUU.js +1 -1
  21. package/dist/browser/chunks/basic-shape-primary-square-rectangle.svg-n2YRykHD.js +1 -1
  22. package/dist/browser/chunks/basic-shape-shield.svg-Bec9cJDA.js +1 -1
  23. package/dist/browser/chunks/browser-67K6wmG9.js +1 -1
  24. package/dist/browser/chunks/computer-device-desktop-monitor.svg-m6j893Hb.js +1 -1
  25. package/dist/browser/chunks/computer-device-desktop.svg-Dk1VVO7k.js +1 -1
  26. package/dist/browser/chunks/computer-device-laptop.svg-BNGVUCYS.js +1 -1
  27. package/dist/browser/chunks/computer-device-mobile-phone-android-samsung-back.svg-CC4UUTFw.js +1 -1
  28. package/dist/browser/chunks/computer-device-mobile-phone-android-samsung.svg-D8ExpMpL.js +1 -1
  29. package/dist/browser/chunks/computer-device-mobile-phone-iphone-x-back.svg-CLm7sgMM.js +1 -1
  30. package/dist/browser/chunks/computer-device-mobile-phone-iphone-x.svg-Xrttg6_7.js +1 -1
  31. package/dist/browser/chunks/computer-device-mobile-tablet-touch.svg-DQ2oTvOG.js +1 -1
  32. package/dist/browser/chunks/computer-device-mobile-tablet.svg-DCr0XlRc.js +1 -1
  33. package/dist/browser/chunks/computer-device-network-ethernet-cat6.svg-AK9l0iOl.js +1 -1
  34. package/dist/browser/chunks/computer-device-network-lan-www.svg-gUIdFBlE.js +1 -1
  35. package/dist/browser/chunks/computer-device-network-wifi-connection.svg-Be-EMm4e.js +1 -1
  36. package/dist/browser/chunks/computer-device-network-wifi-router.svg-Dc2VzyqK.js +1 -1
  37. package/dist/browser/chunks/ecology-science-erlenmeyer-flask.svg-DHgy7Jfd.js +1 -1
  38. package/dist/browser/chunks/image-flash-lightning.svg-EETR_y1q.js +1 -1
  39. package/dist/browser/chunks/image-picture-flower.svg-DN1AYzJU.js +1 -1
  40. package/dist/browser/chunks/interface-alert-exclamation-diamond.svg-C5NozMV_.js +1 -1
  41. package/dist/browser/chunks/interface-alert-exclamation-triangle-warning.svg-DaAg2fF5.js +1 -1
  42. package/dist/browser/chunks/interface-alert-information-circle.svg-ckkkt9Mv.js +1 -1
  43. package/dist/browser/chunks/interface-award-crown.svg-pjvn9hY-.js +1 -1
  44. package/dist/browser/chunks/interface-bookmark-tag.svg-r2r-lQq4.js +1 -1
  45. package/dist/browser/chunks/interface-bookmark.svg-BK9HKQBT.js +1 -1
  46. package/dist/browser/chunks/interface-calendar-date-one.svg-DNnF0M1I.js +1 -1
  47. package/dist/browser/chunks/interface-content-book-open-pages.svg-CQj_w9Vp.js +1 -1
  48. package/dist/browser/chunks/interface-content-book-page.svg-DppoeJRe.js +1 -1
  49. package/dist/browser/chunks/interface-content-file.svg-CvcD7OiY.js +1 -1
  50. package/dist/browser/chunks/interface-content-folder.svg-BmtS5I9A.js +1 -1
  51. package/dist/browser/chunks/interface-copy-clipboard.svg-Be6EI50X.js +1 -1
  52. package/dist/browser/chunks/interface-edit-attachment.svg-G5TKzpWX.js +1 -1
  53. package/dist/browser/chunks/interface-edit-binocular.svg-jzL-DvGB.js +1 -1
  54. package/dist/browser/chunks/interface-edit-magic-wand.svg-BOTlymvp.js +1 -1
  55. package/dist/browser/chunks/interface-edit-tool-paint-roller.svg-s9dwryx7.js +1 -1
  56. package/dist/browser/chunks/interface-edit-tool-pencil.svg-C7v0dbB3.js +1 -1
  57. package/dist/browser/chunks/interface-favorite-award.svg-CYHUgUG0.js +1 -1
  58. package/dist/browser/chunks/interface-favorite-flag.svg-C10zlKOb.js +1 -1
  59. package/dist/browser/chunks/interface-favorite-heart.svg-eoduXxlz.js +1 -1
  60. package/dist/browser/chunks/interface-favorite-star.svg-BSmxJetr.js +1 -1
  61. package/dist/browser/chunks/interface-favorite-stars-sparkles.svg-JTFLj3Ls.js +1 -1
  62. package/dist/browser/chunks/interface-hierarchy-flowchart.svg-Cg_Wy0kN.js +1 -1
  63. package/dist/browser/chunks/interface-home-house.svg-DytoGRnv.js +1 -1
  64. package/dist/browser/chunks/interface-hyperlink.svg-CuEfmmQI.js +1 -1
  65. package/dist/browser/chunks/interface-lighting-brightness.svg-DSK0h_Pa.js +1 -1
  66. package/dist/browser/chunks/interface-lock-closed.svg-Cn6TaLYv.js +1 -1
  67. package/dist/browser/chunks/interface-lock-open-unlock.svg-CVkG5-9W.js +1 -1
  68. package/dist/browser/chunks/interface-login-key.svg-y-cwwXjN.js +1 -1
  69. package/dist/browser/chunks/interface-search.svg-S-puVFJB.js +1 -1
  70. package/dist/browser/chunks/interface-setting-cog.svg-CTH4_lLg.js +1 -1
  71. package/dist/browser/chunks/interface-share-megaphone-bullhorn.svg-Pbp39MyC.js +1 -1
  72. package/dist/browser/chunks/interface-share-rocket.svg-BgCsIRZO.js +1 -1
  73. package/dist/browser/chunks/interface-share-satellite.svg-DAMN9d5G.js +1 -1
  74. package/dist/browser/chunks/interface-share-space-ship.svg-Dt-Mejxk.js +1 -1
  75. package/dist/browser/chunks/interface-share.svg-vFYTXSti.js +1 -1
  76. package/dist/browser/chunks/interface-signal-square.svg-DNG4LpAe.js +1 -1
  77. package/dist/browser/chunks/interface-time-clock-circle.svg-Bpz-nLxo.js +1 -1
  78. package/dist/browser/chunks/interface-time-hour-glass.svg-D7agfKsH.js +1 -1
  79. package/dist/browser/chunks/interface-users-multiple.svg-CkPJBBnG.js +1 -1
  80. package/dist/browser/chunks/interface-weather-moon.svg-CaTZFMPO.js +1 -1
  81. package/dist/browser/chunks/mail-chat-bubble-square.svg-DC8RIihp.js +1 -1
  82. package/dist/browser/chunks/mail-send-email-paper-airplane.svg-D8OLs9jz.js +1 -1
  83. package/dist/browser/chunks/mail-send-envelope.svg-CV33h5Sx.js +1 -1
  84. package/dist/browser/chunks/{modal-Crw1-pTJ.js → modal-Bh68oUy_.js} +2 -2
  85. package/dist/browser/chunks/modal-DHI6k89v.js +66 -0
  86. package/dist/browser/chunks/money-cashier-receipt.svg-CJL8C2gs.js +1 -1
  87. package/dist/browser/chunks/money-currency-dollar-pay.svg-VdBplCTx.js +1 -1
  88. package/dist/browser/chunks/money-graph-arrow-increase.svg-DcRov3zk.js +1 -1
  89. package/dist/browser/chunks/money-graph-bar-chart-increase.svg-Cc7KxjX-.js +1 -1
  90. package/dist/browser/chunks/nature-ecology-leaf.svg-DR3cTZxt.js +1 -1
  91. package/dist/browser/chunks/phone-telephone.svg-DyY0gUlk.js +1 -1
  92. package/dist/browser/chunks/programming-bug.svg-BHLT-Qhp.js +1 -1
  93. package/dist/browser/chunks/programming-cloud.svg-B6YW33ba.js +1 -1
  94. package/dist/browser/chunks/programming-computer-database-server.svg-B6o5Sdn-.js +1 -1
  95. package/dist/browser/chunks/programming-computer-database.svg-Cl7OVtpJ.js +1 -1
  96. package/dist/browser/chunks/programming-module-four-layout.svg-BQrerL1x.js +1 -1
  97. package/dist/browser/chunks/programming-module-three.svg-B2KTQzBM.js +1 -1
  98. package/dist/browser/chunks/programming-module.svg-CiID7kPm.js +1 -1
  99. package/dist/browser/chunks/programming-script-code.svg-D1M5rtkN.js +1 -1
  100. package/dist/browser/chunks/runtime-core.esm-bundler-Byrbbu5Q.js +1 -1
  101. package/dist/browser/chunks/shopping-cart.svg-BEegiIR9.js +1 -1
  102. package/dist/browser/chunks/shopping-gift-present.svg-BUHwuLix.js +1 -1
  103. package/dist/browser/chunks/shopping-shipping-box-parcel-package.svg-D6fxWdYx.js +1 -1
  104. package/dist/browser/chunks/tag-new-circle.svg-jsg1bGs0.js +1 -1
  105. package/dist/browser/chunks/travel-map-earth-globe.svg-ClbMiU-f.js +1 -1
  106. package/dist/browser/chunks/{vendor-Dp5Q6nDX.js → vendor-Cx_C-51y.js} +137 -137
  107. package/dist/browser/standalone.esm.js +21 -19
  108. package/dist/browser/standalone.js +244 -242
  109. package/dist/components/ApiReference.vue.js +1 -1
  110. package/dist/components/ApiReference.vue.js.map +1 -1
  111. package/dist/components/ApiReference.vue.script.js +3 -3
  112. package/dist/components/ApiReference.vue.script.js.map +1 -1
  113. package/dist/components/Content/Content.vue.d.ts.map +1 -1
  114. package/dist/components/Content/Content.vue.js.map +1 -1
  115. package/dist/components/Content/Content.vue.script.js +7 -1
  116. package/dist/components/Content/Content.vue.script.js.map +1 -1
  117. package/dist/components/Content/Operations/TraversedEntry.vue.d.ts.map +1 -1
  118. package/dist/components/Content/Operations/TraversedEntry.vue.js.map +1 -1
  119. package/dist/components/Content/Operations/TraversedEntry.vue.script.js +2 -1
  120. package/dist/components/Content/Operations/TraversedEntry.vue.script.js.map +1 -1
  121. package/dist/features/Search/helpers/create-search-index.d.ts.map +1 -1
  122. package/dist/features/Search/helpers/create-search-index.js +4 -3
  123. package/dist/features/Search/helpers/create-search-index.js.map +1 -1
  124. package/dist/helpers/id-routing.d.ts +6 -13
  125. package/dist/helpers/id-routing.d.ts.map +1 -1
  126. package/dist/helpers/id-routing.js +104 -34
  127. package/dist/helpers/id-routing.js.map +1 -1
  128. package/dist/style.css +32 -32
  129. package/dist/vue-styles.css +32 -32
  130. package/package.json +14 -12
  131. package/dist/browser/chunks/AgentScalarChatInterface-CPqxdcxM.js +0 -27
  132. package/dist/browser/chunks/ScalarTextInput.vue-4iBshoVm.js +0 -27
  133. package/dist/browser/chunks/modal-DuL3rNXj.js +0 -66
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @scalar/api-reference
2
2
 
3
+ ## 1.59.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#9396](https://github.com/scalar/scalar/pull/9396): Refactor URL redirects into a routing-agnostic, list-driven engine. Redirects now operate on the bare navigation id, so each rule works across hash, hash-base-path, and path routing automatically. Also redirect old `models/<name>` bookmarks to the configured models section slug when the section label is customized (e.g. `schemas/`).
8
+ - [#9489](https://github.com/scalar/scalar/pull/9489): Do not render the info link list when no links are available.
9
+ - [#9342](https://github.com/scalar/scalar/pull/9342): fix: resolve operations when OpenAPI path items use `$ref`
10
+
11
+ Path entries and webhooks can reference `components.pathItems` instead of inlining operations. Navigation, mutators, search, and markdown export now resolve path-item references before reading HTTP methods and path-level parameters.
12
+
13
+ - [#9498](https://github.com/scalar/scalar/pull/9498): Sync the SDK installation tabs with the operation code examples: picking a language under "Client Libraries" now switches every operation's code sample to that language's custom example
14
+
15
+ ## 1.59.2
16
+
17
+ ### Patch Changes
18
+
19
+ - [#9471](https://github.com/scalar/scalar/pull/9471): Restore support for the deprecated `source` install command on `x-scalar-sdk-installation`. When set, it is appended to `description` as a fenced code block (or used on its own when there is no `description`). `description` remains the promoted field.
20
+
3
21
  ## 1.59.1
4
22
 
5
23
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"InfoLinks.vue.d.ts","sourceRoot":"","sources":["../../../../src/blocks/scalar-info-block/components/InfoLinks.vue"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAEpE,OAAO,KAAK,EACV,2BAA2B,EAC3B,UAAU,EACX,MAAM,8DAA8D,CAAA;AAMrE,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,UAAU,GAAG,kBAAkB,CAAA;IACrC,YAAY,CAAC,EAAE,2BAA2B,CAAA;CAC3C,CAAC;AAwFF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"InfoLinks.vue.d.ts","sourceRoot":"","sources":["../../../../src/blocks/scalar-info-block/components/InfoLinks.vue"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAEpE,OAAO,KAAK,EACV,2BAA2B,EAC3B,UAAU,EACX,MAAM,8DAA8D,CAAA;AAOrE,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,UAAU,GAAG,kBAAkB,CAAA;IACrC,YAAY,CAAC,EAAE,2BAA2B,CAAA;CAC3C,CAAC;AAmGF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"InfoLinks.vue.js","names":[],"sources":["../../../../src/blocks/scalar-info-block/components/InfoLinks.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AsyncApiInfoObject } from '@scalar/types/asyncapi/3.1'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport type {\n ExternalDocumentationObject,\n InfoObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\n\nimport { LinkList } from '@/components/LinkList'\nimport { ExternalDocs } from '@/features/external-docs'\nimport { Contact, License, TermsOfService } from '@/features/info-object'\n\ndefineProps<{\n info: InfoObject | AsyncApiInfoObject\n externalDocs?: ExternalDocumentationObject\n}>()\n</script>\n\n<template>\n <LinkList>\n <ExternalDocs :value=\"externalDocs\" />\n <Contact\n v-if=\"info.contact\"\n :value=\"info.contact\" />\n <License\n v-if=\"info.license\"\n :value=\"getResolvedRef(info.license)\" />\n <TermsOfService\n v-if=\"info.termsOfService\"\n :value=\"info.termsOfService\" />\n </LinkList>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"InfoLinks.vue.js","names":[],"sources":["../../../../src/blocks/scalar-info-block/components/InfoLinks.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AsyncApiInfoObject } from '@scalar/types/asyncapi/3.1'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport type {\n ExternalDocumentationObject,\n InfoObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed } from 'vue'\n\nimport { LinkList } from '@/components/LinkList'\nimport { ExternalDocs } from '@/features/external-docs'\nimport { Contact, License, TermsOfService } from '@/features/info-object'\n\nconst { info, externalDocs } = defineProps<{\n info: InfoObject | AsyncApiInfoObject\n externalDocs?: ExternalDocumentationObject\n}>()\n\n/** Whether there is at least one link to show, so we do not render an empty list */\nconst hasLinks = computed(() =>\n Boolean(externalDocs || info.contact || info.license || info.termsOfService),\n)\n</script>\n\n<template>\n <LinkList v-if=\"hasLinks\">\n <ExternalDocs\n v-if=\"externalDocs\"\n :value=\"externalDocs\" />\n <Contact\n v-if=\"info.contact\"\n :value=\"info.contact\" />\n <License\n v-if=\"info.license\"\n :value=\"getResolvedRef(info.license)\" />\n <TermsOfService\n v-if=\"info.termsOfService\"\n :value=\"info.termsOfService\" />\n </LinkList>\n</template>\n"],"mappings":""}
@@ -3,7 +3,7 @@ import ExternalDocs_default from "../../../features/external-docs/ExternalDocs.v
3
3
  import Contact_default from "../../../features/info-object/Contact.vue.js";
4
4
  import License_default from "../../../features/info-object/License.vue.js";
5
5
  import TermsOfService_default from "../../../features/info-object/TermsOfService.vue.js";
6
- import { createBlock, createCommentVNode, createVNode, defineComponent, openBlock, unref, withCtx } from "vue";
6
+ import { computed, createBlock, createCommentVNode, defineComponent, openBlock, unref, withCtx } from "vue";
7
7
  import { getResolvedRef } from "@scalar/workspace-store/helpers/get-resolved-ref";
8
8
  //#region src/blocks/scalar-info-block/components/InfoLinks.vue?vue&type=script&setup=true&lang.ts
9
9
  var InfoLinks_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
@@ -13,25 +13,30 @@ var InfoLinks_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defi
13
13
  externalDocs: {}
14
14
  },
15
15
  setup(__props) {
16
+ /** Whether there is at least one link to show, so we do not render an empty list */
17
+ const hasLinks = computed(() => Boolean(__props.externalDocs || __props.info.contact || __props.info.license || __props.info.termsOfService));
16
18
  return (_ctx, _cache) => {
17
- return openBlock(), createBlock(unref(LinkList_default), null, {
19
+ return hasLinks.value ? (openBlock(), createBlock(unref(LinkList_default), { key: 0 }, {
18
20
  default: withCtx(() => [
19
- createVNode(unref(ExternalDocs_default), { value: __props.externalDocs }, null, 8, ["value"]),
20
- __props.info.contact ? (openBlock(), createBlock(unref(Contact_default), {
21
+ __props.externalDocs ? (openBlock(), createBlock(unref(ExternalDocs_default), {
21
22
  key: 0,
23
+ value: __props.externalDocs
24
+ }, null, 8, ["value"])) : createCommentVNode("", true),
25
+ __props.info.contact ? (openBlock(), createBlock(unref(Contact_default), {
26
+ key: 1,
22
27
  value: __props.info.contact
23
28
  }, null, 8, ["value"])) : createCommentVNode("", true),
24
29
  __props.info.license ? (openBlock(), createBlock(unref(License_default), {
25
- key: 1,
30
+ key: 2,
26
31
  value: unref(getResolvedRef)(__props.info.license)
27
32
  }, null, 8, ["value"])) : createCommentVNode("", true),
28
33
  __props.info.termsOfService ? (openBlock(), createBlock(unref(TermsOfService_default), {
29
- key: 2,
34
+ key: 3,
30
35
  value: __props.info.termsOfService
31
36
  }, null, 8, ["value"])) : createCommentVNode("", true)
32
37
  ]),
33
38
  _: 1
34
- });
39
+ })) : createCommentVNode("", true);
35
40
  };
36
41
  }
37
42
  });
@@ -1 +1 @@
1
- {"version":3,"file":"InfoLinks.vue.script.js","names":[],"sources":["../../../../src/blocks/scalar-info-block/components/InfoLinks.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AsyncApiInfoObject } from '@scalar/types/asyncapi/3.1'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport type {\n ExternalDocumentationObject,\n InfoObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\n\nimport { LinkList } from '@/components/LinkList'\nimport { ExternalDocs } from '@/features/external-docs'\nimport { Contact, License, TermsOfService } from '@/features/info-object'\n\ndefineProps<{\n info: InfoObject | AsyncApiInfoObject\n externalDocs?: ExternalDocumentationObject\n}>()\n</script>\n\n<template>\n <LinkList>\n <ExternalDocs :value=\"externalDocs\" />\n <Contact\n v-if=\"info.contact\"\n :value=\"info.contact\" />\n <License\n v-if=\"info.license\"\n :value=\"getResolvedRef(info.license)\" />\n <TermsOfService\n v-if=\"info.termsOfService\"\n :value=\"info.termsOfService\" />\n </LinkList>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;uBAmBE,YAWW,MAAA,iBAAA,EAAA,MAAA;2BAV6B;KAAtC,YAAsC,MAAA,qBAAA,EAAA,EAAvB,OAAO,QAAA,cAAY,EAAA,MAAA,GAAA,CAAA,QAAA,CAAA;KAE1B,QAAA,KAAK,WAAA,WAAA,EADb,YAE0B,MAAA,gBAAA,EAAA;;MAAvB,OAAO,QAAA,KAAK;;KAEP,QAAA,KAAK,WAAA,WAAA,EADb,YAE0C,MAAA,gBAAA,EAAA;;MAAvC,OAAO,MAAA,eAAc,CAAC,QAAA,KAAK,QAAO;;KAE7B,QAAA,KAAK,kBAAA,WAAA,EADb,YAEiC,MAAA,uBAAA,EAAA;;MAA9B,OAAO,QAAA,KAAK"}
1
+ {"version":3,"file":"InfoLinks.vue.script.js","names":[],"sources":["../../../../src/blocks/scalar-info-block/components/InfoLinks.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { AsyncApiInfoObject } from '@scalar/types/asyncapi/3.1'\nimport { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref'\nimport type {\n ExternalDocumentationObject,\n InfoObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed } from 'vue'\n\nimport { LinkList } from '@/components/LinkList'\nimport { ExternalDocs } from '@/features/external-docs'\nimport { Contact, License, TermsOfService } from '@/features/info-object'\n\nconst { info, externalDocs } = defineProps<{\n info: InfoObject | AsyncApiInfoObject\n externalDocs?: ExternalDocumentationObject\n}>()\n\n/** Whether there is at least one link to show, so we do not render an empty list */\nconst hasLinks = computed(() =>\n Boolean(externalDocs || info.contact || info.license || info.termsOfService),\n)\n</script>\n\n<template>\n <LinkList v-if=\"hasLinks\">\n <ExternalDocs\n v-if=\"externalDocs\"\n :value=\"externalDocs\" />\n <Contact\n v-if=\"info.contact\"\n :value=\"info.contact\" />\n <License\n v-if=\"info.license\"\n :value=\"getResolvedRef(info.license)\" />\n <TermsOfService\n v-if=\"info.termsOfService\"\n :value=\"info.termsOfService\" />\n </LinkList>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;EAmBA,MAAM,WAAW,eACf,QAAQ,QAAA,gBAAgB,QAAA,KAAK,WAAW,QAAA,KAAK,WAAW,QAAA,KAAK,eAAe,CAC9E;;UAIkB,SAAA,SAAA,WAAA,EAAhB,YAaW,MAAA,iBAAA,EAAA,EAAA,KAAA,GAAA,EAAA;2BAViB;KADlB,QAAA,gBAAA,WAAA,EADR,YAE0B,MAAA,qBAAA,EAAA;;MAAvB,OAAO,QAAA;;KAEF,QAAA,KAAK,WAAA,WAAA,EADb,YAE0B,MAAA,gBAAA,EAAA;;MAAvB,OAAO,QAAA,KAAK;;KAEP,QAAA,KAAK,WAAA,WAAA,EADb,YAE0C,MAAA,gBAAA,EAAA;;MAAvC,OAAO,MAAA,eAAc,CAAC,QAAA,KAAK,QAAO;;KAE7B,QAAA,KAAK,kBAAA,WAAA,EADb,YAEiC,MAAA,uBAAA,EAAA;;MAA9B,OAAO,QAAA,KAAK"}
@@ -1,7 +1,20 @@
1
+ import { type WorkspaceEventBus } from '@scalar/workspace-store/events';
1
2
  import type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation';
2
3
  type __VLS_Props = {
3
4
  /** Custom SDK installation instructions from `x-scalar-sdk-installation` */
4
5
  xScalarSdkInstallation?: XScalarSdkInstallation['x-scalar-sdk-installation'];
6
+ /**
7
+ * The globally selected client id. When it matches one of the SDK languages
8
+ * (as a `custom/<lang>` id) the matching tab is shown as active, keeping the
9
+ * tabs in sync with the operation code samples.
10
+ */
11
+ selectedClient?: string;
12
+ /**
13
+ * Event bus used to broadcast the selected client. Picking a language here
14
+ * switches the operation code samples to that language's custom example, the
15
+ * same channel the generic client selector uses.
16
+ */
17
+ eventBus?: WorkspaceEventBus;
5
18
  };
6
19
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
20
  declare const _default: typeof __VLS_export;
@@ -1 +1 @@
1
- {"version":3,"file":"SdkInstallationInstructions.vue.d.ts","sourceRoot":"","sources":["../../../../src/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue"],"names":[],"mappings":"AA0cA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+EAA+E,CAAA;AAe3H,KAAK,WAAW,GAAG;IACjB,4EAA4E;IAC5E,sBAAsB,CAAC,EAAE,sBAAsB,CAAC,2BAA2B,CAAC,CAAA;CAC7E,CAAC;AA+cF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"SdkInstallationInstructions.vue.d.ts","sourceRoot":"","sources":["../../../../src/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue"],"names":[],"mappings":"AA8fA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+EAA+E,CAAA;AAe3H,KAAK,WAAW,GAAG;IACjB,4EAA4E;IAC5E,sBAAsB,CAAC,EAAE,sBAAsB,CAAC,2BAA2B,CAAC,CAAA;IAC5E;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CAC7B,CAAC;AAqfF,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -2,7 +2,7 @@ import _plugin_vue_export_helper_default from "../../../_virtual/_plugin-vue_exp
2
2
  import SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default from "./SdkInstallationInstructions.vue.script.js";
3
3
  /* empty css */
4
4
  //#region src/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue
5
- var SdkInstallationInstructions_default = /* @__PURE__ */ _plugin_vue_export_helper_default(SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-9931144b"]]);
5
+ var SdkInstallationInstructions_default = /* @__PURE__ */ _plugin_vue_export_helper_default(SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-4bb21500"]]);
6
6
  //#endregion
7
7
  export { SdkInstallationInstructions_default as default };
8
8
 
@@ -1 +1 @@
1
- {"version":3,"file":"SdkInstallationInstructions.vue.js","names":[],"sources":["../../../../src/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarCombobox,\n type ScalarComboboxOption,\n} from '@scalar/components/combobox'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarMarkdown } from '@scalar/components/markdown'\nimport type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n useId,\n watch,\n} from 'vue'\n\nimport { getLanguageIcon } from '../helpers/language-icon'\nimport { getRenderableSdks } from '../helpers/renderable-sdks'\nimport { getVisibleTabCount } from '../helpers/visible-tab-count'\n\nconst { xScalarSdkInstallation } = defineProps<{\n /** Custom SDK installation instructions from `x-scalar-sdk-installation` */\n xScalarSdkInstallation?: XScalarSdkInstallation['x-scalar-sdk-installation']\n}>()\n\nconst headingId = useId()\n/** Base id used to associate each tab with the shared panel for assistive tech */\nconst baseId = useId()\nconst panelId = `${baseId}-panel`\n\n/** Only the SDKs that actually have something to show, with their resolved icon */\nconst sdks = computed(() =>\n getRenderableSdks(xScalarSdkInstallation).map((sdk) => ({\n ...sdk,\n icon: getLanguageIcon(sdk.lang),\n })),\n)\n\n/** Index of the currently selected SDK */\nconst selectedIndex = ref(0)\n\n/** The currently selected SDK */\nconst selected = computed(() => sdks.value[selectedIndex.value])\n\n// Keep the selection in range and re-measure whenever the set of SDKs changes.\n// Keying on the languages (not just the count) also catches documents that swap\n// in a different set of the same size, which would otherwise leave the cached\n// tab widths — and the \"More\" overflow logic — stale.\nwatch(\n () => sdks.value.map((sdk) => sdk.lang).join('\\n'),\n () => {\n if (selectedIndex.value > sdks.value.length - 1) {\n selectedIndex.value = 0\n }\n void nextTick(measure)\n },\n)\n\nconst tabsRef = ref<HTMLElement>()\nconst measureRef = ref<HTMLElement>()\n\n/** The width available to render the tabs in */\nconst availableWidth = ref(0)\n/** The natural width of each tab */\nconst tabWidths = ref<number[]>([])\n/** The width of the \"More\" dropdown trigger */\nconst moreWidth = ref(0)\n\n/** Measure the available width and the natural width of each tab */\nconst measure = () => {\n const measureEl = measureRef.value\n const tabsEl = tabsRef.value\n\n if (!measureEl || !tabsEl) {\n return\n }\n\n // The measuring row renders every tab followed by the \"More\" trigger\n const widths = Array.from(measureEl.children).map(\n (child) => (child as HTMLElement).offsetWidth,\n )\n\n tabWidths.value = widths.slice(0, sdks.value.length)\n moreWidth.value = widths[sdks.value.length] ?? 0\n availableWidth.value = tabsEl.clientWidth\n}\n\n/** How many tabs fit inline before we need the \"More\" dropdown */\nconst visibleCount = computed(() => {\n // Until we have measured, render everything (also the server-rendered state)\n if (!tabWidths.value.length || availableWidth.value <= 0) {\n return sdks.value.length\n }\n\n return getVisibleTabCount(\n tabWidths.value,\n availableWidth.value,\n moreWidth.value,\n )\n})\n\n/** The SDKs shown as inline tabs */\nconst visibleSdks = computed(() => sdks.value.slice(0, visibleCount.value))\n\n/** Whether the selected SDK lives in the \"More\" dropdown */\nconst isMoreActive = computed(() => selectedIndex.value >= visibleCount.value)\n\n/** The overflowing SDKs as combobox options, keyed by their original index */\nconst moreOptions = computed<ScalarComboboxOption[]>(() =>\n sdks.value.slice(visibleCount.value).map((sdk, index) => ({\n id: String(visibleCount.value + index),\n label: sdk.lang,\n })),\n)\n\n/** The selected option within the \"More\" dropdown, if any */\nconst selectedMoreOption = computed(() =>\n moreOptions.value.find((option) => option.id === String(selectedIndex.value)),\n)\n\nconst selectMore = (option: ScalarComboboxOption | undefined) => {\n if (option) {\n selectedIndex.value = Number(option.id)\n }\n}\n\n/** The id of the tab that labels the panel (falls back to the heading when the selection lives in \"More\") */\nconst activeTabId = computed(() =>\n isMoreActive.value ? headingId : `${baseId}-tab-${selectedIndex.value}`,\n)\n\n/**\n * The visible tab that holds the roving tabindex. When the selection lives in\n * the \"More\" dropdown, that trigger is the tab stop instead, so no inline tab\n * should be focusable.\n */\nconst tabStopIndex = computed(() =>\n isMoreActive.value ? -1 : selectedIndex.value,\n)\n\n/** Focus a visible tab by index after the DOM has settled */\nconst focusTab = (index: number) => {\n void nextTick(() => {\n tabsRef.value\n ?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n [index]?.focus()\n })\n}\n\n/** Arrow / Home / End keyboard navigation across the visible tabs (WAI-ARIA tabs pattern) */\nconst onTabKeydown = (event: KeyboardEvent, index: number) => {\n const lastVisible = visibleCount.value - 1\n\n let next = index\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n next = index >= lastVisible ? 0 : index + 1\n break\n case 'ArrowLeft':\n case 'ArrowUp':\n next = index <= 0 ? lastVisible : index - 1\n break\n case 'Home':\n next = 0\n break\n case 'End':\n next = lastVisible\n break\n default:\n return\n }\n\n event.preventDefault()\n selectedIndex.value = next\n focusTab(next)\n}\n\nlet observer: ResizeObserver | undefined\nlet frame = 0\n\n/** Coalesce resize bursts into a single measure before the next paint */\nconst scheduleMeasure = () => {\n if (typeof requestAnimationFrame === 'undefined') {\n measure()\n return\n }\n cancelAnimationFrame(frame)\n frame = requestAnimationFrame(measure)\n}\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(scheduleMeasure)\n // Observe the visible row for the available width, and the hidden measure\n // row for the natural tab widths. The latter catches changes the container\n // never sees — web fonts loading, or labels changing without the count\n // changing — which would otherwise leave the overflow logic stale.\n if (tabsRef.value) {\n observer.observe(tabsRef.value)\n }\n if (measureRef.value) {\n observer.observe(measureRef.value)\n }\n }\n measure()\n})\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n if (typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(frame)\n }\n})\n</script>\n<template>\n <div v-if=\"sdks.length\">\n <div\n :id=\"headingId\"\n class=\"client-libraries-heading\">\n Client Libraries\n </div>\n\n <!-- Tabs -->\n <div class=\"client-libraries-content\">\n <div\n ref=\"tabsRef\"\n :aria-labelledby=\"headingId\"\n class=\"client-libraries-row\"\n role=\"tablist\">\n <button\n v-for=\"(sdk, index) in visibleSdks\"\n :id=\"`${baseId}-tab-${index}`\"\n :key=\"index\"\n :aria-controls=\"panelId\"\n :aria-selected=\"index === selectedIndex\"\n class=\"client-libraries\"\n :class=\"{ 'client-libraries__active': index === selectedIndex }\"\n role=\"tab\"\n :tabindex=\"index === tabStopIndex ? 0 : -1\"\n type=\"button\"\n @click=\"selectedIndex = index\"\n @keydown=\"onTabKeydown($event, index)\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </button>\n\n <!-- More dropdown -->\n <ScalarCombobox\n v-if=\"visibleCount < sdks.length\"\n :modelValue=\"selectedMoreOption\"\n :options=\"moreOptions\"\n placement=\"bottom-end\"\n teleport\n @update:modelValue=\"selectMore\">\n <button\n class=\"client-libraries client-libraries-more\"\n :class=\"{ 'client-libraries__active': isMoreActive }\"\n type=\"button\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n :icon=\"\n isMoreActive && selected?.icon ? selected.icon : 'Ellipses'\n \" />\n <span class=\"client-libraries-text\">More</span>\n </button>\n </ScalarCombobox>\n </div>\n\n <!-- Hidden row used to measure the natural width of every tab. It is\n clipped to a zero-size box so it never contributes to the scrollable\n width of the (horizontally scrollable) row above. -->\n <div\n aria-hidden=\"true\"\n class=\"client-libraries-measure-clip\">\n <div\n ref=\"measureRef\"\n class=\"client-libraries-row client-libraries-row--measure\">\n <span\n v-for=\"(sdk, index) in sdks\"\n :key=\"index\"\n class=\"client-libraries\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </span>\n <span class=\"client-libraries\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n icon=\"Ellipses\" />\n <span class=\"client-libraries-text\">More</span>\n </span>\n </div>\n </div>\n </div>\n\n <!-- Content: Markdown installation instructions (supports code blocks) -->\n <div\n v-if=\"selected?.description\"\n :id=\"panelId\"\n :aria-labelledby=\"activeTabId\"\n class=\"selected-client\"\n role=\"tabpanel\"\n tabindex=\"0\">\n <ScalarMarkdown :value=\"selected.description\" />\n </div>\n </div>\n</template>\n<style scoped>\n/* One panel holds the Markdown installation instructions */\n.selected-client {\n display: flex;\n flex-direction: column;\n gap: 9px;\n color: var(--scalar-color-1);\n font-size: var(--scalar-small);\n padding: 9px 12px;\n background: var(--scalar-background-1);\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top: none;\n border-bottom-left-radius: var(--scalar-radius-xl);\n border-bottom-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-heading {\n font-size: var(--scalar-small);\n font-weight: var(--scalar-font-medium);\n color: var(--scalar-color-1);\n padding: 9px 12px;\n background-color: var(--scalar-background-2);\n display: flex;\n align-items: center;\n max-height: 32px;\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top-left-radius: var(--scalar-radius-xl);\n border-top-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-content {\n position: relative;\n /*\n * Scroll horizontally rather than clip: before JS measures the tab widths\n * (and without JS at all) every tab is rendered, so overflowing tabs must\n * stay reachable. Once measured, the \"More\" dropdown absorbs the overflow and\n * no scrollbar is shown. The bar itself is hidden — keyboard focus and touch\n * still scroll clipped tabs into view.\n */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n padding: 0 12px;\n background-color: var(--scalar-background-1);\n border-left: var(--scalar-border-width) solid var(--scalar-border-color);\n border-right: var(--scalar-border-width) solid var(--scalar-border-color);\n border-bottom: var(--scalar-border-width) solid var(--scalar-border-color);\n}\n.client-libraries-content::-webkit-scrollbar {\n display: none;\n}\n.client-libraries-row {\n display: flex;\n justify-content: flex-start;\n}\n/* Zero-size clip so the measure row never adds to the scrollable width */\n.client-libraries-measure-clip {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n overflow: hidden;\n}\n/* Off-screen row used only to measure natural tab widths */\n.client-libraries-row--measure {\n width: max-content;\n visibility: hidden;\n pointer-events: none;\n}\n.client-libraries {\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n cursor: pointer;\n white-space: nowrap;\n /* Keep tabs at their natural (measured) width so they scroll rather than squish */\n flex-shrink: 0;\n padding: 8px 12px;\n gap: 6px;\n color: var(--scalar-color-3);\n border-bottom: 1px solid transparent;\n background: transparent;\n user-select: none;\n}\n.client-libraries:not(.client-libraries__active):hover:before {\n content: '';\n position: absolute;\n width: calc(100% - 4px);\n height: calc(100% - 4px);\n background: var(--scalar-background-2);\n left: 2px;\n top: 2px;\n z-index: 0;\n border-radius: var(--scalar-radius);\n}\n.client-libraries:active {\n color: var(--scalar-color-1);\n}\n.client-libraries:focus-visible {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--scalar-color-accent);\n}\n.client-libraries__active {\n color: var(--scalar-color-1);\n border-bottom: 1px solid var(--scalar-color-1);\n}\n/* Always pin the \"More\" dropdown to the right edge */\n.client-libraries-more {\n margin-left: auto;\n}\n.client-libraries-icon {\n max-width: 14px;\n max-height: 14px;\n min-width: 14px;\n width: 100%;\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n box-sizing: border-box;\n color: currentColor;\n}\n.client-libraries .client-libraries-text {\n font-size: var(--scalar-small);\n position: relative;\n display: flex;\n align-items: center;\n}\n.client-libraries__active .client-libraries-text {\n color: var(--scalar-color-1);\n font-weight: var(--scalar-semibold);\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"SdkInstallationInstructions.vue.js","names":[],"sources":["../../../../src/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getCustomClientIds } from '@scalar/api-client/blocks/operation-code-sample'\nimport {\n ScalarCombobox,\n type ScalarComboboxOption,\n} from '@scalar/components/combobox'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarMarkdown } from '@scalar/components/markdown'\nimport { type WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n useId,\n watch,\n} from 'vue'\n\nimport { getLanguageIcon } from '../helpers/language-icon'\nimport { getRenderableSdks } from '../helpers/renderable-sdks'\nimport { getVisibleTabCount } from '../helpers/visible-tab-count'\n\nconst { xScalarSdkInstallation, selectedClient, eventBus } = defineProps<{\n /** Custom SDK installation instructions from `x-scalar-sdk-installation` */\n xScalarSdkInstallation?: XScalarSdkInstallation['x-scalar-sdk-installation']\n /**\n * The globally selected client id. When it matches one of the SDK languages\n * (as a `custom/<lang>` id) the matching tab is shown as active, keeping the\n * tabs in sync with the operation code samples.\n */\n selectedClient?: string\n /**\n * Event bus used to broadcast the selected client. Picking a language here\n * switches the operation code samples to that language's custom example, the\n * same channel the generic client selector uses.\n */\n eventBus?: WorkspaceEventBus\n}>()\n\nconst headingId = useId()\n/** Base id used to associate each tab with the shared panel for assistive tech */\nconst baseId = useId()\nconst panelId = `${baseId}-panel`\n\n/** Only the SDKs that actually have something to show, with their resolved icon */\nconst sdks = computed(() =>\n getRenderableSdks(xScalarSdkInstallation).map((sdk) => ({\n ...sdk,\n icon: getLanguageIcon(sdk.lang),\n })),\n)\n\n/** Index of the currently selected SDK */\nconst selectedIndex = ref(0)\n\n/** The currently selected SDK */\nconst selected = computed(() => sdks.value[selectedIndex.value])\n\n/**\n * The `custom/<lang>` client id for each SDK, aligned by index with `sdks`.\n *\n * We reuse the exact id scheme the operation code samples use for their custom\n * examples, so selecting a language here resolves to the same id those samples\n * are keyed by — that shared id is what keeps the two surfaces in sync.\n */\nconst sdkClientIds = computed(() =>\n getCustomClientIds(sdks.value.map((sdk) => ({ lang: sdk.lang, source: '' }))),\n)\n\n/** Select an SDK by index and broadcast it so the operation code samples follow */\nconst select = (index: number) => {\n selectedIndex.value = index\n\n const id = sdkClientIds.value[index]\n if (id) {\n eventBus?.emit('workspace:update:selected-client', id)\n }\n}\n\n// Keep the active tab aligned with the global selection, re-evaluating whenever\n// either the selection or the available SDKs change. Watching `sdkClientIds`\n// (not just `selectedClient`) is what stops the intro tab and the operation\n// samples from disagreeing after the SDK list is reordered or resized: the tab\n// follows the selected language to its new position. When the selection is a\n// built-in client (or a language this document has no install entry for) we keep\n// the current tab, only clamping it back into range if the list shrank past it.\nwatch(\n [() => selectedClient, sdkClientIds],\n ([client, ids]) => {\n const matched = client ? ids.findIndex((id) => id === client) : -1\n\n if (matched >= 0) {\n selectedIndex.value = matched\n } else if (selectedIndex.value > ids.length - 1) {\n selectedIndex.value = 0\n }\n },\n { immediate: true },\n)\n\n// Re-measure whenever the set of SDKs changes. Keying on the languages (not just\n// the count) also catches documents that swap in a different set of the same\n// size, which would otherwise leave the cached tab widths — and the \"More\"\n// overflow logic — stale.\nwatch(\n () => sdks.value.map((sdk) => sdk.lang).join('\\n'),\n () => void nextTick(measure),\n)\n\nconst tabsRef = ref<HTMLElement>()\nconst measureRef = ref<HTMLElement>()\n\n/** The width available to render the tabs in */\nconst availableWidth = ref(0)\n/** The natural width of each tab */\nconst tabWidths = ref<number[]>([])\n/** The width of the \"More\" dropdown trigger */\nconst moreWidth = ref(0)\n\n/** Measure the available width and the natural width of each tab */\nconst measure = () => {\n const measureEl = measureRef.value\n const tabsEl = tabsRef.value\n\n if (!measureEl || !tabsEl) {\n return\n }\n\n // The measuring row renders every tab followed by the \"More\" trigger\n const widths = Array.from(measureEl.children).map(\n (child) => (child as HTMLElement).offsetWidth,\n )\n\n tabWidths.value = widths.slice(0, sdks.value.length)\n moreWidth.value = widths[sdks.value.length] ?? 0\n availableWidth.value = tabsEl.clientWidth\n}\n\n/** How many tabs fit inline before we need the \"More\" dropdown */\nconst visibleCount = computed(() => {\n // Until we have measured, render everything (also the server-rendered state)\n if (!tabWidths.value.length || availableWidth.value <= 0) {\n return sdks.value.length\n }\n\n return getVisibleTabCount(\n tabWidths.value,\n availableWidth.value,\n moreWidth.value,\n )\n})\n\n/** The SDKs shown as inline tabs */\nconst visibleSdks = computed(() => sdks.value.slice(0, visibleCount.value))\n\n/** Whether the selected SDK lives in the \"More\" dropdown */\nconst isMoreActive = computed(() => selectedIndex.value >= visibleCount.value)\n\n/** The overflowing SDKs as combobox options, keyed by their original index */\nconst moreOptions = computed<ScalarComboboxOption[]>(() =>\n sdks.value.slice(visibleCount.value).map((sdk, index) => ({\n id: String(visibleCount.value + index),\n label: sdk.lang,\n })),\n)\n\n/** The selected option within the \"More\" dropdown, if any */\nconst selectedMoreOption = computed(() =>\n moreOptions.value.find((option) => option.id === String(selectedIndex.value)),\n)\n\nconst selectMore = (option: ScalarComboboxOption | undefined) => {\n if (option) {\n select(Number(option.id))\n }\n}\n\n/** The id of the tab that labels the panel (falls back to the heading when the selection lives in \"More\") */\nconst activeTabId = computed(() =>\n isMoreActive.value ? headingId : `${baseId}-tab-${selectedIndex.value}`,\n)\n\n/**\n * The visible tab that holds the roving tabindex. When the selection lives in\n * the \"More\" dropdown, that trigger is the tab stop instead, so no inline tab\n * should be focusable.\n */\nconst tabStopIndex = computed(() =>\n isMoreActive.value ? -1 : selectedIndex.value,\n)\n\n/** Focus a visible tab by index after the DOM has settled */\nconst focusTab = (index: number) => {\n void nextTick(() => {\n tabsRef.value\n ?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n [index]?.focus()\n })\n}\n\n/** Arrow / Home / End keyboard navigation across the visible tabs (WAI-ARIA tabs pattern) */\nconst onTabKeydown = (event: KeyboardEvent, index: number) => {\n const lastVisible = visibleCount.value - 1\n\n let next = index\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n next = index >= lastVisible ? 0 : index + 1\n break\n case 'ArrowLeft':\n case 'ArrowUp':\n next = index <= 0 ? lastVisible : index - 1\n break\n case 'Home':\n next = 0\n break\n case 'End':\n next = lastVisible\n break\n default:\n return\n }\n\n event.preventDefault()\n select(next)\n focusTab(next)\n}\n\nlet observer: ResizeObserver | undefined\nlet frame = 0\n\n/** Coalesce resize bursts into a single measure before the next paint */\nconst scheduleMeasure = () => {\n if (typeof requestAnimationFrame === 'undefined') {\n measure()\n return\n }\n cancelAnimationFrame(frame)\n frame = requestAnimationFrame(measure)\n}\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(scheduleMeasure)\n // Observe the visible row for the available width, and the hidden measure\n // row for the natural tab widths. The latter catches changes the container\n // never sees — web fonts loading, or labels changing without the count\n // changing — which would otherwise leave the overflow logic stale.\n if (tabsRef.value) {\n observer.observe(tabsRef.value)\n }\n if (measureRef.value) {\n observer.observe(measureRef.value)\n }\n }\n measure()\n})\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n if (typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(frame)\n }\n})\n</script>\n<template>\n <div v-if=\"sdks.length\">\n <div\n :id=\"headingId\"\n class=\"client-libraries-heading\">\n Client Libraries\n </div>\n\n <!-- Tabs -->\n <div class=\"client-libraries-content\">\n <div\n ref=\"tabsRef\"\n :aria-labelledby=\"headingId\"\n class=\"client-libraries-row\"\n role=\"tablist\">\n <button\n v-for=\"(sdk, index) in visibleSdks\"\n :id=\"`${baseId}-tab-${index}`\"\n :key=\"index\"\n :aria-controls=\"panelId\"\n :aria-selected=\"index === selectedIndex\"\n class=\"client-libraries\"\n :class=\"{ 'client-libraries__active': index === selectedIndex }\"\n role=\"tab\"\n :tabindex=\"index === tabStopIndex ? 0 : -1\"\n type=\"button\"\n @click=\"select(index)\"\n @keydown=\"onTabKeydown($event, index)\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </button>\n\n <!-- More dropdown -->\n <ScalarCombobox\n v-if=\"visibleCount < sdks.length\"\n :modelValue=\"selectedMoreOption\"\n :options=\"moreOptions\"\n placement=\"bottom-end\"\n teleport\n @update:modelValue=\"selectMore\">\n <button\n class=\"client-libraries client-libraries-more\"\n :class=\"{ 'client-libraries__active': isMoreActive }\"\n type=\"button\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n :icon=\"\n isMoreActive && selected?.icon ? selected.icon : 'Ellipses'\n \" />\n <span class=\"client-libraries-text\">More</span>\n </button>\n </ScalarCombobox>\n </div>\n\n <!-- Hidden row used to measure the natural width of every tab. It is\n clipped to a zero-size box so it never contributes to the scrollable\n width of the (horizontally scrollable) row above. -->\n <div\n aria-hidden=\"true\"\n class=\"client-libraries-measure-clip\">\n <div\n ref=\"measureRef\"\n class=\"client-libraries-row client-libraries-row--measure\">\n <span\n v-for=\"(sdk, index) in sdks\"\n :key=\"index\"\n class=\"client-libraries\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </span>\n <span class=\"client-libraries\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n icon=\"Ellipses\" />\n <span class=\"client-libraries-text\">More</span>\n </span>\n </div>\n </div>\n </div>\n\n <!-- Content: Markdown installation instructions (supports code blocks) -->\n <div\n v-if=\"selected?.description\"\n :id=\"panelId\"\n :aria-labelledby=\"activeTabId\"\n class=\"selected-client\"\n role=\"tabpanel\"\n tabindex=\"0\">\n <ScalarMarkdown :value=\"selected.description\" />\n </div>\n </div>\n</template>\n<style scoped>\n/* One panel holds the Markdown installation instructions */\n.selected-client {\n display: flex;\n flex-direction: column;\n gap: 9px;\n color: var(--scalar-color-1);\n font-size: var(--scalar-small);\n padding: 9px 12px;\n background: var(--scalar-background-1);\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top: none;\n border-bottom-left-radius: var(--scalar-radius-xl);\n border-bottom-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-heading {\n font-size: var(--scalar-small);\n font-weight: var(--scalar-font-medium);\n color: var(--scalar-color-1);\n padding: 9px 12px;\n background-color: var(--scalar-background-2);\n display: flex;\n align-items: center;\n max-height: 32px;\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top-left-radius: var(--scalar-radius-xl);\n border-top-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-content {\n position: relative;\n /*\n * Scroll horizontally rather than clip: before JS measures the tab widths\n * (and without JS at all) every tab is rendered, so overflowing tabs must\n * stay reachable. Once measured, the \"More\" dropdown absorbs the overflow and\n * no scrollbar is shown. The bar itself is hidden — keyboard focus and touch\n * still scroll clipped tabs into view.\n */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n padding: 0 12px;\n background-color: var(--scalar-background-1);\n border-left: var(--scalar-border-width) solid var(--scalar-border-color);\n border-right: var(--scalar-border-width) solid var(--scalar-border-color);\n border-bottom: var(--scalar-border-width) solid var(--scalar-border-color);\n}\n.client-libraries-content::-webkit-scrollbar {\n display: none;\n}\n.client-libraries-row {\n display: flex;\n justify-content: flex-start;\n}\n/* Zero-size clip so the measure row never adds to the scrollable width */\n.client-libraries-measure-clip {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n overflow: hidden;\n}\n/* Off-screen row used only to measure natural tab widths */\n.client-libraries-row--measure {\n width: max-content;\n visibility: hidden;\n pointer-events: none;\n}\n.client-libraries {\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n cursor: pointer;\n white-space: nowrap;\n /* Keep tabs at their natural (measured) width so they scroll rather than squish */\n flex-shrink: 0;\n padding: 8px 12px;\n gap: 6px;\n color: var(--scalar-color-3);\n border-bottom: 1px solid transparent;\n background: transparent;\n user-select: none;\n}\n.client-libraries:not(.client-libraries__active):hover:before {\n content: '';\n position: absolute;\n width: calc(100% - 4px);\n height: calc(100% - 4px);\n background: var(--scalar-background-2);\n left: 2px;\n top: 2px;\n z-index: 0;\n border-radius: var(--scalar-radius);\n}\n.client-libraries:active {\n color: var(--scalar-color-1);\n}\n.client-libraries:focus-visible {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--scalar-color-accent);\n}\n.client-libraries__active {\n color: var(--scalar-color-1);\n border-bottom: 1px solid var(--scalar-color-1);\n}\n/* Always pin the \"More\" dropdown to the right edge */\n.client-libraries-more {\n margin-left: auto;\n}\n.client-libraries-icon {\n max-width: 14px;\n max-height: 14px;\n min-width: 14px;\n width: 100%;\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n box-sizing: border-box;\n color: currentColor;\n}\n.client-libraries .client-libraries-text {\n font-size: var(--scalar-small);\n position: relative;\n display: flex;\n align-items: center;\n}\n.client-libraries__active .client-libraries-text {\n color: var(--scalar-color-1);\n font-weight: var(--scalar-semibold);\n}\n</style>\n"],"mappings":""}
@@ -2,6 +2,7 @@ import { getLanguageIcon } from "../helpers/language-icon.js";
2
2
  import { getRenderableSdks } from "../helpers/renderable-sdks.js";
3
3
  import { getVisibleTabCount } from "../helpers/visible-tab-count.js";
4
4
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderList, toDisplayString, unref, useId, watch, withCtx } from "vue";
5
+ import { getCustomClientIds } from "@scalar/api-client/blocks/operation-code-sample";
5
6
  import { ScalarMarkdown } from "@scalar/components/markdown";
6
7
  import { ScalarCombobox } from "@scalar/components/combobox";
7
8
  import { ScalarIcon } from "@scalar/components/icon";
@@ -27,7 +28,11 @@ var _hoisted_9 = { class: "client-libraries" };
27
28
  var _hoisted_10 = ["aria-labelledby"];
28
29
  var SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
29
30
  __name: "SdkInstallationInstructions",
30
- props: { xScalarSdkInstallation: {} },
31
+ props: {
32
+ xScalarSdkInstallation: {},
33
+ selectedClient: {},
34
+ eventBus: {}
35
+ },
31
36
  setup(__props) {
32
37
  const headingId = useId();
33
38
  /** Base id used to associate each tab with the shared panel for assistive tech */
@@ -42,10 +47,29 @@ var SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default = /*
42
47
  const selectedIndex = ref(0);
43
48
  /** The currently selected SDK */
44
49
  const selected = computed(() => sdks.value[selectedIndex.value]);
45
- watch(() => sdks.value.map((sdk) => sdk.lang).join("\n"), () => {
46
- if (selectedIndex.value > sdks.value.length - 1) selectedIndex.value = 0;
47
- nextTick(measure);
48
- });
50
+ /**
51
+ * The `custom/<lang>` client id for each SDK, aligned by index with `sdks`.
52
+ *
53
+ * We reuse the exact id scheme the operation code samples use for their custom
54
+ * examples, so selecting a language here resolves to the same id those samples
55
+ * are keyed by — that shared id is what keeps the two surfaces in sync.
56
+ */
57
+ const sdkClientIds = computed(() => getCustomClientIds(sdks.value.map((sdk) => ({
58
+ lang: sdk.lang,
59
+ source: ""
60
+ }))));
61
+ /** Select an SDK by index and broadcast it so the operation code samples follow */
62
+ const select = (index) => {
63
+ selectedIndex.value = index;
64
+ const id = sdkClientIds.value[index];
65
+ if (id) __props.eventBus?.emit("workspace:update:selected-client", id);
66
+ };
67
+ watch([() => __props.selectedClient, sdkClientIds], ([client, ids]) => {
68
+ const matched = client ? ids.findIndex((id) => id === client) : -1;
69
+ if (matched >= 0) selectedIndex.value = matched;
70
+ else if (selectedIndex.value > ids.length - 1) selectedIndex.value = 0;
71
+ }, { immediate: true });
72
+ watch(() => sdks.value.map((sdk) => sdk.lang).join("\n"), () => void nextTick(measure));
49
73
  const tabsRef = ref();
50
74
  const measureRef = ref();
51
75
  /** The width available to render the tabs in */
@@ -81,7 +105,7 @@ var SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default = /*
81
105
  /** The selected option within the "More" dropdown, if any */
82
106
  const selectedMoreOption = computed(() => moreOptions.value.find((option) => option.id === String(selectedIndex.value)));
83
107
  const selectMore = (option) => {
84
- if (option) selectedIndex.value = Number(option.id);
108
+ if (option) select(Number(option.id));
85
109
  };
86
110
  /** The id of the tab that labels the panel (falls back to the heading when the selection lives in "More") */
87
111
  const activeTabId = computed(() => isMoreActive.value ? headingId : `${baseId}-tab-${selectedIndex.value}`);
@@ -119,7 +143,7 @@ var SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default = /*
119
143
  default: return;
120
144
  }
121
145
  event.preventDefault();
122
- selectedIndex.value = next;
146
+ select(next);
123
147
  focusTab(next);
124
148
  };
125
149
  let observer;
@@ -167,7 +191,7 @@ var SdkInstallationInstructions_vue_vue_type_script_setup_true_lang_default = /*
167
191
  role: "tab",
168
192
  tabindex: index === tabStopIndex.value ? 0 : -1,
169
193
  type: "button",
170
- onClick: ($event) => selectedIndex.value = index,
194
+ onClick: ($event) => select(index),
171
195
  onKeydown: ($event) => onTabKeydown($event, index)
172
196
  }, [sdk.icon ? (openBlock(), createBlock(unref(ScalarIcon), {
173
197
  key: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"SdkInstallationInstructions.vue.script.js","names":[],"sources":["../../../../src/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport {\n ScalarCombobox,\n type ScalarComboboxOption,\n} from '@scalar/components/combobox'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarMarkdown } from '@scalar/components/markdown'\nimport type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n useId,\n watch,\n} from 'vue'\n\nimport { getLanguageIcon } from '../helpers/language-icon'\nimport { getRenderableSdks } from '../helpers/renderable-sdks'\nimport { getVisibleTabCount } from '../helpers/visible-tab-count'\n\nconst { xScalarSdkInstallation } = defineProps<{\n /** Custom SDK installation instructions from `x-scalar-sdk-installation` */\n xScalarSdkInstallation?: XScalarSdkInstallation['x-scalar-sdk-installation']\n}>()\n\nconst headingId = useId()\n/** Base id used to associate each tab with the shared panel for assistive tech */\nconst baseId = useId()\nconst panelId = `${baseId}-panel`\n\n/** Only the SDKs that actually have something to show, with their resolved icon */\nconst sdks = computed(() =>\n getRenderableSdks(xScalarSdkInstallation).map((sdk) => ({\n ...sdk,\n icon: getLanguageIcon(sdk.lang),\n })),\n)\n\n/** Index of the currently selected SDK */\nconst selectedIndex = ref(0)\n\n/** The currently selected SDK */\nconst selected = computed(() => sdks.value[selectedIndex.value])\n\n// Keep the selection in range and re-measure whenever the set of SDKs changes.\n// Keying on the languages (not just the count) also catches documents that swap\n// in a different set of the same size, which would otherwise leave the cached\n// tab widths — and the \"More\" overflow logic — stale.\nwatch(\n () => sdks.value.map((sdk) => sdk.lang).join('\\n'),\n () => {\n if (selectedIndex.value > sdks.value.length - 1) {\n selectedIndex.value = 0\n }\n void nextTick(measure)\n },\n)\n\nconst tabsRef = ref<HTMLElement>()\nconst measureRef = ref<HTMLElement>()\n\n/** The width available to render the tabs in */\nconst availableWidth = ref(0)\n/** The natural width of each tab */\nconst tabWidths = ref<number[]>([])\n/** The width of the \"More\" dropdown trigger */\nconst moreWidth = ref(0)\n\n/** Measure the available width and the natural width of each tab */\nconst measure = () => {\n const measureEl = measureRef.value\n const tabsEl = tabsRef.value\n\n if (!measureEl || !tabsEl) {\n return\n }\n\n // The measuring row renders every tab followed by the \"More\" trigger\n const widths = Array.from(measureEl.children).map(\n (child) => (child as HTMLElement).offsetWidth,\n )\n\n tabWidths.value = widths.slice(0, sdks.value.length)\n moreWidth.value = widths[sdks.value.length] ?? 0\n availableWidth.value = tabsEl.clientWidth\n}\n\n/** How many tabs fit inline before we need the \"More\" dropdown */\nconst visibleCount = computed(() => {\n // Until we have measured, render everything (also the server-rendered state)\n if (!tabWidths.value.length || availableWidth.value <= 0) {\n return sdks.value.length\n }\n\n return getVisibleTabCount(\n tabWidths.value,\n availableWidth.value,\n moreWidth.value,\n )\n})\n\n/** The SDKs shown as inline tabs */\nconst visibleSdks = computed(() => sdks.value.slice(0, visibleCount.value))\n\n/** Whether the selected SDK lives in the \"More\" dropdown */\nconst isMoreActive = computed(() => selectedIndex.value >= visibleCount.value)\n\n/** The overflowing SDKs as combobox options, keyed by their original index */\nconst moreOptions = computed<ScalarComboboxOption[]>(() =>\n sdks.value.slice(visibleCount.value).map((sdk, index) => ({\n id: String(visibleCount.value + index),\n label: sdk.lang,\n })),\n)\n\n/** The selected option within the \"More\" dropdown, if any */\nconst selectedMoreOption = computed(() =>\n moreOptions.value.find((option) => option.id === String(selectedIndex.value)),\n)\n\nconst selectMore = (option: ScalarComboboxOption | undefined) => {\n if (option) {\n selectedIndex.value = Number(option.id)\n }\n}\n\n/** The id of the tab that labels the panel (falls back to the heading when the selection lives in \"More\") */\nconst activeTabId = computed(() =>\n isMoreActive.value ? headingId : `${baseId}-tab-${selectedIndex.value}`,\n)\n\n/**\n * The visible tab that holds the roving tabindex. When the selection lives in\n * the \"More\" dropdown, that trigger is the tab stop instead, so no inline tab\n * should be focusable.\n */\nconst tabStopIndex = computed(() =>\n isMoreActive.value ? -1 : selectedIndex.value,\n)\n\n/** Focus a visible tab by index after the DOM has settled */\nconst focusTab = (index: number) => {\n void nextTick(() => {\n tabsRef.value\n ?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n [index]?.focus()\n })\n}\n\n/** Arrow / Home / End keyboard navigation across the visible tabs (WAI-ARIA tabs pattern) */\nconst onTabKeydown = (event: KeyboardEvent, index: number) => {\n const lastVisible = visibleCount.value - 1\n\n let next = index\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n next = index >= lastVisible ? 0 : index + 1\n break\n case 'ArrowLeft':\n case 'ArrowUp':\n next = index <= 0 ? lastVisible : index - 1\n break\n case 'Home':\n next = 0\n break\n case 'End':\n next = lastVisible\n break\n default:\n return\n }\n\n event.preventDefault()\n selectedIndex.value = next\n focusTab(next)\n}\n\nlet observer: ResizeObserver | undefined\nlet frame = 0\n\n/** Coalesce resize bursts into a single measure before the next paint */\nconst scheduleMeasure = () => {\n if (typeof requestAnimationFrame === 'undefined') {\n measure()\n return\n }\n cancelAnimationFrame(frame)\n frame = requestAnimationFrame(measure)\n}\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(scheduleMeasure)\n // Observe the visible row for the available width, and the hidden measure\n // row for the natural tab widths. The latter catches changes the container\n // never sees — web fonts loading, or labels changing without the count\n // changing — which would otherwise leave the overflow logic stale.\n if (tabsRef.value) {\n observer.observe(tabsRef.value)\n }\n if (measureRef.value) {\n observer.observe(measureRef.value)\n }\n }\n measure()\n})\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n if (typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(frame)\n }\n})\n</script>\n<template>\n <div v-if=\"sdks.length\">\n <div\n :id=\"headingId\"\n class=\"client-libraries-heading\">\n Client Libraries\n </div>\n\n <!-- Tabs -->\n <div class=\"client-libraries-content\">\n <div\n ref=\"tabsRef\"\n :aria-labelledby=\"headingId\"\n class=\"client-libraries-row\"\n role=\"tablist\">\n <button\n v-for=\"(sdk, index) in visibleSdks\"\n :id=\"`${baseId}-tab-${index}`\"\n :key=\"index\"\n :aria-controls=\"panelId\"\n :aria-selected=\"index === selectedIndex\"\n class=\"client-libraries\"\n :class=\"{ 'client-libraries__active': index === selectedIndex }\"\n role=\"tab\"\n :tabindex=\"index === tabStopIndex ? 0 : -1\"\n type=\"button\"\n @click=\"selectedIndex = index\"\n @keydown=\"onTabKeydown($event, index)\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </button>\n\n <!-- More dropdown -->\n <ScalarCombobox\n v-if=\"visibleCount < sdks.length\"\n :modelValue=\"selectedMoreOption\"\n :options=\"moreOptions\"\n placement=\"bottom-end\"\n teleport\n @update:modelValue=\"selectMore\">\n <button\n class=\"client-libraries client-libraries-more\"\n :class=\"{ 'client-libraries__active': isMoreActive }\"\n type=\"button\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n :icon=\"\n isMoreActive && selected?.icon ? selected.icon : 'Ellipses'\n \" />\n <span class=\"client-libraries-text\">More</span>\n </button>\n </ScalarCombobox>\n </div>\n\n <!-- Hidden row used to measure the natural width of every tab. It is\n clipped to a zero-size box so it never contributes to the scrollable\n width of the (horizontally scrollable) row above. -->\n <div\n aria-hidden=\"true\"\n class=\"client-libraries-measure-clip\">\n <div\n ref=\"measureRef\"\n class=\"client-libraries-row client-libraries-row--measure\">\n <span\n v-for=\"(sdk, index) in sdks\"\n :key=\"index\"\n class=\"client-libraries\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </span>\n <span class=\"client-libraries\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n icon=\"Ellipses\" />\n <span class=\"client-libraries-text\">More</span>\n </span>\n </div>\n </div>\n </div>\n\n <!-- Content: Markdown installation instructions (supports code blocks) -->\n <div\n v-if=\"selected?.description\"\n :id=\"panelId\"\n :aria-labelledby=\"activeTabId\"\n class=\"selected-client\"\n role=\"tabpanel\"\n tabindex=\"0\">\n <ScalarMarkdown :value=\"selected.description\" />\n </div>\n </div>\n</template>\n<style scoped>\n/* One panel holds the Markdown installation instructions */\n.selected-client {\n display: flex;\n flex-direction: column;\n gap: 9px;\n color: var(--scalar-color-1);\n font-size: var(--scalar-small);\n padding: 9px 12px;\n background: var(--scalar-background-1);\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top: none;\n border-bottom-left-radius: var(--scalar-radius-xl);\n border-bottom-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-heading {\n font-size: var(--scalar-small);\n font-weight: var(--scalar-font-medium);\n color: var(--scalar-color-1);\n padding: 9px 12px;\n background-color: var(--scalar-background-2);\n display: flex;\n align-items: center;\n max-height: 32px;\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top-left-radius: var(--scalar-radius-xl);\n border-top-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-content {\n position: relative;\n /*\n * Scroll horizontally rather than clip: before JS measures the tab widths\n * (and without JS at all) every tab is rendered, so overflowing tabs must\n * stay reachable. Once measured, the \"More\" dropdown absorbs the overflow and\n * no scrollbar is shown. The bar itself is hidden — keyboard focus and touch\n * still scroll clipped tabs into view.\n */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n padding: 0 12px;\n background-color: var(--scalar-background-1);\n border-left: var(--scalar-border-width) solid var(--scalar-border-color);\n border-right: var(--scalar-border-width) solid var(--scalar-border-color);\n border-bottom: var(--scalar-border-width) solid var(--scalar-border-color);\n}\n.client-libraries-content::-webkit-scrollbar {\n display: none;\n}\n.client-libraries-row {\n display: flex;\n justify-content: flex-start;\n}\n/* Zero-size clip so the measure row never adds to the scrollable width */\n.client-libraries-measure-clip {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n overflow: hidden;\n}\n/* Off-screen row used only to measure natural tab widths */\n.client-libraries-row--measure {\n width: max-content;\n visibility: hidden;\n pointer-events: none;\n}\n.client-libraries {\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n cursor: pointer;\n white-space: nowrap;\n /* Keep tabs at their natural (measured) width so they scroll rather than squish */\n flex-shrink: 0;\n padding: 8px 12px;\n gap: 6px;\n color: var(--scalar-color-3);\n border-bottom: 1px solid transparent;\n background: transparent;\n user-select: none;\n}\n.client-libraries:not(.client-libraries__active):hover:before {\n content: '';\n position: absolute;\n width: calc(100% - 4px);\n height: calc(100% - 4px);\n background: var(--scalar-background-2);\n left: 2px;\n top: 2px;\n z-index: 0;\n border-radius: var(--scalar-radius);\n}\n.client-libraries:active {\n color: var(--scalar-color-1);\n}\n.client-libraries:focus-visible {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--scalar-color-accent);\n}\n.client-libraries__active {\n color: var(--scalar-color-1);\n border-bottom: 1px solid var(--scalar-color-1);\n}\n/* Always pin the \"More\" dropdown to the right edge */\n.client-libraries-more {\n margin-left: auto;\n}\n.client-libraries-icon {\n max-width: 14px;\n max-height: 14px;\n min-width: 14px;\n width: 100%;\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n box-sizing: border-box;\n color: currentColor;\n}\n.client-libraries .client-libraries-text {\n font-size: var(--scalar-small);\n position: relative;\n display: flex;\n align-items: center;\n}\n.client-libraries__active .client-libraries-text {\n color: var(--scalar-color-1);\n font-weight: var(--scalar-semibold);\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BA,MAAM,YAAY,OAAM;;EAExB,MAAM,SAAS,OAAM;EACrB,MAAM,UAAU,GAAG,OAAO;;EAG1B,MAAM,OAAO,eACX,kBAAkB,QAAA,uBAAuB,CAAC,KAAK,SAAS;GACtD,GAAG;GACH,MAAM,gBAAgB,IAAI,KAAK;GAChC,EAAE,CACL;;EAGA,MAAM,gBAAgB,IAAI,EAAC;;EAG3B,MAAM,WAAW,eAAe,KAAK,MAAM,cAAc,OAAM;AAM/D,cACQ,KAAK,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,KAAK,QAC5C;AACJ,OAAI,cAAc,QAAQ,KAAK,MAAM,SAAS,EAC5C,eAAc,QAAQ;AAEnB,YAAS,QAAO;IAEzB;EAEA,MAAM,UAAU,KAAiB;EACjC,MAAM,aAAa,KAAiB;;EAGpC,MAAM,iBAAiB,IAAI,EAAC;;EAE5B,MAAM,YAAY,IAAc,EAAE,CAAA;;EAElC,MAAM,YAAY,IAAI,EAAC;;EAGvB,MAAM,gBAAgB;GACpB,MAAM,YAAY,WAAW;GAC7B,MAAM,SAAS,QAAQ;AAEvB,OAAI,CAAC,aAAa,CAAC,OACjB;GAIF,MAAM,SAAS,MAAM,KAAK,UAAU,SAAS,CAAC,KAC3C,UAAW,MAAsB,YACpC;AAEA,aAAU,QAAQ,OAAO,MAAM,GAAG,KAAK,MAAM,OAAM;AACnD,aAAU,QAAQ,OAAO,KAAK,MAAM,WAAW;AAC/C,kBAAe,QAAQ,OAAO;;;EAIhC,MAAM,eAAe,eAAe;AAElC,OAAI,CAAC,UAAU,MAAM,UAAU,eAAe,SAAS,EACrD,QAAO,KAAK,MAAM;AAGpB,UAAO,mBACL,UAAU,OACV,eAAe,OACf,UAAU,MACZ;IACD;;EAGD,MAAM,cAAc,eAAe,KAAK,MAAM,MAAM,GAAG,aAAa,MAAM,CAAA;;EAG1E,MAAM,eAAe,eAAe,cAAc,SAAS,aAAa,MAAK;;EAG7E,MAAM,cAAc,eAClB,KAAK,MAAM,MAAM,aAAa,MAAM,CAAC,KAAK,KAAK,WAAW;GACxD,IAAI,OAAO,aAAa,QAAQ,MAAM;GACtC,OAAO,IAAI;GACZ,EAAE,CACL;;EAGA,MAAM,qBAAqB,eACzB,YAAY,MAAM,MAAM,WAAW,OAAO,OAAO,OAAO,cAAc,MAAM,CAAC,CAC/E;EAEA,MAAM,cAAc,WAA6C;AAC/D,OAAI,OACF,eAAc,QAAQ,OAAO,OAAO,GAAE;;;EAK1C,MAAM,cAAc,eAClB,aAAa,QAAQ,YAAY,GAAG,OAAO,OAAO,cAAc,QAClE;;;;;;EAOA,MAAM,eAAe,eACnB,aAAa,QAAQ,KAAK,cAAc,MAC1C;;EAGA,MAAM,YAAY,UAAkB;AAC7B,kBAAe;AAClB,YAAQ,OACJ,iBAAoC,iBAAc,CACnD,QAAQ,OAAM;KAClB;;;EAIH,MAAM,gBAAgB,OAAsB,UAAkB;GAC5D,MAAM,cAAc,aAAa,QAAQ;GAEzC,IAAI,OAAO;AACX,WAAQ,MAAM,KAAd;IACE,KAAK;IACL,KAAK;AACH,YAAO,SAAS,cAAc,IAAI,QAAQ;AAC1C;IACF,KAAK;IACL,KAAK;AACH,YAAO,SAAS,IAAI,cAAc,QAAQ;AAC1C;IACF,KAAK;AACH,YAAO;AACP;IACF,KAAK;AACH,YAAO;AACP;IACF,QACE;;AAGJ,SAAM,gBAAe;AACrB,iBAAc,QAAQ;AACtB,YAAS,KAAI;;EAGf,IAAI;EACJ,IAAI,QAAQ;;EAGZ,MAAM,wBAAwB;AAC5B,OAAI,OAAO,0BAA0B,aAAa;AAChD,aAAQ;AACR;;AAEF,wBAAqB,MAAK;AAC1B,WAAQ,sBAAsB,QAAO;;AAGvC,kBAAgB;AACd,OAAI,OAAO,mBAAmB,aAAa;AACzC,eAAW,IAAI,eAAe,gBAAe;AAK7C,QAAI,QAAQ,MACV,UAAS,QAAQ,QAAQ,MAAK;AAEhC,QAAI,WAAW,MACb,UAAS,QAAQ,WAAW,MAAK;;AAGrC,YAAQ;IACT;AAED,wBAAsB;AACpB,aAAU,YAAW;AACrB,OAAI,OAAO,yBAAyB,YAClC,sBAAqB,MAAK;IAE7B;;UAGY,KAAA,MAAK,UAAA,WAAA,EAAhB,mBA+FM,OAAA,YAAA;IA9FJ,mBAIM,OAAA;KAHH,IAAI,MAAA,UAAS;KACd,OAAM;OAA2B,sBAEnC,GAAA,WAAA;IAGA,mBA2EM,OA3EN,YA2EM,CA1EJ,mBA6CM,OAAA;cA5CA;KAAJ,KAAI;KACH,mBAAiB,MAAA,UAAS;KAC3B,OAAM;KACN,MAAK;0BACL,mBAkBS,UAAA,MAAA,WAjBgB,YAAA,QAAf,KAAK,UAAK;yBADpB,mBAkBS,UAAA;MAhBN,IAAE,GAAK,MAAA,OAAM,CAAA,OAAQ;MACrB,KAAK;MACL,iBAAe;MACf,iBAAe,UAAU,cAAA;MAC1B,OAAK,eAAA,CAAC,oBAAkB,EAAA,4BACc,UAAU,cAAA,OAAa,CAAA,CAAA;MAC7D,MAAK;MACJ,UAAU,UAAU,aAAA,QAAY,IAAA;MACjC,MAAK;MACJ,UAAK,WAAE,cAAA,QAAgB;MACvB,YAAO,WAAE,aAAa,QAAQ,MAAK;SAE5B,IAAI,QAAA,WAAA,EADZ,YAGqB,MAAA,WAAA,EAAA;;MADnB,OAAM;MACL,MAAM,IAAI;4DACb,mBAAyD,QAAzD,YAAyD,gBAAlB,IAAI,KAAI,EAAA,EAAA,CAAA,EAAA,IAAA,WAAA;eAKzC,aAAA,QAAe,KAAA,MAAK,UAAA,WAAA,EAD5B,YAkBiB,MAAA,eAAA,EAAA;;KAhBd,YAAY,mBAAA;KACZ,SAAS,YAAA;KACV,WAAU;KACV,UAAA;KACC,uBAAmB;;4BAWX,CAVT,mBAUS,UAAA;MATP,OAAK,eAAA,CAAC,0CAAwC,EAAA,4BACR,aAAA,OAAY,CAAA,CAAA;MAClD,MAAK;SACL,YAIM,MAAA,WAAA,EAAA;MAHJ,OAAM;MACL,MAAuB,aAAA,SAAgB,SAAA,OAAU,OAAO,SAAA,MAAS,OAAI;sDAGxE,mBAA+C,QAAA,EAAzC,OAAM,yBAAuB,EAAC,QAAI,GAAA,EAAA,EAAA,EAAA,CAAA,CAAA;;uFAQ9C,mBAuBM,OAvBN,YAuBM,CApBJ,mBAmBM,OAAA;cAlBA;KAAJ,KAAI;KACJ,OAAM;0BACN,mBASO,UAAA,MAAA,WARkB,KAAA,QAAf,KAAK,UAAK;yBADpB,mBASO,QAAA;MAPJ,KAAK;MACN,OAAM;SAEE,IAAI,QAAA,WAAA,EADZ,YAGqB,MAAA,WAAA,EAAA;;MADnB,OAAM;MACL,MAAM,IAAI;4DACb,mBAAyD,QAAzD,YAAyD,gBAAlB,IAAI,KAAI,EAAA,EAAA,CAAA,CAAA;eAEjD,mBAKO,QALP,YAKO,CAJL,YAEoB,MAAA,WAAA,EAAA;KADlB,OAAM;KACN,MAAK;kCACP,mBAA+C,QAAA,EAAzC,OAAM,yBAAuB,EAAC,QAAI,GAAA,EAAA,CAAA,CAAA,EAAA,IAAA,CAAA,CAAA,CAAA,CAAA;IAQxC,SAAA,OAAU,eAAA,WAAA,EADlB,mBAQM,OAAA;;KANH,IAAI;KACJ,mBAAiB,YAAA;KAClB,OAAM;KACN,MAAK;KACL,UAAS;QACT,YAAgD,MAAA,eAAA,EAAA,EAA/B,OAAO,SAAA,MAAS,aAAA,EAAA,MAAA,GAAA,CAAA,QAAA,CAAA,CAAA,EAAA,GAAA,YAAA,IAAA,mBAAA,IAAA,KAAA"}
1
+ {"version":3,"file":"SdkInstallationInstructions.vue.script.js","names":[],"sources":["../../../../src/blocks/scalar-sdk-installation-instructions/components/SdkInstallationInstructions.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { getCustomClientIds } from '@scalar/api-client/blocks/operation-code-sample'\nimport {\n ScalarCombobox,\n type ScalarComboboxOption,\n} from '@scalar/components/combobox'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarMarkdown } from '@scalar/components/markdown'\nimport { type WorkspaceEventBus } from '@scalar/workspace-store/events'\nimport type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation'\nimport {\n computed,\n nextTick,\n onBeforeUnmount,\n onMounted,\n ref,\n useId,\n watch,\n} from 'vue'\n\nimport { getLanguageIcon } from '../helpers/language-icon'\nimport { getRenderableSdks } from '../helpers/renderable-sdks'\nimport { getVisibleTabCount } from '../helpers/visible-tab-count'\n\nconst { xScalarSdkInstallation, selectedClient, eventBus } = defineProps<{\n /** Custom SDK installation instructions from `x-scalar-sdk-installation` */\n xScalarSdkInstallation?: XScalarSdkInstallation['x-scalar-sdk-installation']\n /**\n * The globally selected client id. When it matches one of the SDK languages\n * (as a `custom/<lang>` id) the matching tab is shown as active, keeping the\n * tabs in sync with the operation code samples.\n */\n selectedClient?: string\n /**\n * Event bus used to broadcast the selected client. Picking a language here\n * switches the operation code samples to that language's custom example, the\n * same channel the generic client selector uses.\n */\n eventBus?: WorkspaceEventBus\n}>()\n\nconst headingId = useId()\n/** Base id used to associate each tab with the shared panel for assistive tech */\nconst baseId = useId()\nconst panelId = `${baseId}-panel`\n\n/** Only the SDKs that actually have something to show, with their resolved icon */\nconst sdks = computed(() =>\n getRenderableSdks(xScalarSdkInstallation).map((sdk) => ({\n ...sdk,\n icon: getLanguageIcon(sdk.lang),\n })),\n)\n\n/** Index of the currently selected SDK */\nconst selectedIndex = ref(0)\n\n/** The currently selected SDK */\nconst selected = computed(() => sdks.value[selectedIndex.value])\n\n/**\n * The `custom/<lang>` client id for each SDK, aligned by index with `sdks`.\n *\n * We reuse the exact id scheme the operation code samples use for their custom\n * examples, so selecting a language here resolves to the same id those samples\n * are keyed by — that shared id is what keeps the two surfaces in sync.\n */\nconst sdkClientIds = computed(() =>\n getCustomClientIds(sdks.value.map((sdk) => ({ lang: sdk.lang, source: '' }))),\n)\n\n/** Select an SDK by index and broadcast it so the operation code samples follow */\nconst select = (index: number) => {\n selectedIndex.value = index\n\n const id = sdkClientIds.value[index]\n if (id) {\n eventBus?.emit('workspace:update:selected-client', id)\n }\n}\n\n// Keep the active tab aligned with the global selection, re-evaluating whenever\n// either the selection or the available SDKs change. Watching `sdkClientIds`\n// (not just `selectedClient`) is what stops the intro tab and the operation\n// samples from disagreeing after the SDK list is reordered or resized: the tab\n// follows the selected language to its new position. When the selection is a\n// built-in client (or a language this document has no install entry for) we keep\n// the current tab, only clamping it back into range if the list shrank past it.\nwatch(\n [() => selectedClient, sdkClientIds],\n ([client, ids]) => {\n const matched = client ? ids.findIndex((id) => id === client) : -1\n\n if (matched >= 0) {\n selectedIndex.value = matched\n } else if (selectedIndex.value > ids.length - 1) {\n selectedIndex.value = 0\n }\n },\n { immediate: true },\n)\n\n// Re-measure whenever the set of SDKs changes. Keying on the languages (not just\n// the count) also catches documents that swap in a different set of the same\n// size, which would otherwise leave the cached tab widths — and the \"More\"\n// overflow logic — stale.\nwatch(\n () => sdks.value.map((sdk) => sdk.lang).join('\\n'),\n () => void nextTick(measure),\n)\n\nconst tabsRef = ref<HTMLElement>()\nconst measureRef = ref<HTMLElement>()\n\n/** The width available to render the tabs in */\nconst availableWidth = ref(0)\n/** The natural width of each tab */\nconst tabWidths = ref<number[]>([])\n/** The width of the \"More\" dropdown trigger */\nconst moreWidth = ref(0)\n\n/** Measure the available width and the natural width of each tab */\nconst measure = () => {\n const measureEl = measureRef.value\n const tabsEl = tabsRef.value\n\n if (!measureEl || !tabsEl) {\n return\n }\n\n // The measuring row renders every tab followed by the \"More\" trigger\n const widths = Array.from(measureEl.children).map(\n (child) => (child as HTMLElement).offsetWidth,\n )\n\n tabWidths.value = widths.slice(0, sdks.value.length)\n moreWidth.value = widths[sdks.value.length] ?? 0\n availableWidth.value = tabsEl.clientWidth\n}\n\n/** How many tabs fit inline before we need the \"More\" dropdown */\nconst visibleCount = computed(() => {\n // Until we have measured, render everything (also the server-rendered state)\n if (!tabWidths.value.length || availableWidth.value <= 0) {\n return sdks.value.length\n }\n\n return getVisibleTabCount(\n tabWidths.value,\n availableWidth.value,\n moreWidth.value,\n )\n})\n\n/** The SDKs shown as inline tabs */\nconst visibleSdks = computed(() => sdks.value.slice(0, visibleCount.value))\n\n/** Whether the selected SDK lives in the \"More\" dropdown */\nconst isMoreActive = computed(() => selectedIndex.value >= visibleCount.value)\n\n/** The overflowing SDKs as combobox options, keyed by their original index */\nconst moreOptions = computed<ScalarComboboxOption[]>(() =>\n sdks.value.slice(visibleCount.value).map((sdk, index) => ({\n id: String(visibleCount.value + index),\n label: sdk.lang,\n })),\n)\n\n/** The selected option within the \"More\" dropdown, if any */\nconst selectedMoreOption = computed(() =>\n moreOptions.value.find((option) => option.id === String(selectedIndex.value)),\n)\n\nconst selectMore = (option: ScalarComboboxOption | undefined) => {\n if (option) {\n select(Number(option.id))\n }\n}\n\n/** The id of the tab that labels the panel (falls back to the heading when the selection lives in \"More\") */\nconst activeTabId = computed(() =>\n isMoreActive.value ? headingId : `${baseId}-tab-${selectedIndex.value}`,\n)\n\n/**\n * The visible tab that holds the roving tabindex. When the selection lives in\n * the \"More\" dropdown, that trigger is the tab stop instead, so no inline tab\n * should be focusable.\n */\nconst tabStopIndex = computed(() =>\n isMoreActive.value ? -1 : selectedIndex.value,\n)\n\n/** Focus a visible tab by index after the DOM has settled */\nconst focusTab = (index: number) => {\n void nextTick(() => {\n tabsRef.value\n ?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]')\n [index]?.focus()\n })\n}\n\n/** Arrow / Home / End keyboard navigation across the visible tabs (WAI-ARIA tabs pattern) */\nconst onTabKeydown = (event: KeyboardEvent, index: number) => {\n const lastVisible = visibleCount.value - 1\n\n let next = index\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowDown':\n next = index >= lastVisible ? 0 : index + 1\n break\n case 'ArrowLeft':\n case 'ArrowUp':\n next = index <= 0 ? lastVisible : index - 1\n break\n case 'Home':\n next = 0\n break\n case 'End':\n next = lastVisible\n break\n default:\n return\n }\n\n event.preventDefault()\n select(next)\n focusTab(next)\n}\n\nlet observer: ResizeObserver | undefined\nlet frame = 0\n\n/** Coalesce resize bursts into a single measure before the next paint */\nconst scheduleMeasure = () => {\n if (typeof requestAnimationFrame === 'undefined') {\n measure()\n return\n }\n cancelAnimationFrame(frame)\n frame = requestAnimationFrame(measure)\n}\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined') {\n observer = new ResizeObserver(scheduleMeasure)\n // Observe the visible row for the available width, and the hidden measure\n // row for the natural tab widths. The latter catches changes the container\n // never sees — web fonts loading, or labels changing without the count\n // changing — which would otherwise leave the overflow logic stale.\n if (tabsRef.value) {\n observer.observe(tabsRef.value)\n }\n if (measureRef.value) {\n observer.observe(measureRef.value)\n }\n }\n measure()\n})\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n if (typeof cancelAnimationFrame !== 'undefined') {\n cancelAnimationFrame(frame)\n }\n})\n</script>\n<template>\n <div v-if=\"sdks.length\">\n <div\n :id=\"headingId\"\n class=\"client-libraries-heading\">\n Client Libraries\n </div>\n\n <!-- Tabs -->\n <div class=\"client-libraries-content\">\n <div\n ref=\"tabsRef\"\n :aria-labelledby=\"headingId\"\n class=\"client-libraries-row\"\n role=\"tablist\">\n <button\n v-for=\"(sdk, index) in visibleSdks\"\n :id=\"`${baseId}-tab-${index}`\"\n :key=\"index\"\n :aria-controls=\"panelId\"\n :aria-selected=\"index === selectedIndex\"\n class=\"client-libraries\"\n :class=\"{ 'client-libraries__active': index === selectedIndex }\"\n role=\"tab\"\n :tabindex=\"index === tabStopIndex ? 0 : -1\"\n type=\"button\"\n @click=\"select(index)\"\n @keydown=\"onTabKeydown($event, index)\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </button>\n\n <!-- More dropdown -->\n <ScalarCombobox\n v-if=\"visibleCount < sdks.length\"\n :modelValue=\"selectedMoreOption\"\n :options=\"moreOptions\"\n placement=\"bottom-end\"\n teleport\n @update:modelValue=\"selectMore\">\n <button\n class=\"client-libraries client-libraries-more\"\n :class=\"{ 'client-libraries__active': isMoreActive }\"\n type=\"button\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n :icon=\"\n isMoreActive && selected?.icon ? selected.icon : 'Ellipses'\n \" />\n <span class=\"client-libraries-text\">More</span>\n </button>\n </ScalarCombobox>\n </div>\n\n <!-- Hidden row used to measure the natural width of every tab. It is\n clipped to a zero-size box so it never contributes to the scrollable\n width of the (horizontally scrollable) row above. -->\n <div\n aria-hidden=\"true\"\n class=\"client-libraries-measure-clip\">\n <div\n ref=\"measureRef\"\n class=\"client-libraries-row client-libraries-row--measure\">\n <span\n v-for=\"(sdk, index) in sdks\"\n :key=\"index\"\n class=\"client-libraries\">\n <ScalarIcon\n v-if=\"sdk.icon\"\n class=\"client-libraries-icon\"\n :icon=\"sdk.icon\" />\n <span class=\"client-libraries-text\">{{ sdk.lang }}</span>\n </span>\n <span class=\"client-libraries\">\n <ScalarIcon\n class=\"client-libraries-icon\"\n icon=\"Ellipses\" />\n <span class=\"client-libraries-text\">More</span>\n </span>\n </div>\n </div>\n </div>\n\n <!-- Content: Markdown installation instructions (supports code blocks) -->\n <div\n v-if=\"selected?.description\"\n :id=\"panelId\"\n :aria-labelledby=\"activeTabId\"\n class=\"selected-client\"\n role=\"tabpanel\"\n tabindex=\"0\">\n <ScalarMarkdown :value=\"selected.description\" />\n </div>\n </div>\n</template>\n<style scoped>\n/* One panel holds the Markdown installation instructions */\n.selected-client {\n display: flex;\n flex-direction: column;\n gap: 9px;\n color: var(--scalar-color-1);\n font-size: var(--scalar-small);\n padding: 9px 12px;\n background: var(--scalar-background-1);\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top: none;\n border-bottom-left-radius: var(--scalar-radius-xl);\n border-bottom-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-heading {\n font-size: var(--scalar-small);\n font-weight: var(--scalar-font-medium);\n color: var(--scalar-color-1);\n padding: 9px 12px;\n background-color: var(--scalar-background-2);\n display: flex;\n align-items: center;\n max-height: 32px;\n border: var(--scalar-border-width) solid var(--scalar-border-color);\n border-top-left-radius: var(--scalar-radius-xl);\n border-top-right-radius: var(--scalar-radius-xl);\n}\n.client-libraries-content {\n position: relative;\n /*\n * Scroll horizontally rather than clip: before JS measures the tab widths\n * (and without JS at all) every tab is rendered, so overflowing tabs must\n * stay reachable. Once measured, the \"More\" dropdown absorbs the overflow and\n * no scrollbar is shown. The bar itself is hidden — keyboard focus and touch\n * still scroll clipped tabs into view.\n */\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n padding: 0 12px;\n background-color: var(--scalar-background-1);\n border-left: var(--scalar-border-width) solid var(--scalar-border-color);\n border-right: var(--scalar-border-width) solid var(--scalar-border-color);\n border-bottom: var(--scalar-border-width) solid var(--scalar-border-color);\n}\n.client-libraries-content::-webkit-scrollbar {\n display: none;\n}\n.client-libraries-row {\n display: flex;\n justify-content: flex-start;\n}\n/* Zero-size clip so the measure row never adds to the scrollable width */\n.client-libraries-measure-clip {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n overflow: hidden;\n}\n/* Off-screen row used only to measure natural tab widths */\n.client-libraries-row--measure {\n width: max-content;\n visibility: hidden;\n pointer-events: none;\n}\n.client-libraries {\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n cursor: pointer;\n white-space: nowrap;\n /* Keep tabs at their natural (measured) width so they scroll rather than squish */\n flex-shrink: 0;\n padding: 8px 12px;\n gap: 6px;\n color: var(--scalar-color-3);\n border-bottom: 1px solid transparent;\n background: transparent;\n user-select: none;\n}\n.client-libraries:not(.client-libraries__active):hover:before {\n content: '';\n position: absolute;\n width: calc(100% - 4px);\n height: calc(100% - 4px);\n background: var(--scalar-background-2);\n left: 2px;\n top: 2px;\n z-index: 0;\n border-radius: var(--scalar-radius);\n}\n.client-libraries:active {\n color: var(--scalar-color-1);\n}\n.client-libraries:focus-visible {\n outline: none;\n box-shadow: inset 0 0 0 1px var(--scalar-color-accent);\n}\n.client-libraries__active {\n color: var(--scalar-color-1);\n border-bottom: 1px solid var(--scalar-color-1);\n}\n/* Always pin the \"More\" dropdown to the right edge */\n.client-libraries-more {\n margin-left: auto;\n}\n.client-libraries-icon {\n max-width: 14px;\n max-height: 14px;\n min-width: 14px;\n width: 100%;\n aspect-ratio: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n box-sizing: border-box;\n color: currentColor;\n}\n.client-libraries .client-libraries-text {\n font-size: var(--scalar-small);\n position: relative;\n display: flex;\n align-items: center;\n}\n.client-libraries__active .client-libraries-text {\n color: var(--scalar-color-1);\n font-weight: var(--scalar-semibold);\n}\n</style>\n"],"mappings":"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"}
@@ -1,19 +1,28 @@
1
1
  import type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation';
2
2
  /** The array shape stored under `x-scalar-sdk-installation`. */
3
3
  type SdkInstallationList = NonNullable<XScalarSdkInstallation['x-scalar-sdk-installation']>;
4
+ /** A renderable SDK entry whose `description` is the resolved Markdown to show. */
5
+ type RenderableSdk = {
6
+ lang: string;
7
+ description: string;
8
+ };
4
9
  /**
5
- * The SDK installation entries that actually have something to render.
10
+ * The SDK installation entries that actually have something to render, each
11
+ * resolved to a single Markdown `description`.
6
12
  *
7
- * Entries that only carry a `lang` (no `description`) are ignored so the UI can
8
- * fall back to the generic client selector instead of showing an empty card.
9
- * Both the gate in `Content.vue` and the tab list in the block rely on this, so
10
- * the "has instructions" rule lives in one place.
13
+ * A `description` is the promoted content, but the legacy `source` install
14
+ * command is still supported: when both are present it is appended to the
15
+ * description as a fenced code block, and when only `source` is present it
16
+ * becomes the description on its own. Entries that carry neither are ignored so
17
+ * the UI can fall back to the generic client selector instead of showing an
18
+ * empty card. Both the gate in `Content.vue` and the tab list in the block rely
19
+ * on this, so the "has instructions" rule lives in one place.
11
20
  *
12
21
  * The value comes straight from an untrusted OpenAPI document, so anything
13
22
  * malformed — a non-array extension, a non-object entry, or an entry missing a
14
- * string `lang` (the tab label and icon key) or `description` — is treated as
23
+ * string `lang` (the tab label and icon key) and any content — is treated as
15
24
  * "no instructions" rather than allowed to throw at render time.
16
25
  */
17
- export declare const getRenderableSdks: (xScalarSdkInstallation: SdkInstallationList | undefined) => SdkInstallationList;
26
+ export declare const getRenderableSdks: (xScalarSdkInstallation: SdkInstallationList | undefined) => RenderableSdk[];
18
27
  export {};
19
28
  //# sourceMappingURL=renderable-sdks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderable-sdks.d.ts","sourceRoot":"","sources":["../../../../src/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+EAA+E,CAAA;AAE3H,gEAAgE;AAChE,KAAK,mBAAmB,GAAG,WAAW,CAAC,sBAAsB,CAAC,2BAA2B,CAAC,CAAC,CAAA;AAE3F;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAAI,wBAAwB,mBAAmB,GAAG,SAAS,KAAG,mBAKpF,CAAA"}
1
+ {"version":3,"file":"renderable-sdks.d.ts","sourceRoot":"","sources":["../../../../src/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,+EAA+E,CAAA;AAE3H,gEAAgE;AAChE,KAAK,mBAAmB,GAAG,WAAW,CAAC,sBAAsB,CAAC,2BAA2B,CAAC,CAAC,CAAA;AAE3F,mFAAmF;AACnF,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,GAAI,wBAAwB,mBAAmB,GAAG,SAAS,KAAG,aAAa,EAqBjG,CAAA"}
@@ -1,18 +1,44 @@
1
1
  //#region src/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.ts
2
2
  /**
3
- * The SDK installation entries that actually have something to render.
3
+ * Wrap a raw install command in a fenced code block.
4
4
  *
5
- * Entries that only carry a `lang` (no `description`) are ignored so the UI can
6
- * fall back to the generic client selector instead of showing an empty card.
7
- * Both the gate in `Content.vue` and the tab list in the block rely on this, so
8
- * the "has instructions" rule lives in one place.
5
+ * The fence uses a run of backticks longer than the longest run inside the
6
+ * source, so a `source` that itself contains backticks (or a nested fence)
7
+ * still renders as a single code block instead of breaking out of it.
8
+ */
9
+ var toFencedCodeBlock = (source) => {
10
+ const longestRun = Math.max(0, ...[...source.matchAll(/`+/g)].map((match) => match[0].length));
11
+ const fence = "`".repeat(Math.max(3, longestRun + 1));
12
+ return `${fence}\n${source}\n${fence}`;
13
+ };
14
+ /**
15
+ * The SDK installation entries that actually have something to render, each
16
+ * resolved to a single Markdown `description`.
17
+ *
18
+ * A `description` is the promoted content, but the legacy `source` install
19
+ * command is still supported: when both are present it is appended to the
20
+ * description as a fenced code block, and when only `source` is present it
21
+ * becomes the description on its own. Entries that carry neither are ignored so
22
+ * the UI can fall back to the generic client selector instead of showing an
23
+ * empty card. Both the gate in `Content.vue` and the tab list in the block rely
24
+ * on this, so the "has instructions" rule lives in one place.
9
25
  *
10
26
  * The value comes straight from an untrusted OpenAPI document, so anything
11
27
  * malformed — a non-array extension, a non-object entry, or an entry missing a
12
- * string `lang` (the tab label and icon key) or `description` — is treated as
28
+ * string `lang` (the tab label and icon key) and any content — is treated as
13
29
  * "no instructions" rather than allowed to throw at render time.
14
30
  */
15
- var getRenderableSdks = (xScalarSdkInstallation) => Array.isArray(xScalarSdkInstallation) ? xScalarSdkInstallation.filter((sdk) => typeof sdk?.lang === "string" && typeof sdk.description === "string" && sdk.description.length > 0) : [];
31
+ var getRenderableSdks = (xScalarSdkInstallation) => Array.isArray(xScalarSdkInstallation) ? xScalarSdkInstallation.flatMap((sdk) => {
32
+ if (typeof sdk?.lang !== "string") return [];
33
+ const description = typeof sdk.description === "string" && sdk.description.trim() ? sdk.description : "";
34
+ const source = typeof sdk.source === "string" ? sdk.source.trim() : "";
35
+ if (!description && !source) return [];
36
+ const resolved = [description, source && toFencedCodeBlock(source)].filter(Boolean).join("\n\n");
37
+ return [{
38
+ lang: sdk.lang,
39
+ description: resolved
40
+ }];
41
+ }) : [];
16
42
  //#endregion
17
43
  export { getRenderableSdks };
18
44
 
@@ -1 +1 @@
1
- {"version":3,"file":"renderable-sdks.js","names":[],"sources":["../../../../src/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.ts"],"sourcesContent":["import type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation'\n\n/** The array shape stored under `x-scalar-sdk-installation`. */\ntype SdkInstallationList = NonNullable<XScalarSdkInstallation['x-scalar-sdk-installation']>\n\n/**\n * The SDK installation entries that actually have something to render.\n *\n * Entries that only carry a `lang` (no `description`) are ignored so the UI can\n * fall back to the generic client selector instead of showing an empty card.\n * Both the gate in `Content.vue` and the tab list in the block rely on this, so\n * the \"has instructions\" rule lives in one place.\n *\n * The value comes straight from an untrusted OpenAPI document, so anything\n * malformed — a non-array extension, a non-object entry, or an entry missing a\n * string `lang` (the tab label and icon key) or `description` — is treated as\n * \"no instructions\" rather than allowed to throw at render time.\n */\nexport const getRenderableSdks = (xScalarSdkInstallation: SdkInstallationList | undefined): SdkInstallationList =>\n Array.isArray(xScalarSdkInstallation)\n ? xScalarSdkInstallation.filter(\n (sdk) => typeof sdk?.lang === 'string' && typeof sdk.description === 'string' && sdk.description.length > 0,\n )\n : []\n"],"mappings":";;;;;;;;;;;;;;AAkBA,IAAa,qBAAqB,2BAChC,MAAM,QAAQ,uBAAuB,GACjC,uBAAuB,QACpB,QAAQ,OAAO,KAAK,SAAS,YAAY,OAAO,IAAI,gBAAgB,YAAY,IAAI,YAAY,SAAS,EAC3G,GACD,EAAE"}
1
+ {"version":3,"file":"renderable-sdks.js","names":[],"sources":["../../../../src/blocks/scalar-sdk-installation-instructions/helpers/renderable-sdks.ts"],"sourcesContent":["import type { XScalarSdkInstallation } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-sdk-installation'\n\n/** The array shape stored under `x-scalar-sdk-installation`. */\ntype SdkInstallationList = NonNullable<XScalarSdkInstallation['x-scalar-sdk-installation']>\n\n/** A renderable SDK entry whose `description` is the resolved Markdown to show. */\ntype RenderableSdk = {\n lang: string\n description: string\n}\n\n/**\n * Wrap a raw install command in a fenced code block.\n *\n * The fence uses a run of backticks longer than the longest run inside the\n * source, so a `source` that itself contains backticks (or a nested fence)\n * still renders as a single code block instead of breaking out of it.\n */\nconst toFencedCodeBlock = (source: string): string => {\n const longestRun = Math.max(0, ...[...source.matchAll(/`+/g)].map((match) => match[0].length))\n const fence = '`'.repeat(Math.max(3, longestRun + 1))\n\n return `${fence}\\n${source}\\n${fence}`\n}\n\n/**\n * The SDK installation entries that actually have something to render, each\n * resolved to a single Markdown `description`.\n *\n * A `description` is the promoted content, but the legacy `source` install\n * command is still supported: when both are present it is appended to the\n * description as a fenced code block, and when only `source` is present it\n * becomes the description on its own. Entries that carry neither are ignored so\n * the UI can fall back to the generic client selector instead of showing an\n * empty card. Both the gate in `Content.vue` and the tab list in the block rely\n * on this, so the \"has instructions\" rule lives in one place.\n *\n * The value comes straight from an untrusted OpenAPI document, so anything\n * malformed — a non-array extension, a non-object entry, or an entry missing a\n * string `lang` (the tab label and icon key) and any content — is treated as\n * \"no instructions\" rather than allowed to throw at render time.\n */\nexport const getRenderableSdks = (xScalarSdkInstallation: SdkInstallationList | undefined): RenderableSdk[] =>\n Array.isArray(xScalarSdkInstallation)\n ? xScalarSdkInstallation.flatMap((sdk) => {\n if (typeof sdk?.lang !== 'string') {\n return []\n }\n\n // Keep the description verbatim so indented code blocks and other\n // significant whitespace survive; only a whitespace-only value counts\n // as \"no description\".\n const description = typeof sdk.description === 'string' && sdk.description.trim() ? sdk.description : ''\n const source = typeof sdk.source === 'string' ? sdk.source.trim() : ''\n\n if (!description && !source) {\n return []\n }\n\n const resolved = [description, source && toFencedCodeBlock(source)].filter(Boolean).join('\\n\\n')\n\n return [{ lang: sdk.lang, description: resolved }]\n })\n : []\n"],"mappings":";;;;;;;;AAkBA,IAAM,qBAAqB,WAA2B;CACpD,MAAM,aAAa,KAAK,IAAI,GAAG,GAAG,CAAC,GAAG,OAAO,SAAS,MAAM,CAAC,CAAC,KAAK,UAAU,MAAM,GAAG,OAAO,CAAC;CAC9F,MAAM,QAAQ,IAAI,OAAO,KAAK,IAAI,GAAG,aAAa,EAAE,CAAC;AAErD,QAAO,GAAG,MAAM,IAAI,OAAO,IAAI;;;;;;;;;;;;;;;;;;;AAoBjC,IAAa,qBAAqB,2BAChC,MAAM,QAAQ,uBAAuB,GACjC,uBAAuB,SAAS,QAAQ;AACtC,KAAI,OAAO,KAAK,SAAS,SACvB,QAAO,EAAE;CAMX,MAAM,cAAc,OAAO,IAAI,gBAAgB,YAAY,IAAI,YAAY,MAAM,GAAG,IAAI,cAAc;CACtG,MAAM,SAAS,OAAO,IAAI,WAAW,WAAW,IAAI,OAAO,MAAM,GAAG;AAEpE,KAAI,CAAC,eAAe,CAAC,OACnB,QAAO,EAAE;CAGX,MAAM,WAAW,CAAC,aAAa,UAAU,kBAAkB,OAAO,CAAC,CAAC,OAAO,QAAQ,CAAC,KAAK,OAAO;AAEhG,QAAO,CAAC;EAAE,MAAM,IAAI;EAAM,aAAa;EAAU,CAAC;EAClD,GACF,EAAE"}