@scalar/api-client-react 1.4.20 → 2.0.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @scalar/api-client-react
2
2
 
3
+ ## 2.0.1
4
+
5
+ ## 2.0.0
6
+
7
+ ### Major Changes
8
+
9
+ - [#8760](https://github.com/scalar/scalar/pull/8760): feat: update to use api-client v2 with new hook based api
10
+
3
11
  ## 1.4.20
4
12
 
5
13
  ## 1.4.19
package/README.md CHANGED
@@ -11,34 +11,57 @@
11
11
  npm install @scalar/api-client-react
12
12
  ```
13
13
 
14
+ ## ⚠️ Breaking Changes
15
+
16
+ We have updated the API for the client! Please see the new hook based usage below.
17
+
14
18
  ## Usage
15
19
 
16
- First we need to add the provider, you should add it in the highest place you have a unique spec.
20
+ Call `useApiClient()` from any component. No provider is needed.
17
21
 
18
- ```tsx
19
- import { ApiClientModalProvider } from '@scalar/api-client-react'
22
+ The Vue app is created once and appended to `document.body` where it lives for the lifetime of the
23
+ page it survives client-side navigation without losing state.
20
24
 
25
+ The code is ESM lazy loaded with the dynamic import function to make the smallest possible initial bundle size. We also
26
+ handle de-duplication of documents so as long as the URL is the same you will only get one.
27
+
28
+ ```tsx
29
+ import { useApiClient } from '@scalar/api-client-react'
21
30
  import '@scalar/api-client-react/style.css'
22
- ;<ApiClientModalProvider
23
- configuration={{
24
- url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json',
25
- }}>
26
- {children}
27
- </ApiClientModalProvider>
31
+
32
+ export const OpenButton = () => {
33
+ const client = useApiClient({
34
+ configuration: {
35
+ url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json',
36
+ },
37
+ })
38
+
39
+ return (
40
+ <button onClick={() => client?.open({ path: '/planets', method: 'get' })}>
41
+ Open API Client
42
+ </button>
43
+ )
44
+ }
28
45
  ```
29
46
 
30
- Then you can trigger it from anywhere inside of that provider by calling the `useApiClientModal()`
47
+ ### Options
31
48
 
32
- ```tsx
33
- import { useApiClientModal } from '@scalar/api-client-react'
49
+ | Option | Type | Description |
50
+ |---|---|---|
51
+ | `configuration.url` | `string` | URL of an OpenAPI document to load |
52
+ | `configuration.content` | `Record<string, unknown>` | Inline OpenAPI document object |
53
+
54
+ ### Routing to a specific request
34
55
 
35
- const client = useApiClientModal()
56
+ Pass a `RoutePayload` to `client.open()` to navigate directly to a specific endpoint:
57
+
58
+ ```tsx
59
+ const client = useApiClient({
60
+ configuration: { url: '...' },
61
+ })
36
62
 
37
- return (
38
- <button onClick={() => client?.open({ path: '/auth/token', method: 'get' })}>
39
- Click me to open the Api Client
40
- </button>
41
- )
63
+ // Open to a specific request
64
+ client?.open({ path: '/auth/token', method: 'post' })
42
65
  ```
43
66
 
44
67
  Check out the playground for a working example.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export type { OpenClientPayload } from './ApiClientModalProvider';
2
- export { ApiClientModalProvider, useApiClientModal } from './ApiClientModalProvider';
1
+ export type { RoutePayload } from '@scalar/api-client/v2/features/modal';
2
+ export type { ApiClientConfigurationReact, UseApiClientModalProps } from './use-api-client';
3
+ export { useApiClient } from './use-api-client';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;AAExE,YAAY,EAAE,2BAA2B,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -1,115 +1,136 @@
1
1
  "use client";
2
- import { createContext, useContext, useEffect, useRef, useSyncExternalStore } from "react";
3
- import { jsx, jsxs } from "react/jsx-runtime";
4
- //#region src/client-store.ts
5
- globalThis.__VUE_OPTIONS_API__ = true;
6
- globalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = true;
7
- globalThis.__VUE_PROD_DEVTOOLS__ = false;
8
- /** Client state */
9
- var state = {
10
- createClient: null,
11
- clientDict: {}
12
- };
13
- /** Set of listener functions to be called when state changes */
14
- var listeners = /* @__PURE__ */ new Set();
15
- /** Subscribe to state changes */
16
- var subscribe = (listener) => {
17
- listeners.add(listener);
18
- return () => listeners.delete(listener);
19
- };
20
- /** Get the current state at this moment in time */
21
- var getSnapshot = () => state;
22
- /** Trigger all listeners */
23
- var emit = () => listeners.forEach((listener) => listener());
24
- /** Set the create client state */
25
- var setCreateClient = (clientFactory) => {
26
- state = {
27
- ...state,
28
- createClient: clientFactory
29
- };
30
- emit();
31
- };
32
- /** Add a client to the client dict */
33
- var addClient = (url, client) => {
34
- state = {
35
- ...state,
36
- clientDict: {
37
- ...state.clientDict,
38
- [url]: client
39
- }
40
- };
41
- emit();
42
- };
43
- /** Remove a client from the client dict */
44
- var removeClient = (url) => {
45
- const { [url]: _, ...clientDict } = state.clientDict;
46
- state = {
47
- ...state,
48
- clientDict
2
+ import { useEffect, useState } from "react";
3
+ //#region src/lazy-load.ts
4
+ /**
5
+ * Creates a lazy singleton getter: the factory runs at most once, caches the resulting promise,
6
+ * and clears the cache on failure so the next call can retry.
7
+ *
8
+ * Optional args are forwarded to the factory on the first (cache-miss) call only.
9
+ * Subsequent calls return the cached promise regardless of the args passed.
10
+ */
11
+ var makeLazySingleton = (factory) => {
12
+ let cached;
13
+ return (...args) => {
14
+ cached ??= factory(...args).catch((error) => {
15
+ cached = void 0;
16
+ throw error;
17
+ });
18
+ return cached;
49
19
  };
50
- emit();
51
- };
52
- var clientStore = {
53
- getSnapshot,
54
- subscribe,
55
- setCreateClient,
56
- addClient,
57
- removeClient
58
20
  };
21
+ /** Lazy load the client modal creator */
22
+ var getClientModalCreator = makeLazySingleton(() => import("@scalar/api-client/v2/features/modal").then(({ createApiClientModal }) => createApiClientModal));
23
+ /** Module-scoped singleton workspace store (lazy-loaded on first use). */
24
+ var getWorkspaceStoreSingleton = makeLazySingleton(() => import("@scalar/workspace-store/client").then(({ createWorkspaceStore }) => createWorkspaceStore()));
25
+ /** Module-scoped singleton workspace event bus (lazy-loaded on first use). */
26
+ var getWorkspaceEventBusSingleton = makeLazySingleton(() => import("@scalar/workspace-store/events").then(({ createWorkspaceEventBus }) => createWorkspaceEventBus()));
27
+ /**
28
+ * Lazily creates the singleton Vue app, mounts it as the last child of document.body,
29
+ * and returns the controller. Subsequent calls return the same promise.
30
+ *
31
+ * Only modal-level options (e.g. `proxyUrl`) are accepted here. Document-specific fields
32
+ * (`url`, `content`) must be registered via `workspaceStore.addDocument` after the client
33
+ * is ready — they are not part of the modal constructor.
34
+ */
35
+ var getOrCreateApiClient = makeLazySingleton(async (options = {}) => {
36
+ const el = document.createElement("div");
37
+ el.className = "scalar-app";
38
+ document.body.appendChild(el);
39
+ try {
40
+ const [createModal, workspaceStore, eventBus] = await Promise.all([
41
+ getClientModalCreator(),
42
+ getWorkspaceStoreSingleton(),
43
+ getWorkspaceEventBusSingleton()
44
+ ]);
45
+ return {
46
+ apiClient: createModal({
47
+ el,
48
+ eventBus,
49
+ workspaceStore,
50
+ options
51
+ }),
52
+ workspaceStore
53
+ };
54
+ } catch (error) {
55
+ el.remove();
56
+ throw error;
57
+ }
58
+ });
59
59
  //#endregion
60
- //#region src/ApiClientModalProvider.tsx
61
- var ApiClientModalContext = createContext(null);
62
- /** Ensures we only load createClient once */
63
- var isLoading = false;
64
- /** Hack: this is strictly to prevent creation of extra clients as the store lags a bit */
65
- var clientDict = {};
60
+ //#region src/use-api-client.ts
61
+ globalThis.__VUE_OPTIONS_API__ = true;
62
+ globalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = true;
63
+ globalThis.__VUE_PROD_DEVTOOLS__ = false;
64
+ /** Tracks which documents are/have been loaded so we dont duplicate */
65
+ var documentDict = {};
66
66
  /**
67
- * Api Client Modal React
67
+ * Returns the singleton Api Client
68
+ *
69
+ * On first call the Vue app is lazily created and appended to document.body where it
70
+ * lives for the lifetime of the page — it is never unmounted, so it survives client-side
71
+ * navigation without losing state.
68
72
  *
69
- * Provider which mounts the Scalar Api Client Modal vue app.
70
- * Rebuilt to support multiple instances when using a unique spec.url
73
+ * Subsequent calls from any component share the same instance of the client but can use the same
74
+ * or different documents
71
75
  */
72
- var ApiClientModalProvider = ({ children, initialRequest, configuration = {} }) => {
73
- const key = configuration.spec?.url || "default";
74
- const el = useRef(null);
75
- const state = useSyncExternalStore(clientStore.subscribe, clientStore.getSnapshot, clientStore.getSnapshot);
76
+ var useApiClient = ({ configuration } = {}) => {
77
+ const [client, setClient] = useState(void 0);
78
+ const [workspaceStore, setWorkspaceStore] = useState(void 0);
79
+ const [documentSlug, setDocumentSlug] = useState("");
80
+ /** Small wrapper to set the documentSlug */
81
+ const open = (payload) => client?.open({
82
+ documentSlug,
83
+ ...payload
84
+ });
76
85
  useEffect(() => {
77
- const loadApiClientJs = async () => {
78
- isLoading = true;
79
- const { createApiClientModal } = await import("@scalar/api-client/layouts/Modal");
80
- clientStore.setCreateClient(createApiClientModal);
86
+ let cancelled = false;
87
+ const { url, content, ...modalOptions } = configuration ?? {};
88
+ getOrCreateApiClient(modalOptions)?.then((_client) => {
89
+ if (cancelled || !_client) return;
90
+ const slug = url || content?.info?.title || "";
91
+ setClient(_client.apiClient);
92
+ setWorkspaceStore(_client.workspaceStore);
93
+ setDocumentSlug(slug);
94
+ if (slug && !documentDict[slug]) {
95
+ documentDict[slug] = true;
96
+ _client.workspaceStore.addDocument(content ? {
97
+ name: slug,
98
+ document: content
99
+ } : {
100
+ name: slug,
101
+ url: url ?? ""
102
+ });
103
+ }
104
+ });
105
+ return () => {
106
+ cancelled = true;
81
107
  };
82
- if (!isLoading) loadApiClientJs();
83
108
  }, []);
84
109
  useEffect(() => {
85
- if (!el.current || !state.createClient || clientDict[key]) return () => null;
86
- const { client: _client } = state.createClient({
87
- el: el.current,
88
- configuration
110
+ if (!client || !configuration || !workspaceStore) return;
111
+ const slug = configuration.url || configuration.content?.info?.title || "default";
112
+ setDocumentSlug(slug);
113
+ if (documentDict[slug]) return;
114
+ documentDict[slug] = true;
115
+ workspaceStore.addDocument(configuration.content ? {
116
+ name: slug,
117
+ document: configuration.content
118
+ } : {
119
+ name: slug,
120
+ url: configuration.url ?? ""
89
121
  });
90
- const updateConfig = async () => {
91
- await _client.updateConfig(configuration);
92
- if (initialRequest) _client.route(initialRequest);
93
- };
94
- clientStore.addClient(key, _client);
95
- clientDict[key] = _client;
96
- updateConfig();
97
- return () => {
98
- _client.app.unmount();
99
- clientStore.removeClient(key);
100
- delete clientDict[key];
101
- };
102
- }, [el.current, state.createClient]);
103
- return /* @__PURE__ */ jsxs(ApiClientModalContext.Provider, {
104
- value: state.clientDict[key] ?? null,
105
- children: [/* @__PURE__ */ jsx("div", {
106
- className: "scalar-app",
107
- ref: el
108
- }), children]
109
- });
122
+ }, [
123
+ client,
124
+ configuration?.url,
125
+ configuration?.content,
126
+ workspaceStore
127
+ ]);
128
+ return client ? {
129
+ ...client,
130
+ open
131
+ } : void 0;
110
132
  };
111
- var useApiClientModal = () => useContext(ApiClientModalContext);
112
133
  //#endregion
113
- export { ApiClientModalProvider, useApiClientModal };
134
+ export { useApiClient };
114
135
 
115
136
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/client-store.ts","../src/ApiClientModalProvider.tsx"],"sourcesContent":["import type { ApiClient, createApiClientModal } from '@scalar/api-client/layouts/Modal'\n\n// These are required for the vue bundler version\nglobalThis.__VUE_OPTIONS_API__ = true\nglobalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = true\nglobalThis.__VUE_PROD_DEVTOOLS__ = false\n\n/** Client state */\nlet state = {\n createClient: null as typeof createApiClientModal | null,\n clientDict: {} as Record<string, ApiClient>,\n}\n\n/** Set of listener functions to be called when state changes */\nconst listeners = new Set<() => void>()\n\n/** Subscribe to state changes */\nconst subscribe = (listener: () => void) => {\n listeners.add(listener)\n return () => listeners.delete(listener)\n}\n\n/** Get the current state at this moment in time */\nconst getSnapshot = () => state\n\n/** Trigger all listeners */\nconst emit = () => listeners.forEach((listener) => listener())\n\n/** Set the create client state */\nconst setCreateClient = (clientFactory: typeof createApiClientModal) => {\n state = { ...state, createClient: clientFactory }\n emit()\n}\n\n/** Add a client to the client dict */\nconst addClient = (url: string, client: ApiClient) => {\n state = { ...state, clientDict: { ...state.clientDict, [url]: client } }\n emit()\n}\n\n/** Remove a client from the client dict */\nconst removeClient = (url: string) => {\n const { [url]: _, ...clientDict } = state.clientDict\n state = { ...state, clientDict }\n emit()\n}\n\nexport const clientStore = {\n getSnapshot,\n subscribe,\n setCreateClient,\n addClient,\n removeClient,\n}\n","'use client'\n\nimport type { ApiClient } from '@scalar/api-client/layouts/Modal'\nimport type { OpenClientPayload } from '@scalar/api-client/libs'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport type { PropsWithChildren } from 'react'\nimport { createContext, useContext, useEffect, useRef, useSyncExternalStore } from 'react'\n\nexport type { OpenClientPayload }\n\nimport { clientStore } from './client-store'\n\nimport './style.css'\n\nconst ApiClientModalContext = createContext<ApiClient | null>(null)\n\ntype Props = PropsWithChildren<{\n /** Choose a request to initially route to */\n initialRequest?: OpenClientPayload\n /** Configuration for the Api Client */\n configuration?: Partial<ApiClientConfiguration>\n}>\n\n/** Ensures we only load createClient once */\nlet isLoading = false\n\n/** Hack: this is strictly to prevent creation of extra clients as the store lags a bit */\nconst clientDict: Record<string, ApiClient> = {}\n\n/**\n * Api Client Modal React\n *\n * Provider which mounts the Scalar Api Client Modal vue app.\n * Rebuilt to support multiple instances when using a unique spec.url\n */\nexport const ApiClientModalProvider = ({ children, initialRequest, configuration = {} }: Props) => {\n const key = configuration.spec?.url || 'default'\n const el = useRef<HTMLDivElement | null>(null)\n\n const state = useSyncExternalStore(clientStore.subscribe, clientStore.getSnapshot, clientStore.getSnapshot)\n\n // Lazyload the js to create the client, but we only wanna call this once\n useEffect(() => {\n const loadApiClientJs = async () => {\n isLoading = true\n const { createApiClientModal } = await import('@scalar/api-client/layouts/Modal')\n clientStore.setCreateClient(createApiClientModal)\n }\n if (!isLoading) {\n void loadApiClientJs()\n }\n }, [])\n\n useEffect(() => {\n if (!el.current || !state.createClient || clientDict[key]) {\n return () => null\n }\n\n // Check for cached client first\n const { client: _client } = state.createClient({\n el: el.current,\n configuration,\n })\n\n const updateConfig = async () => {\n await _client.updateConfig(configuration!)\n if (initialRequest) {\n _client.route(initialRequest)\n }\n }\n\n // Add the client to the store and dict\n clientStore.addClient(key, _client)\n clientDict[key] = _client\n\n // We update the config as we are using the sync version\n void updateConfig()\n\n // Ensure we unmount the vue app on unmount\n return () => {\n _client.app.unmount()\n clientStore.removeClient(key)\n delete clientDict[key]\n }\n }, [el.current, state.createClient])\n\n return (\n <ApiClientModalContext.Provider value={state.clientDict[key] ?? null}>\n <div\n className=\"scalar-app\"\n ref={el}\n />\n {children}\n </ApiClientModalContext.Provider>\n )\n}\n\nexport const useApiClientModal = (): ApiClient | null => useContext(ApiClientModalContext)\n"],"mappings":";;;;AAGA,WAAW,sBAAsB;AACjC,WAAW,0CAA0C;AACrD,WAAW,wBAAwB;;AAGnC,IAAI,QAAQ;CACV,cAAc;CACd,YAAY,EAAA;CACb;;AAGD,IAAM,4BAAY,IAAI,KAAiB;;AAGvC,IAAM,aAAa,aAAyB;AAC1C,WAAU,IAAI,SAAS;AACvB,cAAa,UAAU,OAAO,SAAS;;;AAIzC,IAAM,oBAAoB;;AAG1B,IAAM,aAAa,UAAU,SAAS,aAAa,UAAU,CAAC;;AAG9D,IAAM,mBAAmB,kBAA+C;AACtE,SAAQ;EAAE,GAAG;EAAO,cAAc;EAAe;AACjD,OAAM;;;AAIR,IAAM,aAAa,KAAa,WAAsB;AACpD,SAAQ;EAAE,GAAG;EAAO,YAAY;GAAE,GAAG,MAAM;IAAa,MAAM;;EAAU;AACxE,OAAM;;;AAIR,IAAM,gBAAgB,QAAgB;CACpC,MAAM,GAAG,MAAM,GAAG,GAAG,eAAe,MAAM;AAC1C,SAAQ;EAAE,GAAG;EAAO;EAAY;AAChC,OAAM;;AAGR,IAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACD;;;ACvCD,IAAM,wBAAwB,cAAgC,KAAK;;AAUnE,IAAI,YAAY;;AAGhB,IAAM,aAAwC,EAAE;;;;;;;AAQhD,IAAa,0BAA0B,EAAE,UAAU,gBAAgB,gBAAgB,EAAE,OAAc;CACjG,MAAM,MAAM,cAAc,MAAM,OAAO;CACvC,MAAM,KAAK,OAA8B,KAAK;CAE9C,MAAM,QAAQ,qBAAqB,YAAY,WAAW,YAAY,aAAa,YAAY,YAAY;AAG3G,iBAAgB;EACd,MAAM,kBAAkB,YAAY;AAClC,eAAY;GACZ,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAC9C,eAAY,gBAAgB,qBAAqB;;AAEnD,MAAI,CAAC,UACE,kBAAiB;IAEvB,EAAE,CAAC;AAEN,iBAAgB;AACd,MAAI,CAAC,GAAG,WAAW,CAAC,MAAM,gBAAgB,WAAW,KACnD,cAAa;EAIf,MAAM,EAAE,QAAQ,YAAY,MAAM,aAAa;GAC7C,IAAI,GAAG;GACP;GACD,CAAC;EAEF,MAAM,eAAe,YAAY;AAC/B,SAAM,QAAQ,aAAa,cAAe;AAC1C,OAAI,eACF,SAAQ,MAAM,eAAe;;AAKjC,cAAY,UAAU,KAAK,QAAQ;AACnC,aAAW,OAAO;AAGb,gBAAc;AAGnB,eAAa;AACX,WAAQ,IAAI,SAAS;AACrB,eAAY,aAAa,IAAI;AAC7B,UAAO,WAAW;;IAEnB,CAAC,GAAG,SAAS,MAAM,aAAa,CAAC;AAEpC,QACE,qBAAC,sBAAsB,UAAvB;EAAgC,OAAO,MAAM,WAAW,QAAQ;YAAhE,CACE,oBAAC,OAAD;GACE,WAAU;GACV,KAAK;GACL,CAAA,EACD,SAAA;;;AAKP,IAAa,0BAA4C,WAAW,sBAAsB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/lazy-load.ts","../src/use-api-client.ts"],"sourcesContent":["import type { ApiClientConfiguration } from '@scalar/types/api-reference'\n\n/**\n * Creates a lazy singleton getter: the factory runs at most once, caches the resulting promise,\n * and clears the cache on failure so the next call can retry.\n *\n * Optional args are forwarded to the factory on the first (cache-miss) call only.\n * Subsequent calls return the cached promise regardless of the args passed.\n */\nconst makeLazySingleton = <T, Args extends unknown[] = []>(\n factory: (...args: Args) => Promise<T>,\n): ((...args: Args) => Promise<T>) => {\n let cached: Promise<T> | undefined\n return (...args: Args) => {\n cached ??= factory(...args).catch((error) => {\n cached = undefined\n throw error\n })\n return cached\n }\n}\n\n/** Lazy load the client modal creator */\nexport const getClientModalCreator = makeLazySingleton(() =>\n import('@scalar/api-client/v2/features/modal').then(({ createApiClientModal }) => createApiClientModal),\n)\n\n/** Module-scoped singleton workspace store (lazy-loaded on first use). */\nexport const getWorkspaceStoreSingleton = makeLazySingleton(() =>\n import('@scalar/workspace-store/client').then(({ createWorkspaceStore }) => createWorkspaceStore()),\n)\n\n/** Module-scoped singleton workspace event bus (lazy-loaded on first use). */\nexport const getWorkspaceEventBusSingleton = makeLazySingleton(() =>\n import('@scalar/workspace-store/events').then(({ createWorkspaceEventBus }) => createWorkspaceEventBus()),\n)\n\n/**\n * Lazily creates the singleton Vue app, mounts it as the last child of document.body,\n * and returns the controller. Subsequent calls return the same promise.\n *\n * Only modal-level options (e.g. `proxyUrl`) are accepted here. Document-specific fields\n * (`url`, `content`) must be registered via `workspaceStore.addDocument` after the client\n * is ready they are not part of the modal constructor.\n */\nexport const getOrCreateApiClient = makeLazySingleton(async (options: Partial<ApiClientConfiguration> = {}) => {\n const el = document.createElement('div')\n el.className = 'scalar-app'\n document.body.appendChild(el)\n\n try {\n const [createModal, workspaceStore, eventBus] = await Promise.all([\n getClientModalCreator(),\n getWorkspaceStoreSingleton(),\n getWorkspaceEventBusSingleton(),\n ])\n\n const apiClient = createModal({\n el,\n eventBus,\n workspaceStore,\n options,\n // TODO: map plugins from configuration when available\n // plugins: mapConfigPlugins(options),\n })\n\n return { apiClient, workspaceStore }\n } catch (error) {\n el.remove()\n throw error\n }\n})\n","'use client'\n\nimport type { ApiClientModal, RoutePayload } from '@scalar/api-client/v2/features/modal'\nimport type { ApiClientConfiguration } from '@scalar/types/api-reference'\nimport { useEffect, useState } from 'react'\n\nimport './style.css'\n\nimport type { WorkspaceStore } from '@scalar/workspace-store/client'\n\nimport { getOrCreateApiClient } from './lazy-load'\n\nglobalThis.__VUE_OPTIONS_API__ = true\nglobalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = true\nglobalThis.__VUE_PROD_DEVTOOLS__ = false\n\n/** We don't really need all of the content types so we just accept an object instead */\nexport type ApiClientConfigurationReact = Partial<\n Omit<ApiClientConfiguration, 'content' | 'url'> & { content?: Record<string, unknown>; url?: string }\n>\n\nexport type UseApiClientModalProps = {\n /** Configuration for the Api Client (url or inline content) */\n configuration?: ApiClientConfigurationReact\n}\n\n/** Tracks which documents are/have been loaded so we dont duplicate */\nconst documentDict: Record<string, true> = {}\n\n/**\n * Returns the singleton Api Client\n *\n * On first call the Vue app is lazily created and appended to document.body where it\n * lives for the lifetime of the page it is never unmounted, so it survives client-side\n * navigation without losing state.\n *\n * Subsequent calls from any component share the same instance of the client but can use the same\n * or different documents\n */\nexport const useApiClient = ({\n configuration,\n}: UseApiClientModalProps = {}):\n | (Omit<ApiClientModal, 'open'> & { open: (payload: RoutePayload) => void })\n | undefined => {\n const [client, setClient] = useState<ApiClientModal | undefined>(undefined)\n const [workspaceStore, setWorkspaceStore] = useState<WorkspaceStore | undefined>(undefined)\n const [documentSlug, setDocumentSlug] = useState('')\n\n /** Small wrapper to set the documentSlug */\n const open = (payload: RoutePayload) => client?.open({ documentSlug, ...payload })\n\n useEffect(() => {\n let cancelled = false\n\n // Strip document-specific fields before passing to the modal constructor.\n // `url` and `content` are registered separately via workspaceStore.addDocument.\n const { url, content, ...modalOptions } = configuration ?? {}\n\n void getOrCreateApiClient(modalOptions)?.then((_client) => {\n if (cancelled || !_client) {\n return\n }\n\n // Compute the slug here so we can batch all three state updates into one render,\n // preventing a render where `client` is set but `documentSlug` is still ''.\n const slug = url || (content as { info?: { title?: string } })?.info?.title || ''\n\n setClient(_client.apiClient)\n setWorkspaceStore(_client.workspaceStore)\n setDocumentSlug(slug)\n\n if (slug && !documentDict[slug]) {\n documentDict[slug] = true\n void _client.workspaceStore.addDocument(\n content ? { name: slug, document: content } : { name: slug, url: url ?? '' },\n )\n }\n })\n\n return () => {\n cancelled = true\n }\n\n // Only run once per mount\n }, [])\n\n // When url or content changes after the client is already mounted, register the new document\n useEffect(() => {\n if (!client || !configuration || !workspaceStore) {\n return\n }\n\n const slug = configuration.url || (configuration.content as { info?: { title?: string } })?.info?.title || 'default'\n setDocumentSlug(slug)\n\n if (documentDict[slug]) {\n return\n }\n documentDict[slug] = true\n\n void workspaceStore.addDocument(\n configuration.content\n ? { name: slug, document: configuration.content }\n : { name: slug, url: configuration.url ?? '' },\n )\n }, [client, configuration?.url, configuration?.content, workspaceStore])\n\n return client\n ? {\n ...client,\n open,\n }\n : undefined\n}\n"],"mappings":";;;;;;;;;;AASA,IAAM,qBACJ,YACoC;CACpC,IAAI;AACJ,SAAQ,GAAG,SAAe;AACxB,aAAW,QAAQ,GAAG,KAAK,CAAC,OAAO,UAAU;AAC3C,YAAS,KAAA;AACT,SAAM;IACN;AACF,SAAO;;;;AAKX,IAAa,wBAAwB,wBACnC,OAAO,wCAAwC,MAAM,EAAE,2BAA2B,qBAAqB,CACxG;;AAGD,IAAa,6BAA6B,wBACxC,OAAO,kCAAkC,MAAM,EAAE,2BAA2B,sBAAsB,CAAC,CACpG;;AAGD,IAAa,gCAAgC,wBAC3C,OAAO,kCAAkC,MAAM,EAAE,8BAA8B,yBAAyB,CAAC,CAC1G;;;;;;;;;AAUD,IAAa,uBAAuB,kBAAkB,OAAO,UAA2C,EAAE,KAAK;CAC7G,MAAM,KAAK,SAAS,cAAc,MAAM;AACxC,IAAG,YAAY;AACf,UAAS,KAAK,YAAY,GAAG;AAE7B,KAAI;EACF,MAAM,CAAC,aAAa,gBAAgB,YAAY,MAAM,QAAQ,IAAI;GAChE,uBAAuB;GACvB,4BAA4B;GAC5B,+BAAA;GACD,CAAC;AAWF,SAAO;GAAE,WATS,YAAY;IAC5B;IACA;IACA;IACA;IAGD,CAAC;GAEkB;GAAgB;UAC7B,OAAO;AACd,KAAG,QAAQ;AACX,QAAM;;EAER;;;AC3DF,WAAW,sBAAsB;AACjC,WAAW,0CAA0C;AACrD,WAAW,wBAAwB;;AAanC,IAAM,eAAqC,EAAE;;;;;;;;;;;AAY7C,IAAa,gBAAgB,EAC3B,kBAC0B,EAAE,KAEb;CACf,MAAM,CAAC,QAAQ,aAAa,SAAqC,KAAA,EAAU;CAC3E,MAAM,CAAC,gBAAgB,qBAAqB,SAAqC,KAAA,EAAU;CAC3F,MAAM,CAAC,cAAc,mBAAmB,SAAS,GAAG;;CAGpD,MAAM,QAAQ,YAA0B,QAAQ,KAAK;EAAE;EAAc,GAAG;EAAS,CAAC;AAElF,iBAAgB;EACd,IAAI,YAAY;EAIhB,MAAM,EAAE,KAAK,SAAS,GAAG,iBAAiB,iBAAiB,EAAE;AAExD,uBAAqB,aAAa,EAAE,MAAM,YAAY;AACzD,OAAI,aAAa,CAAC,QAChB;GAKF,MAAM,OAAO,OAAQ,SAA2C,MAAM,SAAS;AAE/E,aAAU,QAAQ,UAAU;AAC5B,qBAAkB,QAAQ,eAAe;AACzC,mBAAgB,KAAK;AAErB,OAAI,QAAQ,CAAC,aAAa,OAAO;AAC/B,iBAAa,QAAQ;AAChB,YAAQ,eAAe,YAC1B,UAAU;KAAE,MAAM;KAAM,UAAU;KAAS,GAAG;KAAE,MAAM;KAAM,KAAK,OAAO;KAAI,CAC7E;;IAEH;AAEF,eAAa;AACX,eAAY;;IAIb,EAAE,CAAC;AAGN,iBAAgB;AACd,MAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,eAChC;EAGF,MAAM,OAAO,cAAc,OAAQ,cAAc,SAA2C,MAAM,SAAS;AAC3G,kBAAgB,KAAK;AAErB,MAAI,aAAa,MACf;AAEF,eAAa,QAAQ;AAEhB,iBAAe,YAClB,cAAc,UACV;GAAE,MAAM;GAAM,UAAU,cAAc;GAAS,GAC/C;GAAE,MAAM;GAAM,KAAK,cAAc,OAAO;GAAI,CACjD;IACA;EAAC;EAAQ,eAAe;EAAK,eAAe;EAAS;EAAe,CAAC;AAExE,QAAO,SACH;EACE,GAAG;EACH;EACD,GACD,KAAA"}
@@ -0,0 +1,87 @@
1
+ /** Lazy load the client modal creator */
2
+ export declare const getClientModalCreator: () => Promise<({ el, eventBus, mountOnInitialize, plugins, workspaceStore, options, }: {
3
+ el: HTMLElement | null;
4
+ mountOnInitialize?: boolean;
5
+ eventBus?: import("@scalar/workspace-store/events").WorkspaceEventBus;
6
+ workspaceStore: import("@scalar/workspace-store/client").WorkspaceStore;
7
+ plugins?: import("@scalar/oas-utils/helpers").ClientPlugin[];
8
+ options?: import("vue").MaybeRefOrGetter<Partial<Pick<import("@scalar/types/api-reference").ApiReferenceConfigurationRaw, "authentication" | "baseServerURL" | "hideClientButton" | "servers" | "hiddenClients">>>;
9
+ }) => import("@scalar/api-client/v2/features/modal").ApiClientModal>;
10
+ /** Module-scoped singleton workspace store (lazy-loaded on first use). */
11
+ export declare const getWorkspaceStoreSingleton: () => Promise<import("@scalar/workspace-store/client").WorkspaceStore>;
12
+ /** Module-scoped singleton workspace event bus (lazy-loaded on first use). */
13
+ export declare const getWorkspaceEventBusSingleton: () => Promise<import("@scalar/workspace-store/events").WorkspaceEventBus>;
14
+ /**
15
+ * Lazily creates the singleton Vue app, mounts it as the last child of document.body,
16
+ * and returns the controller. Subsequent calls return the same promise.
17
+ *
18
+ * Only modal-level options (e.g. `proxyUrl`) are accepted here. Document-specific fields
19
+ * (`url`, `content`) must be registered via `workspaceStore.addDocument` after the client
20
+ * is ready — they are not part of the modal constructor.
21
+ */
22
+ export declare const getOrCreateApiClient: (options?: Partial<{
23
+ hideClientButton: boolean;
24
+ showSidebar: boolean;
25
+ showDeveloperTools: "never" | "always" | "localhost";
26
+ showToolbar: "never" | "always" | "localhost";
27
+ operationTitleSource: "summary" | "path";
28
+ theme: "default" | "alternate" | "moon" | "purple" | "solarized" | "bluePlanet" | "deepSpace" | "saturn" | "kepler" | "elysiajs" | "fastify" | "mars" | "laserwave" | "none";
29
+ persistAuth: boolean;
30
+ telemetry: boolean;
31
+ externalUrls: {
32
+ dashboardUrl: string;
33
+ registryUrl: string;
34
+ proxyUrl: string;
35
+ apiBaseUrl: string;
36
+ };
37
+ authentication?: any;
38
+ baseServerURL?: string | undefined;
39
+ proxyUrl?: string | undefined;
40
+ oauth2RedirectUri?: string | undefined;
41
+ searchHotKey?: "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" | undefined;
42
+ servers?: any[] | undefined;
43
+ _integration?: "elysiajs" | "fastify" | "adonisjs" | "astro" | "docusaurus" | "dotnet" | "express" | "fastapi" | "go" | "hono" | "html" | "laravel" | "litestar" | "nestjs" | "nextjs" | "nitro" | "nuxt" | "platformatic" | "react" | "rust" | "svelte" | "vue" | null | undefined;
44
+ onRequestSent?: import("zod/v4/core").$InferOuterFunctionType<import("zod").ZodTuple<readonly [import("zod").ZodString], null>, import("zod").ZodVoid> | undefined;
45
+ plugins?: import("zod/v4/core").$InferOuterFunctionType<import("zod").ZodTuple<readonly [], null>, import("zod").ZodObject<{
46
+ name: import("zod").ZodString;
47
+ views: import("zod").ZodOptional<import("zod").ZodObject<{
48
+ "request.section": import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
49
+ title: import("zod").ZodOptional<import("zod").ZodString>;
50
+ component: import("zod").ZodUnknown;
51
+ props: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
52
+ }, import("zod/v4/core").$strip>>>;
53
+ "response.section": import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
54
+ title: import("zod").ZodOptional<import("zod").ZodString>;
55
+ component: import("zod").ZodUnknown;
56
+ props: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>>;
57
+ }, import("zod/v4/core").$strip>>>;
58
+ }, import("zod/v4/core").$strip>>;
59
+ hooks: import("zod").ZodOptional<import("zod").ZodObject<{
60
+ onBeforeRequest: import("zod").ZodOptional<import("zod").ZodFunction<import("zod").ZodTuple<readonly [import("zod").ZodObject<{
61
+ request: import("zod").ZodAny;
62
+ }, import("zod/v4/core").$strip>], null>, import("zod/v4/core").$ZodFunctionOut>>;
63
+ onResponseReceived: import("zod").ZodOptional<import("zod").ZodFunction<import("zod").ZodTuple<readonly [import("zod").ZodObject<{
64
+ response: import("zod").ZodCustom<Response, Response>;
65
+ operation: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>;
66
+ }, import("zod/v4/core").$strip>], null>, import("zod/v4/core").$ZodFunctionOut>>;
67
+ }, import("zod/v4/core").$strip>>;
68
+ }, import("zod/v4/core").$strip>>[] | undefined;
69
+ default?: boolean | undefined;
70
+ url?: string | undefined;
71
+ content?: string | Record<string, any> | import("zod/v4/core").$InferOuterFunctionType<import("zod").ZodTuple<readonly [], null>, import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>> | null | undefined;
72
+ title?: string | undefined;
73
+ slug?: string | undefined;
74
+ spec?: {
75
+ url?: string | undefined;
76
+ content?: string | Record<string, any> | import("zod/v4/core").$InferOuterFunctionType<import("zod").ZodTuple<readonly [], null>, import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodAny>> | null | undefined;
77
+ } | undefined;
78
+ agent?: {
79
+ key?: string | undefined;
80
+ disabled?: boolean | undefined;
81
+ hideAddApi?: boolean | undefined;
82
+ } | undefined;
83
+ }> | undefined) => Promise<{
84
+ apiClient: import("@scalar/api-client/v2/features/modal").ApiClientModal;
85
+ workspaceStore: import("@scalar/workspace-store/client").WorkspaceStore;
86
+ }>;
87
+ //# sourceMappingURL=lazy-load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lazy-load.d.ts","sourceRoot":"","sources":["../src/lazy-load.ts"],"names":[],"mappings":"AAsBA,yCAAyC;AACzC,eAAO,MAAM,qBAAqB;;;;;;;oEAEjC,CAAA;AAED,0EAA0E;AAC1E,eAAO,MAAM,0BAA0B,wEAEtC,CAAA;AAED,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B,2EAEzC,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0B/B,CAAA"}
package/dist/style.css CHANGED
@@ -1014,6 +1014,7 @@ input[data-v-c1a50a6e]::placeholder {
1014
1014
  --scalar-text-decoration-hover: underline;
1015
1015
  --scalar-link-font-weight: inherit;
1016
1016
  --scalar-sidebar-indent: 20px;
1017
+ --scalar-sidebar-padding: 12px;
1017
1018
  }
1018
1019
 
1019
1020
  .dark-mode {
@@ -1436,6 +1437,15 @@ input[data-v-c1a50a6e]::placeholder {
1436
1437
  width: 50dvw !important;
1437
1438
  }
1438
1439
  }
1440
+ @keyframes border-bottom {
1441
+ from {
1442
+ border-bottom-width: 0;
1443
+ }
1444
+
1445
+ to {
1446
+ border-bottom-width: var(--scalar-border-width);
1447
+ }
1448
+ }
1439
1449
  @property --tw-font-weight {
1440
1450
  syntax: "*";
1441
1451
  inherits: false
@@ -2935,6 +2945,11 @@ input[data-v-c1a50a6e]::placeholder {
2935
2945
  max-height: 3.40282e38px;
2936
2946
  display: flex;
2937
2947
  }
2948
+ .animate-sidebar-border-bottom {
2949
+ animation: forwards border-bottom;
2950
+ animation-timeline: scroll();
2951
+ animation-range-end: 1px;
2952
+ }
2938
2953
  .group\/sidebar-section:first-of-type > .group\/spacer-before, .group\/sidebar-section:last-of-type > .group\/spacer-after {
2939
2954
  height: 0;
2940
2955
  }
@@ -3144,6 +3159,9 @@ input[data-v-c1a50a6e]::placeholder {
3144
3159
  .scalar-app .top-\(--nested-items-offset\)\! {
3145
3160
  top: var(--nested-items-offset) !important;
3146
3161
  }
3162
+ .scalar-app .top-\(--scalar-sidebar-sticky-offset\,0\) {
3163
+ top: var(--scalar-sidebar-sticky-offset, 0);
3164
+ }
3147
3165
  .scalar-app .top-0\.5 {
3148
3166
  top: 2px;
3149
3167
  }
@@ -3231,6 +3249,9 @@ input[data-v-c1a50a6e]::placeholder {
3231
3249
  max-width: 96rem;
3232
3250
  }
3233
3251
  }
3252
+ .scalar-app .-m-\(--scalar-sidebar-padding\) {
3253
+ margin: calc(var(--scalar-sidebar-padding) * -1);
3254
+ }
3234
3255
  .scalar-app .-m-1 {
3235
3256
  margin: -4px;
3236
3257
  }
@@ -3358,6 +3379,9 @@ input[data-v-c1a50a6e]::placeholder {
3358
3379
  .scalar-app .min-h-header {
3359
3380
  min-height: 48px;
3360
3381
  }
3382
+ .scalar-app .w-\(--scalar-sidebar-width\) {
3383
+ width: var(--scalar-sidebar-width);
3384
+ }
3361
3385
  .scalar-app .w-12 {
3362
3386
  width: 48px;
3363
3387
  }
@@ -3543,6 +3567,9 @@ input[data-v-c1a50a6e]::placeholder {
3543
3567
  .scalar-app .border-sidebar-border-search {
3544
3568
  border-color: var(--scalar-sidebar-search-border-color, var(--scalar-border-color));
3545
3569
  }
3570
+ .scalar-app .border-b-sidebar-border {
3571
+ border-bottom-color: var(--scalar-sidebar-border-color, var(--scalar-border-color));
3572
+ }
3546
3573
  .scalar-app .bg-\(--bg-light\) {
3547
3574
  background-color: var(--bg-light);
3548
3575
  }
@@ -3651,6 +3678,9 @@ input[data-v-c1a50a6e]::placeholder {
3651
3678
  -webkit-mask-repeat: repeat;
3652
3679
  mask-repeat: repeat;
3653
3680
  }
3681
+ .scalar-app .p-\(--scalar-sidebar-padding\) {
3682
+ padding: var(--scalar-sidebar-padding);
3683
+ }
3654
3684
  .scalar-app .p-0\.25 {
3655
3685
  padding: 1px;
3656
3686
  }
@@ -3862,11 +3892,11 @@ input[data-v-c1a50a6e]::placeholder {
3862
3892
  .scalar-app .placeholder\:font-\[inherit\]::placeholder {
3863
3893
  font-family: inherit;
3864
3894
  }
3865
- .scalar-app .first\:rounded-t-\[inherit\]:first-child, :is(.scalar-app .\*\:first\:rounded-t-\[inherit\] > *):first-child {
3895
+ .scalar-app .first\:rounded-t-\[inherit\]:first-child {
3866
3896
  border-top-left-radius: inherit;
3867
3897
  border-top-right-radius: inherit;
3868
3898
  }
3869
- .scalar-app .last\:rounded-b-\[inherit\]:last-child, :is(.scalar-app .\*\:last\:rounded-b-\[inherit\] > *):last-child {
3899
+ .scalar-app .last\:rounded-b-\[inherit\]:last-child {
3870
3900
  border-bottom-right-radius: inherit;
3871
3901
  border-bottom-left-radius: inherit;
3872
3902
  }
@@ -4042,6 +4072,9 @@ input[data-v-c1a50a6e]::placeholder {
4042
4072
  inherits: false;
4043
4073
  initial-value: "";
4044
4074
  }
4075
+ :where(.scalar-app) [class*="rotate-"], :where(.scalar-app) [class*="translate-"], :where(.scalar-app) [class*="scale-"] {
4076
+ transform: none;
4077
+ }
4045
4078
  .scalar-app .pointer-events-auto {
4046
4079
  pointer-events: auto;
4047
4080
  }
@@ -4727,12 +4760,18 @@ input[data-v-c1a50a6e]::placeholder {
4727
4760
  .scalar-app .min-h-20 {
4728
4761
  min-height: 80px;
4729
4762
  }
4763
+ .scalar-app .min-h-\[4rem\] {
4764
+ min-height: 4rem;
4765
+ }
4730
4766
  .scalar-app .min-h-\[64px\] {
4731
4767
  min-height: 64px;
4732
4768
  }
4733
4769
  .scalar-app .min-h-\[65px\] {
4734
4770
  min-height: 65px;
4735
4771
  }
4772
+ .scalar-app .min-h-\[300px\] {
4773
+ min-height: 300px;
4774
+ }
4736
4775
  .scalar-app .min-h-\[calc\(1rem\*4\)\] {
4737
4776
  min-height: 4rem;
4738
4777
  }
@@ -4757,6 +4796,9 @@ input[data-v-c1a50a6e]::placeholder {
4757
4796
  .scalar-app .w-1\/2 {
4758
4797
  width: 50%;
4759
4798
  }
4799
+ .scalar-app .w-2 {
4800
+ width: 8px;
4801
+ }
4760
4802
  .scalar-app .w-2\.5 {
4761
4803
  width: 10px;
4762
4804
  }
@@ -5342,7 +5384,7 @@ input[data-v-c1a50a6e]::placeholder {
5342
5384
  .scalar-app .border-\(--scalar-color-alert\) {
5343
5385
  border-color: var(--scalar-color-alert);
5344
5386
  }
5345
- .scalar-app .border-border {
5387
+ .scalar-app .border-\[var\(--scalar-border-color\)\], .scalar-app .border-border {
5346
5388
  border-color: var(--scalar-border-color);
5347
5389
  }
5348
5390
  .scalar-app .border-c-1 {
@@ -5379,6 +5421,18 @@ input[data-v-c1a50a6e]::placeholder {
5379
5421
  .scalar-app .bg-\(--scalar-background-alert\) {
5380
5422
  background-color: var(--scalar-background-alert);
5381
5423
  }
5424
+ .scalar-app .bg-\[var\(--scalar-background-1\)\] {
5425
+ background-color: var(--scalar-background-1);
5426
+ }
5427
+ .scalar-app .bg-\[var\(--scalar-background-2\)\] {
5428
+ background-color: var(--scalar-background-2);
5429
+ }
5430
+ .scalar-app .bg-\[var\(--scalar-background-3\)\] {
5431
+ background-color: var(--scalar-background-3);
5432
+ }
5433
+ .scalar-app .bg-\[var\(--scalar-color-green\)\] {
5434
+ background-color: var(--scalar-color-green);
5435
+ }
5382
5436
  .scalar-app .bg-b-1 {
5383
5437
  background-color: var(--scalar-background-1);
5384
5438
  }
@@ -6036,6 +6090,10 @@ input[data-v-c1a50a6e]::placeholder {
6036
6090
  --tw-shadow: 0 -8px 0 8px var(--tw-shadow-color, var(--scalar-background-1)), 0 0 8px 8px var(--tw-shadow-color, var(--scalar-background-1));
6037
6091
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
6038
6092
  }
6093
+ .scalar-app .shadow-\[var\(--scalar-shadow-1\)\] {
6094
+ --tw-shadow: var(--scalar-shadow-1);
6095
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
6096
+ }
6039
6097
  .scalar-app .shadow-border {
6040
6098
  --tw-shadow: inset 0 0 0 var(--tw-shadow-color, var(--scalar-border-width)) var(--scalar-border-color);
6041
6099
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -6096,6 +6154,11 @@ input[data-v-c1a50a6e]::placeholder {
6096
6154
  transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
6097
6155
  transition-duration: var(--tw-duration, var(--default-transition-duration));
6098
6156
  }
6157
+ .scalar-app .transition-all {
6158
+ transition-property: all;
6159
+ transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
6160
+ transition-duration: var(--tw-duration, var(--default-transition-duration));
6161
+ }
6099
6162
  .scalar-app .transition-colors {
6100
6163
  transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
6101
6164
  transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
@@ -6479,6 +6542,10 @@ input[data-v-c1a50a6e]::placeholder {
6479
6542
  border-color: inherit;
6480
6543
  }
6481
6544
 
6545
+ .scalar-app .hover\:bg-\[var\(--scalar-background-3\)\]:hover {
6546
+ background-color: var(--scalar-background-3);
6547
+ }
6548
+
6482
6549
  .scalar-app .hover\:bg-b-2:hover, .scalar-app .hover\:bg-b-2\/40:hover {
6483
6550
  background-color: var(--scalar-background-2);
6484
6551
  }
@@ -7359,51 +7426,51 @@ input[data-v-c1a50a6e]::placeholder {
7359
7426
  .description[data-v-1b7a32a4] .markdown > *:first-child {
7360
7427
  margin-top: 0;
7361
7428
  }
7362
- [data-v-e06ea441] .cm-editor {
7429
+ [data-v-0ec7147f] .cm-editor {
7363
7430
  height: 100%;
7364
7431
  outline: none;
7365
7432
  width: 100%;
7366
7433
  }
7367
- [data-v-e06ea441] .cm-line {
7434
+ [data-v-0ec7147f] .cm-line {
7368
7435
  padding: 0;
7369
7436
  }
7370
- [data-v-e06ea441] .cm-content {
7437
+ [data-v-0ec7147f] .cm-content {
7371
7438
  padding: 0;
7372
7439
  display: flex;
7373
7440
  align-items: center;
7374
7441
  font-size: var(--scalar-small);
7375
7442
  }
7376
- .scroll-timeline-x[data-v-e06ea441] {
7443
+ .scroll-timeline-x[data-v-0ec7147f] {
7377
7444
  scroll-timeline: --scroll-timeline x;
7378
7445
  /* Firefox supports */
7379
7446
  scroll-timeline: --scroll-timeline horizontal;
7380
7447
  -ms-overflow-style: none; /* IE and Edge */
7381
7448
  }
7382
- .scroll-timeline-x-hidden[data-v-e06ea441] {
7449
+ .scroll-timeline-x-hidden[data-v-0ec7147f] {
7383
7450
  overflow-x: auto;
7384
7451
  }
7385
- .scroll-timeline-x-hidden[data-v-e06ea441] .cm-scroller {
7452
+ .scroll-timeline-x-hidden[data-v-0ec7147f] .cm-scroller {
7386
7453
  scrollbar-width: none;
7387
7454
  -ms-overflow-style: none;
7388
7455
  padding-right: 20px;
7389
7456
  overflow: auto;
7390
7457
  }
7391
- .scroll-timeline-x-hidden[data-v-e06ea441]::-webkit-scrollbar {
7458
+ .scroll-timeline-x-hidden[data-v-0ec7147f]::-webkit-scrollbar {
7392
7459
  width: 0;
7393
7460
  height: 0;
7394
7461
  display: none;
7395
7462
  }
7396
- .scroll-timeline-x-hidden[data-v-e06ea441] .cm-scroller::-webkit-scrollbar {
7463
+ .scroll-timeline-x-hidden[data-v-0ec7147f] .cm-scroller::-webkit-scrollbar {
7397
7464
  width: 0;
7398
7465
  height: 0;
7399
7466
  display: none;
7400
7467
  }
7401
- .scroll-timeline-x-address[data-v-e06ea441] {
7468
+ .scroll-timeline-x-address[data-v-0ec7147f] {
7402
7469
  line-height: 27px;
7403
7470
  scrollbar-width: none; /* Firefox */
7404
7471
  }
7405
7472
  /* make clickable are to left of send button */
7406
- .scroll-timeline-x-address[data-v-e06ea441]:after {
7473
+ .scroll-timeline-x-address[data-v-0ec7147f]:after {
7407
7474
  content: '';
7408
7475
  position: absolute;
7409
7476
  height: 100%;
@@ -7411,24 +7478,24 @@ input[data-v-c1a50a6e]::placeholder {
7411
7478
  right: 0;
7412
7479
  cursor: text;
7413
7480
  }
7414
- .scroll-timeline-x-address[data-v-e06ea441]:empty:before {
7481
+ .scroll-timeline-x-address[data-v-0ec7147f]:empty:before {
7415
7482
  content: 'Enter URL or cURL request';
7416
7483
  color: var(--scalar-color-3);
7417
7484
  pointer-events: none;
7418
7485
  }
7419
- .fade-left[data-v-e06ea441],
7420
- .fade-right[data-v-e06ea441] {
7486
+ .fade-left[data-v-0ec7147f],
7487
+ .fade-right[data-v-0ec7147f] {
7421
7488
  content: '';
7422
7489
  position: sticky;
7423
7490
  height: 100%;
7424
- animation-name: fadein-e06ea441;
7491
+ animation-name: fadein-0ec7147f;
7425
7492
  animation-duration: 1ms;
7426
7493
  animation-direction: reverse;
7427
7494
  animation-timeline: --scroll-timeline;
7428
7495
  pointer-events: none;
7429
7496
  z-index: 1;
7430
7497
  }
7431
- .fade-left[data-v-e06ea441] {
7498
+ .fade-left[data-v-0ec7147f] {
7432
7499
  background: linear-gradient(
7433
7500
  -90deg,
7434
7501
  color-mix(in srgb, var(--scalar-address-bar-bg), transparent 100%) 0%,
@@ -7439,7 +7506,7 @@ input[data-v-c1a50a6e]::placeholder {
7439
7506
  min-width: 6px;
7440
7507
  animation-direction: normal;
7441
7508
  }
7442
- .fade-right[data-v-e06ea441] {
7509
+ .fade-right[data-v-0ec7147f] {
7443
7510
  background: linear-gradient(
7444
7511
  90deg,
7445
7512
  color-mix(in srgb, var(--scalar-address-bar-bg), transparent 100%) 0%,
@@ -7449,7 +7516,7 @@ input[data-v-c1a50a6e]::placeholder {
7449
7516
  right: -1px;
7450
7517
  min-width: 24px;
7451
7518
  }
7452
- @keyframes fadein-e06ea441 {
7519
+ @keyframes fadein-0ec7147f {
7453
7520
  0% {
7454
7521
  opacity: 0;
7455
7522
  }
@@ -7457,7 +7524,7 @@ input[data-v-c1a50a6e]::placeholder {
7457
7524
  opacity: 1;
7458
7525
  }
7459
7526
  }
7460
- .address-bar-bg-states[data-v-e06ea441] {
7527
+ .address-bar-bg-states[data-v-0ec7147f] {
7461
7528
  --scalar-address-bar-bg: color-mix(
7462
7529
  in srgb,
7463
7530
  var(--scalar-background-1),
@@ -7465,14 +7532,14 @@ input[data-v-c1a50a6e]::placeholder {
7465
7532
  );
7466
7533
  background: var(--scalar-address-bar-bg);
7467
7534
  }
7468
- .address-bar-bg-states[data-v-e06ea441]:has(.cm-focused) {
7535
+ .address-bar-bg-states[data-v-0ec7147f]:has(.cm-focused) {
7469
7536
  --scalar-address-bar-bg: var(--scalar-background-1);
7470
7537
  border-color: var(--scalar-border-color);
7471
7538
  outline-width: 1px;
7472
7539
  outline-style: solid;
7473
7540
  }
7474
- .address-bar-bg-states:has(.cm-focused) .fade-left[data-v-e06ea441],
7475
- .address-bar-bg-states:has(.cm-focused) .fade-right[data-v-e06ea441] {
7541
+ .address-bar-bg-states:has(.cm-focused) .fade-left[data-v-0ec7147f],
7542
+ .address-bar-bg-states:has(.cm-focused) .fade-right[data-v-0ec7147f] {
7476
7543
  --scalar-address-bar-bg: var(--scalar-background-1);
7477
7544
  }
7478
7545
  .app-exit-button[data-v-0e03d0d8] {
@@ -7881,27 +7948,27 @@ to {
7881
7948
  .dark-mode .download-app-button[data-v-9b609275]:hover {
7882
7949
  background: linear-gradient(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.1));
7883
7950
  }
7884
- .empty-sidebar-item-content[data-v-6f80f8d3] {
7951
+ .empty-sidebar-item-content[data-v-96a54993] {
7885
7952
  display: none;
7886
7953
  }
7887
- .empty-sidebar-item .empty-sidebar-item-content[data-v-6f80f8d3] {
7954
+ .empty-sidebar-item .empty-sidebar-item-content[data-v-96a54993] {
7888
7955
  display: block;
7889
7956
  }
7890
- .rabbitjump[data-v-6f80f8d3] {
7957
+ .rabbitjump[data-v-96a54993] {
7891
7958
  opacity: 0;
7892
7959
  }
7893
- .empty-sidebar-item:hover .rabbitjump[data-v-6f80f8d3] {
7960
+ .empty-sidebar-item:hover .rabbitjump[data-v-96a54993] {
7894
7961
  opacity: 1;
7895
- animation: rabbitAnimation-6f80f8d3 0.5s steps(1) infinite;
7962
+ animation: rabbitAnimation-96a54993 0.5s steps(1) infinite;
7896
7963
  }
7897
- .empty-sidebar-item:hover .rabbitsit[data-v-6f80f8d3] {
7964
+ .empty-sidebar-item:hover .rabbitsit[data-v-96a54993] {
7898
7965
  opacity: 0;
7899
- animation: rabbitAnimation2-6f80f8d3 0.5s steps(1) infinite;
7966
+ animation: rabbitAnimation2-96a54993 0.5s steps(1) infinite;
7900
7967
  }
7901
- .empty-sidebar-item:hover .rabbit-ascii[data-v-6f80f8d3] {
7902
- animation: rabbitRun-6f80f8d3 8s infinite linear;
7968
+ .empty-sidebar-item:hover .rabbit-ascii[data-v-96a54993] {
7969
+ animation: rabbitRun-96a54993 8s infinite linear;
7903
7970
  }
7904
- @keyframes rabbitRun-6f80f8d3 {
7971
+ @keyframes rabbitRun-96a54993 {
7905
7972
  0% {
7906
7973
  transform: translate3d(0, 0, 0);
7907
7974
  }
@@ -7921,7 +7988,7 @@ to {
7921
7988
  transform: translate3d(0, 0, 0);
7922
7989
  }
7923
7990
  }
7924
- @keyframes rabbitAnimation-6f80f8d3 {
7991
+ @keyframes rabbitAnimation-96a54993 {
7925
7992
  0%,
7926
7993
  100% {
7927
7994
  opacity: 1;
@@ -7930,7 +7997,7 @@ to {
7930
7997
  opacity: 0;
7931
7998
  }
7932
7999
  }
7933
- @keyframes rabbitAnimation2-6f80f8d3 {
8000
+ @keyframes rabbitAnimation2-96a54993 {
7934
8001
  0%,
7935
8002
  100% {
7936
8003
  opacity: 0;
@@ -9030,5 +9097,8 @@ to {
9030
9097
  justify-content: flex-end;
9031
9098
  gap: 1rem;
9032
9099
  }
9100
+ .document-scripts-editors__container[data-v-8c8fa790] {
9101
+ min-height: 300px;
9102
+ }
9033
9103
  /*$vite$:1*/
9034
9104
  /*$vite$:1*/
@@ -0,0 +1,26 @@
1
+ import type { ApiClientModal, RoutePayload } from '@scalar/api-client/v2/features/modal';
2
+ import type { ApiClientConfiguration } from '@scalar/types/api-reference';
3
+ import './style.css';
4
+ /** We don't really need all of the content types so we just accept an object instead */
5
+ export type ApiClientConfigurationReact = Partial<Omit<ApiClientConfiguration, 'content' | 'url'> & {
6
+ content?: Record<string, unknown>;
7
+ url?: string;
8
+ }>;
9
+ export type UseApiClientModalProps = {
10
+ /** Configuration for the Api Client (url or inline content) */
11
+ configuration?: ApiClientConfigurationReact;
12
+ };
13
+ /**
14
+ * Returns the singleton Api Client
15
+ *
16
+ * On first call the Vue app is lazily created and appended to document.body where it
17
+ * lives for the lifetime of the page — it is never unmounted, so it survives client-side
18
+ * navigation without losing state.
19
+ *
20
+ * Subsequent calls from any component share the same instance of the client but can use the same
21
+ * or different documents
22
+ */
23
+ export declare const useApiClient: ({ configuration, }?: UseApiClientModalProps) => (Omit<ApiClientModal, "open"> & {
24
+ open: (payload: RoutePayload) => void;
25
+ }) | undefined;
26
+ //# sourceMappingURL=use-api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-api-client.d.ts","sourceRoot":"","sources":["../src/use-api-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAA;AACxF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AAGzE,OAAO,aAAa,CAAA;AAUpB,wFAAwF;AACxF,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAC/C,IAAI,CAAC,sBAAsB,EAAE,SAAS,GAAG,KAAK,CAAC,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CACtG,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,+DAA+D;IAC/D,aAAa,CAAC,EAAE,2BAA2B,CAAA;CAC5C,CAAA;AAKD;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAI,qBAE1B,sBAA2B,KAC1B,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG;IAAE,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAA;CAAE,CAAC,GAC1E,SAsEH,CAAA"}
@@ -0,0 +1,2 @@
1
+ import '@testing-library/jest-dom';
2
+ //# sourceMappingURL=vitest.setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.setup.d.ts","sourceRoot":"","sources":["../src/vitest.setup.ts"],"names":[],"mappings":"AAAA,OAAO,2BAA2B,CAAA"}
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "testing",
20
20
  "react"
21
21
  ],
22
- "version": "1.4.20",
22
+ "version": "2.0.1",
23
23
  "engines": {
24
24
  "node": ">=22"
25
25
  },
@@ -39,17 +39,23 @@
39
39
  "CHANGELOG.md"
40
40
  ],
41
41
  "dependencies": {
42
- "@scalar/api-client": "2.41.0",
43
- "@scalar/types": "0.7.6"
42
+ "@scalar/api-client": "2.43.0",
43
+ "@scalar/types": "0.8.0",
44
+ "@scalar/workspace-store": "0.45.0"
44
45
  },
45
46
  "devDependencies": {
47
+ "@testing-library/dom": "^10.4.1",
48
+ "@testing-library/jest-dom": "^6.9.1",
49
+ "@testing-library/react": "^16.3.2",
46
50
  "@types/react": "^19.2.7",
47
51
  "@types/react-dom": "^19.2.3",
48
52
  "@vitejs/plugin-react": "6.0.1",
53
+ "jsdom": "^29.0.1",
49
54
  "react": "^19.2.3",
50
55
  "react-dom": "^19.2.3",
51
56
  "rollup-preserve-directives": "^1.1.1",
52
- "vite": "8.0.0"
57
+ "vite": "8.0.0",
58
+ "vitest": "^4.1.2"
53
59
  },
54
60
  "peerDependencies": {
55
61
  "react": "^18.0.0 || ^19.0.0"
@@ -57,6 +63,7 @@
57
63
  "scripts": {
58
64
  "build": "vite build && vue-tsc -p tsconfig.build.json",
59
65
  "dev": "vite ./playground -c ./vite.config.ts",
66
+ "test": "vitest --run",
60
67
  "types:check": "vue-tsc --noEmit"
61
68
  }
62
69
  }
@@ -1,21 +0,0 @@
1
- import type { ApiClient } from '@scalar/api-client/layouts/Modal';
2
- import type { OpenClientPayload } from '@scalar/api-client/libs';
3
- import type { ApiClientConfiguration } from '@scalar/types/api-reference';
4
- import type { PropsWithChildren } from 'react';
5
- export type { OpenClientPayload };
6
- import './style.css';
7
- type Props = PropsWithChildren<{
8
- /** Choose a request to initially route to */
9
- initialRequest?: OpenClientPayload;
10
- /** Configuration for the Api Client */
11
- configuration?: Partial<ApiClientConfiguration>;
12
- }>;
13
- /**
14
- * Api Client Modal React
15
- *
16
- * Provider which mounts the Scalar Api Client Modal vue app.
17
- * Rebuilt to support multiple instances when using a unique spec.url
18
- */
19
- export declare const ApiClientModalProvider: ({ children, initialRequest, configuration }: Props) => import("react/jsx-runtime").JSX.Element;
20
- export declare const useApiClientModal: () => ApiClient | null;
21
- //# sourceMappingURL=ApiClientModalProvider.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ApiClientModalProvider.d.ts","sourceRoot":"","sources":["../src/ApiClientModalProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAA;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAA;AACzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAA;AAG9C,YAAY,EAAE,iBAAiB,EAAE,CAAA;AAIjC,OAAO,aAAa,CAAA;AAIpB,KAAK,KAAK,GAAG,iBAAiB,CAAC;IAC7B,6CAA6C;IAC7C,cAAc,CAAC,EAAE,iBAAiB,CAAA;IAClC,uCAAuC;IACvC,aAAa,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;CAChD,CAAC,CAAA;AAQF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GAAI,6CAAkD,KAAK,4CA4D7F,CAAA;AAED,eAAO,MAAM,iBAAiB,QAAO,SAAS,GAAG,IAAyC,CAAA"}
@@ -1,12 +0,0 @@
1
- import type { ApiClient, createApiClientModal } from '@scalar/api-client/layouts/Modal';
2
- export declare const clientStore: {
3
- getSnapshot: () => {
4
- createClient: typeof createApiClientModal | null;
5
- clientDict: Record<string, ApiClient>;
6
- };
7
- subscribe: (listener: () => void) => () => boolean;
8
- setCreateClient: (clientFactory: typeof createApiClientModal) => void;
9
- addClient: (url: string, client: ApiClient) => void;
10
- removeClient: (url: string) => void;
11
- };
12
- //# sourceMappingURL=client-store.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client-store.d.ts","sourceRoot":"","sources":["../src/client-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAA;AA+CvF,eAAO,MAAM,WAAW;;sBAtCA,OAAO,oBAAoB,GAAG,IAAI;oBACtC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;;0BAOhB,MAAM,IAAI;qCAYC,OAAO,oBAAoB;qBAM3C,MAAM,UAAU,SAAS;wBAMtB,MAAM;CAYhC,CAAA"}