@mcp-elements/core 0.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.
package/dist/index.js ADDED
@@ -0,0 +1,887 @@
1
+ // src/utils/cn.ts
2
+ function cn(...inputs) {
3
+ return inputs.filter(Boolean).join(" ");
4
+ }
5
+
6
+ // src/utils/dom.ts
7
+ function getFocusableElements(container) {
8
+ const elements = container.querySelectorAll(
9
+ 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
10
+ );
11
+ return Array.from(elements);
12
+ }
13
+ function trapFocus(container, event) {
14
+ const focusable = getFocusableElements(container);
15
+ if (focusable.length === 0) return;
16
+ const first = focusable[0];
17
+ const last = focusable[focusable.length - 1];
18
+ if (event.key === "Tab") {
19
+ if (event.shiftKey) {
20
+ if (document.activeElement === first) {
21
+ event.preventDefault();
22
+ last.focus();
23
+ }
24
+ } else {
25
+ if (document.activeElement === last) {
26
+ event.preventDefault();
27
+ first.focus();
28
+ }
29
+ }
30
+ }
31
+ }
32
+ function createClickOutsideHandler(element, callback) {
33
+ const handler = (event) => {
34
+ if (!element.contains(event.target)) {
35
+ callback();
36
+ }
37
+ };
38
+ document.addEventListener("mousedown", handler);
39
+ return () => document.removeEventListener("mousedown", handler);
40
+ }
41
+ function lockScroll() {
42
+ const scrollY = window.scrollY;
43
+ const body = document.body;
44
+ body.style.position = "fixed";
45
+ body.style.top = `-${scrollY}px`;
46
+ body.style.width = "100%";
47
+ body.style.overflow = "hidden";
48
+ return () => {
49
+ body.style.position = "";
50
+ body.style.top = "";
51
+ body.style.width = "";
52
+ body.style.overflow = "";
53
+ window.scrollTo(0, scrollY);
54
+ };
55
+ }
56
+
57
+ // src/utils/keyboard.ts
58
+ var Keys = {
59
+ Enter: "Enter",
60
+ Space: " ",
61
+ Escape: "Escape",
62
+ ArrowUp: "ArrowUp",
63
+ ArrowDown: "ArrowDown",
64
+ ArrowLeft: "ArrowLeft",
65
+ ArrowRight: "ArrowRight",
66
+ Home: "Home",
67
+ End: "End",
68
+ Tab: "Tab"
69
+ };
70
+ function getNextIndex(current, total, direction, loop = true) {
71
+ if (direction === "next") {
72
+ const next = current + 1;
73
+ return next >= total ? loop ? 0 : current : next;
74
+ }
75
+ const prev = current - 1;
76
+ return prev < 0 ? loop ? total - 1 : current : prev;
77
+ }
78
+ function handleArrowNavigation(event, currentIndex, totalItems, orientation, onIndexChange, loop = true) {
79
+ const nextKeys = orientation === "horizontal" ? [Keys.ArrowRight] : orientation === "vertical" ? [Keys.ArrowDown] : [Keys.ArrowRight, Keys.ArrowDown];
80
+ const prevKeys = orientation === "horizontal" ? [Keys.ArrowLeft] : orientation === "vertical" ? [Keys.ArrowUp] : [Keys.ArrowLeft, Keys.ArrowUp];
81
+ if (nextKeys.includes(event.key)) {
82
+ event.preventDefault();
83
+ onIndexChange(getNextIndex(currentIndex, totalItems, "next", loop));
84
+ } else if (prevKeys.includes(event.key)) {
85
+ event.preventDefault();
86
+ onIndexChange(getNextIndex(currentIndex, totalItems, "prev", loop));
87
+ } else if (event.key === Keys.Home) {
88
+ event.preventDefault();
89
+ onIndexChange(0);
90
+ } else if (event.key === Keys.End) {
91
+ event.preventDefault();
92
+ onIndexChange(totalItems - 1);
93
+ }
94
+ }
95
+
96
+ // src/dialog.ts
97
+ var dialogCounter = 0;
98
+ function createDialog(config = {}) {
99
+ const { modal = true, onOpenChange } = config;
100
+ const id = `mcpe-dialog-${++dialogCounter}`;
101
+ return {
102
+ id,
103
+ getTriggerProps: (isOpen) => ({
104
+ "aria-haspopup": "dialog",
105
+ "aria-expanded": isOpen,
106
+ onClick: () => onOpenChange?.(!isOpen)
107
+ }),
108
+ getContentProps: (isOpen) => ({
109
+ role: "dialog",
110
+ "aria-modal": modal,
111
+ "aria-labelledby": `${id}-title`,
112
+ "aria-describedby": `${id}-description`,
113
+ hidden: !isOpen ? true : void 0,
114
+ onKeyDown: (e) => {
115
+ if (e.key === "Escape") onOpenChange?.(false);
116
+ }
117
+ }),
118
+ getOverlayProps: () => ({
119
+ "aria-hidden": true,
120
+ onClick: () => onOpenChange?.(false)
121
+ }),
122
+ getCloseProps: () => ({
123
+ "aria-label": "Close",
124
+ onClick: () => onOpenChange?.(false)
125
+ })
126
+ };
127
+ }
128
+
129
+ // src/tabs.ts
130
+ function createTabs(items, config = {}) {
131
+ const { onValueChange } = config;
132
+ return {
133
+ getListProps: () => ({
134
+ role: "tablist"
135
+ }),
136
+ getTriggerProps: (value, activeValue) => {
137
+ const index = items.findIndex((item2) => item2.value === value);
138
+ const isActive = value === activeValue;
139
+ const item = items[index];
140
+ return {
141
+ role: "tab",
142
+ "aria-selected": isActive,
143
+ "aria-controls": `tabpanel-${value}`,
144
+ id: `tab-${value}`,
145
+ tabIndex: isActive ? 0 : -1,
146
+ disabled: item?.disabled || false,
147
+ onClick: () => {
148
+ if (!item?.disabled) onValueChange?.(value);
149
+ },
150
+ onKeyDown: (e) => {
151
+ const enabledItems = items.filter((i) => !i.disabled);
152
+ const currentEnabledIndex = enabledItems.findIndex((i) => i.value === value);
153
+ handleArrowNavigation(
154
+ e,
155
+ currentEnabledIndex,
156
+ enabledItems.length,
157
+ "horizontal",
158
+ (newIndex) => {
159
+ const newItem = enabledItems[newIndex];
160
+ onValueChange?.(newItem.value);
161
+ }
162
+ );
163
+ }
164
+ };
165
+ },
166
+ getPanelProps: (value, activeValue) => ({
167
+ role: "tabpanel",
168
+ id: `tabpanel-${value}`,
169
+ "aria-labelledby": `tab-${value}`,
170
+ hidden: value !== activeValue || void 0,
171
+ tabIndex: 0
172
+ })
173
+ };
174
+ }
175
+
176
+ // src/accordion.ts
177
+ function createAccordion(items, config = {}) {
178
+ const { type = "single", collapsible = false, onValueChange } = config;
179
+ function toggle(value, expandedValues) {
180
+ const isExpanded = expandedValues.includes(value);
181
+ if (type === "single") {
182
+ if (isExpanded && collapsible) {
183
+ onValueChange?.([]);
184
+ } else if (!isExpanded) {
185
+ onValueChange?.([value]);
186
+ }
187
+ } else {
188
+ if (isExpanded) {
189
+ onValueChange?.(expandedValues.filter((v) => v !== value));
190
+ } else {
191
+ onValueChange?.([...expandedValues, value]);
192
+ }
193
+ }
194
+ }
195
+ return {
196
+ getItemProps: (value) => ({
197
+ "data-value": value
198
+ }),
199
+ getTriggerProps: (value, expandedValues) => {
200
+ const isExpanded = expandedValues.includes(value);
201
+ const item = items.find((i) => i.value === value);
202
+ return {
203
+ "aria-expanded": isExpanded,
204
+ "aria-controls": `accordion-content-${value}`,
205
+ id: `accordion-trigger-${value}`,
206
+ disabled: item?.disabled || false,
207
+ onClick: () => {
208
+ if (!item?.disabled) toggle(value, expandedValues);
209
+ },
210
+ onKeyDown: (e) => {
211
+ const enabledItems = items.filter((i) => !i.disabled);
212
+ const currentEnabledIndex = enabledItems.findIndex((i) => i.value === value);
213
+ if (e.key === Keys.ArrowDown) {
214
+ e.preventDefault();
215
+ const next = currentEnabledIndex + 1;
216
+ if (next < enabledItems.length) {
217
+ const el = document.getElementById(`accordion-trigger-${enabledItems[next].value}`);
218
+ el?.focus();
219
+ }
220
+ } else if (e.key === Keys.ArrowUp) {
221
+ e.preventDefault();
222
+ const prev = currentEnabledIndex - 1;
223
+ if (prev >= 0) {
224
+ const el = document.getElementById(`accordion-trigger-${enabledItems[prev].value}`);
225
+ el?.focus();
226
+ }
227
+ } else if (e.key === Keys.Home) {
228
+ e.preventDefault();
229
+ const el = document.getElementById(`accordion-trigger-${enabledItems[0].value}`);
230
+ el?.focus();
231
+ } else if (e.key === Keys.End) {
232
+ e.preventDefault();
233
+ const el = document.getElementById(`accordion-trigger-${enabledItems[enabledItems.length - 1].value}`);
234
+ el?.focus();
235
+ }
236
+ }
237
+ };
238
+ },
239
+ getContentProps: (value, expandedValues) => {
240
+ const isExpanded = expandedValues.includes(value);
241
+ return {
242
+ role: "region",
243
+ id: `accordion-content-${value}`,
244
+ "aria-labelledby": `accordion-trigger-${value}`,
245
+ hidden: !isExpanded ? true : void 0
246
+ };
247
+ }
248
+ };
249
+ }
250
+
251
+ // src/select.ts
252
+ function createSelect(options, config = {}) {
253
+ const { onValueChange, onOpenChange } = config;
254
+ return {
255
+ getTriggerProps: (isOpen, selectedValue, highlightedIndex) => {
256
+ const selectedOption = options.find((o) => o.value === selectedValue);
257
+ const highlightedOption = highlightedIndex != null ? options[highlightedIndex] : void 0;
258
+ return {
259
+ role: "combobox",
260
+ "aria-expanded": isOpen,
261
+ "aria-haspopup": "listbox",
262
+ "aria-label": selectedOption?.label || "Select an option",
263
+ "aria-activedescendant": isOpen && highlightedOption ? `select-option-${highlightedOption.value}` : void 0,
264
+ onClick: () => onOpenChange?.(!isOpen),
265
+ onKeyDown: (e) => {
266
+ if (e.key === Keys.Enter || e.key === Keys.Space) {
267
+ e.preventDefault();
268
+ onOpenChange?.(!isOpen);
269
+ } else if (e.key === Keys.ArrowDown) {
270
+ e.preventDefault();
271
+ onOpenChange?.(true);
272
+ }
273
+ }
274
+ };
275
+ },
276
+ getListProps: () => ({
277
+ role: "listbox"
278
+ }),
279
+ getOptionProps: (option, isHighlighted, selectedValue, _highlightedIndex) => ({
280
+ role: "option",
281
+ "aria-selected": option.value === selectedValue,
282
+ "aria-disabled": option.disabled || void 0,
283
+ "data-highlighted": isHighlighted || void 0,
284
+ id: `select-option-${option.value}`,
285
+ onClick: () => {
286
+ if (!option.disabled) {
287
+ onValueChange?.(option.value);
288
+ onOpenChange?.(false);
289
+ }
290
+ }
291
+ }),
292
+ getContentProps: (isOpen) => ({
293
+ hidden: !isOpen ? true : void 0
294
+ }),
295
+ handleKeyDown: (e, isOpen, highlightedIndex, onHighlightChange) => {
296
+ if (!isOpen) return;
297
+ const enabledOptions = options.filter((o) => !o.disabled);
298
+ if (e.key === Keys.ArrowDown) {
299
+ e.preventDefault();
300
+ const next = getNextIndex(highlightedIndex, enabledOptions.length, "next");
301
+ onHighlightChange(next);
302
+ } else if (e.key === Keys.ArrowUp) {
303
+ e.preventDefault();
304
+ const prev = getNextIndex(highlightedIndex, enabledOptions.length, "prev");
305
+ onHighlightChange(prev);
306
+ } else if (e.key === Keys.Enter || e.key === Keys.Space) {
307
+ e.preventDefault();
308
+ const option = enabledOptions[highlightedIndex];
309
+ if (option) {
310
+ onValueChange?.(option.value);
311
+ onOpenChange?.(false);
312
+ }
313
+ } else if (e.key === Keys.Escape) {
314
+ e.preventDefault();
315
+ onOpenChange?.(false);
316
+ } else if (e.key === Keys.Home) {
317
+ e.preventDefault();
318
+ onHighlightChange(0);
319
+ } else if (e.key === Keys.End) {
320
+ e.preventDefault();
321
+ onHighlightChange(enabledOptions.length - 1);
322
+ }
323
+ }
324
+ };
325
+ }
326
+
327
+ // src/tooltip.ts
328
+ var tooltipCounter = 0;
329
+ function createTooltip(config = {}) {
330
+ const { delay = 700, onOpenChange } = config;
331
+ const id = `mcpe-tooltip-${++tooltipCounter}`;
332
+ let timeoutId = null;
333
+ function clearDelay() {
334
+ if (timeoutId !== null) {
335
+ clearTimeout(timeoutId);
336
+ timeoutId = null;
337
+ }
338
+ }
339
+ function openWithDelay() {
340
+ clearDelay();
341
+ timeoutId = setTimeout(() => {
342
+ onOpenChange?.(true);
343
+ }, delay);
344
+ }
345
+ function close() {
346
+ clearDelay();
347
+ onOpenChange?.(false);
348
+ }
349
+ return {
350
+ getTriggerProps: () => ({
351
+ "aria-describedby": id,
352
+ onMouseEnter: () => openWithDelay(),
353
+ onMouseLeave: () => close(),
354
+ onFocus: () => openWithDelay(),
355
+ onBlur: () => close()
356
+ }),
357
+ getContentProps: (isOpen) => ({
358
+ id,
359
+ role: "tooltip",
360
+ hidden: !isOpen ? true : void 0
361
+ }),
362
+ destroy: () => clearDelay()
363
+ };
364
+ }
365
+
366
+ // src/popover.ts
367
+ function createPopover(config = {}) {
368
+ const { onOpenChange } = config;
369
+ return {
370
+ getTriggerProps: (isOpen) => ({
371
+ "aria-haspopup": true,
372
+ "aria-expanded": isOpen,
373
+ onClick: () => onOpenChange?.(!isOpen)
374
+ }),
375
+ getContentProps: (isOpen) => ({
376
+ role: "dialog",
377
+ hidden: !isOpen || void 0,
378
+ onKeyDown: (e) => {
379
+ if (e.key === "Escape") onOpenChange?.(false);
380
+ }
381
+ })
382
+ };
383
+ }
384
+
385
+ // src/toast.ts
386
+ var toastCounter = 0;
387
+ function createToastManager(config = {}) {
388
+ const { duration: defaultDuration = 5e3 } = config;
389
+ const listeners = /* @__PURE__ */ new Set();
390
+ let state = { toasts: [] };
391
+ function notify() {
392
+ for (const listener of listeners) {
393
+ listener(state);
394
+ }
395
+ }
396
+ function addToast(toast2) {
397
+ const id = `toast-${++toastCounter}`;
398
+ const newToast = { id, ...toast2 };
399
+ state = { toasts: [...state.toasts, newToast] };
400
+ notify();
401
+ const dur = toast2.duration ?? defaultDuration;
402
+ if (dur > 0) {
403
+ setTimeout(() => removeToast(id), dur);
404
+ }
405
+ return id;
406
+ }
407
+ function removeToast(id) {
408
+ state = { toasts: state.toasts.filter((t) => t.id !== id) };
409
+ notify();
410
+ }
411
+ function subscribe(listener) {
412
+ listeners.add(listener);
413
+ return () => listeners.delete(listener);
414
+ }
415
+ return {
416
+ addToast,
417
+ removeToast,
418
+ subscribe,
419
+ getState: () => state
420
+ };
421
+ }
422
+ var defaultManager = createToastManager();
423
+ var toast = {
424
+ default: (title, description) => defaultManager.addToast({ title, description, variant: "default" }),
425
+ success: (title, description) => defaultManager.addToast({ title, description, variant: "success" }),
426
+ destructive: (title, description) => defaultManager.addToast({ title, description, variant: "destructive" }),
427
+ custom: (t) => defaultManager.addToast(t),
428
+ dismiss: (id) => defaultManager.removeToast(id),
429
+ subscribe: defaultManager.subscribe,
430
+ getState: defaultManager.getState
431
+ };
432
+
433
+ // src/drawer.ts
434
+ function createDrawer(config = {}) {
435
+ const { side = "right", onOpenChange } = config;
436
+ return {
437
+ getOverlayProps: () => ({
438
+ "aria-hidden": true,
439
+ onClick: () => onOpenChange?.(false)
440
+ }),
441
+ getContentProps: (isOpen) => ({
442
+ role: "dialog",
443
+ "aria-modal": true,
444
+ hidden: !isOpen ? true : void 0,
445
+ onKeyDown: (e) => {
446
+ if (e.key === "Escape") onOpenChange?.(false);
447
+ }
448
+ }),
449
+ getCloseProps: () => ({
450
+ "aria-label": "Close",
451
+ onClick: () => onOpenChange?.(false)
452
+ }),
453
+ side
454
+ };
455
+ }
456
+
457
+ // src/dropdown-menu.ts
458
+ function createDropdownMenu(items, config = {}) {
459
+ const { onOpenChange } = config;
460
+ const actionItems = items.filter((i) => i.type !== "separator" && i.type !== "label" && !i.disabled);
461
+ return {
462
+ getTriggerProps: (isOpen) => ({
463
+ "aria-haspopup": "menu",
464
+ "aria-expanded": isOpen,
465
+ onClick: () => onOpenChange?.(!isOpen)
466
+ }),
467
+ getContentProps: (isOpen) => ({
468
+ role: "menu",
469
+ hidden: !isOpen || void 0
470
+ }),
471
+ getItemProps: (item, isHighlighted) => ({
472
+ role: "menuitem",
473
+ "aria-disabled": item.disabled || void 0,
474
+ "data-highlighted": isHighlighted || void 0,
475
+ tabIndex: -1,
476
+ onClick: () => {
477
+ if (!item.disabled && item.type !== "separator" && item.type !== "label") {
478
+ item.onSelect?.();
479
+ onOpenChange?.(false);
480
+ }
481
+ }
482
+ }),
483
+ handleKeyDown: (e, isOpen, highlightedIndex, onHighlightChange) => {
484
+ if (!isOpen) return;
485
+ if (e.key === Keys.ArrowDown) {
486
+ e.preventDefault();
487
+ onHighlightChange(getNextIndex(highlightedIndex, actionItems.length, "next"));
488
+ } else if (e.key === Keys.ArrowUp) {
489
+ e.preventDefault();
490
+ onHighlightChange(getNextIndex(highlightedIndex, actionItems.length, "prev"));
491
+ } else if (e.key === Keys.Enter || e.key === Keys.Space) {
492
+ e.preventDefault();
493
+ const item = actionItems[highlightedIndex];
494
+ if (item) {
495
+ item.onSelect?.();
496
+ onOpenChange?.(false);
497
+ }
498
+ } else if (e.key === Keys.Escape) {
499
+ e.preventDefault();
500
+ onOpenChange?.(false);
501
+ } else if (e.key === Keys.Home) {
502
+ e.preventDefault();
503
+ onHighlightChange(0);
504
+ } else if (e.key === Keys.End) {
505
+ e.preventDefault();
506
+ onHighlightChange(actionItems.length - 1);
507
+ }
508
+ }
509
+ };
510
+ }
511
+
512
+ // src/switch.ts
513
+ function createSwitch(config = {}) {
514
+ const { onCheckedChange } = config;
515
+ return {
516
+ getSwitchProps: (checked, disabled = false) => ({
517
+ role: "switch",
518
+ "aria-checked": checked,
519
+ "aria-disabled": disabled || void 0,
520
+ tabIndex: disabled ? -1 : 0,
521
+ onClick: () => {
522
+ if (!disabled) onCheckedChange?.(!checked);
523
+ },
524
+ onKeyDown: (e) => {
525
+ if (disabled) return;
526
+ if (e.key === " " || e.key === "Enter") {
527
+ e.preventDefault();
528
+ onCheckedChange?.(!checked);
529
+ }
530
+ }
531
+ })
532
+ };
533
+ }
534
+
535
+ // src/mcp/scope.ts
536
+ function parseScope(raw) {
537
+ const trimmed = raw.trim();
538
+ const colonIdx = trimmed.indexOf(":");
539
+ if (colonIdx === -1) {
540
+ return { raw, resource: trimmed, permissions: ["access"] };
541
+ }
542
+ const resource = trimmed.slice(0, colonIdx);
543
+ const permsStr = trimmed.slice(colonIdx + 1);
544
+ const permissions = permsStr.split(",").map((p) => p.trim()).filter(Boolean);
545
+ return { raw, resource, permissions: permissions.length > 0 ? permissions : ["access"] };
546
+ }
547
+ function parseScopes(scopeString) {
548
+ return scopeString.split(/\s+/).filter(Boolean).map(parseScope);
549
+ }
550
+
551
+ // src/mcp/tool-state.ts
552
+ var VALID_TRANSITIONS = {
553
+ idle: ["pending"],
554
+ pending: ["running", "cancelled"],
555
+ running: ["done", "error", "cancelled"],
556
+ done: ["idle"],
557
+ error: ["idle"],
558
+ cancelled: ["idle"]
559
+ };
560
+ function createToolState() {
561
+ let snapshot = { status: "idle" };
562
+ const listeners = /* @__PURE__ */ new Set();
563
+ function transition(to, patch = {}) {
564
+ const allowed = VALID_TRANSITIONS[snapshot.status];
565
+ if (!allowed.includes(to)) {
566
+ throw new Error(`Invalid tool-state transition: ${snapshot.status} \u2192 ${to}`);
567
+ }
568
+ snapshot = { ...snapshot, ...patch, status: to };
569
+ let firstError;
570
+ for (const fn of listeners) {
571
+ try {
572
+ fn(snapshot);
573
+ } catch (e) {
574
+ if (firstError === void 0) firstError = e;
575
+ }
576
+ }
577
+ if (firstError !== void 0) throw firstError;
578
+ }
579
+ return {
580
+ get status() {
581
+ return snapshot.status;
582
+ },
583
+ get tool() {
584
+ return snapshot.tool;
585
+ },
586
+ get args() {
587
+ return snapshot.args;
588
+ },
589
+ get result() {
590
+ return snapshot.result;
591
+ },
592
+ get error() {
593
+ return snapshot.error;
594
+ },
595
+ get startedAt() {
596
+ return snapshot.startedAt;
597
+ },
598
+ get endedAt() {
599
+ return snapshot.endedAt;
600
+ },
601
+ start({ tool, args }) {
602
+ transition("pending", { tool, args, startedAt: Date.now() });
603
+ },
604
+ markRunning() {
605
+ transition("running");
606
+ },
607
+ markDone(result) {
608
+ transition("done", { result, endedAt: Date.now() });
609
+ },
610
+ markError(error) {
611
+ transition("error", { error, endedAt: Date.now() });
612
+ },
613
+ cancel() {
614
+ transition("cancelled", { endedAt: Date.now() });
615
+ },
616
+ reset() {
617
+ if (snapshot.status === "idle") return;
618
+ snapshot = { status: "idle" };
619
+ let firstError;
620
+ for (const fn of listeners) {
621
+ try {
622
+ fn(snapshot);
623
+ } catch (e) {
624
+ if (firstError === void 0) firstError = e;
625
+ }
626
+ }
627
+ if (firstError !== void 0) throw firstError;
628
+ },
629
+ subscribe(fn) {
630
+ listeners.add(fn);
631
+ return () => listeners.delete(fn);
632
+ }
633
+ };
634
+ }
635
+
636
+ // src/mcp/schema-form.ts
637
+ function schemaToFields(schema) {
638
+ if (schema.type !== "object" || !schema.properties) return [];
639
+ const required = new Set(schema.required ?? []);
640
+ const fields = [];
641
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
642
+ fields.push(fieldFromProperty(key, propSchema, required.has(key)));
643
+ }
644
+ return fields;
645
+ }
646
+ function fieldFromProperty(key, schema, required) {
647
+ const base = {
648
+ key,
649
+ label: schema.title ?? key,
650
+ help: schema.description,
651
+ required,
652
+ defaultValue: schema.default
653
+ };
654
+ if (schema.enum && schema.enum.length > 0) {
655
+ return {
656
+ ...base,
657
+ kind: "select",
658
+ options: schema.enum.map((v) => ({ value: String(v), label: String(v) }))
659
+ };
660
+ }
661
+ switch (schema.type) {
662
+ case "string": {
663
+ if (schema.format === "email") return { ...base, kind: "email" };
664
+ if (schema.format === "uri" || schema.format === "url") return { ...base, kind: "url" };
665
+ if (schema.format === "date" || schema.format === "date-time") return { ...base, kind: "date" };
666
+ if ((schema.maxLength ?? 0) > 200) {
667
+ return {
668
+ ...base,
669
+ kind: "textarea",
670
+ minLength: schema.minLength,
671
+ maxLength: schema.maxLength,
672
+ pattern: schema.pattern
673
+ };
674
+ }
675
+ return {
676
+ ...base,
677
+ kind: "text",
678
+ minLength: schema.minLength,
679
+ maxLength: schema.maxLength,
680
+ pattern: schema.pattern
681
+ };
682
+ }
683
+ case "number":
684
+ case "integer":
685
+ return { ...base, kind: "number", min: schema.minimum, max: schema.maximum };
686
+ case "boolean":
687
+ return { ...base, kind: "switch" };
688
+ case "array":
689
+ if (schema.items?.type === "string") return { ...base, kind: "multiselect" };
690
+ return { ...base, kind: "unknown" };
691
+ case "null":
692
+ return { ...base, kind: "unknown" };
693
+ default:
694
+ return { ...base, kind: "unknown" };
695
+ }
696
+ }
697
+
698
+ // src/mcp/oauth.ts
699
+ function randomBytes(n) {
700
+ const arr = new Uint8Array(n);
701
+ globalThis.crypto.getRandomValues(arr);
702
+ return arr;
703
+ }
704
+ function base64urlEncode(bytes) {
705
+ let s = "";
706
+ for (const b of bytes) s += String.fromCharCode(b);
707
+ return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
708
+ }
709
+ async function generatePkcePair() {
710
+ const verifierBytes = randomBytes(64);
711
+ const codeVerifier = base64urlEncode(verifierBytes);
712
+ const verifierBuf = new TextEncoder().encode(codeVerifier);
713
+ const hashBuf = await globalThis.crypto.subtle.digest("SHA-256", verifierBuf);
714
+ const codeChallenge = base64urlEncode(new Uint8Array(hashBuf));
715
+ return { codeVerifier, codeChallenge, codeChallengeMethod: "S256" };
716
+ }
717
+ function buildAuthUrl(input) {
718
+ const u = new URL(input.authorizationEndpoint);
719
+ u.searchParams.set("response_type", "code");
720
+ u.searchParams.set("client_id", input.clientId);
721
+ u.searchParams.set("redirect_uri", input.redirectUri);
722
+ u.searchParams.set("scope", input.scope);
723
+ u.searchParams.set("code_challenge", input.codeChallenge);
724
+ u.searchParams.set("code_challenge_method", "S256");
725
+ u.searchParams.set("state", input.state);
726
+ if (input.resource) u.searchParams.set("resource", input.resource);
727
+ return u.toString();
728
+ }
729
+ function buildTokenExchangeBody(input) {
730
+ const body = new URLSearchParams();
731
+ body.set("grant_type", "authorization_code");
732
+ body.set("client_id", input.clientId);
733
+ body.set("code", input.code);
734
+ body.set("redirect_uri", input.redirectUri);
735
+ body.set("code_verifier", input.codeVerifier);
736
+ return body.toString();
737
+ }
738
+ function buildTokenRefreshBody(input) {
739
+ const body = new URLSearchParams();
740
+ body.set("grant_type", "refresh_token");
741
+ body.set("client_id", input.clientId);
742
+ body.set("refresh_token", input.refreshToken);
743
+ if (input.scope) body.set("scope", input.scope);
744
+ return body.toString();
745
+ }
746
+ var OAUTH_TRANSITIONS = {
747
+ idle: ["authorizing"],
748
+ authorizing: ["authorized", "denied", "error"],
749
+ authorized: ["idle"],
750
+ denied: ["idle"],
751
+ error: ["idle"]
752
+ };
753
+ function createOAuthFlow() {
754
+ let snap = { status: "idle" };
755
+ const listeners = /* @__PURE__ */ new Set();
756
+ function notify() {
757
+ let firstError;
758
+ for (const fn of listeners) {
759
+ try {
760
+ fn(snap);
761
+ } catch (e) {
762
+ if (firstError === void 0) firstError = e;
763
+ }
764
+ }
765
+ if (firstError !== void 0) throw firstError;
766
+ }
767
+ function transition(to, patch = {}) {
768
+ const allowed = OAUTH_TRANSITIONS[snap.status];
769
+ if (!allowed.includes(to)) {
770
+ throw new Error(`Invalid oauth transition: ${snap.status} \u2192 ${to}`);
771
+ }
772
+ snap = { ...snap, ...patch, status: to };
773
+ notify();
774
+ }
775
+ return {
776
+ get status() {
777
+ return snap.status;
778
+ },
779
+ get verifier() {
780
+ return snap.verifier;
781
+ },
782
+ get state() {
783
+ return snap.state;
784
+ },
785
+ get tokens() {
786
+ return snap.tokens;
787
+ },
788
+ get error() {
789
+ return snap.error;
790
+ },
791
+ start({ verifier, state }) {
792
+ transition("authorizing", { verifier, state });
793
+ },
794
+ markAuthorized(tokens) {
795
+ transition("authorized", { tokens });
796
+ },
797
+ markDenied(code, message) {
798
+ transition("denied", { error: { code, message } });
799
+ },
800
+ markError(error) {
801
+ transition("error", { error: { code: "unknown", message: error.message, originalError: error } });
802
+ },
803
+ reset() {
804
+ if (snap.status === "idle") return;
805
+ snap = { status: "idle" };
806
+ notify();
807
+ },
808
+ subscribe(fn) {
809
+ listeners.add(fn);
810
+ return () => listeners.delete(fn);
811
+ }
812
+ };
813
+ }
814
+
815
+ // src/mcp/app-bridge.ts
816
+ function encodeEnvelope(env) {
817
+ return { id: env.id, type: env.type, payload: env.payload };
818
+ }
819
+ function decodeEnvelope(raw) {
820
+ if (raw === null || Array.isArray(raw) || typeof raw !== "object") return null;
821
+ const o = raw;
822
+ if (typeof o.id !== "string") return null;
823
+ if (typeof o.type !== "string") return null;
824
+ return { id: o.id, type: o.type, payload: o.payload };
825
+ }
826
+ function createAppBridge(config) {
827
+ const listeners = /* @__PURE__ */ new Set();
828
+ function dispatch(env) {
829
+ let firstError;
830
+ for (const fn of listeners) {
831
+ try {
832
+ fn(env);
833
+ } catch (e) {
834
+ if (firstError === void 0) firstError = e;
835
+ }
836
+ }
837
+ if (firstError !== void 0) throw firstError;
838
+ }
839
+ return {
840
+ send(env) {
841
+ config.postMessage(encodeEnvelope(env));
842
+ },
843
+ receive(raw) {
844
+ const env = decodeEnvelope(raw);
845
+ if (!env) return;
846
+ dispatch(env);
847
+ },
848
+ onMessage(fn) {
849
+ listeners.add(fn);
850
+ return () => listeners.delete(fn);
851
+ }
852
+ };
853
+ }
854
+ export {
855
+ Keys,
856
+ buildAuthUrl,
857
+ buildTokenExchangeBody,
858
+ buildTokenRefreshBody,
859
+ cn,
860
+ createAccordion,
861
+ createAppBridge,
862
+ createClickOutsideHandler,
863
+ createDialog,
864
+ createDrawer,
865
+ createDropdownMenu,
866
+ createOAuthFlow,
867
+ createPopover,
868
+ createSelect,
869
+ createSwitch,
870
+ createTabs,
871
+ createToastManager,
872
+ createToolState,
873
+ createTooltip,
874
+ decodeEnvelope,
875
+ encodeEnvelope,
876
+ generatePkcePair,
877
+ getFocusableElements,
878
+ getNextIndex,
879
+ handleArrowNavigation,
880
+ lockScroll,
881
+ parseScope,
882
+ parseScopes,
883
+ schemaToFields,
884
+ toast,
885
+ trapFocus
886
+ };
887
+ //# sourceMappingURL=index.js.map