@stoplight/elements-core 7.8.0 → 7.10.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.
@@ -3,9 +3,10 @@ interface ResponsesProps {
3
3
  responses: IHttpOperationResponse[];
4
4
  onMediaTypeChange(mediaType: string): void;
5
5
  onStatusCodeChange(statusCode: string): void;
6
+ isCompact?: boolean;
6
7
  }
7
8
  export declare const Responses: {
8
- ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange }: ResponsesProps): JSX.Element | null;
9
+ ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange, isCompact, }: ResponsesProps): JSX.Element | null;
9
10
  displayName: string;
10
11
  };
11
12
  export {};
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+ import { ServerVariable } from '../../../utils/http-spec/IServer';
3
+ interface ServerVariablesProps<P extends keyof any = string> {
4
+ variables: readonly ServerVariable[];
5
+ values: Record<P, string>;
6
+ onChangeValue: (variableName: P, newValue: string) => void;
7
+ }
8
+ export declare const ServerVariables: React.FC<ServerVariablesProps>;
9
+ export {};
@@ -0,0 +1,11 @@
1
+ import { SelectProps } from '@stoplight/mosaic';
2
+ import * as React from 'react';
3
+ import { ServerVariable } from '../../../utils/http-spec/IServer';
4
+ interface VariableProps {
5
+ variable: ServerVariable;
6
+ value?: string;
7
+ onChange: SelectProps['onChange'];
8
+ validate?: boolean;
9
+ }
10
+ export declare const VariableEditor: React.FC<VariableProps>;
11
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare const useServerVariables: () => {
2
+ serverVariables: {};
3
+ updateServerVariableValue: (name: string, value: string) => void;
4
+ };
@@ -4,6 +4,7 @@ declare const _default: Meta<TryItProps>;
4
4
  export default _default;
5
5
  export declare const SimpleGET: Story<TryItProps>;
6
6
  export declare const WithParameters: Story<TryItProps>;
7
+ export declare const WithVariables: Story<TryItProps>;
7
8
  export declare const UrlEncoded: Story<TryItProps>;
8
9
  export declare const Multipart: Story<TryItProps>;
9
10
  export declare const RequestBodySchema: Story<TryItProps>;
@@ -3,3 +3,4 @@ import { TryItWithRequestSamplesProps } from './TryItWithRequestSamples';
3
3
  declare const _default: Meta<TryItWithRequestSamplesProps>;
4
4
  export default _default;
5
5
  export declare const WithParameters: Story<TryItWithRequestSamplesProps>;
6
+ export declare const WithVariables: Story<TryItWithRequestSamplesProps>;
@@ -7,6 +7,7 @@ interface BuildRequestInput {
7
7
  httpOperation: IHttpOperation;
8
8
  mediaTypeContent: IMediaTypeContent | undefined;
9
9
  parameterValues: Dictionary<string, string>;
10
+ serverVariableValues: Dictionary<string, string>;
10
11
  bodyInput?: BodyParameterValues | string;
11
12
  mockData?: MockData;
12
13
  auth?: HttpSecuritySchemeWithValues;
@@ -18,7 +19,7 @@ export declare const getQueryParams: ({ httpOperation, parameterValues, }: Pick<
18
19
  name: string;
19
20
  value: string;
20
21
  }[];
21
- export declare function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, mockData, auth, chosenServer, credentials, corsProxy, }: BuildRequestInput): Promise<Parameters<typeof fetch>>;
22
- export declare function buildHarRequest({ httpOperation, bodyInput, parameterValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }: BuildRequestInput): Promise<HarRequest>;
22
+ export declare function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, serverVariableValues, mockData, auth, chosenServer, credentials, corsProxy, }: BuildRequestInput): Promise<Parameters<typeof fetch>>;
23
+ export declare function buildHarRequest({ httpOperation, bodyInput, parameterValues, serverVariableValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }: BuildRequestInput): Promise<HarRequest>;
23
24
  export declare function getAcceptedMimeTypes(httpOperation: IHttpOperation): string[];
24
25
  export {};
package/index.esm.js CHANGED
@@ -7,7 +7,7 @@ import isArray from 'lodash/isArray.js';
7
7
  import isPlainObject from 'lodash/isPlainObject.js';
8
8
  import { NodeType, HttpParamStyles } from '@stoplight/types';
9
9
  import { parse } from '@stoplight/yaml';
10
- import { isArray as isArray$1, Box, Panel, CopyButton, Menu, Button, Text, Flex, Input, Icon, Select, FieldButton, Image, Link, useThemeIsDark, HStack, VStack, InvertTheme, Tooltip, Badge, LinkHeading as LinkHeading$1, NodeAnnotation, Tabs, TabList, Tab, TabPanels, TabPanel, Heading, useClipboard, useBreakpoints, useMosaicContext, Provider as Provider$1 } from '@stoplight/mosaic';
10
+ import { isArray as isArray$1, Box, useBreakpoints, Panel, CopyButton, Menu, Button, Text, Flex, Input, Icon, Select, FieldButton, Image, Link, useThemeIsDark, HStack, VStack, InvertTheme, Tooltip, Badge, LinkHeading as LinkHeading$1, NodeAnnotation, useModalState, Modal, ListBox, ListBoxItem, TabList, Tab, Tabs, TabPanels, TabPanel, Heading, useClipboard, useMosaicContext, Provider as Provider$1 } from '@stoplight/mosaic';
11
11
  import isObject from 'lodash/isObject.js';
12
12
  import { withErrorBoundary } from '@stoplight/react-error-boundary';
13
13
  import { useLocation, BrowserRouter, MemoryRouter, HashRouter, StaticRouter, Route } from 'react-router-dom';
@@ -16,7 +16,6 @@ export { DefaultSMDComponents } from '@stoplight/markdown-viewer';
16
16
  import cn from 'classnames';
17
17
  import { atomWithStorage, useAtomValue } from 'jotai/utils';
18
18
  import { atom, useAtom, Provider } from 'jotai';
19
- import isEmpty from 'lodash/isEmpty.js';
20
19
  import URI from 'urijs';
21
20
  import { CodeViewer } from '@stoplight/mosaic-code-viewer';
22
21
  import { isValidTargetId, HTTPSnippet } from 'httpsnippet-lite';
@@ -42,6 +41,7 @@ import entries from 'lodash/entries.js';
42
41
  import keys from 'lodash/keys.js';
43
42
  import { JsonSchemaViewer } from '@stoplight/json-schema-viewer';
44
43
  import sortBy from 'lodash/sortBy.js';
44
+ import isEmpty from 'lodash/isEmpty.js';
45
45
  import isNil from 'lodash/isNil.js';
46
46
  import omitBy from 'lodash/omitBy.js';
47
47
  import { HashLink } from 'react-router-hash-link';
@@ -486,6 +486,22 @@ function useChosenServerUrl(chosenServerUrl) {
486
486
  };
487
487
  }
488
488
 
489
+ const getBreakpoints = (compact) => {
490
+ if (!compact)
491
+ return undefined;
492
+ if (typeof compact === 'number') {
493
+ return [
494
+ ['compact', compact],
495
+ ['regular', Infinity],
496
+ ];
497
+ }
498
+ return [['compact', Infinity]];
499
+ };
500
+ function useIsCompact(layoutOptions) {
501
+ const { ref, breakpoint } = useBreakpoints(getBreakpoints(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.compact));
502
+ return { ref: ref, isCompact: breakpoint === 'compact' };
503
+ }
504
+
489
505
  const chosenServerAtom = atom(undefined);
490
506
 
491
507
  function isValidServer(server) {
@@ -496,10 +512,7 @@ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
496
512
  const fallbackDescription = originalServers.length === 1 ? 'Live Server' : `Server ${i + 1}`;
497
513
  let url = server.url;
498
514
  if (inlineDefaults) {
499
- url = getServerUrlWithDefaultValues(server);
500
- }
501
- else if (isEmpty(server.variables)) {
502
- url = resolveUrl(server.url);
515
+ url = getServerUrlWithVariableValues(server, {});
503
516
  }
504
517
  return Object.assign(Object.assign({}, server), { url, description: server.description || fallbackDescription });
505
518
  });
@@ -512,7 +525,18 @@ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
512
525
  }
513
526
  return servers.filter(isValidServer);
514
527
  };
528
+ const getServerVariables = (server) => {
529
+ var _a;
530
+ return Object.entries((_a = server === null || server === void 0 ? void 0 : server.variables) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => ({
531
+ name: key,
532
+ default: value.default,
533
+ description: value.description,
534
+ enum: value.enum,
535
+ }));
536
+ };
515
537
  function resolveUrl(urlString) {
538
+ if (urlString === null)
539
+ return null;
516
540
  let url;
517
541
  try {
518
542
  url = URI(urlString);
@@ -532,14 +556,15 @@ function resolveUrl(urlString) {
532
556
  }
533
557
  return null;
534
558
  }
535
- const getServerUrlWithDefaultValues = (server) => {
559
+ const getServerUrlWithVariableValues = (server, values) => {
536
560
  var _a;
537
561
  let urlString = server.url;
538
562
  const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
539
563
  variables.forEach(([variableName, variableInfo]) => {
540
- urlString = urlString.replaceAll(`{${variableName}}`, variableInfo.default);
564
+ var _a;
565
+ urlString = urlString.replaceAll(`{${variableName}}`, (_a = values[variableName]) !== null && _a !== void 0 ? _a : variableInfo.default);
541
566
  });
542
- return resolveUrl(urlString);
567
+ return urlString;
543
568
  };
544
569
 
545
570
  const persistAtom = (key, atomInstance) => {
@@ -1418,11 +1443,11 @@ const useTextRequestBodyState = (mediaTypeContent) => {
1418
1443
  };
1419
1444
 
1420
1445
  const nameAndValueObjectToPair = ({ name, value }) => [name, value];
1421
- const getServerUrl = ({ chosenServer, httpOperation, mockData, corsProxy, }) => {
1446
+ const getServerUrl = ({ chosenServer, httpOperation, mockData, corsProxy, serverVariableValues, }) => {
1422
1447
  var _a;
1423
1448
  const server = chosenServer || ((_a = httpOperation.servers) === null || _a === void 0 ? void 0 : _a[0]);
1424
- const chosenServerUrl = server && getServerUrlWithDefaultValues(server);
1425
- const serverUrl = (mockData === null || mockData === void 0 ? void 0 : mockData.url) || chosenServerUrl || window.location.origin;
1449
+ const chosenServerUrl = server && getServerUrlWithVariableValues(server, serverVariableValues);
1450
+ const serverUrl = resolveUrl((mockData === null || mockData === void 0 ? void 0 : mockData.url) || chosenServerUrl || window.location.origin);
1426
1451
  if (corsProxy && !mockData) {
1427
1452
  return `${corsProxy}${serverUrl}`;
1428
1453
  }
@@ -1499,10 +1524,10 @@ const getQueryParams = ({ httpOperation, parameterValues, }) => {
1499
1524
  return acc;
1500
1525
  }, []);
1501
1526
  };
1502
- function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, mockData, auth, chosenServer, credentials = 'omit', corsProxy, }) {
1527
+ function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, serverVariableValues, mockData, auth, chosenServer, credentials = 'omit', corsProxy, }) {
1503
1528
  var _a, _b, _c;
1504
1529
  return __awaiter(this, void 0, void 0, function* () {
1505
- const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy });
1530
+ const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy, serverVariableValues });
1506
1531
  const shouldIncludeBody = ['PUT', 'POST', 'PATCH'].includes(httpOperation.method.toUpperCase()) && bodyInput !== undefined;
1507
1532
  const queryParams = getQueryParams({ httpOperation, parameterValues });
1508
1533
  const rawHeaders = filterOutAuthorizationParams((_b = (_a = httpOperation.request) === null || _a === void 0 ? void 0 : _a.headers) !== null && _b !== void 0 ? _b : [], httpOperation.security)
@@ -1575,10 +1600,10 @@ const runAuthRequestEhancements = (auth, queryParams, headers) => {
1575
1600
  }
1576
1601
  return [newQueryParams, newHeaders];
1577
1602
  };
1578
- function buildHarRequest({ httpOperation, bodyInput, parameterValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }) {
1603
+ function buildHarRequest({ httpOperation, bodyInput, parameterValues, serverVariableValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }) {
1579
1604
  var _a, _b, _c, _d;
1580
1605
  return __awaiter(this, void 0, void 0, function* () {
1581
- const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy });
1606
+ const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy, serverVariableValues });
1582
1607
  const mimeType = (_a = mediaTypeContent === null || mediaTypeContent === void 0 ? void 0 : mediaTypeContent.mediaType) !== null && _a !== void 0 ? _a : 'application/json';
1583
1608
  const shouldIncludeBody = ['PUT', 'POST', 'PATCH'].includes(httpOperation.method.toUpperCase()) && bodyInput !== undefined;
1584
1609
  const queryParams = getQueryParams({ httpOperation, parameterValues });
@@ -1900,8 +1925,18 @@ class NetworkError extends Error {
1900
1925
  }
1901
1926
  const isNetworkError = (error) => error instanceof NetworkError;
1902
1927
 
1928
+ const persistedServerVariableValuesAtom = atom({});
1929
+ const useServerVariables = () => {
1930
+ const [serverVariables, setPersistedServerVariableValues] = useAtom(persistedServerVariableValuesAtom);
1931
+ const updateServerVariableValue = (name, value) => {
1932
+ setPersistedServerVariableValues(Object.assign({}, serverVariables, { [name]: value }));
1933
+ };
1934
+ return { serverVariables, updateServerVariableValue };
1935
+ };
1936
+
1903
1937
  const ServersDropdown = ({ servers }) => {
1904
1938
  const [chosenServer, setChosenServer] = useAtom(chosenServerAtom);
1939
+ const { serverVariables } = useServerVariables();
1905
1940
  const serverItems = [
1906
1941
  {
1907
1942
  type: 'option_group',
@@ -1915,7 +1950,7 @@ const ServersDropdown = ({ servers }) => {
1915
1950
  ...servers.map((server, i) => ({
1916
1951
  id: server.url,
1917
1952
  title: server.description,
1918
- description: server.url,
1953
+ description: getServerUrlWithVariableValues(server, serverVariables),
1919
1954
  value: server.url,
1920
1955
  })),
1921
1956
  ],
@@ -1925,6 +1960,21 @@ const ServersDropdown = ({ servers }) => {
1925
1960
  };
1926
1961
  ServersDropdown.displayName = 'ServersDropdown';
1927
1962
 
1963
+ const VariableEditor = ({ variable, value, onChange }) => {
1964
+ const inputId = useUniqueId(`id_${variable.name}_`);
1965
+ return (React.createElement(React.Fragment, null,
1966
+ React.createElement(Text, { as: "label", "aria-hidden": "true", "data-testid": "param-label", htmlFor: inputId, fontSize: "base" }, variable.name),
1967
+ React.createElement(Text, { mx: 3 }, ":"),
1968
+ React.createElement("div", null, variable.enum ? (React.createElement(Select, { flex: 1, "aria-label": variable.name, options: variable.enum.map(s => ({ value: s })), value: value || variable.default, onChange: onChange })) : (React.createElement(Flex, { flex: 1 },
1969
+ React.createElement(Input, { id: inputId, "aria-label": variable.name, appearance: 'minimal', flex: 1, placeholder: variable.default, type: "text", required: true, intent: 'default', value: value || '', onChange: e => onChange && onChange(e.currentTarget.value) }))))));
1970
+ };
1971
+
1972
+ const ServerVariables = ({ variables, values, onChangeValue }) => {
1973
+ return (React.createElement(Panel, { defaultIsOpen: true },
1974
+ React.createElement(Panel.Titlebar, null, "Server Variables"),
1975
+ React.createElement(Panel.Content, { className: "sl-overflow-y-auto ParameterGrid ServerVariablesContent" }, variables.map(variable => (React.createElement(VariableEditor, { key: variable.name, variable: variable, value: values[variable.name], onChange: (value) => onChangeValue(variable.name, String(value)) }))))));
1976
+ };
1977
+
1928
1978
  const defaultServers = [];
1929
1979
  const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embeddedInMd = false, tryItCredentialsPolicy, corsProxy, }) => {
1930
1980
  var _a, _b, _c, _d, _e, _f, _g;
@@ -1941,10 +1991,12 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1941
1991
  const [textRequestBody, setTextRequestBody] = useTextRequestBodyState(mediaTypeContent);
1942
1992
  const [operationAuthValue, setOperationAuthValue] = usePersistedSecuritySchemeWithValues();
1943
1993
  const servers = React.useMemo(() => {
1944
- return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, true);
1994
+ return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, false);
1945
1995
  }, [httpOperation.servers, mockUrl]);
1946
1996
  const firstServer = servers[0] || null;
1947
1997
  const [chosenServer, setChosenServer] = useAtom(chosenServerAtom);
1998
+ const serverVariables = getServerVariables(chosenServer);
1999
+ const { serverVariables: serverVariableValues, updateServerVariableValue } = useServerVariables();
1948
2000
  const isMockingEnabled = mockUrl && (chosenServer === null || chosenServer === void 0 ? void 0 : chosenServer.url) === mockUrl;
1949
2001
  const hasRequiredButEmptyParameters = allParameters.some(parameter => parameter.required && !parameterValuesWithDefaults[parameter.name]);
1950
2002
  const getValues = () => Object.keys(bodyParameterValues)
@@ -1966,7 +2018,8 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1966
2018
  React.useEffect(() => {
1967
2019
  let isMounted = true;
1968
2020
  if (onRequestChange || embeddedInMd) {
1969
- buildHarRequest(Object.assign(Object.assign({ mediaTypeContent, parameterValues: parameterValuesWithDefaults, httpOperation, bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, auth: operationAuthValue }, (isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) })), { chosenServer,
2021
+ buildHarRequest(Object.assign(Object.assign({ mediaTypeContent, parameterValues: parameterValuesWithDefaults, serverVariableValues,
2022
+ httpOperation, bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, auth: operationAuthValue }, (isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) })), { chosenServer,
1970
2023
  corsProxy })).then(request => {
1971
2024
  if (isMounted) {
1972
2025
  if (onRequestChange) {
@@ -1986,6 +2039,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1986
2039
  parameterValuesWithDefaults,
1987
2040
  formDataState.isFormDataBody,
1988
2041
  bodyParameterValues,
2042
+ serverVariableValues,
1989
2043
  isAllowedEmptyValues,
1990
2044
  textRequestBody,
1991
2045
  operationAuthValue,
@@ -2003,6 +2057,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2003
2057
  const mockData = isMockingEnabled ? getMockData(mockUrl, httpOperation, mockingOptions) : undefined;
2004
2058
  const request = yield buildFetchRequest({
2005
2059
  parameterValues: parameterValuesWithDefaults,
2060
+ serverVariableValues,
2006
2061
  httpOperation,
2007
2062
  mediaTypeContent,
2008
2063
  bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody,
@@ -2043,6 +2098,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2043
2098
  const isOnlySendButton = !((_d = httpOperation.security) === null || _d === void 0 ? void 0 : _d.length) && !allParameters.length && !formDataState.isFormDataBody && !mediaTypeContent;
2044
2099
  const tryItPanelContents = (React.createElement(React.Fragment, null,
2045
2100
  ((_e = httpOperation.security) === null || _e === void 0 ? void 0 : _e.length) ? (React.createElement(TryItAuth, { onChange: setOperationAuthValue, operationSecurityScheme: (_f = httpOperation.security) !== null && _f !== void 0 ? _f : [], value: operationAuthValue })) : null,
2101
+ serverVariables.length > 0 && (React.createElement(ServerVariables, { variables: serverVariables, values: serverVariableValues, onChangeValue: updateServerVariableValue })),
2046
2102
  allParameters.length > 0 && (React.createElement(OperationParameters, { parameters: allParameters, values: parameterValuesWithDefaults, onChangeValue: updateParameterValue, validate: validateParameters })),
2047
2103
  formDataState.isFormDataBody ? (React.createElement(FormDataBody, { specification: formDataState.bodySpecification, values: bodyParameterValues, onChangeValues: setBodyParameterValues, onChangeParameterAllow: setAllowedEmptyValues, isAllowedEmptyValues: isAllowedEmptyValues })) : mediaTypeContent ? (React.createElement(RequestBody, { examples: (_g = mediaTypeContent.examples) !== null && _g !== void 0 ? _g : [], requestBody: textRequestBody, onChange: setTextRequestBody })) : null,
2048
2104
  React.createElement(Panel.Content, { className: "SendButtonHolder", mt: 4, pt: !isOnlySendButton && !embeddedInMd ? 0 : undefined },
@@ -2370,20 +2426,41 @@ const SecuritySchemes$1 = ({ schemes }) => {
2370
2426
  React.createElement(NodeAnnotation, { change: nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: scheme.id }) }))))));
2371
2427
  };
2372
2428
 
2373
- const Responses = ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange }) => {
2429
+ const Responses = ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange, isCompact, }) => {
2374
2430
  var _a, _b;
2375
2431
  const responses = sortBy(uniqBy(unsortedResponses, r => r.code), r => r.code);
2376
2432
  const [activeResponseId, setActiveResponseId] = React.useState((_b = (_a = responses[0]) === null || _a === void 0 ? void 0 : _a.code) !== null && _b !== void 0 ? _b : '');
2433
+ const { isOpen, open, close } = useModalState();
2434
+ const onSelectionChange = React.useCallback(keys => {
2435
+ const selectedId = keys.values().next().value;
2436
+ const selectedResponse = responses === null || responses === void 0 ? void 0 : responses.find(response => response.id === selectedId);
2437
+ if (selectedResponse) {
2438
+ setActiveResponseId(selectedResponse.code);
2439
+ close();
2440
+ }
2441
+ }, [responses, setActiveResponseId, close]);
2377
2442
  React.useEffect(() => {
2378
2443
  onStatusCodeChange(activeResponseId);
2379
2444
  }, [activeResponseId]);
2380
2445
  if (!responses.length)
2381
2446
  return null;
2447
+ const response = responses.find(r => r.code === activeResponseId) || responses[0];
2448
+ const compactResponses = (React.createElement(React.Fragment, null,
2449
+ React.createElement(Button, { onPress: open, iconRight: React.createElement(Icon, { icon: "chevron-down", color: "var(--color-border-button)" }), style: {
2450
+ color: `var(--color-${codeToIntentVal(activeResponseId)})`,
2451
+ } }, activeResponseId),
2452
+ React.createElement(Modal, { title: "Response Code", isOpen: isOpen, onClose: close, size: "sm", footer: React.createElement(HStack, { justifyContent: "end" },
2453
+ React.createElement(Button, { onPress: close, intent: "default", appearance: "primary" }, "Close")) },
2454
+ React.createElement(ListBox, { "aria-label": "Response Code", overflowY: "auto", m: -5, items: responses, selectionMode: "single", onSelectionChange: onSelectionChange }, (response) => (React.createElement(ListBoxItem, { key: response.id },
2455
+ React.createElement(Box, { p: 3, bg: { hover: 'primary-tint' } },
2456
+ React.createElement(Flex, { w: "2xl", align: "center", justify: "end" },
2457
+ response.code === activeResponseId && React.createElement(Box, { as: Icon, icon: "check" }),
2458
+ React.createElement(Text, { ml: 3, fontWeight: "medium" }, response.code)))))))));
2459
+ const tabResponses = (React.createElement(TabList, { density: "compact" }, responses.map(({ code }) => (React.createElement(Tab, { key: code, id: code, intent: codeToIntentVal(code) }, code)))));
2382
2460
  return (React.createElement(VStack, { spacing: 8, as: Tabs, selectedId: activeResponseId, onChange: setActiveResponseId, appearance: "pill" },
2383
- React.createElement(SectionTitle, { title: "Responses" },
2384
- React.createElement(TabList, { density: "compact" }, responses.map(({ code }) => (React.createElement(Tab, { key: code, id: code, intent: codeToIntentVal(code) }, code))))),
2385
- React.createElement(TabPanels, { p: 0 }, responses.map(response => (React.createElement(TabPanel, { key: response.code, id: response.code },
2386
- React.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange })))))));
2461
+ React.createElement(SectionTitle, { title: "Responses" }, isCompact ? compactResponses : tabResponses),
2462
+ isCompact ? (React.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange })) : (React.createElement(TabPanels, { p: 0 }, responses.map(response => (React.createElement(TabPanel, { key: response.code, id: response.code },
2463
+ React.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange }))))))));
2387
2464
  };
2388
2465
  Responses.displayName = 'HttpOperation.Responses';
2389
2466
  const Response = ({ response, onMediaTypeChange }) => {
@@ -2428,6 +2505,7 @@ const codeToIntentVal = (code) => {
2428
2505
  const HttpOperationComponent = React.memo(({ className, data: unresolvedData, layoutOptions, tryItCredentialsPolicy, tryItCorsProxy }) => {
2429
2506
  const { nodeHasChanged } = useOptionsCtx();
2430
2507
  const data = useResolvedObject(unresolvedData);
2508
+ const { ref: layoutRef, isCompact } = useIsCompact(layoutOptions);
2431
2509
  const mocking = React.useContext(MockingContext);
2432
2510
  const isDeprecated = !!data.deprecated;
2433
2511
  const isInternal = !!data.internal;
@@ -2437,15 +2515,16 @@ const HttpOperationComponent = React.memo(({ className, data: unresolvedData, la
2437
2515
  const prettyName = (data.summary || data.iid || '').trim();
2438
2516
  const hasBadges = isDeprecated || isInternal;
2439
2517
  const header = (React.createElement(OperationHeader, { id: data.id, method: data.method, path: data.path, noHeading: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.noHeading, hasBadges: hasBadges, name: prettyName, isDeprecated: isDeprecated, isInternal: isInternal }));
2518
+ const tryItPanel = !(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryItPanel) && (React.createElement(TryItWithRequestSamples, { httpOperation: data, responseMediaType: responseMediaType, responseStatusCode: responseStatusCode, requestBodyIndex: requestBodyIndex, hideTryIt: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryIt, tryItCredentialsPolicy: tryItCredentialsPolicy, mockUrl: mocking.hideMocking ? undefined : mocking.mockUrl, corsProxy: tryItCorsProxy }));
2440
2519
  const descriptionChanged = nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: data.id, attr: 'description' });
2441
2520
  const description = (React.createElement(VStack, { spacing: 10 },
2442
2521
  data.description && (React.createElement(Box, { pos: "relative" },
2443
2522
  React.createElement(MarkdownViewer, { className: "HttpOperation__Description", markdown: data.description }),
2444
2523
  React.createElement(NodeAnnotation, { change: descriptionChanged }))),
2445
2524
  React.createElement(Request, { onChange: setTextRequestBodyIndex, operation: data }),
2446
- data.responses && (React.createElement(Responses, { responses: data.responses, onMediaTypeChange: setResponseMediaType, onStatusCodeChange: setResponseStatusCode }))));
2447
- const tryItPanel = !(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryItPanel) && (React.createElement(TryItWithRequestSamples, { httpOperation: data, responseMediaType: responseMediaType, responseStatusCode: responseStatusCode, requestBodyIndex: requestBodyIndex, hideTryIt: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryIt, tryItCredentialsPolicy: tryItCredentialsPolicy, mockUrl: mocking.hideMocking ? undefined : mocking.mockUrl, corsProxy: tryItCorsProxy }));
2448
- return (React.createElement(TwoColumnLayout, { className: cn('HttpOperation', className), header: header, left: description, right: tryItPanel }));
2525
+ data.responses && (React.createElement(Responses, { responses: data.responses, onMediaTypeChange: setResponseMediaType, onStatusCodeChange: setResponseStatusCode, isCompact: isCompact })),
2526
+ isCompact && tryItPanel));
2527
+ return (React.createElement(TwoColumnLayout, { ref: layoutRef, className: cn('HttpOperation', className), header: header, left: description, right: !isCompact && tryItPanel }));
2449
2528
  });
2450
2529
  HttpOperationComponent.displayName = 'HttpOperation.Component';
2451
2530
  const HttpOperation = withErrorBoundary(HttpOperationComponent, {
@@ -2677,22 +2756,6 @@ const HttpServiceComponent = React.memo(({ data: unresolvedData, location = {},
2677
2756
  HttpServiceComponent.displayName = 'HttpService.Component';
2678
2757
  const HttpService = withErrorBoundary(HttpServiceComponent, { recoverableProps: ['data'] });
2679
2758
 
2680
- const getBreakpoints = (compact) => {
2681
- if (!compact)
2682
- return undefined;
2683
- if (typeof compact === 'number') {
2684
- return [
2685
- ['compact', compact],
2686
- ['regular', Infinity],
2687
- ];
2688
- }
2689
- return [['compact', Infinity]];
2690
- };
2691
- function useIsCompact(layoutOptions) {
2692
- const { ref, breakpoint } = useBreakpoints(getBreakpoints(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.compact));
2693
- return { ref: ref, isCompact: breakpoint === 'compact' };
2694
- }
2695
-
2696
2759
  const ModelComponent = ({ data: unresolvedData, className, nodeTitle, layoutOptions, exportProps, }) => {
2697
2760
  var _a, _b;
2698
2761
  const resolveRef = useSchemaInlineRefResolver();
package/index.js CHANGED
@@ -18,7 +18,6 @@ var markdownViewer = require('@stoplight/markdown-viewer');
18
18
  var cn = require('classnames');
19
19
  var utils = require('jotai/utils');
20
20
  var jotai = require('jotai');
21
- var isEmpty = require('lodash/isEmpty.js');
22
21
  var URI = require('urijs');
23
22
  var mosaicCodeViewer = require('@stoplight/mosaic-code-viewer');
24
23
  var httpsnippetLite = require('httpsnippet-lite');
@@ -44,6 +43,7 @@ var entries = require('lodash/entries.js');
44
43
  var keys = require('lodash/keys.js');
45
44
  var jsonSchemaViewer = require('@stoplight/json-schema-viewer');
46
45
  var sortBy = require('lodash/sortBy.js');
46
+ var isEmpty = require('lodash/isEmpty.js');
47
47
  var isNil = require('lodash/isNil.js');
48
48
  var omitBy = require('lodash/omitBy.js');
49
49
  var reactRouterHashLink = require('react-router-hash-link');
@@ -79,7 +79,6 @@ var isArray__default = /*#__PURE__*/_interopDefaultLegacy(isArray);
79
79
  var isPlainObject__default = /*#__PURE__*/_interopDefaultLegacy(isPlainObject);
80
80
  var isObject__default = /*#__PURE__*/_interopDefaultLegacy(isObject);
81
81
  var cn__default = /*#__PURE__*/_interopDefaultLegacy(cn);
82
- var isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(isEmpty);
83
82
  var URI__default = /*#__PURE__*/_interopDefaultLegacy(URI);
84
83
  var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
85
84
  var capitalize__default = /*#__PURE__*/_interopDefaultLegacy(capitalize);
@@ -100,6 +99,7 @@ var formatXml__default = /*#__PURE__*/_interopDefaultLegacy(formatXml);
100
99
  var entries__default = /*#__PURE__*/_interopDefaultLegacy(entries);
101
100
  var keys__default = /*#__PURE__*/_interopDefaultLegacy(keys);
102
101
  var sortBy__default = /*#__PURE__*/_interopDefaultLegacy(sortBy);
102
+ var isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(isEmpty);
103
103
  var isNil__default = /*#__PURE__*/_interopDefaultLegacy(isNil);
104
104
  var omitBy__default = /*#__PURE__*/_interopDefaultLegacy(omitBy);
105
105
  var $RefParser__default = /*#__PURE__*/_interopDefaultLegacy($RefParser);
@@ -542,6 +542,22 @@ function useChosenServerUrl(chosenServerUrl) {
542
542
  };
543
543
  }
544
544
 
545
+ const getBreakpoints = (compact) => {
546
+ if (!compact)
547
+ return undefined;
548
+ if (typeof compact === 'number') {
549
+ return [
550
+ ['compact', compact],
551
+ ['regular', Infinity],
552
+ ];
553
+ }
554
+ return [['compact', Infinity]];
555
+ };
556
+ function useIsCompact(layoutOptions) {
557
+ const { ref, breakpoint } = mosaic.useBreakpoints(getBreakpoints(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.compact));
558
+ return { ref: ref, isCompact: breakpoint === 'compact' };
559
+ }
560
+
545
561
  const chosenServerAtom = jotai.atom(undefined);
546
562
 
547
563
  function isValidServer(server) {
@@ -552,10 +568,7 @@ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
552
568
  const fallbackDescription = originalServers.length === 1 ? 'Live Server' : `Server ${i + 1}`;
553
569
  let url = server.url;
554
570
  if (inlineDefaults) {
555
- url = getServerUrlWithDefaultValues(server);
556
- }
557
- else if (isEmpty__default["default"](server.variables)) {
558
- url = resolveUrl(server.url);
571
+ url = getServerUrlWithVariableValues(server, {});
559
572
  }
560
573
  return Object.assign(Object.assign({}, server), { url, description: server.description || fallbackDescription });
561
574
  });
@@ -568,7 +581,18 @@ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
568
581
  }
569
582
  return servers.filter(isValidServer);
570
583
  };
584
+ const getServerVariables = (server) => {
585
+ var _a;
586
+ return Object.entries((_a = server === null || server === void 0 ? void 0 : server.variables) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => ({
587
+ name: key,
588
+ default: value.default,
589
+ description: value.description,
590
+ enum: value.enum,
591
+ }));
592
+ };
571
593
  function resolveUrl(urlString) {
594
+ if (urlString === null)
595
+ return null;
572
596
  let url;
573
597
  try {
574
598
  url = URI__default["default"](urlString);
@@ -588,14 +612,15 @@ function resolveUrl(urlString) {
588
612
  }
589
613
  return null;
590
614
  }
591
- const getServerUrlWithDefaultValues = (server) => {
615
+ const getServerUrlWithVariableValues = (server, values) => {
592
616
  var _a;
593
617
  let urlString = server.url;
594
618
  const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
595
619
  variables.forEach(([variableName, variableInfo]) => {
596
- urlString = urlString.replaceAll(`{${variableName}}`, variableInfo.default);
620
+ var _a;
621
+ urlString = urlString.replaceAll(`{${variableName}}`, (_a = values[variableName]) !== null && _a !== void 0 ? _a : variableInfo.default);
597
622
  });
598
- return resolveUrl(urlString);
623
+ return urlString;
599
624
  };
600
625
 
601
626
  const persistAtom = (key, atomInstance) => {
@@ -1474,11 +1499,11 @@ const useTextRequestBodyState = (mediaTypeContent) => {
1474
1499
  };
1475
1500
 
1476
1501
  const nameAndValueObjectToPair = ({ name, value }) => [name, value];
1477
- const getServerUrl = ({ chosenServer, httpOperation, mockData, corsProxy, }) => {
1502
+ const getServerUrl = ({ chosenServer, httpOperation, mockData, corsProxy, serverVariableValues, }) => {
1478
1503
  var _a;
1479
1504
  const server = chosenServer || ((_a = httpOperation.servers) === null || _a === void 0 ? void 0 : _a[0]);
1480
- const chosenServerUrl = server && getServerUrlWithDefaultValues(server);
1481
- const serverUrl = (mockData === null || mockData === void 0 ? void 0 : mockData.url) || chosenServerUrl || window.location.origin;
1505
+ const chosenServerUrl = server && getServerUrlWithVariableValues(server, serverVariableValues);
1506
+ const serverUrl = resolveUrl((mockData === null || mockData === void 0 ? void 0 : mockData.url) || chosenServerUrl || window.location.origin);
1482
1507
  if (corsProxy && !mockData) {
1483
1508
  return `${corsProxy}${serverUrl}`;
1484
1509
  }
@@ -1555,10 +1580,10 @@ const getQueryParams = ({ httpOperation, parameterValues, }) => {
1555
1580
  return acc;
1556
1581
  }, []);
1557
1582
  };
1558
- function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, mockData, auth, chosenServer, credentials = 'omit', corsProxy, }) {
1583
+ function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, serverVariableValues, mockData, auth, chosenServer, credentials = 'omit', corsProxy, }) {
1559
1584
  var _a, _b, _c;
1560
1585
  return tslib.__awaiter(this, void 0, void 0, function* () {
1561
- const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy });
1586
+ const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy, serverVariableValues });
1562
1587
  const shouldIncludeBody = ['PUT', 'POST', 'PATCH'].includes(httpOperation.method.toUpperCase()) && bodyInput !== undefined;
1563
1588
  const queryParams = getQueryParams({ httpOperation, parameterValues });
1564
1589
  const rawHeaders = filterOutAuthorizationParams((_b = (_a = httpOperation.request) === null || _a === void 0 ? void 0 : _a.headers) !== null && _b !== void 0 ? _b : [], httpOperation.security)
@@ -1631,10 +1656,10 @@ const runAuthRequestEhancements = (auth, queryParams, headers) => {
1631
1656
  }
1632
1657
  return [newQueryParams, newHeaders];
1633
1658
  };
1634
- function buildHarRequest({ httpOperation, bodyInput, parameterValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }) {
1659
+ function buildHarRequest({ httpOperation, bodyInput, parameterValues, serverVariableValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }) {
1635
1660
  var _a, _b, _c, _d;
1636
1661
  return tslib.__awaiter(this, void 0, void 0, function* () {
1637
- const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy });
1662
+ const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy, serverVariableValues });
1638
1663
  const mimeType = (_a = mediaTypeContent === null || mediaTypeContent === void 0 ? void 0 : mediaTypeContent.mediaType) !== null && _a !== void 0 ? _a : 'application/json';
1639
1664
  const shouldIncludeBody = ['PUT', 'POST', 'PATCH'].includes(httpOperation.method.toUpperCase()) && bodyInput !== undefined;
1640
1665
  const queryParams = getQueryParams({ httpOperation, parameterValues });
@@ -1956,8 +1981,18 @@ class NetworkError extends Error {
1956
1981
  }
1957
1982
  const isNetworkError = (error) => error instanceof NetworkError;
1958
1983
 
1984
+ const persistedServerVariableValuesAtom = jotai.atom({});
1985
+ const useServerVariables = () => {
1986
+ const [serverVariables, setPersistedServerVariableValues] = jotai.useAtom(persistedServerVariableValuesAtom);
1987
+ const updateServerVariableValue = (name, value) => {
1988
+ setPersistedServerVariableValues(Object.assign({}, serverVariables, { [name]: value }));
1989
+ };
1990
+ return { serverVariables, updateServerVariableValue };
1991
+ };
1992
+
1959
1993
  const ServersDropdown = ({ servers }) => {
1960
1994
  const [chosenServer, setChosenServer] = jotai.useAtom(chosenServerAtom);
1995
+ const { serverVariables } = useServerVariables();
1961
1996
  const serverItems = [
1962
1997
  {
1963
1998
  type: 'option_group',
@@ -1971,7 +2006,7 @@ const ServersDropdown = ({ servers }) => {
1971
2006
  ...servers.map((server, i) => ({
1972
2007
  id: server.url,
1973
2008
  title: server.description,
1974
- description: server.url,
2009
+ description: getServerUrlWithVariableValues(server, serverVariables),
1975
2010
  value: server.url,
1976
2011
  })),
1977
2012
  ],
@@ -1981,6 +2016,21 @@ const ServersDropdown = ({ servers }) => {
1981
2016
  };
1982
2017
  ServersDropdown.displayName = 'ServersDropdown';
1983
2018
 
2019
+ const VariableEditor = ({ variable, value, onChange }) => {
2020
+ const inputId = useUniqueId(`id_${variable.name}_`);
2021
+ return (React__namespace.createElement(React__namespace.Fragment, null,
2022
+ React__namespace.createElement(mosaic.Text, { as: "label", "aria-hidden": "true", "data-testid": "param-label", htmlFor: inputId, fontSize: "base" }, variable.name),
2023
+ React__namespace.createElement(mosaic.Text, { mx: 3 }, ":"),
2024
+ React__namespace.createElement("div", null, variable.enum ? (React__namespace.createElement(mosaic.Select, { flex: 1, "aria-label": variable.name, options: variable.enum.map(s => ({ value: s })), value: value || variable.default, onChange: onChange })) : (React__namespace.createElement(mosaic.Flex, { flex: 1 },
2025
+ React__namespace.createElement(mosaic.Input, { id: inputId, "aria-label": variable.name, appearance: 'minimal', flex: 1, placeholder: variable.default, type: "text", required: true, intent: 'default', value: value || '', onChange: e => onChange && onChange(e.currentTarget.value) }))))));
2026
+ };
2027
+
2028
+ const ServerVariables = ({ variables, values, onChangeValue }) => {
2029
+ return (React__namespace.createElement(mosaic.Panel, { defaultIsOpen: true },
2030
+ React__namespace.createElement(mosaic.Panel.Titlebar, null, "Server Variables"),
2031
+ React__namespace.createElement(mosaic.Panel.Content, { className: "sl-overflow-y-auto ParameterGrid ServerVariablesContent" }, variables.map(variable => (React__namespace.createElement(VariableEditor, { key: variable.name, variable: variable, value: values[variable.name], onChange: (value) => onChangeValue(variable.name, String(value)) }))))));
2032
+ };
2033
+
1984
2034
  const defaultServers = [];
1985
2035
  const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embeddedInMd = false, tryItCredentialsPolicy, corsProxy, }) => {
1986
2036
  var _a, _b, _c, _d, _e, _f, _g;
@@ -1997,10 +2047,12 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1997
2047
  const [textRequestBody, setTextRequestBody] = useTextRequestBodyState(mediaTypeContent);
1998
2048
  const [operationAuthValue, setOperationAuthValue] = usePersistedSecuritySchemeWithValues();
1999
2049
  const servers = React__namespace.useMemo(() => {
2000
- return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, true);
2050
+ return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, false);
2001
2051
  }, [httpOperation.servers, mockUrl]);
2002
2052
  const firstServer = servers[0] || null;
2003
2053
  const [chosenServer, setChosenServer] = jotai.useAtom(chosenServerAtom);
2054
+ const serverVariables = getServerVariables(chosenServer);
2055
+ const { serverVariables: serverVariableValues, updateServerVariableValue } = useServerVariables();
2004
2056
  const isMockingEnabled = mockUrl && (chosenServer === null || chosenServer === void 0 ? void 0 : chosenServer.url) === mockUrl;
2005
2057
  const hasRequiredButEmptyParameters = allParameters.some(parameter => parameter.required && !parameterValuesWithDefaults[parameter.name]);
2006
2058
  const getValues = () => Object.keys(bodyParameterValues)
@@ -2022,7 +2074,8 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2022
2074
  React__namespace.useEffect(() => {
2023
2075
  let isMounted = true;
2024
2076
  if (onRequestChange || embeddedInMd) {
2025
- buildHarRequest(Object.assign(Object.assign({ mediaTypeContent, parameterValues: parameterValuesWithDefaults, httpOperation, bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, auth: operationAuthValue }, (isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) })), { chosenServer,
2077
+ buildHarRequest(Object.assign(Object.assign({ mediaTypeContent, parameterValues: parameterValuesWithDefaults, serverVariableValues,
2078
+ httpOperation, bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, auth: operationAuthValue }, (isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) })), { chosenServer,
2026
2079
  corsProxy })).then(request => {
2027
2080
  if (isMounted) {
2028
2081
  if (onRequestChange) {
@@ -2042,6 +2095,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2042
2095
  parameterValuesWithDefaults,
2043
2096
  formDataState.isFormDataBody,
2044
2097
  bodyParameterValues,
2098
+ serverVariableValues,
2045
2099
  isAllowedEmptyValues,
2046
2100
  textRequestBody,
2047
2101
  operationAuthValue,
@@ -2059,6 +2113,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2059
2113
  const mockData = isMockingEnabled ? getMockData(mockUrl, httpOperation, mockingOptions) : undefined;
2060
2114
  const request = yield buildFetchRequest({
2061
2115
  parameterValues: parameterValuesWithDefaults,
2116
+ serverVariableValues,
2062
2117
  httpOperation,
2063
2118
  mediaTypeContent,
2064
2119
  bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody,
@@ -2099,6 +2154,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2099
2154
  const isOnlySendButton = !((_d = httpOperation.security) === null || _d === void 0 ? void 0 : _d.length) && !allParameters.length && !formDataState.isFormDataBody && !mediaTypeContent;
2100
2155
  const tryItPanelContents = (React__namespace.createElement(React__namespace.Fragment, null,
2101
2156
  ((_e = httpOperation.security) === null || _e === void 0 ? void 0 : _e.length) ? (React__namespace.createElement(TryItAuth, { onChange: setOperationAuthValue, operationSecurityScheme: (_f = httpOperation.security) !== null && _f !== void 0 ? _f : [], value: operationAuthValue })) : null,
2157
+ serverVariables.length > 0 && (React__namespace.createElement(ServerVariables, { variables: serverVariables, values: serverVariableValues, onChangeValue: updateServerVariableValue })),
2102
2158
  allParameters.length > 0 && (React__namespace.createElement(OperationParameters, { parameters: allParameters, values: parameterValuesWithDefaults, onChangeValue: updateParameterValue, validate: validateParameters })),
2103
2159
  formDataState.isFormDataBody ? (React__namespace.createElement(FormDataBody, { specification: formDataState.bodySpecification, values: bodyParameterValues, onChangeValues: setBodyParameterValues, onChangeParameterAllow: setAllowedEmptyValues, isAllowedEmptyValues: isAllowedEmptyValues })) : mediaTypeContent ? (React__namespace.createElement(RequestBody, { examples: (_g = mediaTypeContent.examples) !== null && _g !== void 0 ? _g : [], requestBody: textRequestBody, onChange: setTextRequestBody })) : null,
2104
2160
  React__namespace.createElement(mosaic.Panel.Content, { className: "SendButtonHolder", mt: 4, pt: !isOnlySendButton && !embeddedInMd ? 0 : undefined },
@@ -2426,20 +2482,41 @@ const SecuritySchemes$1 = ({ schemes }) => {
2426
2482
  React__namespace.createElement(mosaic.NodeAnnotation, { change: nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: scheme.id }) }))))));
2427
2483
  };
2428
2484
 
2429
- const Responses = ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange }) => {
2485
+ const Responses = ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange, isCompact, }) => {
2430
2486
  var _a, _b;
2431
2487
  const responses = sortBy__default["default"](uniqBy__default["default"](unsortedResponses, r => r.code), r => r.code);
2432
2488
  const [activeResponseId, setActiveResponseId] = React__namespace.useState((_b = (_a = responses[0]) === null || _a === void 0 ? void 0 : _a.code) !== null && _b !== void 0 ? _b : '');
2489
+ const { isOpen, open, close } = mosaic.useModalState();
2490
+ const onSelectionChange = React__namespace.useCallback(keys => {
2491
+ const selectedId = keys.values().next().value;
2492
+ const selectedResponse = responses === null || responses === void 0 ? void 0 : responses.find(response => response.id === selectedId);
2493
+ if (selectedResponse) {
2494
+ setActiveResponseId(selectedResponse.code);
2495
+ close();
2496
+ }
2497
+ }, [responses, setActiveResponseId, close]);
2433
2498
  React__namespace.useEffect(() => {
2434
2499
  onStatusCodeChange(activeResponseId);
2435
2500
  }, [activeResponseId]);
2436
2501
  if (!responses.length)
2437
2502
  return null;
2503
+ const response = responses.find(r => r.code === activeResponseId) || responses[0];
2504
+ const compactResponses = (React__namespace.createElement(React__namespace.Fragment, null,
2505
+ React__namespace.createElement(mosaic.Button, { onPress: open, iconRight: React__namespace.createElement(mosaic.Icon, { icon: "chevron-down", color: "var(--color-border-button)" }), style: {
2506
+ color: `var(--color-${codeToIntentVal(activeResponseId)})`,
2507
+ } }, activeResponseId),
2508
+ React__namespace.createElement(mosaic.Modal, { title: "Response Code", isOpen: isOpen, onClose: close, size: "sm", footer: React__namespace.createElement(mosaic.HStack, { justifyContent: "end" },
2509
+ React__namespace.createElement(mosaic.Button, { onPress: close, intent: "default", appearance: "primary" }, "Close")) },
2510
+ React__namespace.createElement(mosaic.ListBox, { "aria-label": "Response Code", overflowY: "auto", m: -5, items: responses, selectionMode: "single", onSelectionChange: onSelectionChange }, (response) => (React__namespace.createElement(mosaic.ListBoxItem, { key: response.id },
2511
+ React__namespace.createElement(mosaic.Box, { p: 3, bg: { hover: 'primary-tint' } },
2512
+ React__namespace.createElement(mosaic.Flex, { w: "2xl", align: "center", justify: "end" },
2513
+ response.code === activeResponseId && React__namespace.createElement(mosaic.Box, { as: mosaic.Icon, icon: "check" }),
2514
+ React__namespace.createElement(mosaic.Text, { ml: 3, fontWeight: "medium" }, response.code)))))))));
2515
+ const tabResponses = (React__namespace.createElement(mosaic.TabList, { density: "compact" }, responses.map(({ code }) => (React__namespace.createElement(mosaic.Tab, { key: code, id: code, intent: codeToIntentVal(code) }, code)))));
2438
2516
  return (React__namespace.createElement(mosaic.VStack, { spacing: 8, as: mosaic.Tabs, selectedId: activeResponseId, onChange: setActiveResponseId, appearance: "pill" },
2439
- React__namespace.createElement(SectionTitle, { title: "Responses" },
2440
- React__namespace.createElement(mosaic.TabList, { density: "compact" }, responses.map(({ code }) => (React__namespace.createElement(mosaic.Tab, { key: code, id: code, intent: codeToIntentVal(code) }, code))))),
2441
- React__namespace.createElement(mosaic.TabPanels, { p: 0 }, responses.map(response => (React__namespace.createElement(mosaic.TabPanel, { key: response.code, id: response.code },
2442
- React__namespace.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange })))))));
2517
+ React__namespace.createElement(SectionTitle, { title: "Responses" }, isCompact ? compactResponses : tabResponses),
2518
+ isCompact ? (React__namespace.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange })) : (React__namespace.createElement(mosaic.TabPanels, { p: 0 }, responses.map(response => (React__namespace.createElement(mosaic.TabPanel, { key: response.code, id: response.code },
2519
+ React__namespace.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange }))))))));
2443
2520
  };
2444
2521
  Responses.displayName = 'HttpOperation.Responses';
2445
2522
  const Response = ({ response, onMediaTypeChange }) => {
@@ -2484,6 +2561,7 @@ const codeToIntentVal = (code) => {
2484
2561
  const HttpOperationComponent = React__namespace.memo(({ className, data: unresolvedData, layoutOptions, tryItCredentialsPolicy, tryItCorsProxy }) => {
2485
2562
  const { nodeHasChanged } = useOptionsCtx();
2486
2563
  const data = useResolvedObject(unresolvedData);
2564
+ const { ref: layoutRef, isCompact } = useIsCompact(layoutOptions);
2487
2565
  const mocking = React__namespace.useContext(MockingContext);
2488
2566
  const isDeprecated = !!data.deprecated;
2489
2567
  const isInternal = !!data.internal;
@@ -2493,15 +2571,16 @@ const HttpOperationComponent = React__namespace.memo(({ className, data: unresol
2493
2571
  const prettyName = (data.summary || data.iid || '').trim();
2494
2572
  const hasBadges = isDeprecated || isInternal;
2495
2573
  const header = (React__namespace.createElement(OperationHeader, { id: data.id, method: data.method, path: data.path, noHeading: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.noHeading, hasBadges: hasBadges, name: prettyName, isDeprecated: isDeprecated, isInternal: isInternal }));
2574
+ const tryItPanel = !(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryItPanel) && (React__namespace.createElement(TryItWithRequestSamples, { httpOperation: data, responseMediaType: responseMediaType, responseStatusCode: responseStatusCode, requestBodyIndex: requestBodyIndex, hideTryIt: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryIt, tryItCredentialsPolicy: tryItCredentialsPolicy, mockUrl: mocking.hideMocking ? undefined : mocking.mockUrl, corsProxy: tryItCorsProxy }));
2496
2575
  const descriptionChanged = nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: data.id, attr: 'description' });
2497
2576
  const description = (React__namespace.createElement(mosaic.VStack, { spacing: 10 },
2498
2577
  data.description && (React__namespace.createElement(mosaic.Box, { pos: "relative" },
2499
2578
  React__namespace.createElement(MarkdownViewer, { className: "HttpOperation__Description", markdown: data.description }),
2500
2579
  React__namespace.createElement(mosaic.NodeAnnotation, { change: descriptionChanged }))),
2501
2580
  React__namespace.createElement(Request, { onChange: setTextRequestBodyIndex, operation: data }),
2502
- data.responses && (React__namespace.createElement(Responses, { responses: data.responses, onMediaTypeChange: setResponseMediaType, onStatusCodeChange: setResponseStatusCode }))));
2503
- const tryItPanel = !(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryItPanel) && (React__namespace.createElement(TryItWithRequestSamples, { httpOperation: data, responseMediaType: responseMediaType, responseStatusCode: responseStatusCode, requestBodyIndex: requestBodyIndex, hideTryIt: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryIt, tryItCredentialsPolicy: tryItCredentialsPolicy, mockUrl: mocking.hideMocking ? undefined : mocking.mockUrl, corsProxy: tryItCorsProxy }));
2504
- return (React__namespace.createElement(TwoColumnLayout, { className: cn__default["default"]('HttpOperation', className), header: header, left: description, right: tryItPanel }));
2581
+ data.responses && (React__namespace.createElement(Responses, { responses: data.responses, onMediaTypeChange: setResponseMediaType, onStatusCodeChange: setResponseStatusCode, isCompact: isCompact })),
2582
+ isCompact && tryItPanel));
2583
+ return (React__namespace.createElement(TwoColumnLayout, { ref: layoutRef, className: cn__default["default"]('HttpOperation', className), header: header, left: description, right: !isCompact && tryItPanel }));
2505
2584
  });
2506
2585
  HttpOperationComponent.displayName = 'HttpOperation.Component';
2507
2586
  const HttpOperation = reactErrorBoundary.withErrorBoundary(HttpOperationComponent, {
@@ -2733,22 +2812,6 @@ const HttpServiceComponent = React__namespace.memo(({ data: unresolvedData, loca
2733
2812
  HttpServiceComponent.displayName = 'HttpService.Component';
2734
2813
  const HttpService = reactErrorBoundary.withErrorBoundary(HttpServiceComponent, { recoverableProps: ['data'] });
2735
2814
 
2736
- const getBreakpoints = (compact) => {
2737
- if (!compact)
2738
- return undefined;
2739
- if (typeof compact === 'number') {
2740
- return [
2741
- ['compact', compact],
2742
- ['regular', Infinity],
2743
- ];
2744
- }
2745
- return [['compact', Infinity]];
2746
- };
2747
- function useIsCompact(layoutOptions) {
2748
- const { ref, breakpoint } = mosaic.useBreakpoints(getBreakpoints(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.compact));
2749
- return { ref: ref, isCompact: breakpoint === 'compact' };
2750
- }
2751
-
2752
2815
  const ModelComponent = ({ data: unresolvedData, className, nodeTitle, layoutOptions, exportProps, }) => {
2753
2816
  var _a, _b;
2754
2817
  const resolveRef = useSchemaInlineRefResolver();
package/index.mjs CHANGED
@@ -7,7 +7,7 @@ import isArray from 'lodash/isArray.js';
7
7
  import isPlainObject from 'lodash/isPlainObject.js';
8
8
  import { NodeType, HttpParamStyles } from '@stoplight/types';
9
9
  import { parse } from '@stoplight/yaml';
10
- import { isArray as isArray$1, Box, Panel, CopyButton, Menu, Button, Text, Flex, Input, Icon, Select, FieldButton, Image, Link, useThemeIsDark, HStack, VStack, InvertTheme, Tooltip, Badge, LinkHeading as LinkHeading$1, NodeAnnotation, Tabs, TabList, Tab, TabPanels, TabPanel, Heading, useClipboard, useBreakpoints, useMosaicContext, Provider as Provider$1 } from '@stoplight/mosaic';
10
+ import { isArray as isArray$1, Box, useBreakpoints, Panel, CopyButton, Menu, Button, Text, Flex, Input, Icon, Select, FieldButton, Image, Link, useThemeIsDark, HStack, VStack, InvertTheme, Tooltip, Badge, LinkHeading as LinkHeading$1, NodeAnnotation, useModalState, Modal, ListBox, ListBoxItem, TabList, Tab, Tabs, TabPanels, TabPanel, Heading, useClipboard, useMosaicContext, Provider as Provider$1 } from '@stoplight/mosaic';
11
11
  import isObject from 'lodash/isObject.js';
12
12
  import { withErrorBoundary } from '@stoplight/react-error-boundary';
13
13
  import { useLocation, BrowserRouter, MemoryRouter, HashRouter, StaticRouter, Route } from 'react-router-dom';
@@ -16,7 +16,6 @@ export { DefaultSMDComponents } from '@stoplight/markdown-viewer';
16
16
  import cn from 'classnames';
17
17
  import { atomWithStorage, useAtomValue } from 'jotai/utils';
18
18
  import { atom, useAtom, Provider } from 'jotai';
19
- import isEmpty from 'lodash/isEmpty.js';
20
19
  import URI from 'urijs';
21
20
  import { CodeViewer } from '@stoplight/mosaic-code-viewer';
22
21
  import { isValidTargetId, HTTPSnippet } from 'httpsnippet-lite';
@@ -42,6 +41,7 @@ import entries from 'lodash/entries.js';
42
41
  import keys from 'lodash/keys.js';
43
42
  import { JsonSchemaViewer } from '@stoplight/json-schema-viewer';
44
43
  import sortBy from 'lodash/sortBy.js';
44
+ import isEmpty from 'lodash/isEmpty.js';
45
45
  import isNil from 'lodash/isNil.js';
46
46
  import omitBy from 'lodash/omitBy.js';
47
47
  import { HashLink } from 'react-router-hash-link';
@@ -486,6 +486,22 @@ function useChosenServerUrl(chosenServerUrl) {
486
486
  };
487
487
  }
488
488
 
489
+ const getBreakpoints = (compact) => {
490
+ if (!compact)
491
+ return undefined;
492
+ if (typeof compact === 'number') {
493
+ return [
494
+ ['compact', compact],
495
+ ['regular', Infinity],
496
+ ];
497
+ }
498
+ return [['compact', Infinity]];
499
+ };
500
+ function useIsCompact(layoutOptions) {
501
+ const { ref, breakpoint } = useBreakpoints(getBreakpoints(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.compact));
502
+ return { ref: ref, isCompact: breakpoint === 'compact' };
503
+ }
504
+
489
505
  const chosenServerAtom = atom(undefined);
490
506
 
491
507
  function isValidServer(server) {
@@ -496,10 +512,7 @@ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
496
512
  const fallbackDescription = originalServers.length === 1 ? 'Live Server' : `Server ${i + 1}`;
497
513
  let url = server.url;
498
514
  if (inlineDefaults) {
499
- url = getServerUrlWithDefaultValues(server);
500
- }
501
- else if (isEmpty(server.variables)) {
502
- url = resolveUrl(server.url);
515
+ url = getServerUrlWithVariableValues(server, {});
503
516
  }
504
517
  return Object.assign(Object.assign({}, server), { url, description: server.description || fallbackDescription });
505
518
  });
@@ -512,7 +525,18 @@ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
512
525
  }
513
526
  return servers.filter(isValidServer);
514
527
  };
528
+ const getServerVariables = (server) => {
529
+ var _a;
530
+ return Object.entries((_a = server === null || server === void 0 ? void 0 : server.variables) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => ({
531
+ name: key,
532
+ default: value.default,
533
+ description: value.description,
534
+ enum: value.enum,
535
+ }));
536
+ };
515
537
  function resolveUrl(urlString) {
538
+ if (urlString === null)
539
+ return null;
516
540
  let url;
517
541
  try {
518
542
  url = URI(urlString);
@@ -532,14 +556,15 @@ function resolveUrl(urlString) {
532
556
  }
533
557
  return null;
534
558
  }
535
- const getServerUrlWithDefaultValues = (server) => {
559
+ const getServerUrlWithVariableValues = (server, values) => {
536
560
  var _a;
537
561
  let urlString = server.url;
538
562
  const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
539
563
  variables.forEach(([variableName, variableInfo]) => {
540
- urlString = urlString.replaceAll(`{${variableName}}`, variableInfo.default);
564
+ var _a;
565
+ urlString = urlString.replaceAll(`{${variableName}}`, (_a = values[variableName]) !== null && _a !== void 0 ? _a : variableInfo.default);
541
566
  });
542
- return resolveUrl(urlString);
567
+ return urlString;
543
568
  };
544
569
 
545
570
  const persistAtom = (key, atomInstance) => {
@@ -1418,11 +1443,11 @@ const useTextRequestBodyState = (mediaTypeContent) => {
1418
1443
  };
1419
1444
 
1420
1445
  const nameAndValueObjectToPair = ({ name, value }) => [name, value];
1421
- const getServerUrl = ({ chosenServer, httpOperation, mockData, corsProxy, }) => {
1446
+ const getServerUrl = ({ chosenServer, httpOperation, mockData, corsProxy, serverVariableValues, }) => {
1422
1447
  var _a;
1423
1448
  const server = chosenServer || ((_a = httpOperation.servers) === null || _a === void 0 ? void 0 : _a[0]);
1424
- const chosenServerUrl = server && getServerUrlWithDefaultValues(server);
1425
- const serverUrl = (mockData === null || mockData === void 0 ? void 0 : mockData.url) || chosenServerUrl || window.location.origin;
1449
+ const chosenServerUrl = server && getServerUrlWithVariableValues(server, serverVariableValues);
1450
+ const serverUrl = resolveUrl((mockData === null || mockData === void 0 ? void 0 : mockData.url) || chosenServerUrl || window.location.origin);
1426
1451
  if (corsProxy && !mockData) {
1427
1452
  return `${corsProxy}${serverUrl}`;
1428
1453
  }
@@ -1499,10 +1524,10 @@ const getQueryParams = ({ httpOperation, parameterValues, }) => {
1499
1524
  return acc;
1500
1525
  }, []);
1501
1526
  };
1502
- function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, mockData, auth, chosenServer, credentials = 'omit', corsProxy, }) {
1527
+ function buildFetchRequest({ httpOperation, mediaTypeContent, bodyInput, parameterValues, serverVariableValues, mockData, auth, chosenServer, credentials = 'omit', corsProxy, }) {
1503
1528
  var _a, _b, _c;
1504
1529
  return __awaiter(this, void 0, void 0, function* () {
1505
- const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy });
1530
+ const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy, serverVariableValues });
1506
1531
  const shouldIncludeBody = ['PUT', 'POST', 'PATCH'].includes(httpOperation.method.toUpperCase()) && bodyInput !== undefined;
1507
1532
  const queryParams = getQueryParams({ httpOperation, parameterValues });
1508
1533
  const rawHeaders = filterOutAuthorizationParams((_b = (_a = httpOperation.request) === null || _a === void 0 ? void 0 : _a.headers) !== null && _b !== void 0 ? _b : [], httpOperation.security)
@@ -1575,10 +1600,10 @@ const runAuthRequestEhancements = (auth, queryParams, headers) => {
1575
1600
  }
1576
1601
  return [newQueryParams, newHeaders];
1577
1602
  };
1578
- function buildHarRequest({ httpOperation, bodyInput, parameterValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }) {
1603
+ function buildHarRequest({ httpOperation, bodyInput, parameterValues, serverVariableValues, mediaTypeContent, auth, mockData, chosenServer, corsProxy, }) {
1579
1604
  var _a, _b, _c, _d;
1580
1605
  return __awaiter(this, void 0, void 0, function* () {
1581
- const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy });
1606
+ const serverUrl = getServerUrl({ httpOperation, mockData, chosenServer, corsProxy, serverVariableValues });
1582
1607
  const mimeType = (_a = mediaTypeContent === null || mediaTypeContent === void 0 ? void 0 : mediaTypeContent.mediaType) !== null && _a !== void 0 ? _a : 'application/json';
1583
1608
  const shouldIncludeBody = ['PUT', 'POST', 'PATCH'].includes(httpOperation.method.toUpperCase()) && bodyInput !== undefined;
1584
1609
  const queryParams = getQueryParams({ httpOperation, parameterValues });
@@ -1900,8 +1925,18 @@ class NetworkError extends Error {
1900
1925
  }
1901
1926
  const isNetworkError = (error) => error instanceof NetworkError;
1902
1927
 
1928
+ const persistedServerVariableValuesAtom = atom({});
1929
+ const useServerVariables = () => {
1930
+ const [serverVariables, setPersistedServerVariableValues] = useAtom(persistedServerVariableValuesAtom);
1931
+ const updateServerVariableValue = (name, value) => {
1932
+ setPersistedServerVariableValues(Object.assign({}, serverVariables, { [name]: value }));
1933
+ };
1934
+ return { serverVariables, updateServerVariableValue };
1935
+ };
1936
+
1903
1937
  const ServersDropdown = ({ servers }) => {
1904
1938
  const [chosenServer, setChosenServer] = useAtom(chosenServerAtom);
1939
+ const { serverVariables } = useServerVariables();
1905
1940
  const serverItems = [
1906
1941
  {
1907
1942
  type: 'option_group',
@@ -1915,7 +1950,7 @@ const ServersDropdown = ({ servers }) => {
1915
1950
  ...servers.map((server, i) => ({
1916
1951
  id: server.url,
1917
1952
  title: server.description,
1918
- description: server.url,
1953
+ description: getServerUrlWithVariableValues(server, serverVariables),
1919
1954
  value: server.url,
1920
1955
  })),
1921
1956
  ],
@@ -1925,6 +1960,21 @@ const ServersDropdown = ({ servers }) => {
1925
1960
  };
1926
1961
  ServersDropdown.displayName = 'ServersDropdown';
1927
1962
 
1963
+ const VariableEditor = ({ variable, value, onChange }) => {
1964
+ const inputId = useUniqueId(`id_${variable.name}_`);
1965
+ return (React.createElement(React.Fragment, null,
1966
+ React.createElement(Text, { as: "label", "aria-hidden": "true", "data-testid": "param-label", htmlFor: inputId, fontSize: "base" }, variable.name),
1967
+ React.createElement(Text, { mx: 3 }, ":"),
1968
+ React.createElement("div", null, variable.enum ? (React.createElement(Select, { flex: 1, "aria-label": variable.name, options: variable.enum.map(s => ({ value: s })), value: value || variable.default, onChange: onChange })) : (React.createElement(Flex, { flex: 1 },
1969
+ React.createElement(Input, { id: inputId, "aria-label": variable.name, appearance: 'minimal', flex: 1, placeholder: variable.default, type: "text", required: true, intent: 'default', value: value || '', onChange: e => onChange && onChange(e.currentTarget.value) }))))));
1970
+ };
1971
+
1972
+ const ServerVariables = ({ variables, values, onChangeValue }) => {
1973
+ return (React.createElement(Panel, { defaultIsOpen: true },
1974
+ React.createElement(Panel.Titlebar, null, "Server Variables"),
1975
+ React.createElement(Panel.Content, { className: "sl-overflow-y-auto ParameterGrid ServerVariablesContent" }, variables.map(variable => (React.createElement(VariableEditor, { key: variable.name, variable: variable, value: values[variable.name], onChange: (value) => onChangeValue(variable.name, String(value)) }))))));
1976
+ };
1977
+
1928
1978
  const defaultServers = [];
1929
1979
  const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embeddedInMd = false, tryItCredentialsPolicy, corsProxy, }) => {
1930
1980
  var _a, _b, _c, _d, _e, _f, _g;
@@ -1941,10 +1991,12 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1941
1991
  const [textRequestBody, setTextRequestBody] = useTextRequestBodyState(mediaTypeContent);
1942
1992
  const [operationAuthValue, setOperationAuthValue] = usePersistedSecuritySchemeWithValues();
1943
1993
  const servers = React.useMemo(() => {
1944
- return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, true);
1994
+ return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, false);
1945
1995
  }, [httpOperation.servers, mockUrl]);
1946
1996
  const firstServer = servers[0] || null;
1947
1997
  const [chosenServer, setChosenServer] = useAtom(chosenServerAtom);
1998
+ const serverVariables = getServerVariables(chosenServer);
1999
+ const { serverVariables: serverVariableValues, updateServerVariableValue } = useServerVariables();
1948
2000
  const isMockingEnabled = mockUrl && (chosenServer === null || chosenServer === void 0 ? void 0 : chosenServer.url) === mockUrl;
1949
2001
  const hasRequiredButEmptyParameters = allParameters.some(parameter => parameter.required && !parameterValuesWithDefaults[parameter.name]);
1950
2002
  const getValues = () => Object.keys(bodyParameterValues)
@@ -1966,7 +2018,8 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1966
2018
  React.useEffect(() => {
1967
2019
  let isMounted = true;
1968
2020
  if (onRequestChange || embeddedInMd) {
1969
- buildHarRequest(Object.assign(Object.assign({ mediaTypeContent, parameterValues: parameterValuesWithDefaults, httpOperation, bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, auth: operationAuthValue }, (isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) })), { chosenServer,
2021
+ buildHarRequest(Object.assign(Object.assign({ mediaTypeContent, parameterValues: parameterValuesWithDefaults, serverVariableValues,
2022
+ httpOperation, bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody, auth: operationAuthValue }, (isMockingEnabled && { mockData: getMockData(mockUrl, httpOperation, mockingOptions) })), { chosenServer,
1970
2023
  corsProxy })).then(request => {
1971
2024
  if (isMounted) {
1972
2025
  if (onRequestChange) {
@@ -1986,6 +2039,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1986
2039
  parameterValuesWithDefaults,
1987
2040
  formDataState.isFormDataBody,
1988
2041
  bodyParameterValues,
2042
+ serverVariableValues,
1989
2043
  isAllowedEmptyValues,
1990
2044
  textRequestBody,
1991
2045
  operationAuthValue,
@@ -2003,6 +2057,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2003
2057
  const mockData = isMockingEnabled ? getMockData(mockUrl, httpOperation, mockingOptions) : undefined;
2004
2058
  const request = yield buildFetchRequest({
2005
2059
  parameterValues: parameterValuesWithDefaults,
2060
+ serverVariableValues,
2006
2061
  httpOperation,
2007
2062
  mediaTypeContent,
2008
2063
  bodyInput: formDataState.isFormDataBody ? getValues() : textRequestBody,
@@ -2043,6 +2098,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
2043
2098
  const isOnlySendButton = !((_d = httpOperation.security) === null || _d === void 0 ? void 0 : _d.length) && !allParameters.length && !formDataState.isFormDataBody && !mediaTypeContent;
2044
2099
  const tryItPanelContents = (React.createElement(React.Fragment, null,
2045
2100
  ((_e = httpOperation.security) === null || _e === void 0 ? void 0 : _e.length) ? (React.createElement(TryItAuth, { onChange: setOperationAuthValue, operationSecurityScheme: (_f = httpOperation.security) !== null && _f !== void 0 ? _f : [], value: operationAuthValue })) : null,
2101
+ serverVariables.length > 0 && (React.createElement(ServerVariables, { variables: serverVariables, values: serverVariableValues, onChangeValue: updateServerVariableValue })),
2046
2102
  allParameters.length > 0 && (React.createElement(OperationParameters, { parameters: allParameters, values: parameterValuesWithDefaults, onChangeValue: updateParameterValue, validate: validateParameters })),
2047
2103
  formDataState.isFormDataBody ? (React.createElement(FormDataBody, { specification: formDataState.bodySpecification, values: bodyParameterValues, onChangeValues: setBodyParameterValues, onChangeParameterAllow: setAllowedEmptyValues, isAllowedEmptyValues: isAllowedEmptyValues })) : mediaTypeContent ? (React.createElement(RequestBody, { examples: (_g = mediaTypeContent.examples) !== null && _g !== void 0 ? _g : [], requestBody: textRequestBody, onChange: setTextRequestBody })) : null,
2048
2104
  React.createElement(Panel.Content, { className: "SendButtonHolder", mt: 4, pt: !isOnlySendButton && !embeddedInMd ? 0 : undefined },
@@ -2370,20 +2426,41 @@ const SecuritySchemes$1 = ({ schemes }) => {
2370
2426
  React.createElement(NodeAnnotation, { change: nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: scheme.id }) }))))));
2371
2427
  };
2372
2428
 
2373
- const Responses = ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange }) => {
2429
+ const Responses = ({ responses: unsortedResponses, onStatusCodeChange, onMediaTypeChange, isCompact, }) => {
2374
2430
  var _a, _b;
2375
2431
  const responses = sortBy(uniqBy(unsortedResponses, r => r.code), r => r.code);
2376
2432
  const [activeResponseId, setActiveResponseId] = React.useState((_b = (_a = responses[0]) === null || _a === void 0 ? void 0 : _a.code) !== null && _b !== void 0 ? _b : '');
2433
+ const { isOpen, open, close } = useModalState();
2434
+ const onSelectionChange = React.useCallback(keys => {
2435
+ const selectedId = keys.values().next().value;
2436
+ const selectedResponse = responses === null || responses === void 0 ? void 0 : responses.find(response => response.id === selectedId);
2437
+ if (selectedResponse) {
2438
+ setActiveResponseId(selectedResponse.code);
2439
+ close();
2440
+ }
2441
+ }, [responses, setActiveResponseId, close]);
2377
2442
  React.useEffect(() => {
2378
2443
  onStatusCodeChange(activeResponseId);
2379
2444
  }, [activeResponseId]);
2380
2445
  if (!responses.length)
2381
2446
  return null;
2447
+ const response = responses.find(r => r.code === activeResponseId) || responses[0];
2448
+ const compactResponses = (React.createElement(React.Fragment, null,
2449
+ React.createElement(Button, { onPress: open, iconRight: React.createElement(Icon, { icon: "chevron-down", color: "var(--color-border-button)" }), style: {
2450
+ color: `var(--color-${codeToIntentVal(activeResponseId)})`,
2451
+ } }, activeResponseId),
2452
+ React.createElement(Modal, { title: "Response Code", isOpen: isOpen, onClose: close, size: "sm", footer: React.createElement(HStack, { justifyContent: "end" },
2453
+ React.createElement(Button, { onPress: close, intent: "default", appearance: "primary" }, "Close")) },
2454
+ React.createElement(ListBox, { "aria-label": "Response Code", overflowY: "auto", m: -5, items: responses, selectionMode: "single", onSelectionChange: onSelectionChange }, (response) => (React.createElement(ListBoxItem, { key: response.id },
2455
+ React.createElement(Box, { p: 3, bg: { hover: 'primary-tint' } },
2456
+ React.createElement(Flex, { w: "2xl", align: "center", justify: "end" },
2457
+ response.code === activeResponseId && React.createElement(Box, { as: Icon, icon: "check" }),
2458
+ React.createElement(Text, { ml: 3, fontWeight: "medium" }, response.code)))))))));
2459
+ const tabResponses = (React.createElement(TabList, { density: "compact" }, responses.map(({ code }) => (React.createElement(Tab, { key: code, id: code, intent: codeToIntentVal(code) }, code)))));
2382
2460
  return (React.createElement(VStack, { spacing: 8, as: Tabs, selectedId: activeResponseId, onChange: setActiveResponseId, appearance: "pill" },
2383
- React.createElement(SectionTitle, { title: "Responses" },
2384
- React.createElement(TabList, { density: "compact" }, responses.map(({ code }) => (React.createElement(Tab, { key: code, id: code, intent: codeToIntentVal(code) }, code))))),
2385
- React.createElement(TabPanels, { p: 0 }, responses.map(response => (React.createElement(TabPanel, { key: response.code, id: response.code },
2386
- React.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange })))))));
2461
+ React.createElement(SectionTitle, { title: "Responses" }, isCompact ? compactResponses : tabResponses),
2462
+ isCompact ? (React.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange })) : (React.createElement(TabPanels, { p: 0 }, responses.map(response => (React.createElement(TabPanel, { key: response.code, id: response.code },
2463
+ React.createElement(Response, { response: response, onMediaTypeChange: onMediaTypeChange }))))))));
2387
2464
  };
2388
2465
  Responses.displayName = 'HttpOperation.Responses';
2389
2466
  const Response = ({ response, onMediaTypeChange }) => {
@@ -2428,6 +2505,7 @@ const codeToIntentVal = (code) => {
2428
2505
  const HttpOperationComponent = React.memo(({ className, data: unresolvedData, layoutOptions, tryItCredentialsPolicy, tryItCorsProxy }) => {
2429
2506
  const { nodeHasChanged } = useOptionsCtx();
2430
2507
  const data = useResolvedObject(unresolvedData);
2508
+ const { ref: layoutRef, isCompact } = useIsCompact(layoutOptions);
2431
2509
  const mocking = React.useContext(MockingContext);
2432
2510
  const isDeprecated = !!data.deprecated;
2433
2511
  const isInternal = !!data.internal;
@@ -2437,15 +2515,16 @@ const HttpOperationComponent = React.memo(({ className, data: unresolvedData, la
2437
2515
  const prettyName = (data.summary || data.iid || '').trim();
2438
2516
  const hasBadges = isDeprecated || isInternal;
2439
2517
  const header = (React.createElement(OperationHeader, { id: data.id, method: data.method, path: data.path, noHeading: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.noHeading, hasBadges: hasBadges, name: prettyName, isDeprecated: isDeprecated, isInternal: isInternal }));
2518
+ const tryItPanel = !(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryItPanel) && (React.createElement(TryItWithRequestSamples, { httpOperation: data, responseMediaType: responseMediaType, responseStatusCode: responseStatusCode, requestBodyIndex: requestBodyIndex, hideTryIt: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryIt, tryItCredentialsPolicy: tryItCredentialsPolicy, mockUrl: mocking.hideMocking ? undefined : mocking.mockUrl, corsProxy: tryItCorsProxy }));
2440
2519
  const descriptionChanged = nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: data.id, attr: 'description' });
2441
2520
  const description = (React.createElement(VStack, { spacing: 10 },
2442
2521
  data.description && (React.createElement(Box, { pos: "relative" },
2443
2522
  React.createElement(MarkdownViewer, { className: "HttpOperation__Description", markdown: data.description }),
2444
2523
  React.createElement(NodeAnnotation, { change: descriptionChanged }))),
2445
2524
  React.createElement(Request, { onChange: setTextRequestBodyIndex, operation: data }),
2446
- data.responses && (React.createElement(Responses, { responses: data.responses, onMediaTypeChange: setResponseMediaType, onStatusCodeChange: setResponseStatusCode }))));
2447
- const tryItPanel = !(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryItPanel) && (React.createElement(TryItWithRequestSamples, { httpOperation: data, responseMediaType: responseMediaType, responseStatusCode: responseStatusCode, requestBodyIndex: requestBodyIndex, hideTryIt: layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.hideTryIt, tryItCredentialsPolicy: tryItCredentialsPolicy, mockUrl: mocking.hideMocking ? undefined : mocking.mockUrl, corsProxy: tryItCorsProxy }));
2448
- return (React.createElement(TwoColumnLayout, { className: cn('HttpOperation', className), header: header, left: description, right: tryItPanel }));
2525
+ data.responses && (React.createElement(Responses, { responses: data.responses, onMediaTypeChange: setResponseMediaType, onStatusCodeChange: setResponseStatusCode, isCompact: isCompact })),
2526
+ isCompact && tryItPanel));
2527
+ return (React.createElement(TwoColumnLayout, { ref: layoutRef, className: cn('HttpOperation', className), header: header, left: description, right: !isCompact && tryItPanel }));
2449
2528
  });
2450
2529
  HttpOperationComponent.displayName = 'HttpOperation.Component';
2451
2530
  const HttpOperation = withErrorBoundary(HttpOperationComponent, {
@@ -2677,22 +2756,6 @@ const HttpServiceComponent = React.memo(({ data: unresolvedData, location = {},
2677
2756
  HttpServiceComponent.displayName = 'HttpService.Component';
2678
2757
  const HttpService = withErrorBoundary(HttpServiceComponent, { recoverableProps: ['data'] });
2679
2758
 
2680
- const getBreakpoints = (compact) => {
2681
- if (!compact)
2682
- return undefined;
2683
- if (typeof compact === 'number') {
2684
- return [
2685
- ['compact', compact],
2686
- ['regular', Infinity],
2687
- ];
2688
- }
2689
- return [['compact', Infinity]];
2690
- };
2691
- function useIsCompact(layoutOptions) {
2692
- const { ref, breakpoint } = useBreakpoints(getBreakpoints(layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.compact));
2693
- return { ref: ref, isCompact: breakpoint === 'compact' };
2694
- }
2695
-
2696
2759
  const ModelComponent = ({ data: unresolvedData, className, nodeTitle, layoutOptions, exportProps, }) => {
2697
2760
  var _a, _b;
2698
2761
  const resolveRef = useSchemaInlineRefResolver();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoplight/elements-core",
3
- "version": "7.8.0",
3
+ "version": "7.10.0",
4
4
  "main": "./index.js",
5
5
  "sideEffects": [
6
6
  "web-components.min.js",
@@ -1,3 +1,9 @@
1
- import type { IServer } from '@stoplight/types';
1
+ import type { Dictionary, INodeVariable, IServer } from '@stoplight/types';
2
+ export declare type ServerVariable = INodeVariable & {
3
+ name: string;
4
+ };
2
5
  export declare const getServersToDisplay: (originalServers: IServer[], mockUrl: string | undefined, inlineDefaults: boolean) => IServer[];
3
- export declare const getServerUrlWithDefaultValues: (server: IServer) => string | null;
6
+ export declare const getServerVariables: (server?: IServer | null) => ServerVariable[];
7
+ export declare const getServerVariableDefaults: (server: IServer) => Record<string, string>;
8
+ export declare function resolveUrl(urlString: string | null): string | null;
9
+ export declare const getServerUrlWithVariableValues: (server: IServer, values: Dictionary<string, string>) => string;