@scalar/api-client 3.13.7 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +2 -1
  3. package/dist/style.css +98 -83
  4. package/dist/v2/blocks/request-block/RequestBlock.vue.script.js.map +1 -1
  5. package/dist/v2/blocks/request-block/components/RequestBody.vue.d.ts +6 -6
  6. package/dist/v2/blocks/request-block/components/RequestBody.vue.d.ts.map +1 -1
  7. package/dist/v2/blocks/request-block/components/RequestBody.vue.js +1 -1
  8. package/dist/v2/blocks/request-block/components/RequestBody.vue.js.map +1 -1
  9. package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js +72 -11
  10. package/dist/v2/blocks/request-block/components/RequestBody.vue.script.js.map +1 -1
  11. package/dist/v2/blocks/request-block/components/RequestBodyStructured.vue.d.ts +19 -0
  12. package/dist/v2/blocks/request-block/components/RequestBodyStructured.vue.d.ts.map +1 -0
  13. package/dist/v2/blocks/request-block/components/RequestBodyStructured.vue.js +7 -0
  14. package/dist/v2/blocks/request-block/components/RequestBodyStructured.vue.js.map +1 -0
  15. package/dist/v2/blocks/request-block/components/RequestBodyStructured.vue.script.js +81 -0
  16. package/dist/v2/blocks/request-block/components/RequestBodyStructured.vue.script.js.map +1 -0
  17. package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.d.ts +13 -0
  18. package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.d.ts.map +1 -0
  19. package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.js +7 -0
  20. package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.js.map +1 -0
  21. package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.script.js +34 -0
  22. package/dist/v2/blocks/request-block/components/RequestBodyViewToggle.vue.script.js.map +1 -0
  23. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.d.ts.map +1 -1
  24. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.js.map +1 -1
  25. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.script.js +8 -2
  26. package/dist/v2/blocks/request-block/components/RequestTableRow.vue.script.js.map +1 -1
  27. package/dist/v2/blocks/request-block/helpers/fold-structured-body-rows.d.ts +17 -0
  28. package/dist/v2/blocks/request-block/helpers/fold-structured-body-rows.d.ts.map +1 -0
  29. package/dist/v2/blocks/request-block/helpers/fold-structured-body-rows.js +33 -0
  30. package/dist/v2/blocks/request-block/helpers/fold-structured-body-rows.js.map +1 -0
  31. package/dist/v2/blocks/request-block/helpers/get-form-body-rows.d.ts +28 -0
  32. package/dist/v2/blocks/request-block/helpers/get-form-body-rows.d.ts.map +1 -1
  33. package/dist/v2/blocks/request-block/helpers/get-form-body-rows.js +2 -2
  34. package/dist/v2/blocks/request-block/helpers/get-form-body-rows.js.map +1 -1
  35. package/dist/v2/blocks/request-block/helpers/get-structured-body-rows.d.ts +13 -0
  36. package/dist/v2/blocks/request-block/helpers/get-structured-body-rows.d.ts.map +1 -0
  37. package/dist/v2/blocks/request-block/helpers/get-structured-body-rows.js +39 -0
  38. package/dist/v2/blocks/request-block/helpers/get-structured-body-rows.js.map +1 -0
  39. package/dist/v2/blocks/request-block/helpers/structured-body-codec.d.ts +15 -0
  40. package/dist/v2/blocks/request-block/helpers/structured-body-codec.d.ts.map +1 -0
  41. package/dist/v2/blocks/request-block/helpers/structured-body-codec.js +33 -0
  42. package/dist/v2/blocks/request-block/helpers/structured-body-codec.js.map +1 -0
  43. package/dist/v2/blocks/scalar-auth-selector-block/components/AuthSelector.vue.script.js.map +1 -1
  44. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.d.ts +18 -2
  45. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.d.ts.map +1 -1
  46. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.js.map +1 -1
  47. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.script.js +18 -13
  48. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue.script.js.map +1 -1
  49. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.d.ts +6 -2
  50. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.d.ts.map +1 -1
  51. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.js.map +1 -1
  52. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.script.js +16 -8
  53. package/dist/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue.script.js.map +1 -1
  54. package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.d.ts +7 -3
  55. package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.d.ts.map +1 -1
  56. package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.js.map +1 -1
  57. package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.script.js +338 -25
  58. package/dist/v2/blocks/scalar-auth-selector-block/components/RequestAuthTab.vue.script.js.map +1 -1
  59. package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.d.ts.map +1 -1
  60. package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.js +7 -11
  61. package/dist/v2/blocks/scalar-auth-selector-block/helpers/oauth.js.map +1 -1
  62. package/dist/v2/blocks/scalar-auth-selector-block/helpers/run-oauth2-authorize.d.ts +42 -0
  63. package/dist/v2/blocks/scalar-auth-selector-block/helpers/run-oauth2-authorize.d.ts.map +1 -0
  64. package/dist/v2/blocks/scalar-auth-selector-block/helpers/run-oauth2-authorize.js +60 -0
  65. package/dist/v2/blocks/scalar-auth-selector-block/helpers/run-oauth2-authorize.js.map +1 -0
  66. package/dist/v2/blocks/scalar-auth-selector-block/helpers/security-scheme.d.ts +17 -1
  67. package/dist/v2/blocks/scalar-auth-selector-block/helpers/security-scheme.d.ts.map +1 -1
  68. package/dist/v2/blocks/scalar-auth-selector-block/helpers/security-scheme.js +33 -1
  69. package/dist/v2/blocks/scalar-auth-selector-block/helpers/security-scheme.js.map +1 -1
  70. package/dist/v2/components/data-table/DataTableCheckbox.vue.d.ts +2 -0
  71. package/dist/v2/components/data-table/DataTableCheckbox.vue.d.ts.map +1 -1
  72. package/dist/v2/components/data-table/DataTableCheckbox.vue.js.map +1 -1
  73. package/dist/v2/components/data-table/DataTableCheckbox.vue.script.js +8 -2
  74. package/dist/v2/components/data-table/DataTableCheckbox.vue.script.js.map +1 -1
  75. package/dist/v2/constants.js +1 -1
  76. package/dist/vue-styles.css +1 -1
  77. package/package.json +10 -10
@@ -1 +1 @@
1
- {"version":3,"file":"OAuth2.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport { type XusePkce } from '@scalar/workspace-store/schemas/extensions/security/x-use-pkce'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\n\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n}>()\n\nconst emits = defineEmits<{\n (e: 'update:selectedScopes', payload: { scopes: string[] }): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * PKCE public clients do not use a client_secret. Hide the field when the\n * document enables PKCE so readers are not prompted for an unused secret.\n */\nconst showClientSecret = computed(() => {\n if (!('x-scalar-secret-client-secret' in flow.value)) {\n return false\n }\n const pkceMode =\n 'x-usePkce' in flow.value ? flow.value['x-usePkce'] : undefined\n return pkceMode !== 'SHA-256' && pkceMode !== 'plain'\n})\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation>,\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void =>\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to authorize', 'error')\n }\n}\n\n/** Whether the current flow supports refreshing the access token */\nconst supportsRefreshToken = computed(() => type !== 'implicit')\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n if (loader.isLoading || type === 'implicit') {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to refresh token', 'error')\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n const credentialsLocation = value === 'body' ? 'body' : 'header'\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template v-if=\"Boolean(flow['x-scalar-secret-token'])\">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n Access Token\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"supportsRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n Refresh URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"supportsRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n Refresh\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n Clear\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n handleOauth2Update({ authorizationUrl: v })\n }\n \">\n Auth URL\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n handleOauth2Update({ tokenUrl: v })\n }\n \">\n Token URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : 'Optional redirect URL'\n \"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n Redirect URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n Username\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n Password\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n Client ID\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n Client Secret\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"\n (v) =>\n handleOauth2Update({\n 'x-usePkce': v as XusePkce['x-usePkce'],\n })\n \">\n Use PKCE\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n Credentials Location\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\"\n @delete:scope=\"(v) => emits('delete:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n Clear\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n Authorize\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"OAuth2.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport { type XusePkce } from '@scalar/workspace-store/schemas/extensions/security/x-use-pkce'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\n\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /**\n * Hides the Authorize / Refresh / Clear actions. Used when the form is embedded as a\n * configuration-only view (e.g. behind a gear icon), so the host component owns the\n * authorize action and the tokens are routed to the scheme it chooses.\n */\n hideActions?: boolean\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * Show the client secret whenever the flow supports one. PKCE and a client\n * secret are independent, so confidential clients can use both together\n * (see RFC 9700 Section 2.1.1).\n */\nconst showClientSecret = computed(\n () => 'x-scalar-secret-client-secret' in flow.value,\n)\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation>,\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n // Only OAuth2 schemes carry flows on the document; this component is never\n // rendered for the other scheme types.\n if (scheme.type !== 'oauth2') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void =>\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to authorize', 'error')\n }\n}\n\n/** Whether the current flow supports refreshing the access token */\nconst supportsRefreshToken = computed(() => type !== 'implicit')\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n if (loader.isLoading || type === 'implicit') {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to refresh token', 'error')\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n const credentialsLocation = value === 'body' ? 'body' : 'header'\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template v-if=\"Boolean(flow['x-scalar-secret-token'])\">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n Access Token\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"supportsRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n Refresh URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"supportsRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n Refresh\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n Clear\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n handleOauth2Update({ authorizationUrl: v })\n }\n \">\n Auth URL\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n handleOauth2Update({ tokenUrl: v })\n }\n \">\n Token URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : 'Optional redirect URL'\n \"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n Redirect URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n Username\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n Password\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n Client ID\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n Client Secret\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"\n (v) =>\n handleOauth2Update({\n 'x-usePkce': v as XusePkce['x-usePkce'],\n })\n \">\n Use PKCE\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n Credentials Location\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\"\n @delete:scope=\"(v) => emits('delete:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n Clear\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n Authorize\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":""}
@@ -1,7 +1,7 @@
1
1
  import DataTableRow_default from "../../../components/data-table/DataTableRow.vue.js";
2
- import OAuthScopesInput_default from "./OAuthScopesInput.vue.js";
3
2
  import { authorizeOauth2, refreshOauth2Token } from "../helpers/oauth.js";
4
3
  import { resolveDefaultOAuth2RedirectUri } from "../helpers/resolve-default-oauth2-redirect-url.js";
4
+ import OAuthScopesInput_default from "./OAuthScopesInput.vue.js";
5
5
  import RequestAuthDataTableInput_default from "./RequestAuthDataTableInput.vue.js";
6
6
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, openBlock, ref, unref, watch, withCtx } from "vue";
7
7
  import { ScalarButton } from "@scalar/components/button";
@@ -24,7 +24,8 @@ var OAuth2_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
24
24
  proxyUrl: {},
25
25
  name: {},
26
26
  eventBus: {},
27
- options: { default: () => ({}) }
27
+ options: { default: () => ({}) },
28
+ hideActions: { type: Boolean }
28
29
  },
29
30
  emits: [
30
31
  "update:selectedScopes",
@@ -43,20 +44,18 @@ var OAuth2_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
43
44
  return __props.selectedScopes.filter((scope) => Object.hasOwn(definedScopes, scope));
44
45
  });
45
46
  /**
46
- * PKCE public clients do not use a client_secret. Hide the field when the
47
- * document enables PKCE so readers are not prompted for an unused secret.
47
+ * Show the client secret whenever the flow supports one. PKCE and a client
48
+ * secret are independent, so confidential clients can use both together
49
+ * (see RFC 9700 Section 2.1.1).
48
50
  */
49
- const showClientSecret = computed(() => {
50
- if (!("x-scalar-secret-client-secret" in flow.value)) return false;
51
- const pkceMode = "x-usePkce" in flow.value ? flow.value["x-usePkce"] : void 0;
52
- return pkceMode !== "SHA-256" && pkceMode !== "plain";
53
- });
51
+ const showClientSecret = computed(() => "x-scalar-secret-client-secret" in flow.value);
54
52
  const clientSecretValue = computed(() => {
55
53
  return flow.value["x-scalar-secret-client-secret"] ?? "";
56
54
  });
57
55
  /** Updates the security scheme base */
58
56
  const handleOauth2Update = (payload) => {
59
57
  if (__props.scheme.type === "openIdConnect") return handleOauth2SecretsUpdate(payload);
58
+ if (__props.scheme.type !== "oauth2") return;
60
59
  __props.eventBus.emit("auth:update:security-scheme", {
61
60
  payload: {
62
61
  type: __props.scheme.type,
@@ -196,7 +195,10 @@ var OAuth2_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
196
195
  ])]),
197
196
  _: 1
198
197
  })) : createCommentVNode("", true),
199
- createVNode(unref(DataTableRow_default), { class: "min-w-full" }, {
198
+ !__props.hideActions ? (openBlock(), createBlock(unref(DataTableRow_default), {
199
+ key: 1,
200
+ class: "min-w-full"
201
+ }, {
200
202
  default: withCtx(() => [createElementVNode("div", _hoisted_1, [supportsRefreshToken.value ? (openBlock(), createBlock(unref(ScalarButton), {
201
203
  key: 0,
202
204
  class: "p-0 px-2 py-0.5",
@@ -218,7 +220,7 @@ var OAuth2_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
218
220
  _: 1
219
221
  }, 8, ["disabled"])])]),
220
222
  _: 1
221
- })
223
+ })) : createCommentVNode("", true)
222
224
  ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
223
225
  createVNode(unref(DataTableRow_default), null, {
224
226
  default: withCtx(() => ["authorizationUrl" in flow.value ? (openBlock(), createBlock(RequestAuthDataTableInput_default, {
@@ -364,7 +366,10 @@ var OAuth2_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
364
366
  ])]),
365
367
  _: 1
366
368
  }),
367
- createVNode(unref(DataTableRow_default), { class: "min-w-full" }, {
369
+ !__props.hideActions ? (openBlock(), createBlock(unref(DataTableRow_default), {
370
+ key: 5,
371
+ class: "min-w-full"
372
+ }, {
368
373
  default: withCtx(() => [createElementVNode("div", _hoisted_2, [__props.scheme.type === "openIdConnect" ? (openBlock(), createBlock(unref(ScalarButton), {
369
374
  key: 0,
370
375
  class: "mr-1 p-0 px-2 py-0.5",
@@ -386,7 +391,7 @@ var OAuth2_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineC
386
391
  _: 1
387
392
  }, 8, ["loader"])])]),
388
393
  _: 1
389
- })
394
+ })) : createCommentVNode("", true)
390
395
  ], 64));
391
396
  };
392
397
  }
@@ -1 +1 @@
1
- {"version":3,"file":"OAuth2.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport { type XusePkce } from '@scalar/workspace-store/schemas/extensions/security/x-use-pkce'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\n\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n}>()\n\nconst emits = defineEmits<{\n (e: 'update:selectedScopes', payload: { scopes: string[] }): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * PKCE public clients do not use a client_secret. Hide the field when the\n * document enables PKCE so readers are not prompted for an unused secret.\n */\nconst showClientSecret = computed(() => {\n if (!('x-scalar-secret-client-secret' in flow.value)) {\n return false\n }\n const pkceMode =\n 'x-usePkce' in flow.value ? flow.value['x-usePkce'] : undefined\n return pkceMode !== 'SHA-256' && pkceMode !== 'plain'\n})\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation>,\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void =>\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to authorize', 'error')\n }\n}\n\n/** Whether the current flow supports refreshing the access token */\nconst supportsRefreshToken = computed(() => type !== 'implicit')\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n if (loader.isLoading || type === 'implicit') {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to refresh token', 'error')\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n const credentialsLocation = value === 'body' ? 'body' : 'header'\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template v-if=\"Boolean(flow['x-scalar-secret-token'])\">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n Access Token\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"supportsRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n Refresh URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"supportsRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n Refresh\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n Clear\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n handleOauth2Update({ authorizationUrl: v })\n }\n \">\n Auth URL\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n handleOauth2Update({ tokenUrl: v })\n }\n \">\n Token URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : 'Optional redirect URL'\n \"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n Redirect URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n Username\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n Password\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n Client ID\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n Client Secret\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"\n (v) =>\n handleOauth2Update({\n 'x-usePkce': v as XusePkce['x-usePkce'],\n })\n \">\n Use PKCE\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n Credentials Location\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\"\n @delete:scope=\"(v) => emits('delete:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n Clear\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n Authorize\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"OAuth2.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuth2.vue"],"sourcesContent":["<script lang=\"ts\">\n/** Any config options required for the OAuth2 flow */\nexport type OAuth2Options = Pick<\n ApiClientConfiguration,\n 'oauth2RedirectUri'\n> & {\n /** Optional fetch override (IPC-backed on desktop) used for token exchange, refresh, and OIDC discovery */\n customFetch?: typeof fetch\n /** Optional redirect capture (loopback-backed on desktop) for interactive OAuth2 flows */\n captureOAuth2Callback?: CaptureOAuth2Callback\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { ScalarButton } from '@scalar/components/button'\nimport { useLoadingState } from '@scalar/components/loading'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { pkceOptions } from '@scalar/types/entities'\nimport { useToasts } from '@scalar/use-toasts'\nimport type { SecretsOAuthFlows } from '@scalar/workspace-store/entities/auth'\nimport type {\n ApiReferenceEvents,\n WorkspaceEventBus,\n} from '@scalar/workspace-store/events'\nimport {\n getEnvironmentVariables,\n type OAuthFlowAuthorizationCodeSecret,\n type OAuthFlowClientCredentialsSecret,\n type OAuthFlowPasswordSecret,\n type OAuthFlowsObjectSecret,\n type SecuritySchemeObjectSecret,\n} from '@scalar/workspace-store/request-example'\nimport type { XScalarEnvironment } from '@scalar/workspace-store/schemas/extensions/document/x-scalar-environments'\nimport type { XScalarCredentialsLocation } from '@scalar/workspace-store/schemas/extensions/security/x-scalar-credentials-location'\nimport { type XusePkce } from '@scalar/workspace-store/schemas/extensions/security/x-use-pkce'\nimport type {\n OAuthFlow,\n ServerObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, ref, watch } from 'vue'\n\nimport OAuthScopesInput from '@/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue'\nimport {\n authorizeOauth2,\n refreshOauth2Token,\n type CaptureOAuth2Callback,\n} from '@/v2/blocks/scalar-auth-selector-block/helpers/oauth'\nimport { resolveDefaultOAuth2RedirectUri } from '@/v2/blocks/scalar-auth-selector-block/helpers/resolve-default-oauth2-redirect-url'\nimport { DataTableRow } from '@/v2/components/data-table'\n\nimport RequestAuthDataTableInput from './RequestAuthDataTableInput.vue'\n\nconst {\n environment,\n flows,\n type,\n scheme,\n selectedScopes: selectedScopesProp,\n server,\n proxyUrl,\n eventBus,\n name,\n options = {},\n} = defineProps<{\n /** Current environment configuration */\n environment: XScalarEnvironment\n /** OAuth flows */\n flows: OAuthFlowsObjectSecret\n /** Type of the OAuth flow */\n type: keyof OAuthFlowsObjectSecret\n /** Selected scopes */\n selectedScopes: string[]\n /** Security scheme */\n scheme: SecuritySchemeObjectSecret\n /** Current server configuration */\n server: ServerObject | null\n /** Proxy URL */\n proxyUrl: string\n /** Name of the security scheme */\n name: string\n /** Event bus for authentication updates */\n eventBus: WorkspaceEventBus\n /** Any config options required for the OAuth2 flow */\n options?: OAuth2Options\n /**\n * Hides the Authorize / Refresh / Clear actions. Used when the form is embedded as a\n * configuration-only view (e.g. behind a gear icon), so the host component owns the\n * authorize action and the tokens are routed to the scheme it chooses.\n */\n hideActions?: boolean\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst loader = useLoadingState()\nconst { toast } = useToasts()\n\n/** The current OAuth flow based on the selected type */\nconst flow = computed(() => flows[type]!)\ntype NonImplicitFlow =\n | OAuthFlowPasswordSecret\n | OAuthFlowClientCredentialsSecret\n | OAuthFlowAuthorizationCodeSecret\n\n/** We filter selected scopes to only include scopes that are defined on this flow (own keys only). */\nconst selectedScopes = computed(() => {\n const definedScopes = flow.value.scopes\n if (!definedScopes) {\n return []\n }\n return selectedScopesProp.filter((scope) =>\n Object.hasOwn(definedScopes, scope),\n )\n})\n\n/**\n * Show the client secret whenever the flow supports one. PKCE and a client\n * secret are independent, so confidential clients can use both together\n * (see RFC 9700 Section 2.1.1).\n */\nconst showClientSecret = computed(\n () => 'x-scalar-secret-client-secret' in flow.value,\n)\n\nconst clientSecretValue = computed((): string => {\n const f = flow.value as { 'x-scalar-secret-client-secret'?: string }\n return f['x-scalar-secret-client-secret'] ?? ''\n})\n\n/** Updates the security scheme base */\nconst handleOauth2Update = (\n payload: Partial<OAuthFlow & XScalarCredentialsLocation>,\n): void => {\n // OpenIdConnect uses the secrets update for all\n if (scheme.type === 'openIdConnect') {\n return handleOauth2SecretsUpdate(payload)\n }\n\n // Only OAuth2 schemes carry flows on the document; this component is never\n // rendered for the other scheme types.\n if (scheme.type !== 'oauth2') {\n return\n }\n\n eventBus.emit('auth:update:security-scheme', {\n payload: {\n type: scheme.type,\n flows: {\n [type]: payload,\n },\n },\n name,\n })\n}\n\n/** Updates the flow secrets */\nconst handleOauth2SecretsUpdate = (\n payload: Omit<Partial<SecretsOAuthFlows[keyof SecretsOAuthFlows]>, 'type'>,\n): void =>\n eventBus.emit('auth:update:security-scheme-secrets', {\n payload: {\n type: scheme.type,\n [type]: payload,\n },\n name,\n })\n\n/** Clears the flow secrets */\nconst clearOauth2Secrets = (): void => {\n if (loader.isLoading) {\n return\n }\n eventBus.emit('auth:clear:security-scheme-secrets', {\n name,\n })\n}\n\n/** Clears stored access and refresh tokens (authorized state) */\nconst handleClearAccessTokens = (): void => {\n if (loader.isLoading) {\n return\n }\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': '',\n 'x-scalar-secret-refresh-token': '',\n })\n}\n\n/** Track redirect URI prefill per flow instance to support document switching */\nconst prefilledFlowIdentity = ref<string | null>(null)\nconst hasHandledRedirectPrefill = ref(false)\n\nconst resolveFlowIdentity = (\n currentFlow: OAuthFlowsObjectSecret[keyof OAuthFlowsObjectSecret] | undefined,\n): string =>\n JSON.stringify({\n type,\n authorizationUrl:\n currentFlow && 'authorizationUrl' in currentFlow\n ? currentFlow.authorizationUrl\n : '',\n tokenUrl:\n currentFlow && 'tokenUrl' in currentFlow ? currentFlow.tokenUrl : '',\n refreshUrl: currentFlow?.refreshUrl ?? '',\n scopes: Object.keys(currentFlow?.scopes ?? {}),\n })\n\n/** Default the redirect-uri to the current origin if we have access to window */\nwatch(\n () => flow.value,\n (currentFlow) => {\n if (!currentFlow || !('x-scalar-secret-redirect-uri' in currentFlow)) {\n return\n }\n\n const flowIdentity = resolveFlowIdentity(currentFlow)\n if (prefilledFlowIdentity.value !== flowIdentity) {\n prefilledFlowIdentity.value = flowIdentity\n hasHandledRedirectPrefill.value = false\n }\n\n if (hasHandledRedirectPrefill.value) {\n return\n }\n\n const newRedirectUri = (currentFlow as OAuthFlowAuthorizationCodeSecret)[\n 'x-scalar-secret-redirect-uri'\n ]\n const defaultRedirectUri = resolveDefaultOAuth2RedirectUri(options)\n\n hasHandledRedirectPrefill.value = true\n\n // The desktop loopback path computes the redirect URI at authorize time (an\n // ephemeral 127.0.0.1 port), so persisting a default into the document would\n // only bake in a stale, unused value. Leave it empty and show a hint instead.\n if (\n newRedirectUri ||\n !defaultRedirectUri ||\n options.captureOAuth2Callback\n ) {\n return\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-redirect-uri': defaultRedirectUri,\n })\n },\n { immediate: true },\n)\n\n/**\n * Authorizes the user using the specified OAuth flow.\n * Opens the appropriate OAuth dialog and/or performs the token exchange.\n */\nconst handleAuthorize = async (): Promise<void> => {\n if (loader.isLoading) {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await authorizeOauth2(\n flows,\n type,\n selectedScopes.value,\n server,\n proxyUrl,\n getEnvironmentVariables(environment),\n options.customFetch,\n options.captureOAuth2Callback,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to authorize', 'error')\n }\n}\n\n/** Whether the current flow supports refreshing the access token */\nconst supportsRefreshToken = computed(() => type !== 'implicit')\n\n/**\n * Refresh URL placeholder, shows tokenUrl as hint if refreshUrl is not specified.\n * This helps users understand that tokenUrl will be used as fallback.\n */\nconst refreshUrlPlaceholder = computed(() => {\n if ('tokenUrl' in flow.value && flow.value.tokenUrl) {\n return flow.value.tokenUrl\n }\n return 'https://galaxy.scalar.com/oauth/refresh'\n})\n\n/**\n * Uses the stored refresh token to obtain a new access token\n * via grant_type=refresh_token.\n */\nconst handleRefresh = async (): Promise<void> => {\n if (loader.isLoading || type === 'implicit') {\n return\n }\n\n loader.start()\n\n const [error, tokens] = await refreshOauth2Token(\n flows,\n type,\n proxyUrl,\n server,\n getEnvironmentVariables(environment),\n options.customFetch,\n )\n\n await loader.clear()\n\n if (tokens?.accessToken) {\n handleOauth2SecretsUpdate({\n 'x-scalar-secret-token': tokens.accessToken,\n ...(tokens.refreshToken\n ? { 'x-scalar-secret-refresh-token': tokens.refreshToken }\n : {}),\n })\n } else {\n console.error(error)\n toast(error?.message ?? 'Failed to refresh token', 'error')\n }\n}\n\n/** Updates the secret location */\nconst handleSecretLocationUpdate = (value: string): void => {\n const credentialsLocation = value === 'body' ? 'body' : 'header'\n\n if (scheme.type !== 'openIdConnect') {\n handleOauth2Update({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n }\n\n handleOauth2SecretsUpdate({\n 'x-scalar-credentials-location': credentialsLocation,\n })\n}\n</script>\n\n<template>\n <!-- Access Token Display: Shows when user is already authorized -->\n <template v-if=\"Boolean(flow['x-scalar-secret-token'])\">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token']\"\n placeholder=\"QUxMIFlPVVIgQkFTRSBBUkUgQkVMT05HIFRPIFVT\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-token': v })\n \">\n Access Token\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"supportsRefreshToken\">\n <RequestAuthDataTableInput\n class=\"border-r-transparent\"\n :environment\n :modelValue=\"flow.refreshUrl ?? ''\"\n :placeholder=\"refreshUrlPlaceholder\"\n @update:modelValue=\"(v) => handleOauth2Update({ refreshUrl: v })\">\n Refresh URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 items-center justify-end gap-2 border-t\">\n <ScalarButton\n v-if=\"supportsRefreshToken\"\n class=\"p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleRefresh\">\n Refresh\n </ScalarButton>\n <ScalarButton\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleClearAccessTokens\">\n Clear\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n\n <!-- Authorization Form: Shows when user needs to authorize -->\n <template v-else>\n <DataTableRow>\n <RequestAuthDataTableInput\n v-if=\"'authorizationUrl' in flow\"\n containerClass=\"border-r-0\"\n :environment\n :modelValue=\"flow['x-scalar-secret-auth-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/authorize\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-auth-url': v })\n handleOauth2Update({ authorizationUrl: v })\n }\n \">\n Auth URL\n </RequestAuthDataTableInput>\n\n <RequestAuthDataTableInput\n v-if=\"'tokenUrl' in flow\"\n :environment\n :modelValue=\"flow['x-scalar-secret-token-url'] ?? ''\"\n placeholder=\"https://galaxy.scalar.com/token\"\n @update:modelValue=\"\n (v) => {\n handleOauth2SecretsUpdate({ 'x-scalar-secret-token-url': v })\n handleOauth2Update({ tokenUrl: v })\n }\n \">\n Token URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-scalar-secret-redirect-uri' in flow\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-redirect-uri']\"\n :placeholder=\"\n options.captureOAuth2Callback\n ? `${resolveDefaultOAuth2RedirectUri(options) || 'http://127.0.0.1'} (handled automatically)`\n : 'Optional redirect URL'\n \"\n @update:modelValue=\"\n (v) => {\n hasHandledRedirectPrefill = true\n handleOauth2SecretsUpdate({ 'x-scalar-secret-redirect-uri': v })\n }\n \">\n Redirect URL\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <template\n v-if=\"\n 'x-scalar-secret-username' in flow && 'x-scalar-secret-password' in flow\n \">\n <DataTableRow>\n <RequestAuthDataTableInput\n class=\"text-c-2\"\n :environment\n :modelValue=\"flow['x-scalar-secret-username']\"\n placeholder=\"janedoe\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-username': v })\n \">\n Username\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-password']\"\n placeholder=\"********\"\n type=\"password\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-password': v })\n \">\n Password\n </RequestAuthDataTableInput>\n </DataTableRow>\n </template>\n\n <DataTableRow>\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"flow['x-scalar-secret-client-id']\"\n placeholder=\"12345\"\n @update:modelValue=\"\n (v) => handleOauth2SecretsUpdate({ 'x-scalar-secret-client-id': v })\n \">\n Client ID\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"showClientSecret\">\n <RequestAuthDataTableInput\n :environment\n :modelValue=\"clientSecretValue\"\n placeholder=\"XYZ123\"\n type=\"password\"\n @update:modelValue=\"\n (v) =>\n handleOauth2SecretsUpdate({ 'x-scalar-secret-client-secret': v })\n \">\n Client Secret\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <DataTableRow v-if=\"'x-usePkce' in flow\">\n <RequestAuthDataTableInput\n :enum=\"pkceOptions\"\n :environment\n :modelValue=\"flow['x-usePkce']\"\n readOnly\n @update:modelValue=\"\n (v) =>\n handleOauth2Update({\n 'x-usePkce': v as XusePkce['x-usePkce'],\n })\n \">\n Use PKCE\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Credentials Location -->\n <DataTableRow v-if=\"type !== 'implicit'\">\n <RequestAuthDataTableInput\n :enum=\"['header', 'body']\"\n :environment\n :modelValue=\"\n (flow as NonImplicitFlow)['x-scalar-credentials-location'] || 'header'\n \"\n placeholder=\"header\"\n readOnly\n @update:modelValue=\"(v) => handleSecretLocationUpdate(v)\">\n Credentials Location\n </RequestAuthDataTableInput>\n </DataTableRow>\n\n <!-- Scopes -->\n <DataTableRow>\n <OAuthScopesInput\n :flow\n :flowType=\"type\"\n :selectedScopes\n @update:selectedScopes=\"(v) => emits('update:selectedScopes', v)\"\n @upsert:scope=\"(v) => emits('upsert:scope', v)\"\n @delete:scope=\"(v) => emits('delete:scope', v)\" />\n </DataTableRow>\n\n <DataTableRow\n v-if=\"!hideActions\"\n class=\"min-w-full\">\n <div class=\"flex h-8 w-full items-center justify-end border-t\">\n <!-- Allow clearing the oauth section and going back to discovery -->\n <ScalarButton\n v-if=\"scheme.type === 'openIdConnect'\"\n class=\"mr-1 p-0 px-2 py-0.5\"\n :disabled=\"loader.isLoading\"\n size=\"sm\"\n variant=\"outlined\"\n @click=\"clearOauth2Secrets\">\n Clear\n </ScalarButton>\n\n <ScalarButton\n class=\"mr-0.75 p-0 px-2 py-0.5\"\n :loader\n size=\"sm\"\n variant=\"outlined\"\n @click=\"handleAuthorize\">\n Authorize\n </ScalarButton>\n </div>\n </DataTableRow>\n </template>\n</template>\n"],"mappings":"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"}
@@ -6,7 +6,9 @@ type __VLS_Props = {
6
6
  };
7
7
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
8
8
  "update:selectedScopes": (payload: {
9
- scopes: string[];
9
+ scopes?: string[];
10
+ scope?: string;
11
+ selected?: boolean;
10
12
  }) => any;
11
13
  "upsert:scope": (payload: Omit<{
12
14
  name: string;
@@ -23,7 +25,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
23
25
  }, "name">) => any;
24
26
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
25
27
  "onUpdate:selectedScopes"?: ((payload: {
26
- scopes: string[];
28
+ scopes?: string[];
29
+ scope?: string;
30
+ selected?: boolean;
27
31
  }) => any) | undefined;
28
32
  "onUpsert:scope"?: ((payload: Omit<{
29
33
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"OAuthScopesInput.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue"],"names":[],"mappings":"AA8WA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EACjB,MAAM,8DAA8D,CAAA;AAWrE,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,gBAAgB,CAAA;IAChC,IAAI,EAAE,SAAS,CAAA;IACf,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB,CAAC;AA0sBF,QAAA,MAAM,YAAY;;gBArsBgC,MAAM,EAAE;;;;;;;;;;;;;;;;;gBAAR,MAAM,EAAE;;;;;;;;;;;;;;;kFAwsBxD,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"OAuthScopesInput.vue.d.ts","sourceRoot":"","sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue"],"names":[],"mappings":"AAiXA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EACjB,MAAM,8DAA8D,CAAA;AAWrE,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,MAAM,gBAAgB,CAAA;IAChC,IAAI,EAAE,SAAS,CAAA;IACf,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB,CAAC;AA6sBF,QAAA,MAAM,YAAY;;iBAtsBM,MAAM,EAAE;gBAAU,MAAM;mBAAa,OAAO;;;;;;;;;;;;;;;;;iBAA5C,MAAM,EAAE;gBAAU,MAAM;mBAAa,OAAO;;;;;;;;;;;;;;;kFAysBlE,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"OAuthScopesInput.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { useModal } from '@scalar/components/modal'\nimport { ScalarSearchInput } from '@scalar/components/search-input'\nimport { ScalarIconPencilSimple, ScalarIconTrash } from '@scalar/icons'\nimport type { ApiReferenceEvents } from '@scalar/workspace-store/events'\nimport type {\n OAuthFlow,\n OAuthFlowsObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport {\n DataTableCell,\n DataTableCheckbox,\n DataTableRow,\n} from '@/v2/components/data-table'\n\nimport OAuthScopesAddModal from './OAuthScopesAddModal.vue'\n\nconst { selectedScopes, flow, flowType } = defineProps<{\n flowType: keyof OAuthFlowsObject\n flow: OAuthFlow\n selectedScopes: string[]\n}>()\n\nconst emits = defineEmits<{\n (e: 'update:selectedScopes', payload: { scopes: string[] }): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst searchQuery = ref('')\n\n/**\n * The scope currently being edited, used to switch the shared modal into edit mode.\n * Null means the modal is in \"add\" mode.\n */\nconst editingScope = ref<{ name: string; description: string } | null>(null)\n\n/** List of all available scopes */\nconst scopes = computed(() =>\n Object.entries(flow?.scopes ?? {}).map(([key, val]) => ({\n id: key,\n label: key,\n description: val,\n })),\n)\n\nconst scopeCount = computed(() => Object.keys(flow?.scopes ?? {}).length)\n\n/** Search is only useful once the list is long enough to scan */\nconst showScopeSearch = computed(() => scopeCount.value >= 10)\n\nconst filteredScopes = computed(() => {\n const query = showScopeSearch.value ? searchQuery.value : ''\n if (!query) {\n return scopes.value\n }\n\n const regex = new RegExp(\n query\n .split('')\n .map((c) => c.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*'),\n 'i',\n )\n\n return scopes.value.filter(({ label, description }) =>\n regex.test(`${label} ${description}`),\n )\n})\n\nconst hasScopes = computed(() => Object.keys(flow?.scopes ?? {}).length > 0)\n\nconst allScopesSelected = computed(\n () => selectedScopes.length === Object.keys(flow?.scopes ?? {}).length,\n)\n\nconst setScope = (scopeKey: string, checked: boolean) => {\n if (checked) {\n // Select the scope\n return emits('update:selectedScopes', {\n scopes: Array.from(new Set([...selectedScopes, scopeKey])),\n })\n }\n\n // Deselect the scope\n emits('update:selectedScopes', {\n scopes: selectedScopes.filter((scope) => scope !== scopeKey),\n })\n}\n\n/** Select all scopes */\nconst selectAllScopes = () =>\n emits('update:selectedScopes', { scopes: Object.keys(flow?.scopes ?? {}) })\n\n/** Deselect all scopes */\nconst deselectAllScopes = () => emits('update:selectedScopes', { scopes: [] })\n\nconst scopeFormModal = useModal()\n\n/**\n * Forces the Disclosure to remount with `defaultOpen` set so the panel auto-expands\n * the moment a brand-new scope is added. Bumping this counter is paired with toggling\n * `expandOnNextMount` so the remount opens the panel, then reverts to the default\n * closed-on-mount behavior for any future unrelated remounts (e.g. collapse on empty).\n *\n * The Disclosure `:key` is only this counter — not a separate `hasScopes` segment — so\n * adding the first scope does not remount twice (empty prefix → with-scopes prefix),\n * which would reopen with `defaultOpen=false` and collapse the panel right when rows appear.\n */\nconst remountKey = ref(0)\nconst expandOnNextMount = ref(false)\n\nwatch(hasScopes, (has, had) => {\n if (had && !has) {\n expandOnNextMount.value = false\n remountKey.value += 1\n }\n})\n\n/** Open the modal in \"add new scope\" mode */\nconst openAddScopeModal = () => {\n editingScope.value = null\n scopeFormModal.show()\n}\n\n/** Open the modal in \"edit scope\" mode prefilled with the chosen row */\nconst openEditScopeModal = (scope: { id: string; description: string }) => {\n editingScope.value = {\n name: scope.id,\n description: scope.description ?? '',\n }\n scopeFormModal.show()\n}\n\n/**\n * Submit handler shared by Add Scope and Edit Scope. For renames, the `upsertScope` mutator on\n * the workspace store rewrites the selection state in place, so the component does not need to\n * emit a follow-up `update:selectedScopes`.\n *\n * When adding a brand-new scope (no `oldName`), the payload sets `enable: true` so the\n * `upsertScope` mutator also adds the new scope to any selection requirement that references\n * this scheme. The Disclosure is then remounted with `defaultOpen=true` so the user\n * immediately sees (and sees selected) the scope they just added.\n */\nconst handleScopeFormSubmit = async (payload: {\n name: string\n description: string\n oldName?: string\n}) => {\n const isAdd = !payload.oldName\n\n emits('upsert:scope', {\n scope: payload.name,\n description: payload.description,\n flowType,\n ...(payload.oldName ? { oldScope: payload.oldName } : {}),\n ...(isAdd ? { enable: true } : {}),\n })\n\n if (!isAdd) {\n return\n }\n\n expandOnNextMount.value = true\n remountKey.value += 1\n // Reset after the remount so subsequent unrelated remounts (e.g. when the last\n // scope is deleted) still default to a closed panel.\n await nextTick()\n expandOnNextMount.value = false\n}\n\n/**\n * Remove the scope from the flow. Selection cleanup (dropping the deleted key from any\n * currently-selected scopes) is handled by the `deleteScope` mutator on the workspace store,\n * so the component does not need to emit a follow-up `update:selectedScopes` here.\n */\nconst handleDeleteScope = (scopeKey: string) => {\n emits('delete:scope', { scope: scopeKey, flowType })\n}\n</script>\n\n<template>\n <DataTableCell class=\"h-auto !max-h-[initial] min-h-8 items-center\">\n <div class=\"flex h-fit w-full\">\n <div class=\"text-c-1 h-full items-center\"></div>\n <Disclosure\n :key=\"remountKey\"\n as=\"div\"\n class=\"bl flex w-full flex-col\"\n :defaultOpen=\"expandOnNextMount\">\n <!--\n Keep Add Scope (and other actions) outside the summary DisclosureButton.\n When there are no scopes the summary control is disabled; a native disabled\n button blocks pointer events for all descendants, which would make Add Scope unusable.\n -->\n <div\n class=\"group/scopes-accordion flex h-auto min-h-8 items-center gap-1.5 pr-2.25 pl-3 text-left\">\n <DisclosureButton\n :class=\"[\n 'min-w-0 flex-1 text-left',\n hasScopes ? 'hover:text-c-1 cursor-pointer' : 'cursor-default',\n (selectedScopes.length || 0) > 0 ? 'text-c-1' : 'text-c-3',\n ]\"\n :disabled=\"!hasScopes\">\n <template v-if=\"hasScopes\">\n Scopes Selected\n {{ selectedScopes.length || 0 }} /\n {{ Object.keys(flow?.scopes ?? {}).length || 0 }}\n </template>\n <template v-else> No Scopes Defined </template>\n </DisclosureButton>\n\n <div class=\"flex shrink-0 items-center gap-1.75\">\n <!-- Add new scope -->\n <ScalarButton\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"openAddScopeModal\">\n Add Scope\n </ScalarButton>\n\n <!-- Deselect All -->\n <ScalarButton\n v-if=\"hasScopes && allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"deselectAllScopes\">\n Deselect All\n </ScalarButton>\n\n <!-- Select All -->\n <ScalarButton\n v-if=\"hasScopes && !allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"selectAllScopes\">\n Select All\n </ScalarButton>\n\n <DisclosureButton\n v-if=\"hasScopes\"\n v-slot=\"{ open }\"\n class=\"text-c-3 hover:text-c-2 -m-0.5 flex shrink-0 items-center justify-center rounded p-0.5 focus-visible:outline-offset-2\">\n <ScalarIcon\n class=\"group-hover/scopes-accordion:text-c-2\"\n :icon=\"open ? 'ChevronDown' : 'ChevronRight'\"\n size=\"md\" />\n </DisclosureButton>\n </div>\n </div>\n <!-- Scopes List -->\n <DisclosurePanel as=\"template\">\n <div>\n <ScalarSearchInput\n v-if=\"showScopeSearch\"\n v-model=\"searchQuery\"\n class=\"flex items-center text-xs\" />\n <table\n class=\"grid max-h-40 auto-rows-auto overflow-x-hidden overflow-y-auto\"\n :style=\"{ gridTemplateColumns: '1fr auto' }\">\n <DataTableRow\n v-for=\"{ id, label, description } in filteredScopes\"\n :key=\"id\"\n class=\"text-c-2 group/scope-row\"\n @click=\"setScope(id, !selectedScopes.includes(id))\">\n <DataTableCell\n class=\"box-border flex !max-h-[initial] w-full min-w-0 cursor-pointer items-stretch overflow-hidden px-0 py-0\">\n <!-- Text scrolls horizontally; action rail stays fixed -->\n <div\n class=\"no-scrollbar text-c-2 group-hover/scope-row:text-c-1 flex min-h-8 min-w-0 flex-1 items-center gap-1 overflow-x-auto px-3 py-1.5 pr-20 text-xs text-nowrap\">\n <span class=\"font-code shrink-0 text-xs\">{{ label }}</span>\n <template v-if=\"String(description ?? '').trim()\">\n <span class=\"shrink-0\">&ndash;</span>\n <span class=\"whitespace-nowrap\">{{ description }}</span>\n </template>\n </div>\n <div\n class=\"oauth-scope-row-action-rail absolute top-0 right-0 z-[1] flex h-full min-w-[4.5rem] items-center justify-end gap-0.5 py-1 pr-2 pl-4 opacity-0 transition-opacity group-focus-within/scope-row:opacity-100 group-hover/scope-row:opacity-100\"\n @click.stop>\n <ScalarIconButton\n :icon=\"ScalarIconPencilSimple\"\n :label=\"`Edit ${label}`\"\n size=\"sm\"\n @click.stop=\"\n openEditScopeModal({\n id,\n description: description ?? '',\n })\n \" />\n <ScalarIconButton\n :icon=\"ScalarIconTrash\"\n :label=\"`Delete ${label}`\"\n size=\"sm\"\n @click.stop=\"handleDeleteScope(id)\" />\n </div>\n </DataTableCell>\n <DataTableCheckbox\n :modelValue=\"selectedScopes.includes(id)\"\n @update:modelValue=\"setScope(id, $event)\" />\n </DataTableRow>\n </table>\n </div>\n </DisclosurePanel>\n </Disclosure>\n </div>\n\n <!-- Shared add/edit scope modal -->\n <OAuthScopesAddModal\n :scope=\"editingScope\"\n :scopes=\"Object.keys(flow.scopes ?? {})\"\n :state=\"scopeFormModal\"\n @submit=\"handleScopeFormSubmit\" />\n </DataTableCell>\n</template>\n\n<style>\n/* Matches AddressBar.vue `.address-bar-bg-states` / `.fade-right` (header URL input). */\n.oauth-scope-row-action-rail {\n --oauth-scope-row-rail-bg: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n background: linear-gradient(\n 90deg,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 100%) 0%,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 20%) 30%,\n var(--oauth-scope-row-rail-bg) 100%\n );\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n</style>\n"],"mappings":""}
1
+ {"version":3,"file":"OAuthScopesInput.vue.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { useModal } from '@scalar/components/modal'\nimport { ScalarSearchInput } from '@scalar/components/search-input'\nimport { ScalarIconPencilSimple, ScalarIconTrash } from '@scalar/icons'\nimport type { ApiReferenceEvents } from '@scalar/workspace-store/events'\nimport type {\n OAuthFlow,\n OAuthFlowsObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport {\n DataTableCell,\n DataTableCheckbox,\n DataTableRow,\n} from '@/v2/components/data-table'\n\nimport OAuthScopesAddModal from './OAuthScopesAddModal.vue'\n\nconst { selectedScopes, flow, flowType } = defineProps<{\n flowType: keyof OAuthFlowsObject\n flow: OAuthFlow\n selectedScopes: string[]\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst searchQuery = ref('')\n\n/**\n * The scope currently being edited, used to switch the shared modal into edit mode.\n * Null means the modal is in \"add\" mode.\n */\nconst editingScope = ref<{ name: string; description: string } | null>(null)\n\n/** List of all available scopes */\nconst scopes = computed(() =>\n Object.entries(flow?.scopes ?? {}).map(([key, val]) => ({\n id: key,\n label: key,\n description: val,\n })),\n)\n\nconst scopeCount = computed(() => Object.keys(flow?.scopes ?? {}).length)\n\n/** Search is only useful once the list is long enough to scan */\nconst showScopeSearch = computed(() => scopeCount.value >= 10)\n\nconst filteredScopes = computed(() => {\n const query = showScopeSearch.value ? searchQuery.value : ''\n if (!query) {\n return scopes.value\n }\n\n const regex = new RegExp(\n query\n .split('')\n .map((c) => c.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*'),\n 'i',\n )\n\n return scopes.value.filter(({ label, description }) =>\n regex.test(`${label} ${description}`),\n )\n})\n\nconst hasScopes = computed(() => Object.keys(flow?.scopes ?? {}).length > 0)\n\nconst allScopesSelected = computed(\n () => selectedScopes.length === Object.keys(flow?.scopes ?? {}).length,\n)\n\nconst setScope = (scopeKey: string, checked: boolean) => {\n // Emit a single-scope toggle rather than a freshly computed list. The store applies it\n // against the currently selected scopes, so two quick clicks cannot overwrite each other\n // with a list computed from a prop that has not updated yet.\n emits('update:selectedScopes', { scope: scopeKey, selected: checked })\n}\n\n/** Select all scopes */\nconst selectAllScopes = () =>\n emits('update:selectedScopes', { scopes: Object.keys(flow?.scopes ?? {}) })\n\n/** Deselect all scopes */\nconst deselectAllScopes = () => emits('update:selectedScopes', { scopes: [] })\n\nconst scopeFormModal = useModal()\n\n/**\n * Forces the Disclosure to remount with `defaultOpen` set so the panel auto-expands\n * the moment a brand-new scope is added. Bumping this counter is paired with toggling\n * `expandOnNextMount` so the remount opens the panel, then reverts to the default\n * closed-on-mount behavior for any future unrelated remounts (e.g. collapse on empty).\n *\n * The Disclosure `:key` is only this counter — not a separate `hasScopes` segment — so\n * adding the first scope does not remount twice (empty prefix → with-scopes prefix),\n * which would reopen with `defaultOpen=false` and collapse the panel right when rows appear.\n */\nconst remountKey = ref(0)\nconst expandOnNextMount = ref(false)\n\nwatch(hasScopes, (has, had) => {\n if (had && !has) {\n expandOnNextMount.value = false\n remountKey.value += 1\n }\n})\n\n/** Open the modal in \"add new scope\" mode */\nconst openAddScopeModal = () => {\n editingScope.value = null\n scopeFormModal.show()\n}\n\n/** Open the modal in \"edit scope\" mode prefilled with the chosen row */\nconst openEditScopeModal = (scope: { id: string; description: string }) => {\n editingScope.value = {\n name: scope.id,\n description: scope.description ?? '',\n }\n scopeFormModal.show()\n}\n\n/**\n * Submit handler shared by Add Scope and Edit Scope. For renames, the `upsertScope` mutator on\n * the workspace store rewrites the selection state in place, so the component does not need to\n * emit a follow-up `update:selectedScopes`.\n *\n * When adding a brand-new scope (no `oldName`), the payload sets `enable: true` so the\n * `upsertScope` mutator also adds the new scope to any selection requirement that references\n * this scheme. The Disclosure is then remounted with `defaultOpen=true` so the user\n * immediately sees (and sees selected) the scope they just added.\n */\nconst handleScopeFormSubmit = async (payload: {\n name: string\n description: string\n oldName?: string\n}) => {\n const isAdd = !payload.oldName\n\n emits('upsert:scope', {\n scope: payload.name,\n description: payload.description,\n flowType,\n ...(payload.oldName ? { oldScope: payload.oldName } : {}),\n ...(isAdd ? { enable: true } : {}),\n })\n\n if (!isAdd) {\n return\n }\n\n expandOnNextMount.value = true\n remountKey.value += 1\n // Reset after the remount so subsequent unrelated remounts (e.g. when the last\n // scope is deleted) still default to a closed panel.\n await nextTick()\n expandOnNextMount.value = false\n}\n\n/**\n * Remove the scope from the flow. Selection cleanup (dropping the deleted key from any\n * currently-selected scopes) is handled by the `deleteScope` mutator on the workspace store,\n * so the component does not need to emit a follow-up `update:selectedScopes` here.\n */\nconst handleDeleteScope = (scopeKey: string) => {\n emits('delete:scope', { scope: scopeKey, flowType })\n}\n</script>\n\n<template>\n <DataTableCell class=\"h-auto !max-h-[initial] min-h-8 items-center\">\n <div class=\"flex h-fit w-full\">\n <div class=\"text-c-1 h-full items-center\"></div>\n <Disclosure\n :key=\"remountKey\"\n as=\"div\"\n class=\"bl flex w-full flex-col\"\n :defaultOpen=\"expandOnNextMount\">\n <!--\n Keep Add Scope (and other actions) outside the summary DisclosureButton.\n When there are no scopes the summary control is disabled; a native disabled\n button blocks pointer events for all descendants, which would make Add Scope unusable.\n -->\n <div\n class=\"group/scopes-accordion flex h-auto min-h-8 items-center gap-1.5 pr-2.25 pl-3 text-left\">\n <DisclosureButton\n :class=\"[\n 'min-w-0 flex-1 text-left',\n hasScopes ? 'hover:text-c-1 cursor-pointer' : 'cursor-default',\n (selectedScopes.length || 0) > 0 ? 'text-c-1' : 'text-c-3',\n ]\"\n :disabled=\"!hasScopes\">\n <template v-if=\"hasScopes\">\n Scopes Selected\n {{ selectedScopes.length || 0 }} /\n {{ Object.keys(flow?.scopes ?? {}).length || 0 }}\n </template>\n <template v-else> No Scopes Defined </template>\n </DisclosureButton>\n\n <div class=\"flex shrink-0 items-center gap-1.75\">\n <!-- Add new scope -->\n <ScalarButton\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"openAddScopeModal\">\n Add Scope\n </ScalarButton>\n\n <!-- Deselect All -->\n <ScalarButton\n v-if=\"hasScopes && allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"deselectAllScopes\">\n Deselect All\n </ScalarButton>\n\n <!-- Select All -->\n <ScalarButton\n v-if=\"hasScopes && !allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"selectAllScopes\">\n Select All\n </ScalarButton>\n\n <DisclosureButton\n v-if=\"hasScopes\"\n v-slot=\"{ open }\"\n class=\"text-c-3 hover:text-c-2 -m-0.5 flex shrink-0 items-center justify-center rounded p-0.5 focus-visible:outline-offset-2\">\n <ScalarIcon\n class=\"group-hover/scopes-accordion:text-c-2\"\n :icon=\"open ? 'ChevronDown' : 'ChevronRight'\"\n size=\"md\" />\n </DisclosureButton>\n </div>\n </div>\n <!-- Scopes List -->\n <DisclosurePanel as=\"template\">\n <div>\n <ScalarSearchInput\n v-if=\"showScopeSearch\"\n v-model=\"searchQuery\"\n class=\"flex items-center text-xs\" />\n <table\n class=\"grid max-h-40 auto-rows-auto overflow-x-hidden overflow-y-auto\"\n :style=\"{ gridTemplateColumns: '1fr auto' }\">\n <DataTableRow\n v-for=\"{ id, label, description } in filteredScopes\"\n :key=\"id\"\n class=\"text-c-2 group/scope-row\"\n @click=\"setScope(id, !selectedScopes.includes(id))\">\n <DataTableCell\n class=\"box-border flex !max-h-[initial] w-full min-w-0 cursor-pointer items-stretch overflow-hidden px-0 py-0\">\n <!-- Text scrolls horizontally; action rail stays fixed -->\n <div\n class=\"no-scrollbar text-c-2 group-hover/scope-row:text-c-1 flex min-h-8 min-w-0 flex-1 items-center gap-1 overflow-x-auto px-3 py-1.5 pr-20 text-xs text-nowrap\">\n <span class=\"font-code shrink-0 text-xs\">{{ label }}</span>\n <template v-if=\"String(description ?? '').trim()\">\n <span class=\"shrink-0\">&ndash;</span>\n <span class=\"whitespace-nowrap\">{{ description }}</span>\n </template>\n </div>\n <div\n class=\"oauth-scope-row-action-rail absolute top-0 right-0 z-[1] flex h-full min-w-[4.5rem] items-center justify-end gap-0.5 py-1 pr-2 pl-4 opacity-0 transition-opacity group-focus-within/scope-row:opacity-100 group-hover/scope-row:opacity-100\"\n @click.stop>\n <ScalarIconButton\n :icon=\"ScalarIconPencilSimple\"\n :label=\"`Edit ${label}`\"\n size=\"sm\"\n @click.stop=\"\n openEditScopeModal({\n id,\n description: description ?? '',\n })\n \" />\n <ScalarIconButton\n :icon=\"ScalarIconTrash\"\n :label=\"`Delete ${label}`\"\n size=\"sm\"\n @click.stop=\"handleDeleteScope(id)\" />\n </div>\n </DataTableCell>\n <!--\n Stop the click here so it does not also bubble to the row's `@click`\n toggle. The checkbox already drives the change via `@update:modelValue`;\n letting the row fire too would toggle the scope a second time.\n -->\n <DataTableCheckbox\n :ariaLabel=\"`Select ${label} scope`\"\n :modelValue=\"selectedScopes.includes(id)\"\n @click.stop\n @update:modelValue=\"setScope(id, $event)\" />\n </DataTableRow>\n </table>\n </div>\n </DisclosurePanel>\n </Disclosure>\n </div>\n\n <!-- Shared add/edit scope modal -->\n <OAuthScopesAddModal\n :scope=\"editingScope\"\n :scopes=\"Object.keys(flow.scopes ?? {})\"\n :state=\"scopeFormModal\"\n @submit=\"handleScopeFormSubmit\" />\n </DataTableCell>\n</template>\n\n<style>\n/* Matches AddressBar.vue `.address-bar-bg-states` / `.fade-right` (header URL input). */\n.oauth-scope-row-action-rail {\n --oauth-scope-row-rail-bg: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n background: linear-gradient(\n 90deg,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 100%) 0%,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 20%) 30%,\n var(--oauth-scope-row-rail-bg) 100%\n );\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n</style>\n"],"mappings":""}
@@ -59,8 +59,10 @@ var OAuthScopesInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
59
59
  const hasScopes = computed(() => Object.keys(__props.flow?.scopes ?? {}).length > 0);
60
60
  const allScopesSelected = computed(() => __props.selectedScopes.length === Object.keys(__props.flow?.scopes ?? {}).length);
61
61
  const setScope = (scopeKey, checked) => {
62
- if (checked) return emits("update:selectedScopes", { scopes: Array.from(new Set([...__props.selectedScopes, scopeKey])) });
63
- emits("update:selectedScopes", { scopes: __props.selectedScopes.filter((scope) => scope !== scopeKey) });
62
+ emits("update:selectedScopes", {
63
+ scope: scopeKey,
64
+ selected: checked
65
+ });
64
66
  };
65
67
  /** Select all scopes */
66
68
  const selectAllScopes = () => emits("update:selectedScopes", { scopes: Object.keys(__props.flow?.scopes ?? {}) });
@@ -136,7 +138,7 @@ var OAuthScopesInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
136
138
  };
137
139
  return (_ctx, _cache) => {
138
140
  return openBlock(), createBlock(unref(DataTableCell_default), { class: "h-auto !max-h-[initial] min-h-8 items-center" }, {
139
- default: withCtx(() => [createElementVNode("div", _hoisted_1, [_cache[6] || (_cache[6] = createElementVNode("div", { class: "text-c-1 h-full items-center" }, null, -1)), (openBlock(), createBlock(unref(Disclosure), {
141
+ default: withCtx(() => [createElementVNode("div", _hoisted_1, [_cache[7] || (_cache[7] = createElementVNode("div", { class: "text-c-1 h-full items-center" }, null, -1)), (openBlock(), createBlock(unref(Disclosure), {
140
142
  key: remountKey.value,
141
143
  as: "div",
142
144
  class: "bl flex w-full flex-col",
@@ -159,7 +161,7 @@ var OAuthScopesInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
159
161
  variant: "ghost",
160
162
  onClick: withModifiers(openAddScopeModal, ["stop"])
161
163
  }, {
162
- default: withCtx(() => [..._cache[2] || (_cache[2] = [createTextVNode(" Add Scope ", -1)])]),
164
+ default: withCtx(() => [..._cache[3] || (_cache[3] = [createTextVNode(" Add Scope ", -1)])]),
163
165
  _: 1
164
166
  }),
165
167
  hasScopes.value && allScopesSelected.value ? (openBlock(), createBlock(unref(ScalarButton), {
@@ -169,7 +171,7 @@ var OAuthScopesInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
169
171
  variant: "ghost",
170
172
  onClick: withModifiers(deselectAllScopes, ["stop"])
171
173
  }, {
172
- default: withCtx(() => [..._cache[3] || (_cache[3] = [createTextVNode(" Deselect All ", -1)])]),
174
+ default: withCtx(() => [..._cache[4] || (_cache[4] = [createTextVNode(" Deselect All ", -1)])]),
173
175
  _: 1
174
176
  })) : createCommentVNode("", true),
175
177
  hasScopes.value && !allScopesSelected.value ? (openBlock(), createBlock(unref(ScalarButton), {
@@ -179,7 +181,7 @@ var OAuthScopesInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
179
181
  variant: "ghost",
180
182
  onClick: withModifiers(selectAllScopes, ["stop"])
181
183
  }, {
182
- default: withCtx(() => [..._cache[4] || (_cache[4] = [createTextVNode(" Select All ", -1)])]),
184
+ default: withCtx(() => [..._cache[5] || (_cache[5] = [createTextVNode(" Select All ", -1)])]),
183
185
  _: 1
184
186
  })) : createCommentVNode("", true),
185
187
  hasScopes.value ? (openBlock(), createBlock(unref(DisclosureButton), {
@@ -206,7 +208,7 @@ var OAuthScopesInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
206
208
  onClick: ($event) => setScope(id, !__props.selectedScopes.includes(id))
207
209
  }, {
208
210
  default: withCtx(() => [createVNode(unref(DataTableCell_default), { class: "box-border flex !max-h-[initial] w-full min-w-0 cursor-pointer items-stretch overflow-hidden px-0 py-0" }, {
209
- default: withCtx(() => [createElementVNode("div", _hoisted_5, [createElementVNode("span", _hoisted_6, toDisplayString(label), 1), String(description ?? "").trim() ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[5] || (_cache[5] = createElementVNode("span", { class: "shrink-0" }, "–", -1)), createElementVNode("span", _hoisted_7, toDisplayString(description), 1)], 64)) : createCommentVNode("", true)]), createElementVNode("div", {
211
+ default: withCtx(() => [createElementVNode("div", _hoisted_5, [createElementVNode("span", _hoisted_6, toDisplayString(label), 1), String(description ?? "").trim() ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[6] || (_cache[6] = createElementVNode("span", { class: "shrink-0" }, "–", -1)), createElementVNode("span", _hoisted_7, toDisplayString(description), 1)], 64)) : createCommentVNode("", true)]), createElementVNode("div", {
210
212
  class: "oauth-scope-row-action-rail absolute top-0 right-0 z-[1] flex h-full min-w-[4.5rem] items-center justify-end gap-0.5 py-1 pr-2 pl-4 opacity-0 transition-opacity group-focus-within/scope-row:opacity-100 group-hover/scope-row:opacity-100",
211
213
  onClick: _cache[1] || (_cache[1] = withModifiers(() => {}, ["stop"]))
212
214
  }, [createVNode(unref(ScalarIconButton), {
@@ -233,9 +235,15 @@ var OAuthScopesInput_vue_vue_type_script_setup_true_lang_default = /* @__PURE__
233
235
  ])])]),
234
236
  _: 2
235
237
  }, 1024), createVNode(unref(DataTableCheckbox_default), {
238
+ ariaLabel: `Select ${label} scope`,
236
239
  modelValue: __props.selectedScopes.includes(id),
240
+ onClick: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"])),
237
241
  "onUpdate:modelValue": ($event) => setScope(id, $event)
238
- }, null, 8, ["modelValue", "onUpdate:modelValue"])]),
242
+ }, null, 8, [
243
+ "ariaLabel",
244
+ "modelValue",
245
+ "onUpdate:modelValue"
246
+ ])]),
239
247
  _: 2
240
248
  }, 1032, ["onClick"]);
241
249
  }), 128))])])]),
@@ -1 +1 @@
1
- {"version":3,"file":"OAuthScopesInput.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { useModal } from '@scalar/components/modal'\nimport { ScalarSearchInput } from '@scalar/components/search-input'\nimport { ScalarIconPencilSimple, ScalarIconTrash } from '@scalar/icons'\nimport type { ApiReferenceEvents } from '@scalar/workspace-store/events'\nimport type {\n OAuthFlow,\n OAuthFlowsObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport {\n DataTableCell,\n DataTableCheckbox,\n DataTableRow,\n} from '@/v2/components/data-table'\n\nimport OAuthScopesAddModal from './OAuthScopesAddModal.vue'\n\nconst { selectedScopes, flow, flowType } = defineProps<{\n flowType: keyof OAuthFlowsObject\n flow: OAuthFlow\n selectedScopes: string[]\n}>()\n\nconst emits = defineEmits<{\n (e: 'update:selectedScopes', payload: { scopes: string[] }): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst searchQuery = ref('')\n\n/**\n * The scope currently being edited, used to switch the shared modal into edit mode.\n * Null means the modal is in \"add\" mode.\n */\nconst editingScope = ref<{ name: string; description: string } | null>(null)\n\n/** List of all available scopes */\nconst scopes = computed(() =>\n Object.entries(flow?.scopes ?? {}).map(([key, val]) => ({\n id: key,\n label: key,\n description: val,\n })),\n)\n\nconst scopeCount = computed(() => Object.keys(flow?.scopes ?? {}).length)\n\n/** Search is only useful once the list is long enough to scan */\nconst showScopeSearch = computed(() => scopeCount.value >= 10)\n\nconst filteredScopes = computed(() => {\n const query = showScopeSearch.value ? searchQuery.value : ''\n if (!query) {\n return scopes.value\n }\n\n const regex = new RegExp(\n query\n .split('')\n .map((c) => c.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*'),\n 'i',\n )\n\n return scopes.value.filter(({ label, description }) =>\n regex.test(`${label} ${description}`),\n )\n})\n\nconst hasScopes = computed(() => Object.keys(flow?.scopes ?? {}).length > 0)\n\nconst allScopesSelected = computed(\n () => selectedScopes.length === Object.keys(flow?.scopes ?? {}).length,\n)\n\nconst setScope = (scopeKey: string, checked: boolean) => {\n if (checked) {\n // Select the scope\n return emits('update:selectedScopes', {\n scopes: Array.from(new Set([...selectedScopes, scopeKey])),\n })\n }\n\n // Deselect the scope\n emits('update:selectedScopes', {\n scopes: selectedScopes.filter((scope) => scope !== scopeKey),\n })\n}\n\n/** Select all scopes */\nconst selectAllScopes = () =>\n emits('update:selectedScopes', { scopes: Object.keys(flow?.scopes ?? {}) })\n\n/** Deselect all scopes */\nconst deselectAllScopes = () => emits('update:selectedScopes', { scopes: [] })\n\nconst scopeFormModal = useModal()\n\n/**\n * Forces the Disclosure to remount with `defaultOpen` set so the panel auto-expands\n * the moment a brand-new scope is added. Bumping this counter is paired with toggling\n * `expandOnNextMount` so the remount opens the panel, then reverts to the default\n * closed-on-mount behavior for any future unrelated remounts (e.g. collapse on empty).\n *\n * The Disclosure `:key` is only this counter — not a separate `hasScopes` segment — so\n * adding the first scope does not remount twice (empty prefix → with-scopes prefix),\n * which would reopen with `defaultOpen=false` and collapse the panel right when rows appear.\n */\nconst remountKey = ref(0)\nconst expandOnNextMount = ref(false)\n\nwatch(hasScopes, (has, had) => {\n if (had && !has) {\n expandOnNextMount.value = false\n remountKey.value += 1\n }\n})\n\n/** Open the modal in \"add new scope\" mode */\nconst openAddScopeModal = () => {\n editingScope.value = null\n scopeFormModal.show()\n}\n\n/** Open the modal in \"edit scope\" mode prefilled with the chosen row */\nconst openEditScopeModal = (scope: { id: string; description: string }) => {\n editingScope.value = {\n name: scope.id,\n description: scope.description ?? '',\n }\n scopeFormModal.show()\n}\n\n/**\n * Submit handler shared by Add Scope and Edit Scope. For renames, the `upsertScope` mutator on\n * the workspace store rewrites the selection state in place, so the component does not need to\n * emit a follow-up `update:selectedScopes`.\n *\n * When adding a brand-new scope (no `oldName`), the payload sets `enable: true` so the\n * `upsertScope` mutator also adds the new scope to any selection requirement that references\n * this scheme. The Disclosure is then remounted with `defaultOpen=true` so the user\n * immediately sees (and sees selected) the scope they just added.\n */\nconst handleScopeFormSubmit = async (payload: {\n name: string\n description: string\n oldName?: string\n}) => {\n const isAdd = !payload.oldName\n\n emits('upsert:scope', {\n scope: payload.name,\n description: payload.description,\n flowType,\n ...(payload.oldName ? { oldScope: payload.oldName } : {}),\n ...(isAdd ? { enable: true } : {}),\n })\n\n if (!isAdd) {\n return\n }\n\n expandOnNextMount.value = true\n remountKey.value += 1\n // Reset after the remount so subsequent unrelated remounts (e.g. when the last\n // scope is deleted) still default to a closed panel.\n await nextTick()\n expandOnNextMount.value = false\n}\n\n/**\n * Remove the scope from the flow. Selection cleanup (dropping the deleted key from any\n * currently-selected scopes) is handled by the `deleteScope` mutator on the workspace store,\n * so the component does not need to emit a follow-up `update:selectedScopes` here.\n */\nconst handleDeleteScope = (scopeKey: string) => {\n emits('delete:scope', { scope: scopeKey, flowType })\n}\n</script>\n\n<template>\n <DataTableCell class=\"h-auto !max-h-[initial] min-h-8 items-center\">\n <div class=\"flex h-fit w-full\">\n <div class=\"text-c-1 h-full items-center\"></div>\n <Disclosure\n :key=\"remountKey\"\n as=\"div\"\n class=\"bl flex w-full flex-col\"\n :defaultOpen=\"expandOnNextMount\">\n <!--\n Keep Add Scope (and other actions) outside the summary DisclosureButton.\n When there are no scopes the summary control is disabled; a native disabled\n button blocks pointer events for all descendants, which would make Add Scope unusable.\n -->\n <div\n class=\"group/scopes-accordion flex h-auto min-h-8 items-center gap-1.5 pr-2.25 pl-3 text-left\">\n <DisclosureButton\n :class=\"[\n 'min-w-0 flex-1 text-left',\n hasScopes ? 'hover:text-c-1 cursor-pointer' : 'cursor-default',\n (selectedScopes.length || 0) > 0 ? 'text-c-1' : 'text-c-3',\n ]\"\n :disabled=\"!hasScopes\">\n <template v-if=\"hasScopes\">\n Scopes Selected\n {{ selectedScopes.length || 0 }} /\n {{ Object.keys(flow?.scopes ?? {}).length || 0 }}\n </template>\n <template v-else> No Scopes Defined </template>\n </DisclosureButton>\n\n <div class=\"flex shrink-0 items-center gap-1.75\">\n <!-- Add new scope -->\n <ScalarButton\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"openAddScopeModal\">\n Add Scope\n </ScalarButton>\n\n <!-- Deselect All -->\n <ScalarButton\n v-if=\"hasScopes && allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"deselectAllScopes\">\n Deselect All\n </ScalarButton>\n\n <!-- Select All -->\n <ScalarButton\n v-if=\"hasScopes && !allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"selectAllScopes\">\n Select All\n </ScalarButton>\n\n <DisclosureButton\n v-if=\"hasScopes\"\n v-slot=\"{ open }\"\n class=\"text-c-3 hover:text-c-2 -m-0.5 flex shrink-0 items-center justify-center rounded p-0.5 focus-visible:outline-offset-2\">\n <ScalarIcon\n class=\"group-hover/scopes-accordion:text-c-2\"\n :icon=\"open ? 'ChevronDown' : 'ChevronRight'\"\n size=\"md\" />\n </DisclosureButton>\n </div>\n </div>\n <!-- Scopes List -->\n <DisclosurePanel as=\"template\">\n <div>\n <ScalarSearchInput\n v-if=\"showScopeSearch\"\n v-model=\"searchQuery\"\n class=\"flex items-center text-xs\" />\n <table\n class=\"grid max-h-40 auto-rows-auto overflow-x-hidden overflow-y-auto\"\n :style=\"{ gridTemplateColumns: '1fr auto' }\">\n <DataTableRow\n v-for=\"{ id, label, description } in filteredScopes\"\n :key=\"id\"\n class=\"text-c-2 group/scope-row\"\n @click=\"setScope(id, !selectedScopes.includes(id))\">\n <DataTableCell\n class=\"box-border flex !max-h-[initial] w-full min-w-0 cursor-pointer items-stretch overflow-hidden px-0 py-0\">\n <!-- Text scrolls horizontally; action rail stays fixed -->\n <div\n class=\"no-scrollbar text-c-2 group-hover/scope-row:text-c-1 flex min-h-8 min-w-0 flex-1 items-center gap-1 overflow-x-auto px-3 py-1.5 pr-20 text-xs text-nowrap\">\n <span class=\"font-code shrink-0 text-xs\">{{ label }}</span>\n <template v-if=\"String(description ?? '').trim()\">\n <span class=\"shrink-0\">&ndash;</span>\n <span class=\"whitespace-nowrap\">{{ description }}</span>\n </template>\n </div>\n <div\n class=\"oauth-scope-row-action-rail absolute top-0 right-0 z-[1] flex h-full min-w-[4.5rem] items-center justify-end gap-0.5 py-1 pr-2 pl-4 opacity-0 transition-opacity group-focus-within/scope-row:opacity-100 group-hover/scope-row:opacity-100\"\n @click.stop>\n <ScalarIconButton\n :icon=\"ScalarIconPencilSimple\"\n :label=\"`Edit ${label}`\"\n size=\"sm\"\n @click.stop=\"\n openEditScopeModal({\n id,\n description: description ?? '',\n })\n \" />\n <ScalarIconButton\n :icon=\"ScalarIconTrash\"\n :label=\"`Delete ${label}`\"\n size=\"sm\"\n @click.stop=\"handleDeleteScope(id)\" />\n </div>\n </DataTableCell>\n <DataTableCheckbox\n :modelValue=\"selectedScopes.includes(id)\"\n @update:modelValue=\"setScope(id, $event)\" />\n </DataTableRow>\n </table>\n </div>\n </DisclosurePanel>\n </Disclosure>\n </div>\n\n <!-- Shared add/edit scope modal -->\n <OAuthScopesAddModal\n :scope=\"editingScope\"\n :scopes=\"Object.keys(flow.scopes ?? {})\"\n :state=\"scopeFormModal\"\n @submit=\"handleScopeFormSubmit\" />\n </DataTableCell>\n</template>\n\n<style>\n/* Matches AddressBar.vue `.address-bar-bg-states` / `.fade-right` (header URL input). */\n.oauth-scope-row-action-rail {\n --oauth-scope-row-rail-bg: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n background: linear-gradient(\n 90deg,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 100%) 0%,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 20%) 30%,\n var(--oauth-scope-row-rail-bg) 100%\n );\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n</style>\n"],"mappings":"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"}
1
+ {"version":3,"file":"OAuthScopesInput.vue.script.js","names":[],"sources":["../../../../../src/v2/blocks/scalar-auth-selector-block/components/OAuthScopesInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarButton } from '@scalar/components/button'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { ScalarIconButton } from '@scalar/components/icon-button'\nimport { useModal } from '@scalar/components/modal'\nimport { ScalarSearchInput } from '@scalar/components/search-input'\nimport { ScalarIconPencilSimple, ScalarIconTrash } from '@scalar/icons'\nimport type { ApiReferenceEvents } from '@scalar/workspace-store/events'\nimport type {\n OAuthFlow,\n OAuthFlowsObject,\n} from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed, nextTick, ref, watch } from 'vue'\n\nimport {\n DataTableCell,\n DataTableCheckbox,\n DataTableRow,\n} from '@/v2/components/data-table'\n\nimport OAuthScopesAddModal from './OAuthScopesAddModal.vue'\n\nconst { selectedScopes, flow, flowType } = defineProps<{\n flowType: keyof OAuthFlowsObject\n flow: OAuthFlow\n selectedScopes: string[]\n}>()\n\nconst emits = defineEmits<{\n (\n e: 'update:selectedScopes',\n payload: { scopes?: string[]; scope?: string; selected?: boolean },\n ): void\n (\n e: 'upsert:scope',\n payload: Omit<ApiReferenceEvents['auth:upsert:scopes'], 'name'>,\n ): void\n (\n e: 'delete:scope',\n payload: Omit<ApiReferenceEvents['auth:delete:scopes'], 'name'>,\n ): void\n}>()\n\nconst searchQuery = ref('')\n\n/**\n * The scope currently being edited, used to switch the shared modal into edit mode.\n * Null means the modal is in \"add\" mode.\n */\nconst editingScope = ref<{ name: string; description: string } | null>(null)\n\n/** List of all available scopes */\nconst scopes = computed(() =>\n Object.entries(flow?.scopes ?? {}).map(([key, val]) => ({\n id: key,\n label: key,\n description: val,\n })),\n)\n\nconst scopeCount = computed(() => Object.keys(flow?.scopes ?? {}).length)\n\n/** Search is only useful once the list is long enough to scan */\nconst showScopeSearch = computed(() => scopeCount.value >= 10)\n\nconst filteredScopes = computed(() => {\n const query = showScopeSearch.value ? searchQuery.value : ''\n if (!query) {\n return scopes.value\n }\n\n const regex = new RegExp(\n query\n .split('')\n .map((c) => c.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n .join('.*'),\n 'i',\n )\n\n return scopes.value.filter(({ label, description }) =>\n regex.test(`${label} ${description}`),\n )\n})\n\nconst hasScopes = computed(() => Object.keys(flow?.scopes ?? {}).length > 0)\n\nconst allScopesSelected = computed(\n () => selectedScopes.length === Object.keys(flow?.scopes ?? {}).length,\n)\n\nconst setScope = (scopeKey: string, checked: boolean) => {\n // Emit a single-scope toggle rather than a freshly computed list. The store applies it\n // against the currently selected scopes, so two quick clicks cannot overwrite each other\n // with a list computed from a prop that has not updated yet.\n emits('update:selectedScopes', { scope: scopeKey, selected: checked })\n}\n\n/** Select all scopes */\nconst selectAllScopes = () =>\n emits('update:selectedScopes', { scopes: Object.keys(flow?.scopes ?? {}) })\n\n/** Deselect all scopes */\nconst deselectAllScopes = () => emits('update:selectedScopes', { scopes: [] })\n\nconst scopeFormModal = useModal()\n\n/**\n * Forces the Disclosure to remount with `defaultOpen` set so the panel auto-expands\n * the moment a brand-new scope is added. Bumping this counter is paired with toggling\n * `expandOnNextMount` so the remount opens the panel, then reverts to the default\n * closed-on-mount behavior for any future unrelated remounts (e.g. collapse on empty).\n *\n * The Disclosure `:key` is only this counter — not a separate `hasScopes` segment — so\n * adding the first scope does not remount twice (empty prefix → with-scopes prefix),\n * which would reopen with `defaultOpen=false` and collapse the panel right when rows appear.\n */\nconst remountKey = ref(0)\nconst expandOnNextMount = ref(false)\n\nwatch(hasScopes, (has, had) => {\n if (had && !has) {\n expandOnNextMount.value = false\n remountKey.value += 1\n }\n})\n\n/** Open the modal in \"add new scope\" mode */\nconst openAddScopeModal = () => {\n editingScope.value = null\n scopeFormModal.show()\n}\n\n/** Open the modal in \"edit scope\" mode prefilled with the chosen row */\nconst openEditScopeModal = (scope: { id: string; description: string }) => {\n editingScope.value = {\n name: scope.id,\n description: scope.description ?? '',\n }\n scopeFormModal.show()\n}\n\n/**\n * Submit handler shared by Add Scope and Edit Scope. For renames, the `upsertScope` mutator on\n * the workspace store rewrites the selection state in place, so the component does not need to\n * emit a follow-up `update:selectedScopes`.\n *\n * When adding a brand-new scope (no `oldName`), the payload sets `enable: true` so the\n * `upsertScope` mutator also adds the new scope to any selection requirement that references\n * this scheme. The Disclosure is then remounted with `defaultOpen=true` so the user\n * immediately sees (and sees selected) the scope they just added.\n */\nconst handleScopeFormSubmit = async (payload: {\n name: string\n description: string\n oldName?: string\n}) => {\n const isAdd = !payload.oldName\n\n emits('upsert:scope', {\n scope: payload.name,\n description: payload.description,\n flowType,\n ...(payload.oldName ? { oldScope: payload.oldName } : {}),\n ...(isAdd ? { enable: true } : {}),\n })\n\n if (!isAdd) {\n return\n }\n\n expandOnNextMount.value = true\n remountKey.value += 1\n // Reset after the remount so subsequent unrelated remounts (e.g. when the last\n // scope is deleted) still default to a closed panel.\n await nextTick()\n expandOnNextMount.value = false\n}\n\n/**\n * Remove the scope from the flow. Selection cleanup (dropping the deleted key from any\n * currently-selected scopes) is handled by the `deleteScope` mutator on the workspace store,\n * so the component does not need to emit a follow-up `update:selectedScopes` here.\n */\nconst handleDeleteScope = (scopeKey: string) => {\n emits('delete:scope', { scope: scopeKey, flowType })\n}\n</script>\n\n<template>\n <DataTableCell class=\"h-auto !max-h-[initial] min-h-8 items-center\">\n <div class=\"flex h-fit w-full\">\n <div class=\"text-c-1 h-full items-center\"></div>\n <Disclosure\n :key=\"remountKey\"\n as=\"div\"\n class=\"bl flex w-full flex-col\"\n :defaultOpen=\"expandOnNextMount\">\n <!--\n Keep Add Scope (and other actions) outside the summary DisclosureButton.\n When there are no scopes the summary control is disabled; a native disabled\n button blocks pointer events for all descendants, which would make Add Scope unusable.\n -->\n <div\n class=\"group/scopes-accordion flex h-auto min-h-8 items-center gap-1.5 pr-2.25 pl-3 text-left\">\n <DisclosureButton\n :class=\"[\n 'min-w-0 flex-1 text-left',\n hasScopes ? 'hover:text-c-1 cursor-pointer' : 'cursor-default',\n (selectedScopes.length || 0) > 0 ? 'text-c-1' : 'text-c-3',\n ]\"\n :disabled=\"!hasScopes\">\n <template v-if=\"hasScopes\">\n Scopes Selected\n {{ selectedScopes.length || 0 }} /\n {{ Object.keys(flow?.scopes ?? {}).length || 0 }}\n </template>\n <template v-else> No Scopes Defined </template>\n </DisclosureButton>\n\n <div class=\"flex shrink-0 items-center gap-1.75\">\n <!-- Add new scope -->\n <ScalarButton\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"openAddScopeModal\">\n Add Scope\n </ScalarButton>\n\n <!-- Deselect All -->\n <ScalarButton\n v-if=\"hasScopes && allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"deselectAllScopes\">\n Deselect All\n </ScalarButton>\n\n <!-- Select All -->\n <ScalarButton\n v-if=\"hasScopes && !allScopesSelected\"\n class=\"pr-0.75 pl-1 transition-none\"\n size=\"sm\"\n variant=\"ghost\"\n @click.stop=\"selectAllScopes\">\n Select All\n </ScalarButton>\n\n <DisclosureButton\n v-if=\"hasScopes\"\n v-slot=\"{ open }\"\n class=\"text-c-3 hover:text-c-2 -m-0.5 flex shrink-0 items-center justify-center rounded p-0.5 focus-visible:outline-offset-2\">\n <ScalarIcon\n class=\"group-hover/scopes-accordion:text-c-2\"\n :icon=\"open ? 'ChevronDown' : 'ChevronRight'\"\n size=\"md\" />\n </DisclosureButton>\n </div>\n </div>\n <!-- Scopes List -->\n <DisclosurePanel as=\"template\">\n <div>\n <ScalarSearchInput\n v-if=\"showScopeSearch\"\n v-model=\"searchQuery\"\n class=\"flex items-center text-xs\" />\n <table\n class=\"grid max-h-40 auto-rows-auto overflow-x-hidden overflow-y-auto\"\n :style=\"{ gridTemplateColumns: '1fr auto' }\">\n <DataTableRow\n v-for=\"{ id, label, description } in filteredScopes\"\n :key=\"id\"\n class=\"text-c-2 group/scope-row\"\n @click=\"setScope(id, !selectedScopes.includes(id))\">\n <DataTableCell\n class=\"box-border flex !max-h-[initial] w-full min-w-0 cursor-pointer items-stretch overflow-hidden px-0 py-0\">\n <!-- Text scrolls horizontally; action rail stays fixed -->\n <div\n class=\"no-scrollbar text-c-2 group-hover/scope-row:text-c-1 flex min-h-8 min-w-0 flex-1 items-center gap-1 overflow-x-auto px-3 py-1.5 pr-20 text-xs text-nowrap\">\n <span class=\"font-code shrink-0 text-xs\">{{ label }}</span>\n <template v-if=\"String(description ?? '').trim()\">\n <span class=\"shrink-0\">&ndash;</span>\n <span class=\"whitespace-nowrap\">{{ description }}</span>\n </template>\n </div>\n <div\n class=\"oauth-scope-row-action-rail absolute top-0 right-0 z-[1] flex h-full min-w-[4.5rem] items-center justify-end gap-0.5 py-1 pr-2 pl-4 opacity-0 transition-opacity group-focus-within/scope-row:opacity-100 group-hover/scope-row:opacity-100\"\n @click.stop>\n <ScalarIconButton\n :icon=\"ScalarIconPencilSimple\"\n :label=\"`Edit ${label}`\"\n size=\"sm\"\n @click.stop=\"\n openEditScopeModal({\n id,\n description: description ?? '',\n })\n \" />\n <ScalarIconButton\n :icon=\"ScalarIconTrash\"\n :label=\"`Delete ${label}`\"\n size=\"sm\"\n @click.stop=\"handleDeleteScope(id)\" />\n </div>\n </DataTableCell>\n <!--\n Stop the click here so it does not also bubble to the row's `@click`\n toggle. The checkbox already drives the change via `@update:modelValue`;\n letting the row fire too would toggle the scope a second time.\n -->\n <DataTableCheckbox\n :ariaLabel=\"`Select ${label} scope`\"\n :modelValue=\"selectedScopes.includes(id)\"\n @click.stop\n @update:modelValue=\"setScope(id, $event)\" />\n </DataTableRow>\n </table>\n </div>\n </DisclosurePanel>\n </Disclosure>\n </div>\n\n <!-- Shared add/edit scope modal -->\n <OAuthScopesAddModal\n :scope=\"editingScope\"\n :scopes=\"Object.keys(flow.scopes ?? {})\"\n :state=\"scopeFormModal\"\n @submit=\"handleScopeFormSubmit\" />\n </DataTableCell>\n</template>\n\n<style>\n/* Matches AddressBar.vue `.address-bar-bg-states` / `.fade-right` (header URL input). */\n.oauth-scope-row-action-rail {\n --oauth-scope-row-rail-bg: color-mix(\n in srgb,\n var(--scalar-background-1),\n var(--scalar-background-2)\n );\n background: linear-gradient(\n 90deg,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 100%) 0%,\n color-mix(in srgb, var(--oauth-scope-row-rail-bg), transparent 20%) 30%,\n var(--oauth-scope-row-rail-bg) 100%\n );\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n</style>\n"],"mappings":"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"}