@stoplight/elements-core 7.7.20 → 7.8.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.
@@ -5,4 +5,8 @@ interface ServerInfoProps {
5
5
  mockUrl?: string;
6
6
  }
7
7
  export declare const ServerInfo: React.FC<ServerInfoProps>;
8
+ export declare function useSplitUrl(url: string): {
9
+ kind: 'variable' | 'static';
10
+ value: string;
11
+ }[];
8
12
  export {};
package/index.esm.js CHANGED
@@ -16,6 +16,7 @@ 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';
19
20
  import URI from 'urijs';
20
21
  import { CodeViewer } from '@stoplight/mosaic-code-viewer';
21
22
  import { isValidTargetId, HTTPSnippet } from 'httpsnippet-lite';
@@ -41,6 +42,8 @@ import entries from 'lodash/entries.js';
41
42
  import keys from 'lodash/keys.js';
42
43
  import { JsonSchemaViewer } from '@stoplight/json-schema-viewer';
43
44
  import sortBy from 'lodash/sortBy.js';
45
+ import isNil from 'lodash/isNil.js';
46
+ import omitBy from 'lodash/omitBy.js';
44
47
  import { HashLink } from 'react-router-hash-link';
45
48
  import { QueryClient, useQueryClient, QueryClientProvider } from 'react-query';
46
49
  import $RefParser from '@stoplight/json-schema-ref-parser';
@@ -486,15 +489,20 @@ function useChosenServerUrl(chosenServerUrl) {
486
489
  const chosenServerAtom = atom(undefined);
487
490
 
488
491
  function isValidServer(server) {
489
- return server.url !== null && isProperUrl(server.url);
492
+ return server.url !== null;
490
493
  }
491
- const getServersToDisplay = (originalServers, mockUrl) => {
492
- const servers = originalServers
493
- .map((server, i) => {
494
+ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
495
+ const servers = originalServers.map((server, i) => {
494
496
  const fallbackDescription = originalServers.length === 1 ? 'Live Server' : `Server ${i + 1}`;
495
- return Object.assign(Object.assign({}, server), { url: getServerUrlWithDefaultValues(server), description: server.description || fallbackDescription });
496
- })
497
- .filter(isValidServer);
497
+ let url = server.url;
498
+ if (inlineDefaults) {
499
+ url = getServerUrlWithDefaultValues(server);
500
+ }
501
+ else if (isEmpty(server.variables)) {
502
+ url = resolveUrl(server.url);
503
+ }
504
+ return Object.assign(Object.assign({}, server), { url, description: server.description || fallbackDescription });
505
+ });
498
506
  if (mockUrl) {
499
507
  servers.push({
500
508
  id: 'mock',
@@ -502,27 +510,36 @@ const getServersToDisplay = (originalServers, mockUrl) => {
502
510
  url: mockUrl,
503
511
  });
504
512
  }
505
- return servers;
513
+ return servers.filter(isValidServer);
506
514
  };
507
- const getServerUrlWithDefaultValues = (server) => {
508
- var _a;
509
- let urlString = server.url;
510
- const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
511
- variables.forEach(([variableName, variableInfo]) => {
512
- urlString = urlString.replace(`{${variableName}}`, variableInfo.default);
513
- });
515
+ function resolveUrl(urlString) {
514
516
  let url;
515
517
  try {
516
518
  url = URI(urlString);
517
519
  }
518
- catch (_b) {
520
+ catch (_a) {
519
521
  return null;
520
522
  }
523
+ let stringifiedUrl;
521
524
  if (url.is('relative') && typeof window !== 'undefined') {
522
- url = url.absoluteTo(window.location.origin);
525
+ stringifiedUrl = url.absoluteTo(window.location.origin).toString();
526
+ }
527
+ else {
528
+ stringifiedUrl = url.toString();
523
529
  }
524
- const stringifiedUrl = url.toString();
525
- return stringifiedUrl.endsWith('/') ? stringifiedUrl.slice(0, -1) : stringifiedUrl;
530
+ if (isProperUrl(stringifiedUrl)) {
531
+ return stringifiedUrl.endsWith('/') ? stringifiedUrl.slice(0, -1) : stringifiedUrl;
532
+ }
533
+ return null;
534
+ }
535
+ const getServerUrlWithDefaultValues = (server) => {
536
+ var _a;
537
+ let urlString = server.url;
538
+ const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
539
+ variables.forEach(([variableName, variableInfo]) => {
540
+ urlString = urlString.replaceAll(`{${variableName}}`, variableInfo.default);
541
+ });
542
+ return resolveUrl(urlString);
526
543
  };
527
544
 
528
545
  const persistAtom = (key, atomInstance) => {
@@ -1924,8 +1941,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1924
1941
  const [textRequestBody, setTextRequestBody] = useTextRequestBodyState(mediaTypeContent);
1925
1942
  const [operationAuthValue, setOperationAuthValue] = usePersistedSecuritySchemeWithValues();
1926
1943
  const servers = React.useMemo(() => {
1927
- const toDisplay = getServersToDisplay(httpOperation.servers || defaultServers, mockUrl);
1928
- return toDisplay;
1944
+ return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, true);
1929
1945
  }, [httpOperation.servers, mockUrl]);
1930
1946
  const firstServer = servers[0] || null;
1931
1947
  const [chosenServer, setChosenServer] = useAtom(chosenServerAtom);
@@ -2538,7 +2554,8 @@ const ServerInfo = ({ servers, mockUrl }) => {
2538
2554
  const mocking = React.useContext(MockingContext);
2539
2555
  const showMocking = !mocking.hideMocking && mockUrl && isProperUrl(mockUrl);
2540
2556
  const $mockUrl = showMocking ? mockUrl || mocking.mockUrl : undefined;
2541
- const serversToDisplay = getServersToDisplay(servers, $mockUrl);
2557
+ const serversToDisplay = React.useMemo(() => getServersToDisplay(servers, $mockUrl, false), [servers, $mockUrl]);
2558
+ const firstServerVariableIndex = React.useMemo(() => serversToDisplay.findIndex(server => !isEmpty(server.variables)), [serversToDisplay]);
2542
2559
  if (!showMocking && serversToDisplay.length === 0) {
2543
2560
  return null;
2544
2561
  }
@@ -2546,25 +2563,88 @@ const ServerInfo = ({ servers, mockUrl }) => {
2546
2563
  React.createElement(Panel, { rounded: true, isCollapsible: false, className: "BaseURLContent", w: "full" },
2547
2564
  React.createElement(Panel.Titlebar, { whitespace: "nowrap" }, "API Base URL"),
2548
2565
  React.createElement(Panel.Content, { w: "full", className: "sl-flex sl-flex-col" },
2549
- React.createElement(VStack, { spacing: 1, divider: true }, serversToDisplay.map((server, index) => (React.createElement(ServerUrl, Object.assign({}, server, { key: index })))))))));
2566
+ React.createElement(VStack, { spacing: 1, divider: true }, serversToDisplay.map((server, index) => (React.createElement(ServerUrl, Object.assign({}, server, { defaultIsOpen: index === firstServerVariableIndex, hasAnyServerVariables: firstServerVariableIndex !== -1, key: server.id })))))))));
2550
2567
  };
2551
- const ServerUrl = ({ id, description, url }) => {
2568
+ const ServerUrl = ({ id, description, url, variables, hasAnyServerVariables, defaultIsOpen, }) => {
2552
2569
  const { nodeHasChanged } = useOptionsCtx();
2553
2570
  const { onCopy, hasCopied } = useClipboard(url);
2571
+ const urlFragments = useSplitUrl(url);
2554
2572
  const hasChanged = nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: id });
2555
- return (React.createElement(Box, { whitespace: "nowrap", pos: "relative" },
2556
- React.createElement(Text, { pr: 2, fontWeight: "bold" },
2557
- description,
2558
- ":"),
2559
- React.createElement(Tooltip, { placement: "right", renderTrigger: () => React.createElement(Text, { "aria-label": description }, url) },
2560
- !hasCopied && (React.createElement(Box, { p: 1, onClick: onCopy, cursor: "pointer" },
2561
- "Copy Server URL ",
2562
- React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'copy'] }))),
2563
- hasCopied && (React.createElement(Box, { p: 1 },
2564
- "Copied Server URL ",
2565
- React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'check'] })))),
2566
- React.createElement(NodeAnnotation, { change: hasChanged, additionalLeftOffset: 16 })));
2567
- };
2573
+ const variablesSchema = useVariablesJSONSchema(variables);
2574
+ const titlePaddingLeft = hasAnyServerVariables && !variablesSchema ? 4 : 0;
2575
+ const handleCopyClick = React.useCallback(e => {
2576
+ e.stopPropagation();
2577
+ onCopy();
2578
+ }, [onCopy]);
2579
+ return (React.createElement(Panel, { isCollapsible: !!variablesSchema, defaultIsOpen: defaultIsOpen, w: "full" },
2580
+ React.createElement(Panel.Titlebar, { whitespace: "nowrap" },
2581
+ React.createElement(Text, { pl: titlePaddingLeft, pr: 2, fontWeight: "bold" },
2582
+ description,
2583
+ ":"),
2584
+ React.createElement(Tooltip, { placement: "right", renderTrigger: () => (React.createElement(Text, { "aria-label": description }, urlFragments.map(({ kind, value }, i) => (React.createElement(Text, { key: i, fontWeight: kind === 'variable' ? 'semibold' : 'normal' }, value))))) },
2585
+ !hasCopied && (React.createElement(Box, { p: 1, onClick: handleCopyClick, cursor: "pointer" },
2586
+ "Copy Server URL ",
2587
+ React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'copy'] }))),
2588
+ hasCopied && (React.createElement(Box, { p: 1 },
2589
+ "Copied Server URL ",
2590
+ React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'check'] })))),
2591
+ React.createElement(NodeAnnotation, { change: hasChanged, additionalLeftOffset: 16 })),
2592
+ variablesSchema && (React.createElement(Panel.Content, { w: "full" },
2593
+ React.createElement(Box, { pl: 4 },
2594
+ React.createElement(JsonSchemaViewer, { schema: variablesSchema }))))));
2595
+ };
2596
+ function useVariablesJSONSchema(variables) {
2597
+ return React.useMemo(() => {
2598
+ if (isEmpty(variables))
2599
+ return;
2600
+ const propertiesPairs = Object.entries(variables).map(([name, variable]) => [
2601
+ name,
2602
+ Object.assign({ type: 'string' }, omitBy({
2603
+ description: variable.description,
2604
+ enum: variable.enum,
2605
+ default: variable.default,
2606
+ }, isNil)),
2607
+ ]);
2608
+ return {
2609
+ type: 'object',
2610
+ properties: Object.fromEntries(propertiesPairs),
2611
+ };
2612
+ }, [variables]);
2613
+ }
2614
+ function useSplitUrl(url) {
2615
+ return React.useMemo(() => {
2616
+ const curly = /[{}]/g;
2617
+ const fragments = [];
2618
+ let startOffset = 0;
2619
+ let curPos = 0;
2620
+ let match;
2621
+ while ((match = curly.exec(url))) {
2622
+ if (match[0] === '{' || startOffset + 1 === match.index) {
2623
+ startOffset = match.index;
2624
+ continue;
2625
+ }
2626
+ if (startOffset !== curPos) {
2627
+ fragments.push({
2628
+ kind: 'static',
2629
+ value: url.slice(curPos, startOffset),
2630
+ });
2631
+ }
2632
+ const variable = url.slice(startOffset, match.index + 1);
2633
+ fragments.push({
2634
+ kind: 'variable',
2635
+ value: variable,
2636
+ });
2637
+ curPos = startOffset + variable.length;
2638
+ }
2639
+ if (curPos < url.length) {
2640
+ fragments.push({
2641
+ kind: 'static',
2642
+ value: url.slice(curPos),
2643
+ });
2644
+ }
2645
+ return fragments;
2646
+ }, [url]);
2647
+ }
2568
2648
 
2569
2649
  const HttpServiceComponent = React.memo(({ data: unresolvedData, location = {}, layoutOptions, exportProps }) => {
2570
2650
  var _a, _b, _c, _d;
package/index.js CHANGED
@@ -18,6 +18,7 @@ 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');
21
22
  var URI = require('urijs');
22
23
  var mosaicCodeViewer = require('@stoplight/mosaic-code-viewer');
23
24
  var httpsnippetLite = require('httpsnippet-lite');
@@ -43,6 +44,8 @@ var entries = require('lodash/entries.js');
43
44
  var keys = require('lodash/keys.js');
44
45
  var jsonSchemaViewer = require('@stoplight/json-schema-viewer');
45
46
  var sortBy = require('lodash/sortBy.js');
47
+ var isNil = require('lodash/isNil.js');
48
+ var omitBy = require('lodash/omitBy.js');
46
49
  var reactRouterHashLink = require('react-router-hash-link');
47
50
  var reactQuery = require('react-query');
48
51
  var $RefParser = require('@stoplight/json-schema-ref-parser');
@@ -76,6 +79,7 @@ var isArray__default = /*#__PURE__*/_interopDefaultLegacy(isArray);
76
79
  var isPlainObject__default = /*#__PURE__*/_interopDefaultLegacy(isPlainObject);
77
80
  var isObject__default = /*#__PURE__*/_interopDefaultLegacy(isObject);
78
81
  var cn__default = /*#__PURE__*/_interopDefaultLegacy(cn);
82
+ var isEmpty__default = /*#__PURE__*/_interopDefaultLegacy(isEmpty);
79
83
  var URI__default = /*#__PURE__*/_interopDefaultLegacy(URI);
80
84
  var flatten__default = /*#__PURE__*/_interopDefaultLegacy(flatten);
81
85
  var capitalize__default = /*#__PURE__*/_interopDefaultLegacy(capitalize);
@@ -96,6 +100,8 @@ var formatXml__default = /*#__PURE__*/_interopDefaultLegacy(formatXml);
96
100
  var entries__default = /*#__PURE__*/_interopDefaultLegacy(entries);
97
101
  var keys__default = /*#__PURE__*/_interopDefaultLegacy(keys);
98
102
  var sortBy__default = /*#__PURE__*/_interopDefaultLegacy(sortBy);
103
+ var isNil__default = /*#__PURE__*/_interopDefaultLegacy(isNil);
104
+ var omitBy__default = /*#__PURE__*/_interopDefaultLegacy(omitBy);
99
105
  var $RefParser__default = /*#__PURE__*/_interopDefaultLegacy($RefParser);
100
106
  var PropTypes__namespace = /*#__PURE__*/_interopNamespace(PropTypes);
101
107
  var isEqual__default = /*#__PURE__*/_interopDefaultLegacy(isEqual);
@@ -539,15 +545,20 @@ function useChosenServerUrl(chosenServerUrl) {
539
545
  const chosenServerAtom = jotai.atom(undefined);
540
546
 
541
547
  function isValidServer(server) {
542
- return server.url !== null && isProperUrl(server.url);
548
+ return server.url !== null;
543
549
  }
544
- const getServersToDisplay = (originalServers, mockUrl) => {
545
- const servers = originalServers
546
- .map((server, i) => {
550
+ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
551
+ const servers = originalServers.map((server, i) => {
547
552
  const fallbackDescription = originalServers.length === 1 ? 'Live Server' : `Server ${i + 1}`;
548
- return Object.assign(Object.assign({}, server), { url: getServerUrlWithDefaultValues(server), description: server.description || fallbackDescription });
549
- })
550
- .filter(isValidServer);
553
+ let url = server.url;
554
+ if (inlineDefaults) {
555
+ url = getServerUrlWithDefaultValues(server);
556
+ }
557
+ else if (isEmpty__default["default"](server.variables)) {
558
+ url = resolveUrl(server.url);
559
+ }
560
+ return Object.assign(Object.assign({}, server), { url, description: server.description || fallbackDescription });
561
+ });
551
562
  if (mockUrl) {
552
563
  servers.push({
553
564
  id: 'mock',
@@ -555,27 +566,36 @@ const getServersToDisplay = (originalServers, mockUrl) => {
555
566
  url: mockUrl,
556
567
  });
557
568
  }
558
- return servers;
569
+ return servers.filter(isValidServer);
559
570
  };
560
- const getServerUrlWithDefaultValues = (server) => {
561
- var _a;
562
- let urlString = server.url;
563
- const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
564
- variables.forEach(([variableName, variableInfo]) => {
565
- urlString = urlString.replace(`{${variableName}}`, variableInfo.default);
566
- });
571
+ function resolveUrl(urlString) {
567
572
  let url;
568
573
  try {
569
574
  url = URI__default["default"](urlString);
570
575
  }
571
- catch (_b) {
576
+ catch (_a) {
572
577
  return null;
573
578
  }
579
+ let stringifiedUrl;
574
580
  if (url.is('relative') && typeof window !== 'undefined') {
575
- url = url.absoluteTo(window.location.origin);
581
+ stringifiedUrl = url.absoluteTo(window.location.origin).toString();
582
+ }
583
+ else {
584
+ stringifiedUrl = url.toString();
576
585
  }
577
- const stringifiedUrl = url.toString();
578
- return stringifiedUrl.endsWith('/') ? stringifiedUrl.slice(0, -1) : stringifiedUrl;
586
+ if (isProperUrl(stringifiedUrl)) {
587
+ return stringifiedUrl.endsWith('/') ? stringifiedUrl.slice(0, -1) : stringifiedUrl;
588
+ }
589
+ return null;
590
+ }
591
+ const getServerUrlWithDefaultValues = (server) => {
592
+ var _a;
593
+ let urlString = server.url;
594
+ const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
595
+ variables.forEach(([variableName, variableInfo]) => {
596
+ urlString = urlString.replaceAll(`{${variableName}}`, variableInfo.default);
597
+ });
598
+ return resolveUrl(urlString);
579
599
  };
580
600
 
581
601
  const persistAtom = (key, atomInstance) => {
@@ -1977,8 +1997,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1977
1997
  const [textRequestBody, setTextRequestBody] = useTextRequestBodyState(mediaTypeContent);
1978
1998
  const [operationAuthValue, setOperationAuthValue] = usePersistedSecuritySchemeWithValues();
1979
1999
  const servers = React__namespace.useMemo(() => {
1980
- const toDisplay = getServersToDisplay(httpOperation.servers || defaultServers, mockUrl);
1981
- return toDisplay;
2000
+ return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, true);
1982
2001
  }, [httpOperation.servers, mockUrl]);
1983
2002
  const firstServer = servers[0] || null;
1984
2003
  const [chosenServer, setChosenServer] = jotai.useAtom(chosenServerAtom);
@@ -2591,7 +2610,8 @@ const ServerInfo = ({ servers, mockUrl }) => {
2591
2610
  const mocking = React__namespace.useContext(MockingContext);
2592
2611
  const showMocking = !mocking.hideMocking && mockUrl && isProperUrl(mockUrl);
2593
2612
  const $mockUrl = showMocking ? mockUrl || mocking.mockUrl : undefined;
2594
- const serversToDisplay = getServersToDisplay(servers, $mockUrl);
2613
+ const serversToDisplay = React__namespace.useMemo(() => getServersToDisplay(servers, $mockUrl, false), [servers, $mockUrl]);
2614
+ const firstServerVariableIndex = React__namespace.useMemo(() => serversToDisplay.findIndex(server => !isEmpty__default["default"](server.variables)), [serversToDisplay]);
2595
2615
  if (!showMocking && serversToDisplay.length === 0) {
2596
2616
  return null;
2597
2617
  }
@@ -2599,25 +2619,88 @@ const ServerInfo = ({ servers, mockUrl }) => {
2599
2619
  React__namespace.createElement(mosaic.Panel, { rounded: true, isCollapsible: false, className: "BaseURLContent", w: "full" },
2600
2620
  React__namespace.createElement(mosaic.Panel.Titlebar, { whitespace: "nowrap" }, "API Base URL"),
2601
2621
  React__namespace.createElement(mosaic.Panel.Content, { w: "full", className: "sl-flex sl-flex-col" },
2602
- React__namespace.createElement(mosaic.VStack, { spacing: 1, divider: true }, serversToDisplay.map((server, index) => (React__namespace.createElement(ServerUrl, Object.assign({}, server, { key: index })))))))));
2622
+ React__namespace.createElement(mosaic.VStack, { spacing: 1, divider: true }, serversToDisplay.map((server, index) => (React__namespace.createElement(ServerUrl, Object.assign({}, server, { defaultIsOpen: index === firstServerVariableIndex, hasAnyServerVariables: firstServerVariableIndex !== -1, key: server.id })))))))));
2603
2623
  };
2604
- const ServerUrl = ({ id, description, url }) => {
2624
+ const ServerUrl = ({ id, description, url, variables, hasAnyServerVariables, defaultIsOpen, }) => {
2605
2625
  const { nodeHasChanged } = useOptionsCtx();
2606
2626
  const { onCopy, hasCopied } = mosaic.useClipboard(url);
2627
+ const urlFragments = useSplitUrl(url);
2607
2628
  const hasChanged = nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: id });
2608
- return (React__namespace.createElement(mosaic.Box, { whitespace: "nowrap", pos: "relative" },
2609
- React__namespace.createElement(mosaic.Text, { pr: 2, fontWeight: "bold" },
2610
- description,
2611
- ":"),
2612
- React__namespace.createElement(mosaic.Tooltip, { placement: "right", renderTrigger: () => React__namespace.createElement(mosaic.Text, { "aria-label": description }, url) },
2613
- !hasCopied && (React__namespace.createElement(mosaic.Box, { p: 1, onClick: onCopy, cursor: "pointer" },
2614
- "Copy Server URL ",
2615
- React__namespace.createElement(mosaic.Icon, { className: "sl-ml-1", icon: ['fas', 'copy'] }))),
2616
- hasCopied && (React__namespace.createElement(mosaic.Box, { p: 1 },
2617
- "Copied Server URL ",
2618
- React__namespace.createElement(mosaic.Icon, { className: "sl-ml-1", icon: ['fas', 'check'] })))),
2619
- React__namespace.createElement(mosaic.NodeAnnotation, { change: hasChanged, additionalLeftOffset: 16 })));
2620
- };
2629
+ const variablesSchema = useVariablesJSONSchema(variables);
2630
+ const titlePaddingLeft = hasAnyServerVariables && !variablesSchema ? 4 : 0;
2631
+ const handleCopyClick = React__namespace.useCallback(e => {
2632
+ e.stopPropagation();
2633
+ onCopy();
2634
+ }, [onCopy]);
2635
+ return (React__namespace.createElement(mosaic.Panel, { isCollapsible: !!variablesSchema, defaultIsOpen: defaultIsOpen, w: "full" },
2636
+ React__namespace.createElement(mosaic.Panel.Titlebar, { whitespace: "nowrap" },
2637
+ React__namespace.createElement(mosaic.Text, { pl: titlePaddingLeft, pr: 2, fontWeight: "bold" },
2638
+ description,
2639
+ ":"),
2640
+ React__namespace.createElement(mosaic.Tooltip, { placement: "right", renderTrigger: () => (React__namespace.createElement(mosaic.Text, { "aria-label": description }, urlFragments.map(({ kind, value }, i) => (React__namespace.createElement(mosaic.Text, { key: i, fontWeight: kind === 'variable' ? 'semibold' : 'normal' }, value))))) },
2641
+ !hasCopied && (React__namespace.createElement(mosaic.Box, { p: 1, onClick: handleCopyClick, cursor: "pointer" },
2642
+ "Copy Server URL ",
2643
+ React__namespace.createElement(mosaic.Icon, { className: "sl-ml-1", icon: ['fas', 'copy'] }))),
2644
+ hasCopied && (React__namespace.createElement(mosaic.Box, { p: 1 },
2645
+ "Copied Server URL ",
2646
+ React__namespace.createElement(mosaic.Icon, { className: "sl-ml-1", icon: ['fas', 'check'] })))),
2647
+ React__namespace.createElement(mosaic.NodeAnnotation, { change: hasChanged, additionalLeftOffset: 16 })),
2648
+ variablesSchema && (React__namespace.createElement(mosaic.Panel.Content, { w: "full" },
2649
+ React__namespace.createElement(mosaic.Box, { pl: 4 },
2650
+ React__namespace.createElement(jsonSchemaViewer.JsonSchemaViewer, { schema: variablesSchema }))))));
2651
+ };
2652
+ function useVariablesJSONSchema(variables) {
2653
+ return React__namespace.useMemo(() => {
2654
+ if (isEmpty__default["default"](variables))
2655
+ return;
2656
+ const propertiesPairs = Object.entries(variables).map(([name, variable]) => [
2657
+ name,
2658
+ Object.assign({ type: 'string' }, omitBy__default["default"]({
2659
+ description: variable.description,
2660
+ enum: variable.enum,
2661
+ default: variable.default,
2662
+ }, isNil__default["default"])),
2663
+ ]);
2664
+ return {
2665
+ type: 'object',
2666
+ properties: Object.fromEntries(propertiesPairs),
2667
+ };
2668
+ }, [variables]);
2669
+ }
2670
+ function useSplitUrl(url) {
2671
+ return React__namespace.useMemo(() => {
2672
+ const curly = /[{}]/g;
2673
+ const fragments = [];
2674
+ let startOffset = 0;
2675
+ let curPos = 0;
2676
+ let match;
2677
+ while ((match = curly.exec(url))) {
2678
+ if (match[0] === '{' || startOffset + 1 === match.index) {
2679
+ startOffset = match.index;
2680
+ continue;
2681
+ }
2682
+ if (startOffset !== curPos) {
2683
+ fragments.push({
2684
+ kind: 'static',
2685
+ value: url.slice(curPos, startOffset),
2686
+ });
2687
+ }
2688
+ const variable = url.slice(startOffset, match.index + 1);
2689
+ fragments.push({
2690
+ kind: 'variable',
2691
+ value: variable,
2692
+ });
2693
+ curPos = startOffset + variable.length;
2694
+ }
2695
+ if (curPos < url.length) {
2696
+ fragments.push({
2697
+ kind: 'static',
2698
+ value: url.slice(curPos),
2699
+ });
2700
+ }
2701
+ return fragments;
2702
+ }, [url]);
2703
+ }
2621
2704
 
2622
2705
  const HttpServiceComponent = React__namespace.memo(({ data: unresolvedData, location = {}, layoutOptions, exportProps }) => {
2623
2706
  var _a, _b, _c, _d;
package/index.mjs CHANGED
@@ -16,6 +16,7 @@ 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';
19
20
  import URI from 'urijs';
20
21
  import { CodeViewer } from '@stoplight/mosaic-code-viewer';
21
22
  import { isValidTargetId, HTTPSnippet } from 'httpsnippet-lite';
@@ -41,6 +42,8 @@ import entries from 'lodash/entries.js';
41
42
  import keys from 'lodash/keys.js';
42
43
  import { JsonSchemaViewer } from '@stoplight/json-schema-viewer';
43
44
  import sortBy from 'lodash/sortBy.js';
45
+ import isNil from 'lodash/isNil.js';
46
+ import omitBy from 'lodash/omitBy.js';
44
47
  import { HashLink } from 'react-router-hash-link';
45
48
  import { QueryClient, useQueryClient, QueryClientProvider } from 'react-query';
46
49
  import $RefParser from '@stoplight/json-schema-ref-parser';
@@ -486,15 +489,20 @@ function useChosenServerUrl(chosenServerUrl) {
486
489
  const chosenServerAtom = atom(undefined);
487
490
 
488
491
  function isValidServer(server) {
489
- return server.url !== null && isProperUrl(server.url);
492
+ return server.url !== null;
490
493
  }
491
- const getServersToDisplay = (originalServers, mockUrl) => {
492
- const servers = originalServers
493
- .map((server, i) => {
494
+ const getServersToDisplay = (originalServers, mockUrl, inlineDefaults) => {
495
+ const servers = originalServers.map((server, i) => {
494
496
  const fallbackDescription = originalServers.length === 1 ? 'Live Server' : `Server ${i + 1}`;
495
- return Object.assign(Object.assign({}, server), { url: getServerUrlWithDefaultValues(server), description: server.description || fallbackDescription });
496
- })
497
- .filter(isValidServer);
497
+ let url = server.url;
498
+ if (inlineDefaults) {
499
+ url = getServerUrlWithDefaultValues(server);
500
+ }
501
+ else if (isEmpty(server.variables)) {
502
+ url = resolveUrl(server.url);
503
+ }
504
+ return Object.assign(Object.assign({}, server), { url, description: server.description || fallbackDescription });
505
+ });
498
506
  if (mockUrl) {
499
507
  servers.push({
500
508
  id: 'mock',
@@ -502,27 +510,36 @@ const getServersToDisplay = (originalServers, mockUrl) => {
502
510
  url: mockUrl,
503
511
  });
504
512
  }
505
- return servers;
513
+ return servers.filter(isValidServer);
506
514
  };
507
- const getServerUrlWithDefaultValues = (server) => {
508
- var _a;
509
- let urlString = server.url;
510
- const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
511
- variables.forEach(([variableName, variableInfo]) => {
512
- urlString = urlString.replace(`{${variableName}}`, variableInfo.default);
513
- });
515
+ function resolveUrl(urlString) {
514
516
  let url;
515
517
  try {
516
518
  url = URI(urlString);
517
519
  }
518
- catch (_b) {
520
+ catch (_a) {
519
521
  return null;
520
522
  }
523
+ let stringifiedUrl;
521
524
  if (url.is('relative') && typeof window !== 'undefined') {
522
- url = url.absoluteTo(window.location.origin);
525
+ stringifiedUrl = url.absoluteTo(window.location.origin).toString();
526
+ }
527
+ else {
528
+ stringifiedUrl = url.toString();
523
529
  }
524
- const stringifiedUrl = url.toString();
525
- return stringifiedUrl.endsWith('/') ? stringifiedUrl.slice(0, -1) : stringifiedUrl;
530
+ if (isProperUrl(stringifiedUrl)) {
531
+ return stringifiedUrl.endsWith('/') ? stringifiedUrl.slice(0, -1) : stringifiedUrl;
532
+ }
533
+ return null;
534
+ }
535
+ const getServerUrlWithDefaultValues = (server) => {
536
+ var _a;
537
+ let urlString = server.url;
538
+ const variables = Object.entries((_a = server.variables) !== null && _a !== void 0 ? _a : {});
539
+ variables.forEach(([variableName, variableInfo]) => {
540
+ urlString = urlString.replaceAll(`{${variableName}}`, variableInfo.default);
541
+ });
542
+ return resolveUrl(urlString);
526
543
  };
527
544
 
528
545
  const persistAtom = (key, atomInstance) => {
@@ -1924,8 +1941,7 @@ const TryIt = ({ httpOperation, mockUrl, onRequestChange, requestBodyIndex, embe
1924
1941
  const [textRequestBody, setTextRequestBody] = useTextRequestBodyState(mediaTypeContent);
1925
1942
  const [operationAuthValue, setOperationAuthValue] = usePersistedSecuritySchemeWithValues();
1926
1943
  const servers = React.useMemo(() => {
1927
- const toDisplay = getServersToDisplay(httpOperation.servers || defaultServers, mockUrl);
1928
- return toDisplay;
1944
+ return getServersToDisplay(httpOperation.servers || defaultServers, mockUrl, true);
1929
1945
  }, [httpOperation.servers, mockUrl]);
1930
1946
  const firstServer = servers[0] || null;
1931
1947
  const [chosenServer, setChosenServer] = useAtom(chosenServerAtom);
@@ -2538,7 +2554,8 @@ const ServerInfo = ({ servers, mockUrl }) => {
2538
2554
  const mocking = React.useContext(MockingContext);
2539
2555
  const showMocking = !mocking.hideMocking && mockUrl && isProperUrl(mockUrl);
2540
2556
  const $mockUrl = showMocking ? mockUrl || mocking.mockUrl : undefined;
2541
- const serversToDisplay = getServersToDisplay(servers, $mockUrl);
2557
+ const serversToDisplay = React.useMemo(() => getServersToDisplay(servers, $mockUrl, false), [servers, $mockUrl]);
2558
+ const firstServerVariableIndex = React.useMemo(() => serversToDisplay.findIndex(server => !isEmpty(server.variables)), [serversToDisplay]);
2542
2559
  if (!showMocking && serversToDisplay.length === 0) {
2543
2560
  return null;
2544
2561
  }
@@ -2546,25 +2563,88 @@ const ServerInfo = ({ servers, mockUrl }) => {
2546
2563
  React.createElement(Panel, { rounded: true, isCollapsible: false, className: "BaseURLContent", w: "full" },
2547
2564
  React.createElement(Panel.Titlebar, { whitespace: "nowrap" }, "API Base URL"),
2548
2565
  React.createElement(Panel.Content, { w: "full", className: "sl-flex sl-flex-col" },
2549
- React.createElement(VStack, { spacing: 1, divider: true }, serversToDisplay.map((server, index) => (React.createElement(ServerUrl, Object.assign({}, server, { key: index })))))))));
2566
+ React.createElement(VStack, { spacing: 1, divider: true }, serversToDisplay.map((server, index) => (React.createElement(ServerUrl, Object.assign({}, server, { defaultIsOpen: index === firstServerVariableIndex, hasAnyServerVariables: firstServerVariableIndex !== -1, key: server.id })))))))));
2550
2567
  };
2551
- const ServerUrl = ({ id, description, url }) => {
2568
+ const ServerUrl = ({ id, description, url, variables, hasAnyServerVariables, defaultIsOpen, }) => {
2552
2569
  const { nodeHasChanged } = useOptionsCtx();
2553
2570
  const { onCopy, hasCopied } = useClipboard(url);
2571
+ const urlFragments = useSplitUrl(url);
2554
2572
  const hasChanged = nodeHasChanged === null || nodeHasChanged === void 0 ? void 0 : nodeHasChanged({ nodeId: id });
2555
- return (React.createElement(Box, { whitespace: "nowrap", pos: "relative" },
2556
- React.createElement(Text, { pr: 2, fontWeight: "bold" },
2557
- description,
2558
- ":"),
2559
- React.createElement(Tooltip, { placement: "right", renderTrigger: () => React.createElement(Text, { "aria-label": description }, url) },
2560
- !hasCopied && (React.createElement(Box, { p: 1, onClick: onCopy, cursor: "pointer" },
2561
- "Copy Server URL ",
2562
- React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'copy'] }))),
2563
- hasCopied && (React.createElement(Box, { p: 1 },
2564
- "Copied Server URL ",
2565
- React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'check'] })))),
2566
- React.createElement(NodeAnnotation, { change: hasChanged, additionalLeftOffset: 16 })));
2567
- };
2573
+ const variablesSchema = useVariablesJSONSchema(variables);
2574
+ const titlePaddingLeft = hasAnyServerVariables && !variablesSchema ? 4 : 0;
2575
+ const handleCopyClick = React.useCallback(e => {
2576
+ e.stopPropagation();
2577
+ onCopy();
2578
+ }, [onCopy]);
2579
+ return (React.createElement(Panel, { isCollapsible: !!variablesSchema, defaultIsOpen: defaultIsOpen, w: "full" },
2580
+ React.createElement(Panel.Titlebar, { whitespace: "nowrap" },
2581
+ React.createElement(Text, { pl: titlePaddingLeft, pr: 2, fontWeight: "bold" },
2582
+ description,
2583
+ ":"),
2584
+ React.createElement(Tooltip, { placement: "right", renderTrigger: () => (React.createElement(Text, { "aria-label": description }, urlFragments.map(({ kind, value }, i) => (React.createElement(Text, { key: i, fontWeight: kind === 'variable' ? 'semibold' : 'normal' }, value))))) },
2585
+ !hasCopied && (React.createElement(Box, { p: 1, onClick: handleCopyClick, cursor: "pointer" },
2586
+ "Copy Server URL ",
2587
+ React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'copy'] }))),
2588
+ hasCopied && (React.createElement(Box, { p: 1 },
2589
+ "Copied Server URL ",
2590
+ React.createElement(Icon, { className: "sl-ml-1", icon: ['fas', 'check'] })))),
2591
+ React.createElement(NodeAnnotation, { change: hasChanged, additionalLeftOffset: 16 })),
2592
+ variablesSchema && (React.createElement(Panel.Content, { w: "full" },
2593
+ React.createElement(Box, { pl: 4 },
2594
+ React.createElement(JsonSchemaViewer, { schema: variablesSchema }))))));
2595
+ };
2596
+ function useVariablesJSONSchema(variables) {
2597
+ return React.useMemo(() => {
2598
+ if (isEmpty(variables))
2599
+ return;
2600
+ const propertiesPairs = Object.entries(variables).map(([name, variable]) => [
2601
+ name,
2602
+ Object.assign({ type: 'string' }, omitBy({
2603
+ description: variable.description,
2604
+ enum: variable.enum,
2605
+ default: variable.default,
2606
+ }, isNil)),
2607
+ ]);
2608
+ return {
2609
+ type: 'object',
2610
+ properties: Object.fromEntries(propertiesPairs),
2611
+ };
2612
+ }, [variables]);
2613
+ }
2614
+ function useSplitUrl(url) {
2615
+ return React.useMemo(() => {
2616
+ const curly = /[{}]/g;
2617
+ const fragments = [];
2618
+ let startOffset = 0;
2619
+ let curPos = 0;
2620
+ let match;
2621
+ while ((match = curly.exec(url))) {
2622
+ if (match[0] === '{' || startOffset + 1 === match.index) {
2623
+ startOffset = match.index;
2624
+ continue;
2625
+ }
2626
+ if (startOffset !== curPos) {
2627
+ fragments.push({
2628
+ kind: 'static',
2629
+ value: url.slice(curPos, startOffset),
2630
+ });
2631
+ }
2632
+ const variable = url.slice(startOffset, match.index + 1);
2633
+ fragments.push({
2634
+ kind: 'variable',
2635
+ value: variable,
2636
+ });
2637
+ curPos = startOffset + variable.length;
2638
+ }
2639
+ if (curPos < url.length) {
2640
+ fragments.push({
2641
+ kind: 'static',
2642
+ value: url.slice(curPos),
2643
+ });
2644
+ }
2645
+ return fragments;
2646
+ }, [url]);
2647
+ }
2568
2648
 
2569
2649
  const HttpServiceComponent = React.memo(({ data: unresolvedData, location = {}, layoutOptions, exportProps }) => {
2570
2650
  var _a, _b, _c, _d;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stoplight/elements-core",
3
- "version": "7.7.20",
3
+ "version": "7.8.0",
4
4
  "main": "./index.js",
5
5
  "sideEffects": [
6
6
  "web-components.min.js",
@@ -28,7 +28,7 @@
28
28
  "@stoplight/json": "^3.18.1",
29
29
  "@stoplight/json-schema-ref-parser": "^9.0.5",
30
30
  "@stoplight/json-schema-sampler": "0.2.3",
31
- "@stoplight/json-schema-viewer": "^4.10.1",
31
+ "@stoplight/json-schema-viewer": "^4.10.2",
32
32
  "@stoplight/markdown-viewer": "^5.6.0",
33
33
  "@stoplight/mosaic": "^1.33.0",
34
34
  "@stoplight/mosaic-code-editor": "^1.33.0",
@@ -1,3 +1,3 @@
1
1
  import type { IServer } from '@stoplight/types';
2
- export declare const getServersToDisplay: (originalServers: IServer[], mockUrl?: string) => IServer[];
2
+ export declare const getServersToDisplay: (originalServers: IServer[], mockUrl: string | undefined, inlineDefaults: boolean) => IServer[];
3
3
  export declare const getServerUrlWithDefaultValues: (server: IServer) => string | null;