@mcp-elements/angular 0.1.0 → 0.1.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.
Files changed (44) hide show
  1. package/dist/index.d.ts +506 -0
  2. package/dist/index.js +2714 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +28 -11
  5. package/src/accordion.component.ts +0 -74
  6. package/src/ai-badge.component.ts +0 -26
  7. package/src/alert.component.ts +0 -25
  8. package/src/avatar.component.ts +0 -24
  9. package/src/badge.component.ts +0 -17
  10. package/src/button.component.ts +0 -27
  11. package/src/card.component.ts +0 -46
  12. package/src/chat-bubble.component.ts +0 -53
  13. package/src/chips.component.ts +0 -33
  14. package/src/counter.component.ts +0 -48
  15. package/src/dialog.component.ts +0 -42
  16. package/src/drawer.component.ts +0 -48
  17. package/src/dropdown-menu.component.ts +0 -62
  18. package/src/feedback.component.ts +0 -71
  19. package/src/index.ts +0 -86
  20. package/src/input.component.ts +0 -46
  21. package/src/loader.component.ts +0 -12
  22. package/src/mcp/index.ts +0 -9
  23. package/src/mcp/mcp-app-frame.component.ts +0 -60
  24. package/src/mcp/mcp-consent-dialog.component.ts +0 -63
  25. package/src/mcp/mcp-resource-browser.component.ts +0 -86
  26. package/src/mcp/mcp-scope-inspector.component.ts +0 -81
  27. package/src/mcp/mcp-server-status.component.ts +0 -44
  28. package/src/mcp/mcp-tool-call.component.ts +0 -105
  29. package/src/mcp/mcp-tool-form.component.ts +0 -127
  30. package/src/password-input.component.ts +0 -35
  31. package/src/popover.component.ts +0 -40
  32. package/src/progress.component.ts +0 -20
  33. package/src/prompt-input.component.ts +0 -70
  34. package/src/select.component.ts +0 -106
  35. package/src/separator.component.ts +0 -15
  36. package/src/skeleton.component.ts +0 -11
  37. package/src/source-card.component.ts +0 -34
  38. package/src/streaming-text.component.ts +0 -43
  39. package/src/suggestion-chips.component.ts +0 -23
  40. package/src/switch.component.ts +0 -32
  41. package/src/tabs.component.ts +0 -95
  42. package/src/textarea.component.ts +0 -22
  43. package/src/toast.component.ts +0 -62
  44. package/src/tooltip.directive.ts +0 -63
@@ -1,86 +0,0 @@
1
- import { Component, input, output, computed } from '@angular/core'
2
- import { CommonModule } from '@angular/common'
3
- import { cn } from '@mcp-elements/core'
4
-
5
- export interface McpResource {
6
- uri: string
7
- name: string
8
- mimeType?: string
9
- description?: string
10
- }
11
-
12
- function mimeTypeLabel(mimeType?: string): string {
13
- if (!mimeType) return 'res'
14
- if (mimeType.includes('json')) return 'json'
15
- if (mimeType.includes('text')) return 'txt'
16
- if (mimeType.includes('image')) return 'img'
17
- if (mimeType.includes('pdf')) return 'pdf'
18
- return mimeType.split('/')[1]?.slice(0, 4) ?? 'res'
19
- }
20
-
21
- @Component({
22
- selector: 'mcpe-mcp-resource-browser',
23
- standalone: true,
24
- imports: [CommonModule],
25
- template: `
26
- @if (loading()) {
27
- <div [class]="classes()">
28
- @for (n of skeletonItems; track n) {
29
- <div class="flex items-center gap-3 px-3 py-2.5">
30
- <div class="h-8 w-8 rounded-md animate-pulse bg-muted"></div>
31
- <div class="h-4 flex-1 rounded animate-pulse bg-muted"></div>
32
- </div>
33
- }
34
- </div>
35
- } @else if (resources().length === 0) {
36
- <div [class]="classes()">
37
- <p class="mcpe-mcp-resource-browser-empty">No resources available</p>
38
- </div>
39
- } @else {
40
- <div [class]="classes()" role="list">
41
- @for (r of resources(); track r.uri) {
42
- <button
43
- type="button"
44
- role="listitem"
45
- [class]="itemClass(r.uri)"
46
- [attr.aria-selected]="selectedUri() === r.uri"
47
- [attr.aria-label]="r.name"
48
- (click)="select(r)"
49
- >
50
- <span class="mcpe-mcp-resource-browser-icon" aria-hidden="true">{{ mimeLabel(r.mimeType) }}</span>
51
- <span class="mcpe-mcp-resource-browser-name">{{ r.name }}</span>
52
- @if (r.mimeType) {
53
- <span class="mcpe-mcp-resource-browser-type">{{ r.mimeType.split('/')[0] }}</span>
54
- }
55
- </button>
56
- }
57
- </div>
58
- }
59
- `,
60
- })
61
- export class McpeMcpResourceBrowserComponent {
62
- resources = input<McpResource[]>([])
63
- selectedUri = input<string>()
64
- loading = input(false)
65
- class = input('')
66
- onSelect = output<McpResource>()
67
-
68
- readonly skeletonItems = [1, 2, 3, 4]
69
-
70
- classes = computed(() => cn('mcpe-mcp-resource-browser', this.class()))
71
-
72
- itemClass(uri: string): string {
73
- return cn(
74
- 'mcpe-mcp-resource-browser-item w-full text-left',
75
- this.selectedUri() === uri ? 'mcpe-mcp-resource-browser-item-selected' : ''
76
- )
77
- }
78
-
79
- mimeLabel(mimeType?: string): string {
80
- return mimeTypeLabel(mimeType)
81
- }
82
-
83
- select(r: McpResource) {
84
- this.onSelect.emit(r)
85
- }
86
- }
@@ -1,81 +0,0 @@
1
- import { Component, input, signal, computed } from '@angular/core'
2
- import { CommonModule } from '@angular/common'
3
- import { cn, parseScopes } from '@mcp-elements/core'
4
- import type { ScopeDescriptor } from '@mcp-elements/core'
5
-
6
- @Component({
7
- selector: 'mcpe-mcp-scope-inspector',
8
- standalone: true,
9
- imports: [CommonModule],
10
- template: `
11
- <div [class]="classes()" role="list">
12
- @for (s of parsedScopes(); track s.raw) {
13
- <div class="mcpe-mcp-scope-inspector-item" role="listitem">
14
- <button
15
- type="button"
16
- class="mcpe-mcp-scope-inspector-trigger"
17
- [attr.aria-expanded]="isOpen(s.raw)"
18
- (click)="toggle(s.raw)"
19
- >
20
- <div class="flex items-center gap-3">
21
- <span class="mcpe-mcp-scope-inspector-resource">{{ s.resource }}</span>
22
- <div class="mcpe-mcp-scope-inspector-perms">
23
- @for (p of s.permissions; track p) {
24
- <span class="mcpe-mcp-scope-inspector-perm">{{ p }}</span>
25
- }
26
- </div>
27
- </div>
28
- <svg
29
- [class]="chevronClass(s.raw)"
30
- xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
31
- fill="none" stroke="currentColor" stroke-width="2"
32
- stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
33
- <path d="m6 9 6 6 6-6"/>
34
- </svg>
35
- </button>
36
- @if (isOpen(s.raw) && getDescription(s)) {
37
- <div role="region" class="mcpe-mcp-scope-inspector-body">
38
- {{ getDescription(s) }}
39
- </div>
40
- }
41
- </div>
42
- }
43
- </div>
44
- `,
45
- })
46
- export class McpeMcpScopeInspectorComponent {
47
- scopes = input<string | ScopeDescriptor[]>('')
48
- descriptions = input<Record<string, string>>({})
49
- class = input('')
50
-
51
- openKeys = signal<Set<string>>(new Set())
52
-
53
- parsedScopes = computed((): ScopeDescriptor[] => {
54
- const s = this.scopes()
55
- return typeof s === 'string' ? parseScopes(s) : s
56
- })
57
-
58
- classes = computed(() => cn('mcpe-mcp-scope-inspector', this.class()))
59
-
60
- toggle(key: string) {
61
- this.openKeys.update((prev: Set<string>) => {
62
- const next = new Set(prev)
63
- if (next.has(key)) next.delete(key)
64
- else next.add(key)
65
- return next
66
- })
67
- }
68
-
69
- isOpen(key: string): boolean {
70
- return this.openKeys().has(key)
71
- }
72
-
73
- chevronClass(key: string): string {
74
- return cn('mcpe-mcp-scope-inspector-chevron', this.isOpen(key) ? 'mcpe-mcp-scope-inspector-chevron-open' : '')
75
- }
76
-
77
- getDescription(s: ScopeDescriptor): string | undefined {
78
- const d = this.descriptions()
79
- return d[s.raw] ?? d[s.resource] ?? s.description
80
- }
81
- }
@@ -1,44 +0,0 @@
1
- import { Component, input, computed } from '@angular/core'
2
- import { cn } from '@mcp-elements/core'
3
-
4
- export type McpConnectionStatus = 'connected' | 'connecting' | 'disconnected' | 'error'
5
-
6
- const STATUS_LABELS: Record<McpConnectionStatus, string> = {
7
- connected: 'Connected',
8
- connecting: 'Connecting',
9
- disconnected: 'Disconnected',
10
- error: 'Error',
11
- }
12
-
13
- @Component({
14
- selector: 'mcpe-mcp-server-status',
15
- standalone: true,
16
- template: `
17
- <span
18
- [class]="classes()"
19
- role="status"
20
- aria-live="polite"
21
- [attr.aria-label]="ariaLabel()"
22
- >
23
- <span class="mcpe-mcp-server-status-dot" aria-hidden="true"></span>
24
- {{ label() }}
25
- </span>
26
- `,
27
- })
28
- export class McpeMcpServerStatusComponent {
29
- status = input.required<McpConnectionStatus>()
30
- serverName = input<string>()
31
- class = input('')
32
-
33
- classes = computed(() => cn('mcpe-mcp-server-status', `mcpe-mcp-server-status-${this.status()}`, this.class()))
34
- label = computed(() => {
35
- const s = this.serverName()
36
- const statusLabel = STATUS_LABELS[this.status() as McpConnectionStatus]
37
- return s ? `${s} · ${statusLabel}` : statusLabel
38
- })
39
- ariaLabel = computed(() => {
40
- const s = this.serverName()
41
- const statusLabel = STATUS_LABELS[this.status() as McpConnectionStatus]
42
- return s ? `${s}: ${statusLabel}` : statusLabel
43
- })
44
- }
@@ -1,105 +0,0 @@
1
- import { Component, input, output, signal, effect, computed, OnDestroy } from '@angular/core'
2
- import { CommonModule } from '@angular/common'
3
- import { cn } from '@mcp-elements/core'
4
- import type { ToolStateApi, ToolStateSnapshot } from '@mcp-elements/core'
5
-
6
- const STATUS_LABELS: Record<string, string> = {
7
- idle: 'idle',
8
- pending: 'pending',
9
- running: 'running',
10
- done: 'done',
11
- error: 'error',
12
- cancelled: 'cancelled',
13
- }
14
-
15
- @Component({
16
- selector: 'mcpe-mcp-tool-call',
17
- standalone: true,
18
- imports: [CommonModule],
19
- template: `
20
- <div [class]="classes()">
21
- <!-- Header -->
22
- <div class="mcpe-mcp-tool-call-header">
23
- <div class="mcpe-mcp-tool-call-name">
24
- <span class="mcpe-mcp-tool-call-icon" aria-hidden="true">fn</span>
25
- <span class="mcpe-mcp-tool-call-title">{{ displayName() }}</span>
26
- </div>
27
- <span [class]="badgeClass()">
28
- @if (snap().status === 'running') {
29
- <svg class="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24" aria-hidden="true">
30
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
31
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
32
- </svg>
33
- }
34
- {{ statusLabels[snap().status] }}
35
- </span>
36
- </div>
37
- <!-- Args -->
38
- @if (displayArgs()) {
39
- <pre class="mcpe-mcp-tool-call-args">{{ displayArgs() | json }}</pre>
40
- }
41
- <!-- Progress bar -->
42
- @if (snap().status === 'running') {
43
- <div class="mcpe-mcp-tool-call-progress" role="progressbar" aria-label="Tool running">
44
- <div class="mcpe-mcp-tool-call-progress-bar" style="width: 60%"></div>
45
- </div>
46
- }
47
- <!-- Result -->
48
- @if (snap().status === 'done' && snap().result) {
49
- <div class="mcpe-mcp-tool-call-result mcpe-mcp-tool-call-result-done">
50
- @for (block of textBlocks(); track $index) {
51
- <p class="whitespace-pre-wrap text-sm">{{ block }}</p>
52
- }
53
- </div>
54
- }
55
- <!-- Error -->
56
- @if (snap().status === 'error' && snap().error) {
57
- <div class="mcpe-mcp-tool-call-result mcpe-mcp-tool-call-result-error">
58
- <p class="text-sm">{{ snap().error?.message }}</p>
59
- <button (click)="onRetry.emit()" class="text-xs underline underline-offset-2">Retry</button>
60
- </div>
61
- }
62
- </div>
63
- `,
64
- })
65
- export class McpeMcpToolCallComponent implements OnDestroy {
66
- state = input.required<ToolStateApi>()
67
- toolName = input<string>()
68
- args = input<Record<string, unknown>>()
69
- onRetry = output<void>()
70
- class = input('')
71
-
72
- readonly statusLabels = STATUS_LABELS
73
-
74
- snap = signal<ToolStateSnapshot>({ status: 'idle' })
75
- private _unsub: (() => void) | undefined
76
-
77
- constructor() {
78
- effect(() => {
79
- this._unsub?.()
80
- const s = this.state()
81
- this.snap.set({
82
- status: s.status,
83
- tool: s.tool,
84
- args: s.args,
85
- result: s.result,
86
- error: s.error,
87
- startedAt: s.startedAt,
88
- endedAt: s.endedAt,
89
- })
90
- this._unsub = s.subscribe((snapshot: ToolStateSnapshot) => this.snap.set({ ...snapshot }))
91
- })
92
- }
93
-
94
- ngOnDestroy() { this._unsub?.() }
95
-
96
- classes = computed(() => cn('mcpe-mcp-tool-call', this.class()))
97
- badgeClass = computed(() => cn('mcpe-mcp-tool-call-badge', `mcpe-mcp-tool-call-badge-${this.snap().status}`))
98
- displayName = computed(() => this.snap().tool ?? this.toolName() ?? 'unknown')
99
- displayArgs = computed(() => this.snap().args ?? this.args())
100
- textBlocks = computed(() =>
101
- this.snap().result?.content
102
- .filter((c: { type: string }) => c.type === 'text')
103
- .map((c: { type: string; text?: string }) => (c as { type: 'text'; text: string }).text) ?? []
104
- )
105
- }
@@ -1,127 +0,0 @@
1
- import { Component, input, output, signal, computed, OnInit } from '@angular/core'
2
- import { CommonModule } from '@angular/common'
3
- import { cn, schemaToFields } from '@mcp-elements/core'
4
- import type { JsonSchema, FieldDescriptor } from '@mcp-elements/core'
5
-
6
- @Component({
7
- selector: 'mcpe-mcp-tool-form',
8
- standalone: true,
9
- imports: [CommonModule],
10
- template: `
11
- <form [class]="classes()" (ngSubmit)="handleSubmit()">
12
- @if (fields().length === 0) {
13
- <p class="text-sm text-muted-foreground">This tool takes no inputs.</p>
14
- }
15
- @for (field of fields(); track field.key) {
16
- <div class="mcpe-mcp-tool-form-field">
17
- <label
18
- [for]="field.key"
19
- [class]="labelClass(field)"
20
- >{{ field.label }}</label>
21
- @switch (field.kind) {
22
- @case ('switch') {
23
- <input type="checkbox" [id]="field.key" class="mcpe-switch"
24
- [checked]="getBool(field.key)"
25
- (change)="onCheckChange(field.key, $event)" />
26
- }
27
- @case ('select') {
28
- <select [id]="field.key" class="mcpe-select"
29
- [value]="getStr(field.key)"
30
- (change)="onInputChange(field.key, $event)">
31
- <option value="">Select…</option>
32
- @for (opt of field.options ?? []; track opt.value) {
33
- <option [value]="opt.value">{{ opt.label }}</option>
34
- }
35
- </select>
36
- }
37
- @case ('textarea') {
38
- <textarea [id]="field.key" class="mcpe-textarea" rows="4"
39
- [value]="getStr(field.key)"
40
- (input)="onInputChange(field.key, $event)"></textarea>
41
- }
42
- @case ('number') {
43
- <input type="number" [id]="field.key" class="mcpe-input"
44
- [value]="getStr(field.key)"
45
- (input)="onNumberChange(field.key, $event)" />
46
- }
47
- @default {
48
- <input [type]="inputType(field)" [id]="field.key" class="mcpe-input"
49
- [value]="getStr(field.key)"
50
- (input)="onInputChange(field.key, $event)" />
51
- }
52
- }
53
- @if (field.help) {
54
- <p class="mcpe-mcp-tool-form-help">{{ field.help }}</p>
55
- }
56
- </div>
57
- }
58
- <div class="mcpe-mcp-tool-form-submit">
59
- <button type="submit" class="mcpe-btn mcpe-btn-primary mcpe-btn-sm" [disabled]="loading()">
60
- {{ loading() ? 'Running…' : submitLabel() }}
61
- </button>
62
- </div>
63
- </form>
64
- `,
65
- })
66
- export class McpeMcpToolFormComponent implements OnInit {
67
- schema = input.required<JsonSchema>()
68
- loading = input(false)
69
- submitLabel = input('Run')
70
- class = input('')
71
- onSubmit = output<Record<string, unknown>>()
72
-
73
- fields = computed(() => schemaToFields(this.schema()))
74
- values = signal<Record<string, unknown>>({})
75
-
76
- ngOnInit() {
77
- const defaults: Record<string, unknown> = {}
78
- for (const f of this.fields()) {
79
- if (f.defaultValue !== undefined) defaults[f.key] = f.defaultValue
80
- }
81
- this.values.set(defaults)
82
- }
83
-
84
- classes = computed(() => cn('mcpe-mcp-tool-form', this.class()))
85
-
86
- setValue(key: string, value: unknown) {
87
- this.values.update((v: Record<string, unknown>) => ({ ...v, [key]: value }))
88
- }
89
-
90
- onInputChange(key: string, event: Event) {
91
- this.setValue(key, (event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement).value)
92
- }
93
-
94
- onNumberChange(key: string, event: Event) {
95
- this.setValue(key, (event.target as HTMLInputElement).valueAsNumber)
96
- }
97
-
98
- onCheckChange(key: string, event: Event) {
99
- this.setValue(key, (event.target as HTMLInputElement).checked)
100
- }
101
-
102
- getStr(key: string): string {
103
- const v = this.values()[key]
104
- return v == null ? '' : String(v)
105
- }
106
-
107
- getBool(key: string): boolean {
108
- return Boolean(this.values()[key])
109
- }
110
-
111
- labelClass(field: FieldDescriptor): string {
112
- return cn('mcpe-mcp-tool-form-label', field.required ? 'mcpe-mcp-tool-form-label-required' : '')
113
- }
114
-
115
- inputType(field: FieldDescriptor): string {
116
- switch (field.kind) {
117
- case 'email': return 'email'
118
- case 'url': return 'url'
119
- case 'date': return 'date'
120
- default: return 'text'
121
- }
122
- }
123
-
124
- handleSubmit() {
125
- this.onSubmit.emit(this.values())
126
- }
127
- }
@@ -1,35 +0,0 @@
1
- import { Component, input, signal, computed } from '@angular/core'
2
-
3
- @Component({
4
- selector: 'mcpe-password-input',
5
- standalone: true,
6
- template: `
7
- <div class="mcpe-password-input-wrapper">
8
- <input
9
- [type]="showPassword() ? 'text' : 'password'"
10
- class="mcpe-password-input"
11
- [placeholder]="placeholder()"
12
- [disabled]="disabled()"
13
- />
14
- <button type="button" class="mcpe-password-toggle" (click)="showPassword.update(v => !v)"
15
- [attr.aria-label]="showPassword() ? 'Hide password' : 'Show password'" tabindex="-1">
16
- @if (showPassword()) {
17
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
18
- <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" />
19
- <line x1="1" y1="1" x2="23" y2="23" />
20
- </svg>
21
- } @else {
22
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
23
- <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
24
- <circle cx="12" cy="12" r="3" />
25
- </svg>
26
- }
27
- </button>
28
- </div>
29
- `,
30
- })
31
- export class SnxPasswordInputComponent {
32
- placeholder = input('')
33
- disabled = input(false)
34
- showPassword = signal(false)
35
- }
@@ -1,40 +0,0 @@
1
- import { Component, input, signal, ElementRef, viewChild } from '@angular/core'
2
-
3
- @Component({
4
- selector: 'mcpe-popover',
5
- standalone: true,
6
- template: `
7
- <div class="relative inline-block" #container>
8
- <div (click)="toggle()">
9
- <ng-content select="[trigger]" />
10
- </div>
11
- @if (isOpen()) {
12
- <div
13
- class="mcpe-popover-content absolute top-full mt-2"
14
- role="dialog"
15
- (keydown.escape)="close()"
16
- >
17
- <ng-content />
18
- </div>
19
- }
20
- </div>
21
- `,
22
- host: {
23
- '(document:click)': 'onDocumentClick($event)',
24
- },
25
- })
26
- export class SnxPopoverComponent {
27
- isOpen = signal(false)
28
- container = viewChild<ElementRef>('container')
29
-
30
- toggle() { this.isOpen.update(v => !v) }
31
- close() { this.isOpen.set(false) }
32
- show() { this.isOpen.set(true) }
33
-
34
- onDocumentClick(event: MouseEvent) {
35
- const el = this.container()?.nativeElement
36
- if (el && !el.contains(event.target as Node)) {
37
- this.isOpen.set(false)
38
- }
39
- }
40
- }
@@ -1,20 +0,0 @@
1
- import { Component, input, computed } from '@angular/core'
2
-
3
- @Component({
4
- selector: 'mcpe-progress',
5
- standalone: true,
6
- template: `
7
- <div class="mcpe-progress" role="progressbar"
8
- [attr.aria-valuenow]="value()" [attr.aria-valuemin]="0" [attr.aria-valuemax]="max()">
9
- <div class="mcpe-progress-indicator" [style.transform]="transform()"></div>
10
- </div>
11
- `,
12
- })
13
- export class SnxProgressComponent {
14
- value = input(0)
15
- max = input(100)
16
- transform = computed(() => {
17
- const pct = Math.min(Math.max((this.value() / this.max()) * 100, 0), 100)
18
- return `translateX(-${100 - pct}%)`
19
- })
20
- }
@@ -1,70 +0,0 @@
1
- import { Component, input, output, computed } from '@angular/core'
2
-
3
- @Component({
4
- selector: 'mcpe-prompt-input',
5
- standalone: true,
6
- template: `<div [class]="classes()"><ng-content /></div>`,
7
- })
8
- export class SnxPromptInputComponent {
9
- class = input('')
10
- classes = computed(() => ['mcpe-prompt-input', this.class()].filter(Boolean).join(' '))
11
- }
12
-
13
- @Component({
14
- selector: 'mcpe-prompt-input-textarea',
15
- standalone: true,
16
- template: `<textarea class="mcpe-prompt-input-textarea" [placeholder]="placeholder()" [rows]="rows()"></textarea>`,
17
- })
18
- export class SnxPromptInputTextareaComponent {
19
- placeholder = input('')
20
- rows = input(3)
21
- }
22
-
23
- @Component({
24
- selector: 'mcpe-prompt-input-footer',
25
- standalone: true,
26
- template: `<div class="mcpe-prompt-input-footer"><ng-content /></div>`,
27
- })
28
- export class SnxPromptInputFooterComponent {}
29
-
30
- @Component({
31
- selector: 'mcpe-prompt-input-actions',
32
- standalone: true,
33
- template: `<div class="mcpe-prompt-input-actions"><ng-content /></div>`,
34
- })
35
- export class SnxPromptInputActionsComponent {}
36
-
37
- @Component({
38
- selector: 'mcpe-prompt-input-char-count',
39
- standalone: true,
40
- template: `<span class="mcpe-prompt-input-char-count">{{ count() }}@if (max()) { / {{ max() }} }</span>`,
41
- })
42
- export class SnxPromptInputCharCountComponent {
43
- count = input(0)
44
- max = input<number | undefined>(undefined)
45
- }
46
-
47
- @Component({
48
- selector: 'mcpe-prompt-input-attachments',
49
- standalone: true,
50
- template: `<div class="mcpe-prompt-input-attachments"><ng-content /></div>`,
51
- })
52
- export class SnxPromptInputAttachmentsComponent {}
53
-
54
- @Component({
55
- selector: 'mcpe-prompt-input-attachment',
56
- standalone: true,
57
- template: `
58
- <span class="mcpe-prompt-input-attachment">
59
- <ng-content />
60
- <button type="button" class="mcpe-prompt-input-attachment-remove" (click)="remove.emit()" aria-label="Remove">
61
- <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
62
- <path d="M18 6 6 18" /><path d="m6 6 12 12" />
63
- </svg>
64
- </button>
65
- </span>
66
- `,
67
- })
68
- export class SnxPromptInputAttachmentComponent {
69
- remove = output()
70
- }