@api-client/ui 0.5.10 → 0.5.12

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 (34) hide show
  1. package/build/src/elements/code-editor/internals/CodeEditor.d.ts +197 -0
  2. package/build/src/elements/code-editor/internals/CodeEditor.d.ts.map +1 -0
  3. package/build/src/elements/code-editor/internals/CodeEditor.js +612 -0
  4. package/build/src/elements/code-editor/internals/CodeEditor.js.map +1 -0
  5. package/build/src/elements/code-editor/internals/CodeEditor.styles.d.ts +3 -0
  6. package/build/src/elements/code-editor/internals/CodeEditor.styles.d.ts.map +1 -0
  7. package/build/src/elements/code-editor/internals/CodeEditor.styles.js +150 -0
  8. package/build/src/elements/code-editor/internals/CodeEditor.styles.js.map +1 -0
  9. package/build/src/elements/code-editor/internals/Linter.d.ts +5 -0
  10. package/build/src/elements/code-editor/internals/Linter.d.ts.map +1 -0
  11. package/build/src/elements/code-editor/internals/Linter.js +74 -0
  12. package/build/src/elements/code-editor/internals/Linter.js.map +1 -0
  13. package/build/src/elements/code-editor/ui-code-editor.d.ts +13 -0
  14. package/build/src/elements/code-editor/ui-code-editor.d.ts.map +1 -0
  15. package/build/src/elements/code-editor/ui-code-editor.js +28 -0
  16. package/build/src/elements/code-editor/ui-code-editor.js.map +1 -0
  17. package/build/src/index.d.ts +2 -0
  18. package/build/src/index.d.ts.map +1 -1
  19. package/build/src/index.js +2 -0
  20. package/build/src/index.js.map +1 -1
  21. package/build/src/md/chip/internals/Chip.styles.d.ts.map +1 -1
  22. package/build/src/md/chip/internals/Chip.styles.js +1 -0
  23. package/build/src/md/chip/internals/Chip.styles.js.map +1 -1
  24. package/demo/elements/code-editor/CodeEditorDemo.ts +212 -0
  25. package/demo/elements/code-editor/index.html +19 -0
  26. package/demo/elements/index.html +3 -0
  27. package/package.json +11 -3
  28. package/src/elements/code-editor/README.md +204 -0
  29. package/src/elements/code-editor/internals/CodeEditor.styles.ts +150 -0
  30. package/src/elements/code-editor/internals/CodeEditor.ts +595 -0
  31. package/src/elements/code-editor/internals/Linter.ts +87 -0
  32. package/src/elements/code-editor/ui-code-editor.ts +24 -0
  33. package/src/index.ts +10 -0
  34. package/src/md/chip/internals/Chip.styles.ts +1 -0
@@ -0,0 +1,595 @@
1
+ import { html, LitElement, PropertyValues, TemplateResult, nothing } from 'lit'
2
+ import { property, query, state } from 'lit/decorators.js'
3
+ import { classMap } from 'lit/directives/class-map.js'
4
+ import { EditorState, Extension } from '@codemirror/state'
5
+ import { autocompletion, CompletionContext, CompletionResult, CompletionSource } from '@codemirror/autocomplete'
6
+ import { javascript } from '@codemirror/lang-javascript'
7
+ import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'
8
+ import { oneDark } from '@codemirror/theme-one-dark'
9
+ import { keymap } from '@codemirror/view'
10
+ import { defaultKeymap } from '@codemirror/commands'
11
+ import {
12
+ EditorView,
13
+ MatchDecorator,
14
+ Decoration,
15
+ DecorationSet,
16
+ ViewPlugin,
17
+ ViewUpdate,
18
+ WidgetType,
19
+ } from '@codemirror/view'
20
+
21
+ export interface FunctionSchema {
22
+ /** Unique identifier for the function */
23
+ id: string
24
+ /** Function name */
25
+ name: string
26
+ /** Function description */
27
+ description?: string
28
+ /** Function parameters */
29
+ parameters?: FunctionParameter[]
30
+ /** Return type description */
31
+ returns?: string
32
+ /** Additional metadata */
33
+ metadata?: Record<string, unknown>
34
+ }
35
+
36
+ export interface FunctionParameter {
37
+ /** Parameter name */
38
+ name: string
39
+ /** Parameter type */
40
+ type: string
41
+ /** Parameter description */
42
+ description?: string
43
+ /** Whether parameter is required */
44
+ required?: boolean
45
+ /** Default value */
46
+ defaultValue?: unknown
47
+ }
48
+
49
+ export interface Suggestion {
50
+ /** Unique identifier for the suggestion */
51
+ id: string
52
+ /** Main label displayed */
53
+ label: string
54
+ /** Supporting description text */
55
+ description?: string
56
+ /** Suffix text (e.g., type, category) */
57
+ suffix?: string
58
+ /** Additional data associated with the suggestion */
59
+ data?: Record<string, unknown>
60
+ }
61
+
62
+ export interface FunctionInsertEvent {
63
+ /** The inserted function schema */
64
+ functionSchema: FunctionSchema
65
+ /** The position where the function was inserted */
66
+ position: number
67
+ }
68
+
69
+ export interface SuggestionInsertEvent {
70
+ /** The inserted suggestion */
71
+ suggestion: Suggestion
72
+ /** The position where the suggestion was inserted */
73
+ position: number
74
+ }
75
+
76
+ class PlaceholderWidget extends WidgetType {
77
+ constructor(public placeholderText: string) {
78
+ super()
79
+ }
80
+
81
+ override eq(other: WidgetType): boolean {
82
+ return this.placeholderText == (other as PlaceholderWidget).placeholderText
83
+ }
84
+
85
+ toDOM(): HTMLElement {
86
+ const placeholder = document.createElement('span')
87
+ placeholder.className = 'cm-pill'
88
+ placeholder.textContent = this.placeholderText
89
+ return placeholder
90
+ }
91
+
92
+ override ignoreEvent(): boolean {
93
+ return false
94
+ }
95
+ }
96
+
97
+ // We do variables Salesforce style, with double curly braces.
98
+ // This decorator replaces {{variable}} with a placeholder widget.
99
+ const placeholderMatcher = new MatchDecorator({
100
+ regexp: /\{\{(\w+)\}\}/g,
101
+ decoration: (match) =>
102
+ Decoration.replace({
103
+ widget: new PlaceholderWidget(match[1]),
104
+ }),
105
+ })
106
+
107
+ /**
108
+ * @see https://codemirror.net/examples/decoration/
109
+ */
110
+ class AtomicDecorationRange {
111
+ placeholders: DecorationSet
112
+ constructor(view: EditorView) {
113
+ this.placeholders = placeholderMatcher.createDeco(view)
114
+ }
115
+ update(update: ViewUpdate) {
116
+ this.placeholders = placeholderMatcher.updateDeco(update, this.placeholders)
117
+ }
118
+ }
119
+
120
+ const placeholders = ViewPlugin.fromClass(AtomicDecorationRange, {
121
+ decorations: (instance) => instance.placeholders,
122
+ provide: (plugin) =>
123
+ EditorView.atomicRanges.of((view) => {
124
+ return view.plugin(plugin)?.placeholders || Decoration.none
125
+ }),
126
+ })
127
+
128
+ /**
129
+ * A CodeMirror 6 based editor component that supports function autocomplete and suggestion placeholders.
130
+ *
131
+ * Features:
132
+ * - Dynamic function schema loading
133
+ * - Autocomplete for functions and suggestions
134
+ * - Suggestion placeholders with double curly braces ({{suggestion}})
135
+ * - Keyboard navigation
136
+ * - Accessibility support
137
+ *
138
+ * @fires function-insert - When a function is inserted
139
+ * @fires suggestion-insert - When a suggestion is inserted
140
+ * @fires input - When the editor content changes
141
+ * @fires change - When the editor loses focus and content has changed
142
+ */
143
+ export default class CodeEditor extends LitElement {
144
+ /**
145
+ * Shadow root configuration for the component.
146
+ * Uses 'open' mode for accessibility and delegates focus to enable proper focus management.
147
+ */
148
+ static override shadowRootOptions: ShadowRootInit = {
149
+ mode: 'open',
150
+ delegatesFocus: true,
151
+ }
152
+
153
+ /**
154
+ * The label text displayed as placeholder/floating label
155
+ */
156
+ @property({ type: String })
157
+ accessor label = ''
158
+
159
+ /**
160
+ * Supporting text displayed below the editor
161
+ */
162
+ @property({ type: String, attribute: 'supporting-text' })
163
+ accessor supportingText = ''
164
+
165
+ /**
166
+ * Whether the component is disabled
167
+ */
168
+ @property({ type: Boolean, reflect: true })
169
+ accessor disabled = false
170
+
171
+ /**
172
+ * Whether the component is in an invalid state
173
+ */
174
+ @property({ type: Boolean, reflect: true })
175
+ accessor invalid = false
176
+
177
+ /**
178
+ * The name attribute for form integration
179
+ */
180
+ @property({ type: String })
181
+ accessor name = ''
182
+
183
+ /**
184
+ * Whether the input is required
185
+ */
186
+ @property({ type: Boolean })
187
+ accessor required = false
188
+
189
+ /**
190
+ * Placeholder text shown when editor is empty
191
+ */
192
+ @property({ type: String })
193
+ accessor placeholder = ''
194
+
195
+ /**
196
+ * The editor content value
197
+ */
198
+ @property({ type: String })
199
+ accessor value = ''
200
+
201
+ /**
202
+ * Available function schemas for autocomplete
203
+ */
204
+ @property({ type: Array, attribute: false })
205
+ accessor functionSchemas: FunctionSchema[] = []
206
+
207
+ /**
208
+ * Available suggestions for autocomplete
209
+ */
210
+ @property({ type: Array, attribute: false })
211
+ accessor suggestions: Suggestion[] = []
212
+
213
+ /**
214
+ * Whether to use dark theme
215
+ */
216
+ @property({ type: Boolean, attribute: 'dark-theme' })
217
+ accessor darkTheme = false
218
+
219
+ /**
220
+ * Programming language for syntax highlighting
221
+ */
222
+ @property({ type: String })
223
+ accessor language = 'javascript'
224
+
225
+ @query('.editor-container')
226
+ private accessor editorContainer!: HTMLDivElement
227
+
228
+ @state()
229
+ private accessor hasContent = false
230
+
231
+ @state()
232
+ private accessor isEditorFocus = false
233
+
234
+ private editorView?: EditorView
235
+ private suggestionMap = new Map<string, Suggestion>()
236
+ private functionMap = new Map<string, FunctionSchema>()
237
+ private _previousValue = ''
238
+
239
+ /**
240
+ * Get all suggestions (placeholders) currently in the editor
241
+ */
242
+ get activeSuggestions(): Suggestion[] {
243
+ const suggestions: Suggestion[] = []
244
+ const placeholderPattern = /\{\{(\w+)\}\}/g
245
+ let match: RegExpExecArray | null
246
+ while ((match = placeholderPattern.exec(this.value)) !== null) {
247
+ const placeholderText = match[1]
248
+ // Find suggestion by label
249
+ const suggestion = this.suggestions.find((s) => s.label.toLowerCase() === placeholderText.toLowerCase())
250
+ if (suggestion) {
251
+ suggestions.push(suggestion)
252
+ }
253
+ }
254
+ return suggestions
255
+ }
256
+
257
+ override connectedCallback(): void {
258
+ super.connectedCallback()
259
+ this.setupSuggestionMaps()
260
+ }
261
+
262
+ override disconnectedCallback(): void {
263
+ super.disconnectedCallback()
264
+ this.editorView?.destroy()
265
+ }
266
+
267
+ override firstUpdated(): void {
268
+ this.initializeCodeMirror()
269
+ this.updateEditorContent()
270
+ }
271
+
272
+ override willUpdate(changedProperties: PropertyValues): void {
273
+ super.willUpdate(changedProperties)
274
+
275
+ if (changedProperties.has('suggestions') || changedProperties.has('functionSchemas')) {
276
+ this.setupSuggestionMaps()
277
+ }
278
+
279
+ if (changedProperties.has('value') && this.editorView) {
280
+ this.updateEditorContent()
281
+ }
282
+
283
+ if (changedProperties.has('disabled')) {
284
+ this.updateEditorState()
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Initialize CodeMirror editor with extensions
290
+ */
291
+ private initializeCodeMirror(): void {
292
+ const extensions: Extension[] = [
293
+ keymap.of(defaultKeymap),
294
+ syntaxHighlighting(defaultHighlightStyle),
295
+ autocompletion({
296
+ override: [this.createCompletionSource()],
297
+ activateOnTyping: true,
298
+ maxRenderedOptions: 10,
299
+ }),
300
+ EditorView.updateListener.of((update: ViewUpdate) => {
301
+ if (update.docChanged) {
302
+ this.handleEditorChange()
303
+ }
304
+ if (update.focusChanged) {
305
+ this.handleFocusChange(update.view.hasFocus)
306
+ }
307
+ }),
308
+ placeholders,
309
+ // linter((view) => functionLinter(view as unknown as EditorView, this.functionSchemas)),
310
+ ]
311
+
312
+ // Add language support
313
+ if (this.language === 'javascript') {
314
+ extensions.push(javascript())
315
+ }
316
+
317
+ // Add theme
318
+ if (this.darkTheme) {
319
+ extensions.push(oneDark)
320
+ }
321
+
322
+ // Add placeholder
323
+ if (this.placeholder) {
324
+ extensions.push(
325
+ EditorView.contentAttributes.of({
326
+ 'aria-placeholder': this.placeholder,
327
+ })
328
+ )
329
+ }
330
+
331
+ const state = EditorState.create({
332
+ doc: this.value,
333
+ extensions,
334
+ })
335
+
336
+ this.editorView = new EditorView({
337
+ state,
338
+ parent: this.editorContainer,
339
+ })
340
+ }
341
+
342
+ /**
343
+ * Create completion source for functions and suggestions
344
+ */
345
+ private createCompletionSource(): CompletionSource {
346
+ return (context: CompletionContext): CompletionResult | null => {
347
+ const { pos } = context
348
+ const line = context.state.doc.lineAt(pos)
349
+ const textBefore = line.text.slice(0, pos - line.from)
350
+
351
+ // Check if we're typing a function name
352
+ const functionMatch = textBefore.match(/(\w+)$/)
353
+ if (functionMatch) {
354
+ const prefix = functionMatch[1]
355
+ const functions = this.functionSchemas
356
+ .filter((fn) => fn.name.toLowerCase().startsWith(prefix.toLowerCase()))
357
+ .map((fn) => ({
358
+ label: fn.name,
359
+ detail: fn.description || '',
360
+ info: this.formatFunctionInfo(fn),
361
+ apply: (view: EditorView, completion: unknown, from: number, to: number) => {
362
+ const functionCall = this.formatFunctionCall(fn)
363
+ view.dispatch({
364
+ changes: { from, to, insert: functionCall },
365
+ selection: { anchor: from + functionCall.length },
366
+ })
367
+ this.dispatchFunctionInsert(fn, from)
368
+ },
369
+ }))
370
+
371
+ if (functions.length > 0) {
372
+ return {
373
+ from: pos - prefix.length,
374
+ options: functions,
375
+ }
376
+ }
377
+ }
378
+
379
+ // Check if we're typing a suggestion trigger (e.g., {{)
380
+ const suggestionMatch = textBefore.match(/\{\{(\w*)$/)
381
+ if (suggestionMatch) {
382
+ const prefix = suggestionMatch[1]
383
+ const suggestions = this.suggestions
384
+ .filter((suggestion) => suggestion.label.toLowerCase().startsWith(prefix.toLowerCase()))
385
+ .map((suggestion) => ({
386
+ label: suggestion.label,
387
+ detail: suggestion.description || '',
388
+ info: suggestion.suffix || '',
389
+ apply: (view: EditorView, completion: unknown, from: number, to: number) => {
390
+ const placeholderText = `{{${suggestion.label}}}`
391
+ view.dispatch({
392
+ changes: { from: from - 2, to, insert: placeholderText }, // -2 to include the {{
393
+ selection: { anchor: from - 2 + placeholderText.length },
394
+ })
395
+ this.dispatchSuggestionInsert(suggestion, from - 2)
396
+ },
397
+ }))
398
+
399
+ if (suggestions.length > 0) {
400
+ return {
401
+ from: pos - prefix.length,
402
+ options: suggestions,
403
+ }
404
+ }
405
+ }
406
+
407
+ return null
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Format function information for autocomplete
413
+ */
414
+ private formatFunctionInfo(fn: FunctionSchema): string {
415
+ let info = fn.description || ''
416
+ if (fn.parameters && fn.parameters.length > 0) {
417
+ info += '\n\nParameters:\n'
418
+ fn.parameters.forEach((param) => {
419
+ info += `• ${param.name}: ${param.type}`
420
+ if (param.description) {
421
+ info += ` - ${param.description}`
422
+ }
423
+ info += '\n'
424
+ })
425
+ }
426
+ if (fn.returns) {
427
+ info += `\nReturns: ${fn.returns}`
428
+ }
429
+ return info
430
+ }
431
+
432
+ /**
433
+ * Format function call with parameters
434
+ */
435
+ private formatFunctionCall(fn: FunctionSchema): string {
436
+ if (!fn.parameters || fn.parameters.length === 0) {
437
+ return `${fn.name}()`
438
+ }
439
+
440
+ const params = fn.parameters
441
+ .map((param) => {
442
+ if (param.required) {
443
+ return param.name
444
+ }
445
+ return `${param.name}?`
446
+ })
447
+ .join(', ')
448
+
449
+ return `${fn.name}(${params})`
450
+ }
451
+
452
+ /**
453
+ * Setup suggestion and function maps for quick lookup
454
+ */
455
+ private setupSuggestionMaps(): void {
456
+ this.suggestionMap.clear()
457
+ this.functionMap.clear()
458
+
459
+ this.suggestions.forEach((suggestion) => {
460
+ this.suggestionMap.set(suggestion.id, suggestion)
461
+ })
462
+
463
+ this.functionSchemas.forEach((fn) => {
464
+ this.functionMap.set(fn.id, fn)
465
+ })
466
+ }
467
+
468
+ /**
469
+ * Update editor content when value changes
470
+ */
471
+ private updateEditorContent(): void {
472
+ if (!this.editorView) return
473
+
474
+ const currentValue = this.editorView.state.doc.toString()
475
+ if (currentValue !== this.value) {
476
+ this.editorView.dispatch({
477
+ changes: {
478
+ from: 0,
479
+ to: this.editorView.state.doc.length,
480
+ insert: this.value,
481
+ },
482
+ })
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Update editor state (e.g., disabled state)
488
+ */
489
+ private updateEditorState(): void {
490
+ if (!this.editorView) return
491
+
492
+ // For now, we'll handle disabled state differently
493
+ // CodeMirror 6 doesn't use reconfigure for editable
494
+ if (this.disabled) {
495
+ this.editorView.contentDOM.setAttribute('contenteditable', 'false')
496
+ } else {
497
+ this.editorView.contentDOM.setAttribute('contenteditable', 'true')
498
+ }
499
+ }
500
+
501
+ /**
502
+ * Handle editor content change
503
+ */
504
+ private handleEditorChange(): void {
505
+ if (!this.editorView) return
506
+
507
+ const newValue = this.editorView.state.doc.toString()
508
+ if (newValue !== this._previousValue) {
509
+ this._previousValue = newValue
510
+ this.value = newValue
511
+ this.hasContent = newValue.length > 0
512
+
513
+ this.dispatchEvent(new Event('input', { bubbles: true }))
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Handle focus change
519
+ */
520
+ private handleFocusChange(hasFocus: boolean): void {
521
+ this.isEditorFocus = hasFocus
522
+
523
+ if (!hasFocus && this.value !== this._previousValue) {
524
+ this.dispatchEvent(new Event('change', { bubbles: true }))
525
+ }
526
+ }
527
+
528
+ /**
529
+ * Dispatch function insert event
530
+ */
531
+ private dispatchFunctionInsert(functionSchema: FunctionSchema, position: number): void {
532
+ const event = new CustomEvent<FunctionInsertEvent>('function-insert', {
533
+ detail: { functionSchema, position },
534
+ bubbles: true,
535
+ })
536
+ this.dispatchEvent(event)
537
+ }
538
+
539
+ /**
540
+ * Dispatch suggestion insert event
541
+ */
542
+ private dispatchSuggestionInsert(suggestion: Suggestion, position: number): void {
543
+ const event = new CustomEvent<SuggestionInsertEvent>('suggestion-insert', {
544
+ detail: { suggestion, position },
545
+ bubbles: true,
546
+ })
547
+ this.dispatchEvent(event)
548
+ }
549
+
550
+ /**
551
+ * Focus the editor
552
+ */
553
+ override focus(): void {
554
+ this.editorView?.focus()
555
+ }
556
+
557
+ /**
558
+ * Get the editor's current selection
559
+ */
560
+ getSelection(): { from: number; to: number } | null {
561
+ if (!this.editorView) return null
562
+ const { from, to } = this.editorView.state.selection.main
563
+ return { from, to }
564
+ }
565
+
566
+ /**
567
+ * Insert text at the current cursor position
568
+ */
569
+ insertText(text: string): void {
570
+ if (!this.editorView) return
571
+
572
+ const { from, to } = this.editorView.state.selection.main
573
+ this.editorView.dispatch({
574
+ changes: { from, to, insert: text },
575
+ selection: { anchor: from + text.length },
576
+ })
577
+ }
578
+
579
+ override render(): TemplateResult {
580
+ const hasLabel = !!this.label
581
+ const hasSupportingText = !!this.supportingText
582
+
583
+ return html`
584
+ <div class="surface ${classMap({ 'has-focus': this.isEditorFocus, 'invalid': this.invalid })}">
585
+ <div class="content">
586
+ ${hasLabel ? html`<div class="label">${this.label}</div>` : nothing}
587
+
588
+ <div class="editor-container" part="editor"></div>
589
+ </div>
590
+
591
+ ${hasSupportingText ? html`<div class="supporting-text">${this.supportingText}</div>` : nothing}
592
+ </div>
593
+ `
594
+ }
595
+ }
@@ -0,0 +1,87 @@
1
+ import type { Diagnostic } from '@codemirror/lint'
2
+ import type { EditorView } from '@codemirror/view'
3
+ import Element from './CodeEditor.js'
4
+
5
+ export function functionLinter(view: EditorView, element: Element): readonly Diagnostic[] {
6
+ const diagnostics: Diagnostic[] = []
7
+ const doc = view.state.doc
8
+ const text = doc.toString()
9
+
10
+ const functions = element.functionSchemas
11
+ if (!functions || functions.length === 0) {
12
+ return diagnostics
13
+ }
14
+
15
+ // Regular expression to match function calls like functionName(args)
16
+ const functionCallRegex = /(\w+)\s*\(/g
17
+ let match: RegExpExecArray | null
18
+
19
+ while ((match = functionCallRegex.exec(text)) !== null) {
20
+ const functionName = match[1]
21
+ const startPos = match.index
22
+ const endPos = startPos + functionName.length
23
+
24
+ // Check if this function exists in our schemas
25
+ const functionExists = functions.some((schema) => schema.name === functionName)
26
+
27
+ if (!functionExists) {
28
+ diagnostics.push({
29
+ from: startPos,
30
+ to: endPos,
31
+ severity: 'error',
32
+ message: `Unknown function "${functionName}". Only functions defined in the schema are allowed.`,
33
+ actions: [
34
+ {
35
+ name: 'View available functions',
36
+ apply: () => {
37
+ // This could trigger showing a help dialog or documentation
38
+ element.dispatchEvent(
39
+ new CustomEvent('show-available-functions', {
40
+ detail: { availableFunctions: functions.map((s) => s.name) },
41
+ bubbles: true,
42
+ })
43
+ )
44
+ },
45
+ },
46
+ ],
47
+ })
48
+ } else {
49
+ // Optional: Validate function usage (parameters, etc.)
50
+ const fn = functions.find((schema) => schema.name === functionName)
51
+
52
+ if (fn) {
53
+ // Extract the function call content to validate parameters
54
+ const functionCallMatch = text.substring(startPos).match(/(\w+)\s*\(([^)]*)\)/)
55
+ if (functionCallMatch && fn.parameters) {
56
+ const argsString = functionCallMatch[2].trim()
57
+ const args = argsString ? argsString.split(',').map((arg) => arg.trim()) : []
58
+
59
+ // Check required parameters
60
+ const requiredParams = fn.parameters.filter((p) => p.required)
61
+ if (args.length < requiredParams.length) {
62
+ const functionEndPos = startPos + functionCallMatch[0].length
63
+ diagnostics.push({
64
+ from: startPos,
65
+ to: functionEndPos,
66
+ severity: 'error',
67
+ message: `Function "${functionName}" requires ${requiredParams.length} parameters but got ${args.length}. Required: ${requiredParams.map((p) => p.name).join(', ')}`,
68
+ })
69
+ }
70
+
71
+ // Check if too many parameters
72
+ if (fn.parameters && args.length > fn.parameters.length) {
73
+ const functionEndPos = startPos + functionCallMatch[0].length
74
+ diagnostics.push({
75
+ from: startPos,
76
+ to: functionEndPos,
77
+ severity: 'warning',
78
+ message: `Function "${functionName}" expects at most ${fn.parameters.length} parameters but got ${args.length}`,
79
+ })
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ return diagnostics
87
+ }
@@ -0,0 +1,24 @@
1
+ import type { CSSResultOrNative } from 'lit'
2
+ import { customElement } from 'lit/decorators.js'
3
+ import Element from './internals/CodeEditor.js'
4
+ import styles from './internals/CodeEditor.styles.js'
5
+
6
+ @customElement('code-editor')
7
+ export class CodeEditorElement extends Element {
8
+ static override styles: CSSResultOrNative[] = [styles]
9
+ }
10
+
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ 'code-editor': CodeEditorElement
14
+ }
15
+ }
16
+
17
+ export default CodeEditorElement
18
+ export type {
19
+ FunctionSchema,
20
+ FunctionParameter,
21
+ Suggestion,
22
+ FunctionInsertEvent,
23
+ SuggestionInsertEvent,
24
+ } from './internals/CodeEditor.js'