rapidoc 11.0.1 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +171 -54
  2. package/dist/rapidoc-flow-min.js +580 -0
  3. package/dist/rapidoc-flow-min.js.LICENSE.txt +171 -0
  4. package/dist/rapidoc-min.js +645 -202
  5. package/dist/rapidoc-min.js.LICENSE.txt +381 -0
  6. package/package.json +57 -42
  7. package/src/README.md +150 -0
  8. package/src/components/README.md +91 -0
  9. package/src/components/api-request.ts +912 -0
  10. package/src/components/api-response.ts +514 -0
  11. package/src/components/json-tree.ts +171 -0
  12. package/src/components/schema-base-element.ts +329 -0
  13. package/src/components/schema-table.ts +475 -0
  14. package/src/components/schema-tree.ts +393 -0
  15. package/src/components/tag-input.ts +180 -0
  16. package/src/flow.js +3 -0
  17. package/src/index.js +6 -0
  18. package/src/oauth-receiver.ts +29 -0
  19. package/src/rapi-flow.ts +1178 -0
  20. package/src/rapidoc-mini.ts +603 -0
  21. package/src/rapidoc.ts +1854 -0
  22. package/src/styles/README.md +96 -0
  23. package/src/styles/auth-styles.js +318 -0
  24. package/src/styles/border-styles.js +24 -0
  25. package/src/styles/css-bundle.js +129 -0
  26. package/src/styles/custom-styles.js +8 -0
  27. package/src/styles/dialog-box-styles.js +102 -0
  28. package/src/styles/endpoint-styles.js +564 -0
  29. package/src/styles/flex-styles.js +16 -0
  30. package/src/styles/flow-styles.js +248 -0
  31. package/src/styles/font-styles.js +432 -0
  32. package/src/styles/info-styles.js +36 -0
  33. package/src/styles/input-styles.js +555 -0
  34. package/src/styles/layout-styles.js +396 -0
  35. package/src/styles/microlighter-styles.js +99 -0
  36. package/src/styles/nav-styles.js +633 -0
  37. package/src/styles/param-list-styles.js +261 -0
  38. package/src/styles/schema-styles.js +296 -0
  39. package/src/styles/tab-styles.js +106 -0
  40. package/src/styles/theme-tokens.js +582 -0
  41. package/src/templates/README.md +89 -0
  42. package/src/templates/advanced-search-dialog-template.js +94 -0
  43. package/src/templates/api-response-template.js +223 -0
  44. package/src/templates/callback-template.js +86 -0
  45. package/src/templates/code-samples-template.js +52 -0
  46. package/src/templates/components-template.js +86 -0
  47. package/src/templates/downloads-template.js +70 -0
  48. package/src/templates/endpoint-template.js +377 -0
  49. package/src/templates/expanded-endpoint-template.js +270 -0
  50. package/src/templates/focused-endpoint-template.js +146 -0
  51. package/src/templates/header-template.js +92 -0
  52. package/src/templates/logo-template.js +21 -0
  53. package/src/templates/main-body-template.js +102 -0
  54. package/src/templates/navbar-template.js +498 -0
  55. package/src/templates/overview-template.js +104 -0
  56. package/src/templates/request-body-template.js +673 -0
  57. package/src/templates/request-params-template.js +485 -0
  58. package/src/templates/security-scheme-template.js +1272 -0
  59. package/src/templates/server-template.js +153 -0
  60. package/src/utils/README.md +146 -0
  61. package/src/utils/arazzo/README.md +119 -0
  62. package/src/utils/arazzo/components.js +15 -0
  63. package/src/utils/arazzo/graph.js +269 -0
  64. package/src/utils/arazzo/index.js +52 -0
  65. package/src/utils/arazzo/operations.js +106 -0
  66. package/src/utils/arazzo/parse.js +196 -0
  67. package/src/utils/arazzo/refs.js +40 -0
  68. package/src/utils/arazzo/svg.js +707 -0
  69. package/src/utils/auto-color-scheme.js +99 -0
  70. package/src/utils/bind-navbar.js +175 -0
  71. package/src/utils/color-utils.js +59 -0
  72. package/src/utils/common-utils.js +191 -0
  73. package/src/utils/curl-utils.js +126 -0
  74. package/src/utils/highlighter.js +419 -0
  75. package/src/utils/markdown-utils.js +110 -0
  76. package/src/utils/mcp/README.md +155 -0
  77. package/src/utils/mcp/common.js +166 -0
  78. package/src/utils/mcp/manifest.js +382 -0
  79. package/src/utils/mcp/project/server.ts +53 -0
  80. package/src/utils/mcp/project.js +88 -0
  81. package/src/utils/mcp/runtime.js +289 -0
  82. package/src/utils/mcp/schema.js +98 -0
  83. package/src/utils/mcp/workflow-runtime.js +374 -0
  84. package/src/utils/mcp/workflows.js +240 -0
  85. package/src/utils/mcp/zip.js +102 -0
  86. package/src/utils/mcp-generator.js +47 -0
  87. package/src/utils/mock-interceptor.js +557 -0
  88. package/src/utils/oauth-utils.js +87 -0
  89. package/src/utils/request-utils.js +264 -0
  90. package/src/utils/response-utils.js +203 -0
  91. package/src/utils/sanitize.js +57 -0
  92. package/src/utils/schema-utils.js +1954 -0
  93. package/src/utils/slugger.js +187 -0
  94. package/src/utils/spec-parser.js +941 -0
  95. package/src/utils/url-utils.js +246 -0
  96. package/src/utils/xml-utils.js +61 -0
@@ -0,0 +1,393 @@
1
+ import { html, css, nothing } from "lit";
2
+ import { customElement } from "lit/decorators.js";
3
+ import FontStyles from "../styles/font-styles.js";
4
+ import SchemaStyles from "../styles/schema-styles.js";
5
+ import BorderStyles from "../styles/border-styles.js";
6
+ import CustomStyles from "../styles/custom-styles.js";
7
+ import SchemaBaseElement from "./schema-base-element";
8
+
9
+ @customElement("schema-tree")
10
+ export default class SchemaTree extends SchemaBaseElement {
11
+ static get styles() {
12
+ return [
13
+ FontStyles,
14
+ SchemaStyles,
15
+ BorderStyles,
16
+ css`
17
+ .tree {
18
+ font-size: var(--font-size-small);
19
+ text-align: left;
20
+ direction: ltr;
21
+ line-height: calc(var(--font-size-small) + 6px);
22
+
23
+ & .tr:hover {
24
+ background-color: rgba(0, 0, 0, 0.15);
25
+ }
26
+ & .tr.expanded-descr {
27
+ max-height: none;
28
+ }
29
+ /* faint separators where descriptions wrap: every row in multiline mode, or around a row expanded by click */
30
+ &.expanded-all-descr .tr,
31
+ &.collapsed-all-descr .tr.expanded-descr {
32
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
33
+ }
34
+ &.collapsed-all-descr .tr.expanded-descr {
35
+ box-shadow: 0 -1px 0 color-mix(in srgb, var(--border) 60%, transparent);
36
+ }
37
+ & .key {
38
+ max-width: 300px;
39
+ }
40
+ &.collapsed-all-descr .tr:not(.expanded-descr) {
41
+ overflow: hidden;
42
+ max-height: calc(var(--font-size-small) + 8px);
43
+
44
+ & .key {
45
+ white-space: nowrap;
46
+ text-overflow: ellipsis;
47
+ overflow: hidden;
48
+ }
49
+ }
50
+ }
51
+
52
+ .tr {
53
+ &.expanded {
54
+ &:hover {
55
+ & > .td.key > .open-bracket,
56
+ & + .inside-bracket + .close-bracket {
57
+ color: var(--primary);
58
+ }
59
+ & + .inside-bracket {
60
+ border-left: 1px solid var(--muted-foreground);
61
+ }
62
+ }
63
+ & .open-bracket .open-bracket-collapsed {
64
+ display: none;
65
+ }
66
+ }
67
+ &.collapsed {
68
+ & + .inside-bracket,
69
+ & + .inside-bracket + .close-bracket {
70
+ overflow: hidden;
71
+ display: none;
72
+ }
73
+ & .open-bracket .open-bracket-expanded {
74
+ display: none;
75
+ }
76
+ }
77
+ }
78
+
79
+ /* Narrow schemas: a collapsed row holds its field line and the description's line under it (#1074) */
80
+ @container (width < 500px) {
81
+ .tree.collapsed-all-descr .tr:not(.expanded-descr) {
82
+ max-height: calc(2 * (var(--font-size-small) + 8px));
83
+ }
84
+ }
85
+
86
+ .inside-bracket {
87
+ &.xxx-of-option {
88
+ border-left: 1px solid transparent;
89
+ }
90
+ &.object,
91
+ &.array {
92
+ border-left: 1px dotted var(--border);
93
+ }
94
+ }
95
+
96
+ .open-bracket {
97
+ display: inline-block;
98
+ padding: 0 20px 0 0;
99
+ cursor: pointer;
100
+ border: 1px solid transparent;
101
+ border-radius: min(var(--radius), 0.5rem);
102
+
103
+ &:hover {
104
+ color: var(--primary);
105
+ border: 1px solid var(--border);
106
+ }
107
+ &:focus-visible {
108
+ outline: 2px solid var(--primary);
109
+ border-color: var(--primary);
110
+ }
111
+ }
112
+
113
+ .close-bracket {
114
+ display: inline-block;
115
+ font-family: var(--font-mono);
116
+ }
117
+ `,
118
+ CustomStyles,
119
+ ];
120
+ }
121
+
122
+ render() {
123
+ if (!this.data) {
124
+ return html`
125
+ <div class="tree">
126
+ <span class="mono-font" style="color:var(--destructive)"> Schema not found </span>
127
+ </div>
128
+ `;
129
+ }
130
+
131
+ const rootType = this.data.kind === "primitive" ? this.data.type : this.data.kind || "";
132
+ const rootDescription = this.data.description || "";
133
+
134
+ return html` <div
135
+ class="tree ${this.schemaDescriptionExpanded === "true" ? "expanded-all-descr" : "collapsed-all-descr"}"
136
+ @click="${(e) => this.handleAllEvents(e)}"
137
+ @keydown="${(e) => this.handleKeyDown(e)}"
138
+ >
139
+ ${this.renderToolbar(rootType)}
140
+ <span part="schema-description" class="m-markdown"> ${this.renderDescriptionHtml(rootDescription)}</span>
141
+ ${this.renderAST(this.data)}
142
+ </div>`;
143
+ }
144
+
145
+ renderAST(node, parentType = "", schemaLevel = 0, indentLevel = 0) {
146
+ if (this.isFieldHidden(node)) {
147
+ return "";
148
+ }
149
+
150
+ const leftPadding = 12;
151
+ const minFieldColWidth = 400 - indentLevel * leftPadding;
152
+ const isExpanded = schemaLevel < this.schemaExpandLevel;
153
+ const readWriteBadge = this.getReadWriteBadge(node);
154
+ const readWriteTip = this.getReadWriteTip(node);
155
+ const nullMarker = node.nullable ? "null┃" : "";
156
+ const nullClass = node.nullable ? "nullable" : "";
157
+ const deprecatedIcon = this.renderDeprecatedIcon(node);
158
+
159
+ // 1. Array kind
160
+ if (node.kind === "array") {
161
+ const items = node.items;
162
+ if (!items) {
163
+ const fieldTitle = node.name
164
+ ? `${node.name}${readWriteTip ? ` (${readWriteTip})` : ""}`
165
+ : "";
166
+ return html`
167
+ <div class="tr ${isExpanded ? "expanded" : "collapsed"} array ${nullClass}" title="${node.deprecated ? "Deprecated" : nothing}">
168
+ <div class="td key ${node.deprecated ? "deprecated" : ""}" style="min-width:${minFieldColWidth}px">
169
+ ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${fieldTitle}">${node.name}${node.required ? html`<span style="color:var(--destructive)">*</span>` : ""}${readWriteBadge}:</span>` : ""}
170
+ <span class="open-bracket array">${nullMarker}[ ]</span>
171
+ </div>
172
+ ${this.renderDescrCell(node.description)}
173
+ </div>
174
+ `;
175
+ }
176
+
177
+ // If array of primitives
178
+ if (items.kind === "primitive") {
179
+ const itemType = items.format || items.contentMediaType || items.type || "string";
180
+ const dataTypeCss = this.getDataTypeCss(itemType);
181
+ const fieldTitle = node.name
182
+ ? `${node.name}${readWriteTip ? ` (${readWriteTip})` : ""}`
183
+ : "";
184
+
185
+ return html`
186
+ <div class="tr primitive ${nullClass}" title="${node.deprecated || items.deprecated ? "Deprecated" : nothing}">
187
+ <div class="td key ${node.deprecated || items.deprecated ? "deprecated" : ""}" style="min-width:${minFieldColWidth}px">
188
+ ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${fieldTitle}">${node.name}${node.required ? html`<span style="color:var(--destructive)">*</span>` : ""}:</span>` : ""}
189
+ <span class="${dataTypeCss}" title="${readWriteTip || nothing}">${nullMarker}[${itemType}]${readWriteBadge}</span>
190
+ </div>
191
+ ${this.renderDescrCell(node.description || items.description, items)}
192
+ </div>
193
+ `;
194
+ }
195
+
196
+ // If array of objects
197
+ if (items.kind === "object") {
198
+ const openBracket = html`<span
199
+ class="open-bracket array-of-object"
200
+ role="button"
201
+ tabindex="0"
202
+ aria-label="Toggle ${node.name || "object"}"
203
+ aria-expanded="${isExpanded ? "true" : "false"}"
204
+ >${nullMarker}<span class="open-bracket-expanded">[{</span><span class="open-bracket-collapsed">[{...}]</span></span
205
+ >`;
206
+ const closeBracket = "}]";
207
+ const fieldTitle = node.name
208
+ ? `${node.name}${readWriteTip ? ` (${readWriteTip})` : ""}`
209
+ : "";
210
+
211
+ return html`
212
+ <div
213
+ class="tr ${isExpanded ? "expanded" : "collapsed"} object ${nullClass}"
214
+ title="${node.deprecated || items.deprecated ? "Deprecated" : nothing}"
215
+ >
216
+ <div class="td key ${node.deprecated || items.deprecated ? "deprecated" : ""}" style="min-width:${minFieldColWidth}px">
217
+ ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${fieldTitle}">${node.name}${node.required ? html`<span style="color:var(--destructive)">*</span>` : ""}${readWriteBadge}:</span>` : ""}
218
+ ${openBracket}
219
+ </div>
220
+ ${this.renderDescrCell(node.description || items.description)}
221
+ </div>
222
+ <div class="inside-bracket object" style="padding-left:${leftPadding}px;">
223
+ ${items.properties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + 1))}
224
+ ${items.patternProperties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + 1))}
225
+ ${items.additionalProperties ? this.renderAST(items.additionalProperties, "object", schemaLevel + 1, indentLevel + 1) : ""}
226
+ ${items.unions?.map((u) => this.renderAST(u, "object", schemaLevel + 1, indentLevel + 1))}
227
+ </div>
228
+ <div class="close-bracket">${closeBracket}</div>
229
+ `;
230
+ }
231
+
232
+ // If array of arrays
233
+ if (items.kind === "array") {
234
+ const arrType = node.arrayType !== "object" ? node.arrayType : "";
235
+ const openBracket = html`<span
236
+ class="open-bracket array-of-array"
237
+ data-array-type="${arrType}"
238
+ role="button"
239
+ tabindex="0"
240
+ aria-label="Toggle ${node.name || "array"}"
241
+ aria-expanded="${isExpanded ? "true" : "false"}"
242
+ >${nullMarker}<span class="open-bracket-expanded">[[ ${arrType} </span><span class="open-bracket-collapsed">[[...]]</span></span
243
+ >`;
244
+ const closeBracket = "]]";
245
+ const fieldTitle = node.name
246
+ ? `${node.name}${readWriteTip ? ` (${readWriteTip})` : ""}`
247
+ : "";
248
+
249
+ return html`
250
+ <div class="tr ${isExpanded ? "expanded" : "collapsed"} array ${nullClass}" title="${node.deprecated ? "Deprecated" : nothing}">
251
+ <div class="td key ${node.deprecated ? "deprecated" : ""}" style="min-width:${minFieldColWidth}px">
252
+ ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${fieldTitle}">${node.name}${node.required ? html`<span style="color:var(--destructive)">*</span>` : ""}${readWriteBadge}:</span>` : ""}
253
+ ${openBracket}
254
+ </div>
255
+ ${this.renderDescrCell(node.description)}
256
+ </div>
257
+ <div class="inside-bracket array" style="padding-left:${leftPadding}px;">
258
+ ${this.renderAST(items, "array", schemaLevel + 1, indentLevel + 1)}
259
+ </div>
260
+ <div class="close-bracket">${closeBracket}</div>
261
+ `;
262
+ }
263
+
264
+ // If array of unions or others
265
+ const fieldTitle = node.name ? `${node.name}${readWriteTip ? ` (${readWriteTip})` : ""}` : "";
266
+ return html`
267
+ <div class="tr ${isExpanded ? "expanded" : "collapsed"} array ${nullClass}" title="${node.deprecated ? "Deprecated" : nothing}">
268
+ <div class="td key ${node.deprecated ? "deprecated" : ""}" style="min-width:${minFieldColWidth}px">
269
+ ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${fieldTitle}">${node.name}${node.required ? html`<span style="color:var(--destructive)">*</span>` : ""}${readWriteBadge}:</span>` : ""}
270
+ <span
271
+ class="open-bracket array"
272
+ role="button"
273
+ tabindex="0"
274
+ aria-label="Toggle ${node.name || "array"}"
275
+ aria-expanded="${isExpanded ? "true" : "false"}"
276
+ >${nullMarker}<span class="open-bracket-expanded">[</span><span class="open-bracket-collapsed">[...]</span></span
277
+ >
278
+ </div>
279
+ ${this.renderDescrCell(node.description)}
280
+ </div>
281
+ <div class="inside-bracket array" style="padding-left:${leftPadding}px;">
282
+ ${this.renderAST(items, "array", schemaLevel + 1, indentLevel + 1)}
283
+ </div>
284
+ <div class="close-bracket">]</div>
285
+ `;
286
+ }
287
+
288
+ // 2. Object kind
289
+ if (node.kind === "object") {
290
+ const openBracket = html`<span
291
+ class="open-bracket object"
292
+ role="button"
293
+ tabindex="0"
294
+ aria-label="Toggle ${node.name || "object"}"
295
+ aria-expanded="${isExpanded ? "true" : "false"}"
296
+ >${nullMarker}<span class="open-bracket-expanded">{</span><span class="open-bracket-collapsed">{...}</span></span
297
+ >`;
298
+ const closeBracket = "}";
299
+ const fieldTitle = node.name ? `${node.name}${readWriteTip ? ` (${readWriteTip})` : ""}` : "";
300
+
301
+ return html`
302
+ <div class="tr ${isExpanded ? "expanded" : "collapsed"} object ${nullClass}" title="${node.deprecated ? "Deprecated" : nothing}">
303
+ <div class="td key ${node.deprecated ? "deprecated" : ""}" style="min-width:${minFieldColWidth}px">
304
+ ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${fieldTitle}">${node.name}${node.required ? html`<span style="color:var(--destructive)">*</span>` : ""}${readWriteBadge}:</span>` : ""}
305
+ ${openBracket}
306
+ </div>
307
+ ${this.renderDescrCell(node.description)}
308
+ </div>
309
+ <div class="inside-bracket object" style="padding-left:${leftPadding}px;">
310
+ ${node.properties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + 1))}
311
+ ${node.patternProperties?.map((p) => this.renderAST(p, "object", schemaLevel + 1, indentLevel + 1))}
312
+ ${node.additionalProperties ? this.renderAST(node.additionalProperties, "object", schemaLevel + 1, indentLevel + 1) : ""}
313
+ ${node.unions?.map((u) => this.renderAST(u, "object", schemaLevel + 1, indentLevel + 1))}
314
+ </div>
315
+ <div class="close-bracket">${closeBracket}</div>
316
+ `;
317
+ }
318
+
319
+ // 3. Union kind (oneOf / anyOf)
320
+ if (node.kind === "union") {
321
+ const opLabel =
322
+ (node.operator === "anyOf" ? "ANY OF" : "ONE OF") + (node.suffix ? ` ${node.suffix}` : "");
323
+ const newIndent = parentType === "xxx-of-option" ? indentLevel : indentLevel + 1;
324
+ // Options are already labelled by their OPT row, so only property names are shown
325
+ const showName = node.name && parentType !== "xxx-of-option";
326
+ // A named union puts its label and options one level inside the property row
327
+ const bodyFieldColWidth = showName ? minFieldColWidth - leftPadding : minFieldColWidth;
328
+
329
+ const unionBody = html`
330
+ <div class="tr expanded xxx-of-option">
331
+ <div class="td key" style="min-width:${bodyFieldColWidth}px">
332
+ <span class="key-label xxx-of-key">${opLabel}</span>
333
+ </div>
334
+ ${showName ? "" : this.renderDescrCell(node.description)}
335
+ </div>
336
+ <div class="inside-bracket xxx-of-option" style="padding-left:0px;">
337
+ ${node.options?.map(
338
+ (opt, i) => html`
339
+ <div>
340
+ <div class="tr expanded xxx-of-option">
341
+ <div class="td key" style="min-width:${bodyFieldColWidth}px">
342
+ <span class="key-label xxx-of-key">OPT ${opt.optionIndex || i + 1}</span>
343
+ ${opt.optionTitle ? html`<span class="xxx-of-descr">${opt.optionTitle}</span>` : ""}
344
+ </div>
345
+ </div>
346
+ ${this.renderAST(opt, "xxx-of-option", schemaLevel, newIndent)}
347
+ </div>
348
+ `,
349
+ )}
350
+ </div>
351
+ `;
352
+
353
+ return html`
354
+ ${node.properties?.map((p) => this.renderAST(p, "object", schemaLevel, indentLevel))}
355
+ ${
356
+ showName
357
+ ? html`
358
+ <div class="tr expanded xxx-of-prop">
359
+ <div class="td key" style="min-width:${minFieldColWidth}px">
360
+ <span class="key-label" title="${node.name}"
361
+ >${node.name}${node.required ? html`<span style="color:var(--destructive)">*</span>` : ""}:</span
362
+ >
363
+ </div>
364
+ ${this.renderDescrCell(node.description)}
365
+ </div>
366
+ <div class="inside-bracket xxx-of-option" style="padding-left:${leftPadding}px;">${unionBody}</div>
367
+ `
368
+ : unionBody
369
+ }
370
+ `;
371
+ }
372
+
373
+ // 4. Primitive kind
374
+ if (node.kind === "primitive") {
375
+ const dataTypeCss = this.getDataTypeCss(node.type);
376
+ const fieldTitle = node.name ? `${node.name}${readWriteTip ? ` (${readWriteTip})` : ""}` : "";
377
+
378
+ return html`
379
+ <div class="tr primitive" title="${node.deprecated ? "Deprecated" : nothing}">
380
+ <div class="td key ${node.deprecated ? "deprecated" : ""}" style="min-width:${minFieldColWidth}px">
381
+ ${deprecatedIcon}${node.name ? html`<span class="key-label" title="${fieldTitle}">${node.name}${node.required ? html`<span style="color:var(--destructive);">*</span>` : ""}:</span>` : ""}
382
+ <span class="${dataTypeCss}" title="${readWriteTip || nothing}"
383
+ >${parentType === "array" ? `[${node.type}]` : node.type}${readWriteBadge}</span
384
+ >
385
+ </div>
386
+ ${this.renderDescrCell(node.description, node, node.title)}
387
+ </div>
388
+ `;
389
+ }
390
+
391
+ return "";
392
+ }
393
+ }
@@ -0,0 +1,180 @@
1
+ import { LitElement, html, css } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ @customElement("tag-input")
5
+ export default class TagInput extends LitElement {
6
+ @property({ type: String }) placeholder = "";
7
+
8
+ @property({
9
+ type: Array,
10
+ attribute: "value",
11
+ converter: {
12
+ // `value="a,b"` is a comma-separated list, not the JSON Lit's Array converter expects
13
+ fromAttribute: (value) => (value ? value.split(",").filter((v) => v.trim() !== "") : []),
14
+ toAttribute: (value) => (Array.isArray(value) ? value.join(",") : value),
15
+ },
16
+ hasChanged(newVal, oldVal) {
17
+ if (newVal === oldVal) return false;
18
+ if (!Array.isArray(newVal) || !Array.isArray(oldVal)) return true;
19
+ if (newVal.length !== oldVal.length) return true;
20
+ return newVal.some((v, i) => v !== oldVal[i]);
21
+ },
22
+ })
23
+ value = [];
24
+
25
+ // For backwards compatibility with static properties inspection in tests
26
+ static get properties() {
27
+ return Object.fromEntries(this.elementProperties || []);
28
+ }
29
+
30
+ render() {
31
+ let tagItemTmpl = "";
32
+ if (Array.isArray(this.value)) {
33
+ tagItemTmpl = html`${this.value
34
+ .filter((v) => v !== "" && v !== null && v !== undefined && String(v).trim() !== "")
35
+ .map((v) => html`<span class="tag">${v}</span>`)}`;
36
+ }
37
+ return html`<div class="tags" @click="${(e) => this.focusEditor(e)}">
38
+ ${tagItemTmpl}
39
+ <input
40
+ type="text"
41
+ class="editor"
42
+ @paste="${(e) => this.afterPaste(e)}"
43
+ @keydown="${(e) => this.afterKeyDown(e)}"
44
+ @blur="${(e) => this.onBlur(e)}"
45
+ placeholder="${this.placeholder || ""}"
46
+ />
47
+ </div>`;
48
+ }
49
+
50
+ focusEditor(e) {
51
+ if (e.target.classList.contains("tags")) {
52
+ this.shadowRoot?.querySelector(".editor")?.focus();
53
+ }
54
+ }
55
+
56
+ dispatchEvents() {
57
+ this.dispatchEvent(
58
+ new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } }),
59
+ );
60
+ this.dispatchEvent(
61
+ new CustomEvent("input", { bubbles: true, composed: true, detail: { value: this.value } }),
62
+ );
63
+ }
64
+
65
+ afterPaste(e) {
66
+ const clipboardData = e.clipboardData || window.clipboardData;
67
+ const pastedData = clipboardData.getData("Text");
68
+ const pastedArray = pastedData ? pastedData.split(",").filter((v) => v.trim() !== "") : "";
69
+ if (pastedArray) {
70
+ if (Array.isArray(this.value)) {
71
+ this.value = [...this.value, ...pastedArray];
72
+ } else {
73
+ this.value = pastedArray;
74
+ }
75
+ this.dispatchEvents();
76
+ }
77
+ e.preventDefault();
78
+ }
79
+
80
+ afterKeyDown(e) {
81
+ if (e.key === "Enter" || e.keyCode === 13) {
82
+ e.stopPropagation();
83
+ e.preventDefault();
84
+ const val = e.target.value ? e.target.value.trim() : "";
85
+ if (val) {
86
+ if (Array.isArray(this.value)) {
87
+ this.value = [...this.value, val];
88
+ } else {
89
+ this.value = [val];
90
+ }
91
+ e.target.value = "";
92
+ this.dispatchEvents();
93
+ }
94
+ } else if (e.key === "Backspace" || e.key === "Delete" || e.keyCode === 8 || e.keyCode === 46) {
95
+ if (!e.target.value || e.target.value.length === 0) {
96
+ if (Array.isArray(this.value) && this.value.length > 0) {
97
+ e.preventDefault();
98
+ this.value = this.value.slice(0, -1);
99
+ this.dispatchEvents();
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ onBlur(e) {
106
+ const val = e.target.value ? e.target.value.trim() : "";
107
+ if (val) {
108
+ if (Array.isArray(this.value)) {
109
+ this.value = [...this.value, val];
110
+ } else {
111
+ this.value = [val];
112
+ }
113
+ e.target.value = "";
114
+ this.dispatchEvents();
115
+ }
116
+ }
117
+
118
+ static get styles() {
119
+ return [
120
+ css`
121
+ .tags {
122
+ display: flex;
123
+ flex-wrap: wrap;
124
+ align-items: center;
125
+ box-sizing: border-box;
126
+ /* Same height as the text inputs in a parameter row (see param-list-styles.js) */
127
+ min-height: calc(2.75 * var(--font-size-small) + 2px);
128
+ outline: none;
129
+ padding: 0;
130
+ border-radius: var(--radius);
131
+ border: 1px solid var(--input);
132
+ cursor: text;
133
+ overflow: hidden;
134
+ background: var(--input-background);
135
+ transition:
136
+ border-color 0.2s ease,
137
+ box-shadow 0.2s ease;
138
+
139
+ /* Same focus ring as the text inputs */
140
+ &:focus-within {
141
+ border-color: var(--primary);
142
+ box-shadow: var(--focus-shadow);
143
+ }
144
+ }
145
+ .tag,
146
+ .editor {
147
+ padding: 3px;
148
+ margin: 2px;
149
+ }
150
+ /* Chips: a muted fill with the form-field border, so each one stands apart on a white or dark field */
151
+ .tag {
152
+ border: 1px solid var(--input);
153
+ background: var(--muted);
154
+ color: var(--foreground);
155
+ border-radius: var(--radius);
156
+ word-break: break-all;
157
+ font-size: var(--font-size-small);
158
+ }
159
+ .tag:hover ~ #cursor {
160
+ display: block;
161
+ }
162
+ .editor {
163
+ flex: 1;
164
+ border: 1px solid transparent;
165
+ color: var(--foreground);
166
+ min-width: 60px;
167
+ outline: none;
168
+ line-height: inherit;
169
+ font-family: inherit;
170
+ background: transparent;
171
+ font-size: calc(var(--font-size-small) + 1px);
172
+ }
173
+ .editor::placeholder {
174
+ color: var(--muted-foreground);
175
+ opacity: 1;
176
+ }
177
+ `,
178
+ ];
179
+ }
180
+ }
package/src/flow.js ADDED
@@ -0,0 +1,3 @@
1
+ import RapiFlow from "~/rapi-flow";
2
+
3
+ export default RapiFlow;
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import RapiDoc from "~/rapidoc";
2
+ import RapiDocMini from "~/rapidoc-mini";
3
+ import OAuthReceiver from "~/oauth-receiver";
4
+
5
+ export default { RapiDoc };
6
+ export { RapiDocMini, OAuthReceiver };
@@ -0,0 +1,29 @@
1
+ import { customElement } from "lit/decorators.js";
2
+ import { OAUTH_CHANNEL, readOAuthReply } from "~/utils/oauth-utils";
3
+
4
+ /**
5
+ * The OAuth return page's element: the provider sends the sign-in popup back here with a code (or, in the legacy
6
+ * implicit flow, a token). It hands the reply to the page that opened the popup, which checks it (origin, popup
7
+ * window, state) before using it, then removes it from the address bar and history.
8
+ *
9
+ * Two ways to reach the opener, both limited to this site's origin: `window.opener.postMessage`, and a
10
+ * BroadcastChannel for when the provider's page cut the link to the opener (Cross-Origin-Opener-Policy).
11
+ */
12
+ @customElement("oauth-receiver")
13
+ export default class OauthReceiver extends HTMLElement {
14
+ connectedCallback() {
15
+ const reply = readOAuthReply(window.location);
16
+ if (!reply) {
17
+ return;
18
+ }
19
+ window.history.replaceState(null, "", window.location.pathname);
20
+ if (window.opener) {
21
+ window.opener.postMessage(reply, window.location.origin);
22
+ }
23
+ if (typeof BroadcastChannel === "function") {
24
+ const channel = new BroadcastChannel(OAUTH_CHANNEL);
25
+ channel.postMessage(reply);
26
+ channel.close();
27
+ }
28
+ }
29
+ }