@progress/kendo-angular-webmcp 24.0.0-develop.41

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.
@@ -0,0 +1,4873 @@
1
+ /**-----------------------------------------------------------------------------------------
2
+ * Copyright © 2026 Progress Software Corporation. All rights reserved.
3
+ * Licensed under commercial license. See LICENSE.md in the project root for more information
4
+ *-------------------------------------------------------------------------------------------*/
5
+ import * as i0 from '@angular/core';
6
+ import { Input, Host, Optional, Inject, Directive, NgModule } from '@angular/core';
7
+ import { isDocumentAvailable, KENDO_WEBMCP_HOST } from '@progress/kendo-angular-common';
8
+ import { validatePackage } from '@progress/kendo-licensing';
9
+ import { saveAs } from '@progress/kendo-file-saver';
10
+ import { Workbook } from '@progress/kendo-ooxml';
11
+
12
+ /**
13
+ * @hidden
14
+ */
15
+ const packageMetadata = {
16
+ name: '@progress/kendo-angular-webmcp',
17
+ productName: 'Kendo UI for Angular',
18
+ productCode: 'KENDOUIANGULAR',
19
+ productCodes: ['KENDOUIANGULAR'],
20
+ publishDate: 1779255940,
21
+ version: '24.0.0-develop.41',
22
+ licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/?utm_medium=product&utm_source=kendoangular&utm_campaign=kendo-ui-angular-purchase-license-keys-warning',
23
+ };
24
+
25
+ /**
26
+ * Resolves a `boolean | WebMcpConfig` input into a full config with defaults.
27
+ *
28
+ * @hidden
29
+ */
30
+ function resolveConfig(value) {
31
+ if (value === false) {
32
+ return null;
33
+ }
34
+ if (value === true) {
35
+ return {};
36
+ }
37
+ return value;
38
+ }
39
+
40
+ /**
41
+ * @hidden
42
+ *
43
+ * Emits dataStateChange so kendoGridBinding re-processes data.
44
+ */
45
+ function emitDataStateChange(grid) {
46
+ grid.dataStateChange.emit({
47
+ skip: grid.skip || 0,
48
+ take: grid.pageSize,
49
+ sort: grid.sort,
50
+ filter: grid.filter,
51
+ group: grid.group
52
+ });
53
+ }
54
+ /**
55
+ * @hidden
56
+ *
57
+ * Returns column fields that satisfy a predicate.
58
+ */
59
+ function getColumnFields(grid, predicate) {
60
+ const fields = [];
61
+ const columns = grid.columns?.toArray() || [];
62
+ for (const col of columns) {
63
+ if ('field' in col && col.field) {
64
+ if (!predicate || predicate(col)) {
65
+ fields.push(col.field);
66
+ }
67
+ }
68
+ }
69
+ return fields;
70
+ }
71
+ /**
72
+ * @hidden
73
+ *
74
+ * Evaluates a highlight condition against an item value.
75
+ */
76
+ function matchesHighlight(itemValue, operator, value) {
77
+ const numItem = Number(itemValue);
78
+ const numVal = Number(value);
79
+ const useNum = !isNaN(numItem) && !isNaN(numVal);
80
+ switch (operator) {
81
+ case 'eq': return useNum ? numItem === numVal : String(itemValue).toLowerCase() === String(value).toLowerCase();
82
+ case 'neq': return useNum ? numItem !== numVal : String(itemValue).toLowerCase() !== String(value).toLowerCase();
83
+ case 'gt': return useNum && numItem > numVal;
84
+ case 'gte': return useNum && numItem >= numVal;
85
+ case 'lt': return useNum && numItem < numVal;
86
+ case 'lte': return useNum && numItem <= numVal;
87
+ case 'contains': return String(itemValue).toLowerCase().includes(String(value).toLowerCase());
88
+ case 'startswith': return String(itemValue).toLowerCase().startsWith(String(value).toLowerCase());
89
+ case 'endswith': return String(itemValue).toLowerCase().endsWith(String(value).toLowerCase());
90
+ default: return false;
91
+ }
92
+ }
93
+ /**
94
+ * @hidden
95
+ *
96
+ * Finds a column by field name.
97
+ */
98
+ function findColumn(grid, field) {
99
+ return (grid.columns?.toArray() || []).find(c => c.field === field);
100
+ }
101
+ /**
102
+ * @hidden
103
+ *
104
+ * Web MCP tool adapter for the Kendo Angular Grid component.
105
+ * Supports: sort, filter, group, page, select, export, column operations.
106
+ */
107
+ class GridToolAdapter {
108
+ selector = 'kendo-grid';
109
+ registerTools(grid, config, modelContext, ngZone) {
110
+ const handles = [];
111
+ const prefix = config.dataName || 'kendo-grid';
112
+ const label = config.dataName
113
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' grid'
114
+ : 'Kendo UI Grid';
115
+ const rawOptions = this.buildToolOptions(grid, config, label);
116
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
117
+ const enabled = new Set(finalOptions.filter(t => t.enabled).map(t => t.name));
118
+ handles.push(...this.registerSortTools(grid, modelContext, ngZone, prefix, label, enabled));
119
+ handles.push(...this.registerFilterTools(grid, modelContext, ngZone, prefix, label, enabled));
120
+ handles.push(...this.registerGroupTools(grid, modelContext, ngZone, prefix, label, enabled));
121
+ handles.push(...this.registerPageTools(grid, modelContext, ngZone, prefix, label, enabled));
122
+ handles.push(...this.registerSelectionTools(grid, modelContext, ngZone, prefix, label, enabled));
123
+ handles.push(...this.registerExportTools(grid, modelContext, ngZone, prefix, label, enabled));
124
+ handles.push(...this.registerColumnTools(grid, modelContext, ngZone, prefix, label, enabled));
125
+ handles.push(...this.registerSelectAllTool(grid, modelContext, ngZone, prefix, label, enabled));
126
+ handles.push(...this.registerGroupExpandTools(grid, modelContext, ngZone, prefix, label, enabled));
127
+ handles.push(...this.registerDetailTools(grid, modelContext, ngZone, prefix, label, enabled));
128
+ handles.push(...this.registerGetDataTool(grid, modelContext, ngZone, prefix, label, enabled));
129
+ handles.push(...this.registerHighlightTools(grid, modelContext, ngZone, prefix, label, enabled));
130
+ return handles;
131
+ }
132
+ // Tool options builder
133
+ buildToolOptions(grid, config, label) {
134
+ const sort = !!grid.sortable;
135
+ const filter = !!grid.filterable;
136
+ const group = !!grid.groupable;
137
+ const page = !!grid.pageable;
138
+ const select = !!grid.selectable;
139
+ return [
140
+ { name: 'sort-column', description: `Sort the ${label} by a specified column field.`, enabled: sort },
141
+ { name: 'clear-sort', description: `Remove all sorting from the ${label}.`, enabled: sort },
142
+ { name: 'filter', description: `Apply a filter to a column in the ${label}.`, enabled: filter },
143
+ { name: 'clear-filters', description: `Remove all filters from the ${label}.`, enabled: filter },
144
+ { name: 'group', description: `Group the ${label} rows by a column field.`, enabled: group },
145
+ { name: 'clear-groups', description: `Remove all grouping from the ${label}.`, enabled: group },
146
+ { name: 'go-to-page', description: `Navigate to a specific page in the ${label}.`, enabled: page },
147
+ { name: 'set-page-size', description: `Change the number of rows per page in the ${label}.`, enabled: page },
148
+ { name: 'select-rows', description: `Select rows in the ${label} by their 0-based indices.`, enabled: select },
149
+ { name: 'clear-selection', description: `Deselect all rows in the ${label}.`, enabled: select },
150
+ { name: 'select-all', description: `Select all rows in the ${label}.`, enabled: select },
151
+ { name: 'export-pdf', description: `Export the ${label} to a PDF file.`, enabled: true },
152
+ { name: 'export-excel', description: `Export the ${label} data to an Excel file.`, enabled: true },
153
+ { name: 'export-csv', description: `Export the ${label} data to a CSV file.`, enabled: true },
154
+ { name: 'show-column', description: `Show a hidden column in the ${label}.`, enabled: true },
155
+ { name: 'hide-column', description: `Hide a visible column in the ${label}.`, enabled: true },
156
+ { name: 'lock-column', description: `Lock (freeze) a column in the ${label}.`, enabled: true },
157
+ { name: 'unlock-column', description: `Unlock a frozen column in the ${label}.`, enabled: true },
158
+ { name: 'reorder-column', description: `Move a column to a new position in the ${label}.`, enabled: !!grid.reorderable },
159
+ { name: 'resize-column', description: `Resize a column in the ${label} to a specified pixel width.`, enabled: !!grid.resizable },
160
+ { name: 'group-expand', description: `Expand a specific group in the ${label}.`, enabled: group },
161
+ { name: 'group-collapse', description: `Collapse a specific group in the ${label}.`, enabled: group },
162
+ { name: 'group-expand-all', description: `Expand all groups in the ${label}.`, enabled: group },
163
+ { name: 'group-collapse-all', description: `Collapse all groups in the ${label}.`, enabled: group },
164
+ { name: 'detail-expand', description: `Expand the detail row at a specific index in the ${label}.`, enabled: true },
165
+ { name: 'detail-collapse', description: `Collapse the detail row at a specific index in the ${label}.`, enabled: true },
166
+ { name: 'detail-expand-all', description: `Expand all detail rows in the ${label}.`, enabled: true },
167
+ { name: 'detail-collapse-all', description: `Collapse all detail rows in the ${label}.`, enabled: true },
168
+ { name: 'get-data', description: `Read the rows currently visible in the ${label} (after any active filter/sort/page) as JSON.`, enabled: true },
169
+ { name: 'highlight', description: `Visually highlight rows in the ${label} that match a condition. Uses the grid rowClass callback to apply the k-selected CSS class.`, enabled: true },
170
+ { name: 'clear-highlight', description: `Remove all row highlights from the ${label}.`, enabled: true }
171
+ ];
172
+ }
173
+ // Sort
174
+ registerSortTools(grid, mc, zone, prefix, label, enabled) {
175
+ const handles = [];
176
+ if (enabled.has('sort-column')) {
177
+ handles.push(mc.registerTool({
178
+ name: `${prefix}-sort-column`,
179
+ description: `Sort the ${label} by a specified column field. Sets a new sort descriptor, replacing any existing sort.`,
180
+ inputSchema: {
181
+ type: 'object',
182
+ properties: {
183
+ field: {
184
+ type: 'string',
185
+ description: 'The column field name to sort by. Omit to discover available sortable fields.'
186
+ },
187
+ dir: {
188
+ type: 'string',
189
+ enum: ['asc', 'desc'],
190
+ description: 'Sort direction. Defaults to "asc".'
191
+ }
192
+ },
193
+ required: []
194
+ },
195
+ execute: (args) => {
196
+ const sortableFields = getColumnFields(grid, col => col.sortable !== false);
197
+ const field = args['field'];
198
+ const direction = args['dir'];
199
+ if (!field) {
200
+ return { success: true, message: 'Sortable fields: ' + sortableFields.join(', '), data: { sortableFields, currentSort: grid.sort || [] } };
201
+ }
202
+ if (!sortableFields.includes(field)) {
203
+ return { success: false, message: `Field "${field}" is not sortable. Available: ${sortableFields.join(', ')}` };
204
+ }
205
+ const dir = direction === 'desc' ? 'desc' : 'asc';
206
+ const descriptors = [{ field, dir }];
207
+ zone.run(() => {
208
+ grid.sort = descriptors;
209
+ grid.sortChange.emit(descriptors);
210
+ emitDataStateChange(grid);
211
+ });
212
+ return { success: true, message: `Sorted by "${field}" ${dir}ending.`, data: { sort: descriptors } };
213
+ }
214
+ }));
215
+ }
216
+ if (enabled.has('clear-sort')) {
217
+ handles.push(mc.registerTool({
218
+ name: `${prefix}-clear-sort`,
219
+ description: `Remove all sorting from the ${label}.`,
220
+ inputSchema: { type: 'object', properties: {}, required: [] },
221
+ execute: () => {
222
+ zone.run(() => {
223
+ grid.sort = [];
224
+ grid.sortChange.emit([]);
225
+ emitDataStateChange(grid);
226
+ });
227
+ return { success: true, message: 'Sort cleared.' };
228
+ }
229
+ }));
230
+ }
231
+ return handles;
232
+ }
233
+ // Filter
234
+ registerFilterTools(grid, mc, zone, prefix, label, enabled) {
235
+ const handles = [];
236
+ if (enabled.has('filter')) {
237
+ handles.push(mc.registerTool({
238
+ name: `${prefix}-filter`,
239
+ description: `Apply a filter to a column in the ${label}. Supports operators: eq, neq, contains, startswith, endswith, gt, gte, lt, lte, isnull, isnotnull.`,
240
+ inputSchema: {
241
+ type: 'object',
242
+ properties: {
243
+ field: {
244
+ type: 'string',
245
+ description: 'The column field name to filter. Omit to discover available filterable fields.'
246
+ },
247
+ operator: {
248
+ type: 'string',
249
+ enum: ['eq', 'neq', 'contains', 'startswith', 'endswith', 'gt', 'gte', 'lt', 'lte', 'isnull', 'isnotnull'],
250
+ description: 'Filter operator. Defaults to "contains". Use "eq", "gt", "lt", etc. for numeric or date columns.'
251
+ },
252
+ value: {
253
+ description: 'The filter value.'
254
+ }
255
+ },
256
+ required: []
257
+ },
258
+ execute: (args) => {
259
+ const filterableFields = getColumnFields(grid);
260
+ const field = args['field'];
261
+ const operator = args['operator'] || 'contains';
262
+ const value = args['value'];
263
+ if (!field) {
264
+ return { success: true, message: 'Filterable fields: ' + filterableFields.join(', '), data: { filterableFields, currentFilter: grid.filter } };
265
+ }
266
+ if (!filterableFields.includes(field)) {
267
+ return { success: false, message: `Field "${field}" not found. Available: ${filterableFields.join(', ')}` };
268
+ }
269
+ const currentFilter = grid.filter || { logic: 'and', filters: [] };
270
+ // Remove existing filters on the same field
271
+ const otherFilters = currentFilter.filters.filter((f) => !('field' in f && f.field === field));
272
+ const newFilter = {
273
+ logic: currentFilter.logic || 'and',
274
+ filters: [...otherFilters, { field, operator, value }]
275
+ };
276
+ zone.run(() => {
277
+ grid.filter = newFilter;
278
+ grid.filterChange.emit(newFilter);
279
+ emitDataStateChange(grid);
280
+ });
281
+ return { success: true, message: `Filtered "${field}" ${operator} "${value}".`, data: { filter: newFilter } };
282
+ }
283
+ }));
284
+ }
285
+ if (enabled.has('clear-filters')) {
286
+ handles.push(mc.registerTool({
287
+ name: `${prefix}-clear-filters`,
288
+ description: `Remove all filters from the ${label}.`,
289
+ inputSchema: { type: 'object', properties: {}, required: [] },
290
+ execute: () => {
291
+ const emptyFilter = { logic: 'and', filters: [] };
292
+ zone.run(() => {
293
+ grid.filter = emptyFilter;
294
+ grid.filterChange.emit(emptyFilter);
295
+ emitDataStateChange(grid);
296
+ });
297
+ return { success: true, message: 'All filters cleared.' };
298
+ }
299
+ }));
300
+ }
301
+ return handles;
302
+ }
303
+ // Group
304
+ registerGroupTools(grid, mc, zone, prefix, label, enabled) {
305
+ const handles = [];
306
+ if (enabled.has('group')) {
307
+ handles.push(mc.registerTool({
308
+ name: `${prefix}-group`,
309
+ description: `Group the ${label} rows by a column field.`,
310
+ inputSchema: {
311
+ type: 'object',
312
+ properties: {
313
+ field: {
314
+ type: 'string',
315
+ description: 'The column field name to group by. Omit to discover available fields.'
316
+ }
317
+ },
318
+ required: []
319
+ },
320
+ execute: (args) => {
321
+ const fields = getColumnFields(grid);
322
+ const field = args['field'];
323
+ if (!field) {
324
+ return { success: true, message: 'Groupable fields: ' + fields.join(', '), data: { fields, currentGroup: grid.group || [] } };
325
+ }
326
+ if (!fields.includes(field)) {
327
+ return { success: false, message: `Field "${field}" not found. Available: ${fields.join(', ')}` };
328
+ }
329
+ const currentGroups = grid.group || [];
330
+ if (currentGroups.some((g) => g.field === field)) {
331
+ return { success: true, message: `Already grouped by "${field}".`, data: { group: currentGroups } };
332
+ }
333
+ const newGroups = [...currentGroups, { field }];
334
+ zone.run(() => {
335
+ grid.group = newGroups;
336
+ grid.groupChange.emit(newGroups);
337
+ emitDataStateChange(grid);
338
+ });
339
+ return { success: true, message: `Grouped by "${field}".`, data: { group: newGroups } };
340
+ }
341
+ }));
342
+ }
343
+ if (enabled.has('clear-groups')) {
344
+ handles.push(mc.registerTool({
345
+ name: `${prefix}-clear-groups`,
346
+ description: `Remove all grouping from the ${label}.`,
347
+ inputSchema: { type: 'object', properties: {}, required: [] },
348
+ execute: () => {
349
+ zone.run(() => {
350
+ grid.group = [];
351
+ grid.groupChange.emit([]);
352
+ emitDataStateChange(grid);
353
+ });
354
+ return { success: true, message: 'Groups cleared.' };
355
+ }
356
+ }));
357
+ }
358
+ return handles;
359
+ }
360
+ // Page
361
+ registerPageTools(grid, mc, zone, prefix, label, enabled) {
362
+ const handles = [];
363
+ if (enabled.has('go-to-page')) {
364
+ handles.push(mc.registerTool({
365
+ name: `${prefix}-go-to-page`,
366
+ description: `Navigate to a specific page in the ${label}. Pages are 1-based.`,
367
+ inputSchema: {
368
+ type: 'object',
369
+ properties: {
370
+ page: {
371
+ type: 'number',
372
+ description: 'The 1-based page number. Omit to get current page info.'
373
+ }
374
+ },
375
+ required: []
376
+ },
377
+ execute: (args) => {
378
+ const page = args['page'];
379
+ const pageSize = grid.pageSize || 10;
380
+ const totalItems = Array.isArray(grid.data) ? grid.data.length : (grid.data?.total || 0);
381
+ const totalPages = Math.ceil(totalItems / pageSize) || 1;
382
+ const currentPage = Math.floor((grid.skip || 0) / pageSize) + 1;
383
+ if (!page) {
384
+ return { success: true, message: `Page ${currentPage} of ${totalPages}.`, data: { currentPage, totalPages, pageSize, totalItems } };
385
+ }
386
+ if (page < 1 || page > totalPages) {
387
+ return { success: false, message: `Page ${page} out of range (1–${totalPages}).` };
388
+ }
389
+ const newSkip = (page - 1) * pageSize;
390
+ zone.run(() => {
391
+ grid.skip = newSkip;
392
+ grid.pageChange.emit({ skip: newSkip, take: pageSize });
393
+ emitDataStateChange(grid);
394
+ });
395
+ return { success: true, message: `Navigated to page ${page}.`, data: { page, skip: newSkip, pageSize } };
396
+ }
397
+ }));
398
+ }
399
+ if (enabled.has('set-page-size')) {
400
+ handles.push(mc.registerTool({
401
+ name: `${prefix}-set-page-size`,
402
+ description: `Change the number of rows per page in the ${label}.`,
403
+ inputSchema: {
404
+ type: 'object',
405
+ properties: {
406
+ pageSize: {
407
+ type: 'number',
408
+ description: 'Number of rows per page.'
409
+ }
410
+ },
411
+ required: ['pageSize']
412
+ },
413
+ execute: (args) => {
414
+ const newPageSize = args['pageSize'];
415
+ if (!newPageSize || newPageSize < 1) {
416
+ return { success: false, message: 'pageSize must be a positive number.' };
417
+ }
418
+ zone.run(() => {
419
+ grid.pageSize = newPageSize;
420
+ grid.skip = 0;
421
+ grid.pageChange.emit({ skip: 0, take: newPageSize });
422
+ emitDataStateChange(grid);
423
+ });
424
+ return { success: true, message: `Page size set to ${newPageSize}.`, data: { pageSize: newPageSize } };
425
+ }
426
+ }));
427
+ }
428
+ return handles;
429
+ }
430
+ // Selection
431
+ registerSelectionTools(grid, mc, zone, prefix, label, enabled) {
432
+ const handles = [];
433
+ if (enabled.has('select-rows')) {
434
+ handles.push(mc.registerTool({
435
+ name: `${prefix}-select-rows`,
436
+ description: `Select rows in the ${label} by their data item indices (0-based).`,
437
+ inputSchema: {
438
+ type: 'object',
439
+ properties: {
440
+ indices: {
441
+ type: 'array',
442
+ items: { type: 'number' },
443
+ description: 'Array of 0-based row indices to select.'
444
+ }
445
+ },
446
+ required: ['indices']
447
+ },
448
+ execute: (args) => {
449
+ const indices = args['indices'];
450
+ const data = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
451
+ zone.run(() => {
452
+ grid.selectionChange.emit({
453
+ selectedRows: indices.map(i => ({ dataItem: data[i] ?? null, index: i })),
454
+ deselectedRows: [],
455
+ ctrlKey: false,
456
+ shiftKey: false
457
+ });
458
+ });
459
+ return { success: true, message: `Selected ${indices.length} row(s).` };
460
+ }
461
+ }));
462
+ }
463
+ if (enabled.has('clear-selection')) {
464
+ handles.push(mc.registerTool({
465
+ name: `${prefix}-clear-selection`,
466
+ description: `Deselect all rows in the ${label}.`,
467
+ inputSchema: { type: 'object', properties: {}, required: [] },
468
+ execute: () => {
469
+ const data = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
470
+ zone.run(() => {
471
+ grid.selectionChange.emit({
472
+ selectedRows: [],
473
+ deselectedRows: data.map((item, i) => ({ dataItem: item, index: i })),
474
+ ctrlKey: false,
475
+ shiftKey: false
476
+ });
477
+ });
478
+ return { success: true, message: 'Selection cleared.' };
479
+ }
480
+ }));
481
+ }
482
+ return handles;
483
+ }
484
+ // Export
485
+ registerExportTools(grid, mc, zone, prefix, label, enabled) {
486
+ const handles = [];
487
+ if (enabled.has('export-pdf')) {
488
+ handles.push(mc.registerTool({
489
+ name: `${prefix}-export-pdf`,
490
+ description: `Export the ${label} to a PDF file.`,
491
+ inputSchema: { type: 'object', properties: {}, required: [] },
492
+ execute: () => {
493
+ if (typeof grid.saveAsPDF !== 'function') {
494
+ return { success: false, message: 'PDF export is not configured. Add <kendo-grid-pdf> to the grid.' };
495
+ }
496
+ zone.run(() => grid.saveAsPDF());
497
+ return { success: true, message: 'PDF export started.' };
498
+ }
499
+ }));
500
+ }
501
+ if (enabled.has('export-excel')) {
502
+ handles.push(mc.registerTool({
503
+ name: `${prefix}-export-excel`,
504
+ description: `Export the ${label} data to an Excel file.`,
505
+ inputSchema: { type: 'object', properties: {}, required: [] },
506
+ execute: () => {
507
+ if (typeof grid.saveAsExcel !== 'function') {
508
+ return { success: false, message: 'Excel export is not configured. Add <kendo-grid-excel> to the grid.' };
509
+ }
510
+ zone.run(() => grid.saveAsExcel());
511
+ return { success: true, message: 'Excel export started.' };
512
+ }
513
+ }));
514
+ }
515
+ if (enabled.has('export-csv')) {
516
+ handles.push(mc.registerTool({
517
+ name: `${prefix}-export-csv`,
518
+ description: `Export the ${label} data to a CSV file.`,
519
+ inputSchema: { type: 'object', properties: {}, required: [] },
520
+ execute: () => {
521
+ if (typeof grid.saveAsCSV !== 'function') {
522
+ return { success: false, message: 'CSV export is not available.' };
523
+ }
524
+ zone.run(() => grid.saveAsCSV());
525
+ return { success: true, message: 'CSV export started.' };
526
+ }
527
+ }));
528
+ }
529
+ return handles;
530
+ }
531
+ // Column Operations
532
+ registerColumnTools(grid, mc, zone, prefix, label, enabled) {
533
+ const handles = [];
534
+ if (enabled.has('show-column')) {
535
+ handles.push(mc.registerTool({
536
+ name: `${prefix}-show-column`,
537
+ description: `Show a hidden column in the ${label}.`,
538
+ inputSchema: {
539
+ type: 'object',
540
+ properties: {
541
+ field: { type: 'string', description: 'The field name of the column to show. Omit to list hidden columns.' }
542
+ },
543
+ required: []
544
+ },
545
+ execute: (args) => {
546
+ const field = args['field'];
547
+ const allFields = getColumnFields(grid);
548
+ const hiddenFields = allFields.filter(f => findColumn(grid, f)?.hidden);
549
+ if (!field) {
550
+ return { success: true, message: 'Hidden columns: ' + (hiddenFields.length ? hiddenFields.join(', ') : 'none'), data: { hiddenFields } };
551
+ }
552
+ const col = findColumn(grid, field);
553
+ if (!col) {
554
+ return { success: false, message: `Column "${field}" not found. Available: ${allFields.join(', ')}` };
555
+ }
556
+ if (!col.hidden) {
557
+ return { success: true, message: `Column "${field}" is already visible.` };
558
+ }
559
+ zone.run(() => {
560
+ col.hidden = false;
561
+ grid.columnInfoService?.changeVisibility([{ column: col, hidden: false }]);
562
+ });
563
+ return { success: true, message: `Column "${field}" is now visible.` };
564
+ }
565
+ }));
566
+ }
567
+ if (enabled.has('hide-column')) {
568
+ handles.push(mc.registerTool({
569
+ name: `${prefix}-hide-column`,
570
+ description: `Hide a visible column in the ${label}.`,
571
+ inputSchema: {
572
+ type: 'object',
573
+ properties: {
574
+ field: { type: 'string', description: 'The field name of the column to hide. Omit to list visible columns.' }
575
+ },
576
+ required: []
577
+ },
578
+ execute: (args) => {
579
+ const field = args['field'];
580
+ const allFields = getColumnFields(grid);
581
+ const visibleFields = allFields.filter(f => !findColumn(grid, f)?.hidden);
582
+ if (!field) {
583
+ return { success: true, message: 'Visible columns: ' + visibleFields.join(', '), data: { visibleFields } };
584
+ }
585
+ const col = findColumn(grid, field);
586
+ if (!col) {
587
+ return { success: false, message: `Column "${field}" not found. Available: ${allFields.join(', ')}` };
588
+ }
589
+ if (col.hidden) {
590
+ return { success: true, message: `Column "${field}" is already hidden.` };
591
+ }
592
+ zone.run(() => {
593
+ col.hidden = true;
594
+ grid.columnInfoService?.changeVisibility([{ column: col, hidden: true }]);
595
+ });
596
+ return { success: true, message: `Column "${field}" is now hidden.` };
597
+ }
598
+ }));
599
+ }
600
+ if (enabled.has('lock-column')) {
601
+ handles.push(mc.registerTool({
602
+ name: `${prefix}-lock-column`,
603
+ description: `Lock (freeze) a column in the ${label} so it stays visible during horizontal scrolling.`,
604
+ inputSchema: {
605
+ type: 'object',
606
+ properties: {
607
+ field: { type: 'string', description: 'The field name of the column to lock.' }
608
+ },
609
+ required: ['field']
610
+ },
611
+ execute: (args) => {
612
+ const field = args['field'];
613
+ const col = findColumn(grid, field);
614
+ if (!col) {
615
+ return { success: false, message: `Column "${field}" not found.` };
616
+ }
617
+ if (col.locked) {
618
+ return { success: true, message: `Column "${field}" is already locked.` };
619
+ }
620
+ zone.run(() => {
621
+ col.locked = true;
622
+ grid.columnInfoService?.changeLocked([{ column: col, locked: true }]);
623
+ });
624
+ return { success: true, message: `Column "${field}" locked.` };
625
+ }
626
+ }));
627
+ }
628
+ if (enabled.has('unlock-column')) {
629
+ handles.push(mc.registerTool({
630
+ name: `${prefix}-unlock-column`,
631
+ description: `Unlock a frozen column in the ${label}.`,
632
+ inputSchema: {
633
+ type: 'object',
634
+ properties: {
635
+ field: { type: 'string', description: 'The field name of the column to unlock.' }
636
+ },
637
+ required: ['field']
638
+ },
639
+ execute: (args) => {
640
+ const field = args['field'];
641
+ const col = findColumn(grid, field);
642
+ if (!col) {
643
+ return { success: false, message: `Column "${field}" not found.` };
644
+ }
645
+ if (!col.locked) {
646
+ return { success: true, message: `Column "${field}" is already unlocked.` };
647
+ }
648
+ zone.run(() => {
649
+ col.locked = false;
650
+ grid.columnInfoService?.changeLocked([{ column: col, locked: false }]);
651
+ });
652
+ return { success: true, message: `Column "${field}" unlocked.` };
653
+ }
654
+ }));
655
+ }
656
+ if (enabled.has('reorder-column')) {
657
+ handles.push(mc.registerTool({
658
+ name: `${prefix}-reorder-column`,
659
+ description: `Move a column to a new position in the ${label}. Positions are 0-based.`,
660
+ inputSchema: {
661
+ type: 'object',
662
+ properties: {
663
+ field: { type: 'string', description: 'The field name of the column to move.' },
664
+ position: { type: 'number', description: 'The new 0-based position index.' }
665
+ },
666
+ required: ['field', 'position']
667
+ },
668
+ execute: (args) => {
669
+ const field = args['field'];
670
+ const position = args['position'];
671
+ const col = findColumn(grid, field);
672
+ const allCols = grid.columns?.toArray() || [];
673
+ if (!col) {
674
+ return { success: false, message: `Column "${field}" not found.` };
675
+ }
676
+ if (position < 0 || position >= allCols.length) {
677
+ return { success: false, message: `Position ${position} out of range (0–${allCols.length - 1}).` };
678
+ }
679
+ const sorted = [...allCols].sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0));
680
+ const currentVisualPos = sorted.indexOf(col);
681
+ if (currentVisualPos === position) {
682
+ return { success: true, message: `Column "${field}" is already at position ${position}.` };
683
+ }
684
+ zone.run(() => {
685
+ // before:true = "insert before target" (used when moving left)
686
+ // before:false = "insert after target" (used when moving right)
687
+ grid.reorderColumn(col, position, { before: position < currentVisualPos });
688
+ });
689
+ return { success: true, message: `Column "${field}" moved to position ${position}.` };
690
+ }
691
+ }));
692
+ }
693
+ if (enabled.has('resize-column')) {
694
+ handles.push(mc.registerTool({
695
+ name: `${prefix}-resize-column`,
696
+ description: `Resize a column in the ${label} to a specified width in pixels.`,
697
+ inputSchema: {
698
+ type: 'object',
699
+ properties: {
700
+ field: { type: 'string', description: 'The field name of the column to resize.' },
701
+ width: { type: 'number', description: 'The new width in pixels.' }
702
+ },
703
+ required: ['field', 'width']
704
+ },
705
+ execute: (args) => {
706
+ const field = args['field'];
707
+ const width = args['width'];
708
+ const col = findColumn(grid, field);
709
+ if (!col) {
710
+ return { success: false, message: `Column "${field}" not found.` };
711
+ }
712
+ if (col.resizable === false) {
713
+ return { success: false, message: `Column "${field}" is not resizable.` };
714
+ }
715
+ if (width < 1) {
716
+ return { success: false, message: 'Width must be a positive number.' };
717
+ }
718
+ const oldWidth = col.width;
719
+ zone.run(() => {
720
+ col.width = width;
721
+ grid.columnResize.emit([{ column: col, newWidth: width, oldWidth }]);
722
+ });
723
+ return { success: true, message: `Column "${field}" resized to ${width}px.` };
724
+ }
725
+ }));
726
+ }
727
+ return handles;
728
+ }
729
+ // Select All
730
+ registerSelectAllTool(grid, mc, zone, prefix, label, enabled) {
731
+ if (!enabled.has('select-all')) {
732
+ return [];
733
+ }
734
+ return [mc.registerTool({
735
+ name: `${prefix}-select-all`,
736
+ description: `Select all rows in the ${label}.`,
737
+ inputSchema: { type: 'object', properties: {}, required: [] },
738
+ execute: () => {
739
+ const data = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
740
+ const indices = data.map((_, i) => i);
741
+ zone.run(() => {
742
+ grid.selectionChange.emit({
743
+ selectedRows: indices.map((i) => ({ dataItem: data[i], index: i })),
744
+ deselectedRows: [],
745
+ ctrlKey: false,
746
+ shiftKey: false
747
+ });
748
+ });
749
+ return { success: true, message: `Selected all ${indices.length} row(s).` };
750
+ }
751
+ })];
752
+ }
753
+ // Group Expand/Collapse
754
+ registerGroupExpandTools(grid, mc, zone, prefix, label, enabled) {
755
+ const handles = [];
756
+ if (enabled.has('group-expand')) {
757
+ handles.push(mc.registerTool({
758
+ name: `${prefix}-group-expand`,
759
+ description: `Expand a specific group in the ${label} by group index.`,
760
+ inputSchema: {
761
+ type: 'object',
762
+ properties: {
763
+ index: { type: 'string', description: 'The group index path (e.g. "0" or "0_1" for nested groups).' }
764
+ },
765
+ required: ['index']
766
+ },
767
+ execute: (args) => {
768
+ const index = args['index'];
769
+ zone.run(() => {
770
+ grid.expandGroup(index);
771
+ grid.groupExpand.emit({ group: { index } });
772
+ });
773
+ return { success: true, message: `Group "${index}" expanded.` };
774
+ }
775
+ }));
776
+ }
777
+ if (enabled.has('group-collapse')) {
778
+ handles.push(mc.registerTool({
779
+ name: `${prefix}-group-collapse`,
780
+ description: `Collapse a specific group in the ${label} by group index.`,
781
+ inputSchema: {
782
+ type: 'object',
783
+ properties: {
784
+ index: { type: 'string', description: 'The group index path (e.g. "0" or "0_1" for nested groups).' }
785
+ },
786
+ required: ['index']
787
+ },
788
+ execute: (args) => {
789
+ const index = args['index'];
790
+ zone.run(() => {
791
+ grid.collapseGroup(index);
792
+ grid.groupCollapse.emit({ group: { index } });
793
+ });
794
+ return { success: true, message: `Group "${index}" collapsed.` };
795
+ }
796
+ }));
797
+ }
798
+ if (enabled.has('group-expand-all')) {
799
+ handles.push(mc.registerTool({
800
+ name: `${prefix}-group-expand-all`,
801
+ description: `Expand all groups in the ${label}.`,
802
+ inputSchema: { type: 'object', properties: {}, required: [] },
803
+ execute: () => {
804
+ zone.run(() => {
805
+ grid.resetGroupsState();
806
+ });
807
+ return { success: true, message: 'All groups expanded.' };
808
+ }
809
+ }));
810
+ }
811
+ if (enabled.has('group-collapse-all')) {
812
+ handles.push(mc.registerTool({
813
+ name: `${prefix}-group-collapse-all`,
814
+ description: `Collapse all groups in the ${label}.`,
815
+ inputSchema: { type: 'object', properties: {}, required: [] },
816
+ execute: () => {
817
+ const currentGroups = grid.group || [];
818
+ if (currentGroups.length === 0) {
819
+ return { success: true, message: 'No groups are configured.' };
820
+ }
821
+ // grid.data is GroupResult[] when grouping is active — each top-level item is a group
822
+ const topLevelGroups = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
823
+ zone.run(() => {
824
+ for (let i = 0; i < topLevelGroups.length; i++) {
825
+ grid.collapseGroup(String(i));
826
+ }
827
+ });
828
+ return { success: true, message: 'All groups collapsed.' };
829
+ }
830
+ }));
831
+ }
832
+ return handles;
833
+ }
834
+ // Detail Expand/Collapse
835
+ registerDetailTools(grid, mc, zone, prefix, label, enabled) {
836
+ const handles = [];
837
+ if (enabled.has('detail-expand')) {
838
+ handles.push(mc.registerTool({
839
+ name: `${prefix}-detail-expand`,
840
+ description: `Expand the detail row at a specific index in the ${label}.`,
841
+ inputSchema: {
842
+ type: 'object',
843
+ properties: {
844
+ index: { type: 'number', description: '0-based row index to expand.' }
845
+ },
846
+ required: ['index']
847
+ },
848
+ execute: (args) => {
849
+ const index = args['index'];
850
+ zone.run(() => {
851
+ grid.expandRow(index);
852
+ grid.detailExpand.emit({ index });
853
+ });
854
+ return { success: true, message: `Detail row ${index} expanded.` };
855
+ }
856
+ }));
857
+ }
858
+ if (enabled.has('detail-collapse')) {
859
+ handles.push(mc.registerTool({
860
+ name: `${prefix}-detail-collapse`,
861
+ description: `Collapse the detail row at a specific index in the ${label}.`,
862
+ inputSchema: {
863
+ type: 'object',
864
+ properties: {
865
+ index: { type: 'number', description: '0-based row index to collapse.' }
866
+ },
867
+ required: ['index']
868
+ },
869
+ execute: (args) => {
870
+ const index = args['index'];
871
+ zone.run(() => {
872
+ grid.collapseRow(index);
873
+ grid.detailCollapse.emit({ index });
874
+ });
875
+ return { success: true, message: `Detail row ${index} collapsed.` };
876
+ }
877
+ }));
878
+ }
879
+ if (enabled.has('detail-expand-all')) {
880
+ handles.push(mc.registerTool({
881
+ name: `${prefix}-detail-expand-all`,
882
+ description: `Expand all detail rows in the ${label}.`,
883
+ inputSchema: { type: 'object', properties: {}, required: [] },
884
+ execute: () => {
885
+ const data = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
886
+ zone.run(() => {
887
+ for (let i = 0; i < data.length; i++) {
888
+ grid.expandRow(i);
889
+ }
890
+ });
891
+ return { success: true, message: `Expanded all ${data.length} detail row(s).` };
892
+ }
893
+ }));
894
+ }
895
+ if (enabled.has('detail-collapse-all')) {
896
+ handles.push(mc.registerTool({
897
+ name: `${prefix}-detail-collapse-all`,
898
+ description: `Collapse all detail rows in the ${label}.`,
899
+ inputSchema: { type: 'object', properties: {}, required: [] },
900
+ execute: () => {
901
+ const data = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
902
+ zone.run(() => {
903
+ for (let i = 0; i < data.length; i++) {
904
+ grid.collapseRow(i);
905
+ }
906
+ });
907
+ return { success: true, message: `Collapsed all ${data.length} detail row(s).` };
908
+ }
909
+ }));
910
+ }
911
+ return handles;
912
+ }
913
+ // Get Data
914
+ registerGetDataTool(grid, mc, zone, prefix, label, enabled) {
915
+ if (!enabled.has('get-data')) {
916
+ return [];
917
+ }
918
+ return [mc.registerTool({
919
+ name: `${prefix}-get-data`,
920
+ description: `Read the rows currently visible in the ${label} (after any active filter/sort/page) as JSON. Returns an array of objects with one entry per visible row. Each object contains the column fields and their values.`,
921
+ inputSchema: { type: 'object', properties: {}, required: [] },
922
+ execute: () => {
923
+ const data = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
924
+ const fields = getColumnFields(grid);
925
+ const rows = [];
926
+ const extractRows = (items) => {
927
+ for (const item of items) {
928
+ // Grouped data wraps items in { field, value, items } objects
929
+ if (item.items) {
930
+ extractRows(item.items);
931
+ }
932
+ else {
933
+ const row = {};
934
+ for (const f of fields) {
935
+ row[f] = item[f];
936
+ }
937
+ rows.push(row);
938
+ }
939
+ }
940
+ };
941
+ extractRows(data);
942
+ return { success: true, message: `${rows.length} row(s) returned.`, data: rows };
943
+ }
944
+ })];
945
+ }
946
+ // Highlight
947
+ registerHighlightTools(grid, mc, zone, prefix, label, enabled) {
948
+ const highlightDirective = grid.ctx?.highlightDirective;
949
+ // Highlight tools require the kendoGridHighlight directive to be applied
950
+ if (!highlightDirective) {
951
+ return [];
952
+ }
953
+ const handles = [];
954
+ if (enabled.has('highlight')) {
955
+ const fields = getColumnFields(grid);
956
+ handles.push(mc.registerTool({
957
+ name: `${prefix}-highlight`,
958
+ description: `Visually highlight rows in the ${label} that match a condition using the built-in highlight mechanism.`,
959
+ inputSchema: {
960
+ type: 'object',
961
+ properties: {
962
+ field: {
963
+ type: 'string',
964
+ enum: fields.length ? fields : undefined,
965
+ description: 'The column field to evaluate.'
966
+ },
967
+ operator: {
968
+ type: 'string',
969
+ enum: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'contains', 'startswith', 'endswith'],
970
+ description: 'Comparison operator.'
971
+ },
972
+ value: {
973
+ description: 'The value to compare against. Use a number for numeric fields.'
974
+ }
975
+ },
976
+ required: ['field', 'operator', 'value']
977
+ },
978
+ execute: (args) => {
979
+ const field = args['field'];
980
+ const op = args['operator'];
981
+ const value = args['value'];
982
+ const data = Array.isArray(grid.data) ? grid.data : (grid.data?.data ?? []);
983
+ const highlightItems = [];
984
+ for (let i = 0; i < data.length; i++) {
985
+ const item = data[i];
986
+ if (matchesHighlight(item[field], op, value)) {
987
+ const key = highlightDirective.highlightItemKey
988
+ ? highlightDirective['getItemKey']({ data: item, index: i })
989
+ : i;
990
+ highlightItems.push({ itemKey: key });
991
+ }
992
+ }
993
+ zone.run(() => {
994
+ highlightDirective['setState'](highlightItems);
995
+ });
996
+ return { success: true, message: `${highlightItems.length} row(s) highlighted.` };
997
+ }
998
+ }));
999
+ }
1000
+ if (enabled.has('clear-highlight')) {
1001
+ handles.push(mc.registerTool({
1002
+ name: `${prefix}-clear-highlight`,
1003
+ description: `Remove all row highlights from the ${label}.`,
1004
+ inputSchema: { type: 'object', properties: {}, required: [] },
1005
+ execute: () => {
1006
+ zone.run(() => {
1007
+ highlightDirective['setState']([]);
1008
+ });
1009
+ return { success: true, message: 'Highlights cleared.' };
1010
+ }
1011
+ }));
1012
+ }
1013
+ return handles;
1014
+ }
1015
+ }
1016
+
1017
+ function getTreeListData(treeList) {
1018
+ if (Array.isArray(treeList.data) && treeList.data.length) {
1019
+ return treeList.data;
1020
+ }
1021
+ const result = treeList.data?.data;
1022
+ if (Array.isArray(result) && result.length) {
1023
+ return result;
1024
+ }
1025
+ // When kendoTreeListFlatBinding is used, data lives in the binding directive
1026
+ const bindingData = treeList.localEditService?.bindingDirective?.originalData;
1027
+ if (Array.isArray(bindingData) && bindingData.length) {
1028
+ return bindingData;
1029
+ }
1030
+ // Fallback to rendered view items
1031
+ try {
1032
+ return (treeList.view?.data || []).map((item) => item.data);
1033
+ }
1034
+ catch {
1035
+ return [];
1036
+ }
1037
+ }
1038
+ /**
1039
+ * @hidden
1040
+ *
1041
+ * Web MCP tool adapter for the Kendo Angular TreeList component.
1042
+ * Supports: sort, filter, select, expand/collapse, export.
1043
+ */
1044
+ class TreeListToolAdapter {
1045
+ selector = 'kendo-treelist';
1046
+ registerTools(treeList, config, modelContext, ngZone) {
1047
+ const handles = [];
1048
+ const prefix = config.dataName || 'kendo-treelist';
1049
+ const label = config.dataName
1050
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' tree list'
1051
+ : 'Kendo UI TreeList';
1052
+ const rawOptions = this.buildToolOptions(treeList, config, label);
1053
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
1054
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
1055
+ handles.push(...this.registerSortTools(treeList, modelContext, ngZone, prefix, label, enabled));
1056
+ handles.push(...this.registerFilterTools(treeList, modelContext, ngZone, prefix, label, enabled));
1057
+ handles.push(...this.registerSelectionTools(treeList, modelContext, ngZone, prefix, label, enabled));
1058
+ handles.push(...this.registerExportTools(treeList, modelContext, ngZone, prefix, label, enabled));
1059
+ handles.push(...this.registerExpandTools(treeList, modelContext, ngZone, prefix, label, enabled));
1060
+ return handles;
1061
+ }
1062
+ buildToolOptions(treeList, config, label) {
1063
+ return [
1064
+ { name: 'sort-column', description: `Sort the ${label} by a column field and direction.`, enabled: !!treeList.sortable },
1065
+ { name: 'clear-sort', description: `Remove all sorting from the ${label}.`, enabled: !!treeList.sortable },
1066
+ { name: 'filter', description: `Apply a filter to a column in the ${label}.`, enabled: !!treeList.filterable },
1067
+ { name: 'clear-filters', description: `Remove all filters from the ${label}.`, enabled: !!treeList.filterable },
1068
+ { name: 'select-rows', description: `Select one or more rows in the ${label} by index.`, enabled: !!treeList.selectable },
1069
+ { name: 'clear-selection', description: `Clear all row selections in the ${label}.`, enabled: !!treeList.selectable },
1070
+ { name: 'export-pdf', description: `Export the ${label} to PDF.`, enabled: true },
1071
+ { name: 'export-excel', description: `Export the ${label} to Excel.`, enabled: true },
1072
+ { name: 'expand', description: `Expand a row in the ${label}.`, enabled: true },
1073
+ { name: 'collapse', description: `Collapse a row in the ${label}.`, enabled: true },
1074
+ { name: 'expand-all', description: `Expand all rows in the ${label}.`, enabled: true },
1075
+ { name: 'collapse-all', description: `Collapse all rows in the ${label}.`, enabled: true }
1076
+ ];
1077
+ }
1078
+ // Sort
1079
+ registerSortTools(treeList, mc, zone, prefix, label, enabled) {
1080
+ const handles = [];
1081
+ if (enabled.has('sort-column')) {
1082
+ handles.push(mc.registerTool({
1083
+ name: `${prefix}-sort-column`,
1084
+ description: `Sort the ${label} by a column field and direction.`,
1085
+ inputSchema: {
1086
+ type: 'object',
1087
+ properties: {
1088
+ field: { type: 'string', description: 'Column field name to sort by.' },
1089
+ dir: { type: 'string', enum: ['asc', 'desc'], description: 'Sort direction.' }
1090
+ },
1091
+ required: ['field', 'dir']
1092
+ },
1093
+ execute: (args) => {
1094
+ const field = args['field'];
1095
+ const dir = args['dir'];
1096
+ const descriptor = [{ field, dir }];
1097
+ zone.run(() => {
1098
+ treeList.sort = descriptor;
1099
+ treeList.sortChange.emit(descriptor);
1100
+ });
1101
+ return { success: true, message: `Sorted by "${field}" ${dir}.` };
1102
+ }
1103
+ }));
1104
+ }
1105
+ if (enabled.has('clear-sort')) {
1106
+ handles.push(mc.registerTool({
1107
+ name: `${prefix}-clear-sort`,
1108
+ description: `Remove all sorting from the ${label}.`,
1109
+ inputSchema: { type: 'object', properties: {}, required: [] },
1110
+ execute: () => {
1111
+ zone.run(() => {
1112
+ treeList.sort = [];
1113
+ treeList.sortChange.emit([]);
1114
+ });
1115
+ return { success: true, message: 'Sort cleared.' };
1116
+ }
1117
+ }));
1118
+ }
1119
+ return handles;
1120
+ }
1121
+ // Filter
1122
+ registerFilterTools(treeList, mc, zone, prefix, label, enabled) {
1123
+ const handles = [];
1124
+ if (enabled.has('filter')) {
1125
+ handles.push(mc.registerTool({
1126
+ name: `${prefix}-filter`,
1127
+ description: `Apply a filter to a column in the ${label}.`,
1128
+ inputSchema: {
1129
+ type: 'object',
1130
+ properties: {
1131
+ field: { type: 'string', description: 'Column field name to filter.' },
1132
+ operator: { type: 'string', description: 'Filter operator (eq, contains, startswith, etc.).' },
1133
+ value: { type: 'string', description: 'Filter value.' }
1134
+ },
1135
+ required: ['field', 'operator', 'value']
1136
+ },
1137
+ execute: (args) => {
1138
+ const { field, operator, value } = args;
1139
+ const descriptor = { logic: 'and', filters: [{ field, operator, value }] };
1140
+ zone.run(() => {
1141
+ treeList.filter = descriptor;
1142
+ treeList.filterChange.emit(descriptor);
1143
+ });
1144
+ return { success: true, message: `Filtered "${field}" ${operator} "${value}".` };
1145
+ }
1146
+ }));
1147
+ }
1148
+ if (enabled.has('clear-filters')) {
1149
+ handles.push(mc.registerTool({
1150
+ name: `${prefix}-clear-filters`,
1151
+ description: `Remove all filters from the ${label}.`,
1152
+ inputSchema: { type: 'object', properties: {}, required: [] },
1153
+ execute: () => {
1154
+ const descriptor = { logic: 'and', filters: [] };
1155
+ zone.run(() => {
1156
+ treeList.filter = descriptor;
1157
+ treeList.filterChange.emit(descriptor);
1158
+ });
1159
+ return { success: true, message: 'Filters cleared.' };
1160
+ }
1161
+ }));
1162
+ }
1163
+ return handles;
1164
+ }
1165
+ // Selection
1166
+ registerSelectionTools(treeList, mc, zone, prefix, label, enabled) {
1167
+ const handles = [];
1168
+ if (enabled.has('select-rows')) {
1169
+ handles.push(mc.registerTool({
1170
+ name: `${prefix}-select-rows`,
1171
+ description: `Select one or more rows in the ${label} by index.`,
1172
+ inputSchema: {
1173
+ type: 'object',
1174
+ properties: {
1175
+ indices: { type: 'array', items: { type: 'number' }, description: '0-based row indices to select.' }
1176
+ },
1177
+ required: ['indices']
1178
+ },
1179
+ execute: (args) => {
1180
+ const indices = args['indices'];
1181
+ zone.run(() => {
1182
+ treeList.selectionChange.emit({
1183
+ selectedRows: indices.map(i => ({ dataItem: null, index: i })),
1184
+ deselectedRows: [],
1185
+ ctrlKey: false,
1186
+ shiftKey: false
1187
+ });
1188
+ });
1189
+ return { success: true, message: `Selected ${indices.length} row(s).` };
1190
+ }
1191
+ }));
1192
+ }
1193
+ if (enabled.has('clear-selection')) {
1194
+ handles.push(mc.registerTool({
1195
+ name: `${prefix}-clear-selection`,
1196
+ description: `Clear all row selections in the ${label}.`,
1197
+ inputSchema: { type: 'object', properties: {}, required: [] },
1198
+ execute: () => {
1199
+ const data = getTreeListData(treeList);
1200
+ zone.run(() => {
1201
+ treeList.selectionChange.emit({
1202
+ selectedRows: [],
1203
+ deselectedRows: data.map((item, i) => ({ dataItem: item, index: i })),
1204
+ ctrlKey: false,
1205
+ shiftKey: false
1206
+ });
1207
+ });
1208
+ return { success: true, message: 'Selection cleared.' };
1209
+ }
1210
+ }));
1211
+ }
1212
+ return handles;
1213
+ }
1214
+ // Export
1215
+ registerExportTools(treeList, mc, zone, prefix, label, enabled) {
1216
+ const handles = [];
1217
+ if (enabled.has('export-pdf') && typeof treeList.saveAsPDF === 'function') {
1218
+ handles.push(mc.registerTool({
1219
+ name: `${prefix}-export-pdf`,
1220
+ description: `Export the ${label} to PDF.`,
1221
+ inputSchema: { type: 'object', properties: {}, required: [] },
1222
+ execute: () => {
1223
+ zone.run(() => treeList.saveAsPDF());
1224
+ return { success: true, message: 'PDF export started.' };
1225
+ }
1226
+ }));
1227
+ }
1228
+ if (enabled.has('export-excel') && typeof treeList.saveAsExcel === 'function') {
1229
+ handles.push(mc.registerTool({
1230
+ name: `${prefix}-export-excel`,
1231
+ description: `Export the ${label} to Excel.`,
1232
+ inputSchema: { type: 'object', properties: {}, required: [] },
1233
+ execute: () => {
1234
+ zone.run(() => treeList.saveAsExcel());
1235
+ return { success: true, message: 'Excel export started.' };
1236
+ }
1237
+ }));
1238
+ }
1239
+ return handles;
1240
+ }
1241
+ // Expand / Collapse
1242
+ registerExpandTools(treeList, mc, zone, prefix, label, enabled) {
1243
+ const handles = [];
1244
+ if (enabled.has('expand')) {
1245
+ handles.push(mc.registerTool({
1246
+ name: `${prefix}-expand`,
1247
+ description: `Expand a row in the ${label}.`,
1248
+ inputSchema: {
1249
+ type: 'object',
1250
+ properties: {
1251
+ id: { type: 'number', description: 'Row id to expand.' }
1252
+ },
1253
+ required: ['id']
1254
+ },
1255
+ execute: (args) => {
1256
+ const data = getTreeListData(treeList);
1257
+ const item = data.find((d) => d.id === args['id']);
1258
+ if (!item) {
1259
+ return { success: false, message: `Row with id ${args['id']} not found.` };
1260
+ }
1261
+ zone.run(() => {
1262
+ treeList.expand(item);
1263
+ });
1264
+ return { success: true, message: `Row ${args['id']} expanded.` };
1265
+ }
1266
+ }));
1267
+ }
1268
+ if (enabled.has('collapse')) {
1269
+ handles.push(mc.registerTool({
1270
+ name: `${prefix}-collapse`,
1271
+ description: `Collapse a row in the ${label}.`,
1272
+ inputSchema: {
1273
+ type: 'object',
1274
+ properties: {
1275
+ id: { type: 'number', description: 'Row id to collapse.' }
1276
+ },
1277
+ required: ['id']
1278
+ },
1279
+ execute: (args) => {
1280
+ const data = getTreeListData(treeList);
1281
+ const item = data.find((d) => d.id === args['id']);
1282
+ if (!item) {
1283
+ return { success: false, message: `Row with id ${args['id']} not found.` };
1284
+ }
1285
+ zone.run(() => {
1286
+ treeList.collapse(item);
1287
+ });
1288
+ return { success: true, message: `Row ${args['id']} collapsed.` };
1289
+ }
1290
+ }));
1291
+ }
1292
+ if (enabled.has('expand-all')) {
1293
+ handles.push(mc.registerTool({
1294
+ name: `${prefix}-expand-all`,
1295
+ description: `Expand all rows in the ${label}.`,
1296
+ inputSchema: { type: 'object', properties: {}, required: [] },
1297
+ execute: () => {
1298
+ const data = getTreeListData(treeList);
1299
+ zone.run(() => {
1300
+ for (const item of data) {
1301
+ treeList.expand(item);
1302
+ }
1303
+ });
1304
+ return { success: true, message: `Expanded all ${data.length} row(s).` };
1305
+ }
1306
+ }));
1307
+ }
1308
+ if (enabled.has('collapse-all')) {
1309
+ handles.push(mc.registerTool({
1310
+ name: `${prefix}-collapse-all`,
1311
+ description: `Collapse all rows in the ${label}.`,
1312
+ inputSchema: { type: 'object', properties: {}, required: [] },
1313
+ execute: () => {
1314
+ const data = getTreeListData(treeList);
1315
+ zone.run(() => {
1316
+ for (const item of data) {
1317
+ treeList.collapse(item);
1318
+ }
1319
+ });
1320
+ return { success: true, message: `Collapsed all ${data.length} row(s).` };
1321
+ }
1322
+ }));
1323
+ }
1324
+ return handles;
1325
+ }
1326
+ }
1327
+
1328
+ /**
1329
+ * @hidden
1330
+ *
1331
+ * Web MCP tool adapter for the Kendo Angular Scheduler component.
1332
+ * Supports: create, update, delete events, set view, navigate.
1333
+ */
1334
+ class SchedulerToolAdapter {
1335
+ selector = 'kendo-scheduler';
1336
+ registerTools(scheduler, config, modelContext, ngZone) {
1337
+ const handles = [];
1338
+ const prefix = config.dataName || 'kendo-scheduler';
1339
+ const label = config.dataName
1340
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' scheduler'
1341
+ : 'Kendo UI Scheduler';
1342
+ const rawOptions = this.buildToolOptions(config, label);
1343
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
1344
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
1345
+ handles.push(...this.registerEventTools(scheduler, modelContext, ngZone, prefix, label, enabled));
1346
+ handles.push(...this.registerNavigationTools(scheduler, modelContext, ngZone, prefix, label, enabled));
1347
+ handles.push(...this.registerExportTools(scheduler, modelContext, ngZone, prefix, label, enabled));
1348
+ return handles;
1349
+ }
1350
+ buildToolOptions(config, label) {
1351
+ return [
1352
+ { name: 'create-event', description: `Create a new event in the ${label}.`, enabled: true },
1353
+ { name: 'update-event', description: `Update an existing event in the ${label}.`, enabled: true },
1354
+ { name: 'delete-event', description: `Delete an event from the ${label}.`, enabled: true },
1355
+ { name: 'set-view', description: `Change the view of the ${label}.`, enabled: true },
1356
+ { name: 'navigate', description: `Navigate to a specific date in the ${label}.`, enabled: true },
1357
+ { name: 'export-pdf', description: `Export the ${label} to PDF.`, enabled: true }
1358
+ ];
1359
+ }
1360
+ // Event CRUD
1361
+ registerEventTools(scheduler, mc, zone, prefix, label, enabled) {
1362
+ const handles = [];
1363
+ if (enabled.has('create-event')) {
1364
+ handles.push(mc.registerTool({
1365
+ name: `${prefix}-create-event`,
1366
+ description: `Create a new event in the ${label}.`,
1367
+ inputSchema: {
1368
+ type: 'object',
1369
+ properties: {
1370
+ title: { type: 'string', description: 'Event title.' },
1371
+ start: { type: 'string', description: 'Start date/time (ISO 8601).' },
1372
+ end: { type: 'string', description: 'End date/time (ISO 8601).' },
1373
+ description: { type: 'string', description: 'Event description (optional).' }
1374
+ },
1375
+ required: ['title', 'start', 'end']
1376
+ },
1377
+ execute: (args) => {
1378
+ const event = {
1379
+ title: args['title'],
1380
+ start: new Date(args['start']),
1381
+ end: new Date(args['end']),
1382
+ description: args['description'] || ''
1383
+ };
1384
+ zone.run(() => {
1385
+ scheduler.save?.emit({ dataItem: event, isNew: true });
1386
+ });
1387
+ const descPart = event.description ? ', Description: "' + event.description + '"' : '';
1388
+ return { success: true, message: 'Event created. Title: "' + event.title + '", Start: ' + args['start'] + ', End: ' + args['end'] + descPart + '.' };
1389
+ }
1390
+ }));
1391
+ }
1392
+ if (enabled.has('update-event')) {
1393
+ handles.push(mc.registerTool({
1394
+ name: `${prefix}-update-event`,
1395
+ description: `Update an existing event in the ${label} by title.`,
1396
+ inputSchema: {
1397
+ type: 'object',
1398
+ properties: {
1399
+ title: { type: 'string', description: 'Title of the event to update.' },
1400
+ start: { type: 'string', description: 'New start date/time (ISO 8601).' },
1401
+ end: { type: 'string', description: 'New end date/time (ISO 8601).' },
1402
+ newTitle: { type: 'string', description: 'New title for the event.' }
1403
+ },
1404
+ required: ['title']
1405
+ },
1406
+ execute: (args) => {
1407
+ const updates = { _originalTitle: args['title'] };
1408
+ if (args['start'])
1409
+ updates['start'] = new Date(args['start']);
1410
+ if (args['end'])
1411
+ updates['end'] = new Date(args['end']);
1412
+ if (args['newTitle'])
1413
+ updates['title'] = args['newTitle'];
1414
+ zone.run(() => {
1415
+ scheduler.save?.emit({
1416
+ dataItem: { title: args['title'], ...updates },
1417
+ isNew: false
1418
+ });
1419
+ });
1420
+ return { success: true, message: `Event "${args['title']}" updated.` };
1421
+ }
1422
+ }));
1423
+ }
1424
+ if (enabled.has('delete-event')) {
1425
+ handles.push(mc.registerTool({
1426
+ name: `${prefix}-delete-event`,
1427
+ description: `Delete an event from the ${label} by title.`,
1428
+ inputSchema: {
1429
+ type: 'object',
1430
+ properties: {
1431
+ title: { type: 'string', description: 'Title of the event to delete.' }
1432
+ },
1433
+ required: ['title']
1434
+ },
1435
+ execute: (args) => {
1436
+ zone.run(() => {
1437
+ scheduler.remove?.emit({
1438
+ dataItem: { title: args['title'] }
1439
+ });
1440
+ });
1441
+ return { success: true, message: `Event "${args['title']}" deleted.` };
1442
+ }
1443
+ }));
1444
+ }
1445
+ return handles;
1446
+ }
1447
+ // Navigation
1448
+ registerNavigationTools(scheduler, mc, zone, prefix, label, enabled) {
1449
+ const handles = [];
1450
+ if (enabled.has('set-view')) {
1451
+ handles.push(mc.registerTool({
1452
+ name: `${prefix}-set-view`,
1453
+ description: `Change the view of the ${label} (e.g. day, week, month).`,
1454
+ inputSchema: {
1455
+ type: 'object',
1456
+ properties: {
1457
+ view: { type: 'string', description: 'View name: day, week, month, timeline, agenda.' }
1458
+ },
1459
+ required: ['view']
1460
+ },
1461
+ execute: (args) => {
1462
+ const view = (args['view'] || '').toLowerCase();
1463
+ const viewToIndex = { day: 0, week: 1, month: 2, timeline: 3, agenda: 4 };
1464
+ const index = viewToIndex[view] ?? 1;
1465
+ zone.run(() => {
1466
+ scheduler.selectedViewIndex = index;
1467
+ scheduler.navigate?.emit({ index, view: args['view'] });
1468
+ });
1469
+ return { success: true, message: `View set to "${args['view']}".` };
1470
+ }
1471
+ }));
1472
+ }
1473
+ if (enabled.has('navigate')) {
1474
+ handles.push(mc.registerTool({
1475
+ name: `${prefix}-navigate`,
1476
+ description: `Navigate to a specific date in the ${label}.`,
1477
+ inputSchema: {
1478
+ type: 'object',
1479
+ properties: {
1480
+ date: { type: 'string', description: 'Target date (ISO 8601).' }
1481
+ },
1482
+ required: ['date']
1483
+ },
1484
+ execute: (args) => {
1485
+ const date = new Date(args['date']);
1486
+ zone.run(() => {
1487
+ scheduler.selectedDate = date;
1488
+ scheduler.dateChange?.emit({
1489
+ selectedDate: date,
1490
+ dateRange: { start: date, end: date }
1491
+ });
1492
+ });
1493
+ return { success: true, message: `Navigated to ${date.toISOString().split('T')[0]}.` };
1494
+ }
1495
+ }));
1496
+ }
1497
+ return handles;
1498
+ }
1499
+ // Export
1500
+ registerExportTools(scheduler, mc, zone, prefix, label, enabled) {
1501
+ const handles = [];
1502
+ if (enabled.has('export-pdf') && typeof scheduler.saveAsPDF === 'function') {
1503
+ handles.push(mc.registerTool({
1504
+ name: `${prefix}-export-pdf`,
1505
+ description: `Export the ${label} to PDF.`,
1506
+ inputSchema: { type: 'object', properties: {}, required: [] },
1507
+ execute: () => {
1508
+ zone.run(() => scheduler.saveAsPDF());
1509
+ return { success: true, message: 'PDF export started.' };
1510
+ }
1511
+ }));
1512
+ }
1513
+ return handles;
1514
+ }
1515
+ }
1516
+
1517
+ /**
1518
+ * @hidden
1519
+ *
1520
+ * Web MCP tool adapter for the Kendo Angular Chart component.
1521
+ * Supports: drilldown, export.
1522
+ */
1523
+ class ChartToolAdapter {
1524
+ selector = 'kendo-chart';
1525
+ registerTools(chart, config, modelContext, ngZone) {
1526
+ const handles = [];
1527
+ const prefix = config.dataName || 'kendo-chart';
1528
+ const label = config.dataName
1529
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' chart'
1530
+ : 'Kendo UI Chart';
1531
+ const rawOptions = this.buildToolOptions(config, label);
1532
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
1533
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
1534
+ if (enabled.has('drilldown')) {
1535
+ handles.push(modelContext.registerTool({
1536
+ name: `${prefix}-drilldown`,
1537
+ description: `Drill down into a specific category/series in the ${label}. Triggers the full chart drilldown pipeline.`,
1538
+ inputSchema: {
1539
+ type: 'object',
1540
+ properties: {
1541
+ value: { type: 'string', description: 'The drilldown field value (category) to drill into.' },
1542
+ series: { type: 'string', description: 'Series name to drill into. If omitted, uses the first drilldown-enabled series.' }
1543
+ },
1544
+ required: ['value']
1545
+ },
1546
+ execute: (args) => {
1547
+ let seriesName = args['series'];
1548
+ if (!seriesName) {
1549
+ const drilldownSeries = chart.seriesComponents?.find((sc) => sc.drilldownTemplate);
1550
+ seriesName = drilldownSeries?.name;
1551
+ }
1552
+ if (!seriesName) {
1553
+ return { success: false, message: 'No drilldown-enabled series found.' };
1554
+ }
1555
+ ngZone.run(() => {
1556
+ chart.trigger?.('drilldown', {
1557
+ value: args['value'],
1558
+ series: { name: seriesName },
1559
+ point: {}
1560
+ });
1561
+ });
1562
+ return { success: true, message: `Drilled down into "${args['value']}".` };
1563
+ }
1564
+ }));
1565
+ }
1566
+ if (enabled.has('export')) {
1567
+ handles.push(modelContext.registerTool({
1568
+ name: `${prefix}-export`,
1569
+ description: `Export the ${label} to an image format.`,
1570
+ inputSchema: {
1571
+ type: 'object',
1572
+ properties: {
1573
+ format: { type: 'string', enum: ['png', 'svg'], description: 'Export format.' }
1574
+ },
1575
+ required: ['format']
1576
+ },
1577
+ execute: (args) => {
1578
+ const format = args['format'];
1579
+ const fileName = `${prefix}.${format === 'svg' ? 'svg' : 'png'}`;
1580
+ ngZone.run(() => {
1581
+ const exportPromise = format === 'svg'
1582
+ ? chart.exportSVG()
1583
+ : chart.exportImage();
1584
+ exportPromise.then((dataURI) => {
1585
+ saveAs(dataURI, fileName);
1586
+ });
1587
+ });
1588
+ return { success: true, message: `${format.toUpperCase()} export started.` };
1589
+ }
1590
+ }));
1591
+ }
1592
+ return handles;
1593
+ }
1594
+ buildToolOptions(config, label) {
1595
+ return [
1596
+ {
1597
+ name: 'drilldown',
1598
+ description: `Drill down into a specific category/series in the ${label}. Triggers the full chart drilldown pipeline.`,
1599
+ enabled: true
1600
+ },
1601
+ { name: 'export', description: `Export the ${label} to an image format.`, enabled: true }
1602
+ ];
1603
+ }
1604
+ }
1605
+
1606
+ /**
1607
+ * @hidden
1608
+ *
1609
+ * Web MCP tool adapter for the Kendo Angular Editor component.
1610
+ * Supports: get content, set content, export PDF.
1611
+ */
1612
+ class EditorToolAdapter {
1613
+ selector = 'kendo-editor';
1614
+ registerTools(editor, config, modelContext, ngZone) {
1615
+ const handles = [];
1616
+ const prefix = config.dataName || 'kendo-editor';
1617
+ const label = config.dataName
1618
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' editor'
1619
+ : 'Kendo UI Editor';
1620
+ const rawOptions = this.buildToolOptions(config, label);
1621
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
1622
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
1623
+ if (enabled.has('get-content')) {
1624
+ handles.push(modelContext.registerTool({
1625
+ name: `${prefix}-get-content`,
1626
+ description: `Get the current HTML content of the ${label}.`,
1627
+ inputSchema: { type: 'object', properties: {}, required: [] },
1628
+ execute: () => {
1629
+ const value = editor.value;
1630
+ return { success: true, message: 'Content retrieved.', data: value };
1631
+ }
1632
+ }));
1633
+ }
1634
+ if (enabled.has('set-content')) {
1635
+ handles.push(modelContext.registerTool({
1636
+ name: `${prefix}-set-content`,
1637
+ description: `Set the HTML content of the ${label}.`,
1638
+ inputSchema: {
1639
+ type: 'object',
1640
+ properties: {
1641
+ html: { type: 'string', description: 'HTML content to set.' }
1642
+ },
1643
+ required: ['html']
1644
+ },
1645
+ execute: (args) => {
1646
+ const html = args['html'];
1647
+ ngZone.run(() => {
1648
+ editor.value = html;
1649
+ editor.valueChange.emit(html);
1650
+ });
1651
+ return { success: true, message: 'Content updated.' };
1652
+ }
1653
+ }));
1654
+ }
1655
+ if (enabled.has('insert-html')) {
1656
+ handles.push(modelContext.registerTool({
1657
+ name: `${prefix}-insert-html`,
1658
+ description: `Insert HTML at the end of the ${label} content.`,
1659
+ inputSchema: {
1660
+ type: 'object',
1661
+ properties: {
1662
+ html: { type: 'string', description: 'HTML markup to insert.' }
1663
+ },
1664
+ required: ['html']
1665
+ },
1666
+ execute: (args) => {
1667
+ const html = args['html'];
1668
+ ngZone.run(() => {
1669
+ const current = editor.value || '';
1670
+ const updated = current + html;
1671
+ editor.value = updated;
1672
+ editor.valueChange.emit(updated);
1673
+ });
1674
+ const preview = html.length > 100 ? html.substring(0, 100) + '...' : html;
1675
+ return { success: true, message: `HTML inserted (${html.length} chars). Content: "${preview}".` };
1676
+ }
1677
+ }));
1678
+ }
1679
+ if (enabled.has('clear-content')) {
1680
+ handles.push(modelContext.registerTool({
1681
+ name: `${prefix}-clear-content`,
1682
+ description: `Clear all content from the ${label}.`,
1683
+ inputSchema: { type: 'object', properties: {}, required: [] },
1684
+ execute: () => {
1685
+ ngZone.run(() => {
1686
+ editor.value = '';
1687
+ editor.valueChange.emit('');
1688
+ });
1689
+ return { success: true, message: 'Content cleared.' };
1690
+ }
1691
+ }));
1692
+ }
1693
+ if (enabled.has('export-pdf') && typeof editor.saveAsPDF === 'function') {
1694
+ handles.push(modelContext.registerTool({
1695
+ name: `${prefix}-export-pdf`,
1696
+ description: `Export the ${label} content to PDF.`,
1697
+ inputSchema: { type: 'object', properties: {}, required: [] },
1698
+ execute: () => {
1699
+ ngZone.run(() => editor.saveAsPDF());
1700
+ return { success: true, message: 'PDF export started.' };
1701
+ }
1702
+ }));
1703
+ }
1704
+ return handles;
1705
+ }
1706
+ buildToolOptions(config, label) {
1707
+ return [
1708
+ { name: 'get-content', description: `Get the current HTML content of the ${label}.`, enabled: true },
1709
+ { name: 'set-content', description: `Set the HTML content of the ${label}.`, enabled: true },
1710
+ { name: 'insert-html', description: `Insert HTML at the end of the ${label} content.`, enabled: true },
1711
+ { name: 'clear-content', description: `Clear all content from the ${label}.`, enabled: true },
1712
+ { name: 'export-pdf', description: `Export the ${label} content to PDF.`, enabled: true }
1713
+ ];
1714
+ }
1715
+ }
1716
+
1717
+ /**
1718
+ * @hidden
1719
+ *
1720
+ * Web MCP tool adapter for the Kendo Angular Spreadsheet component.
1721
+ * Supports: set cell, navigate sheet, add sheet, rename sheet, export.
1722
+ */
1723
+ class SpreadsheetToolAdapter {
1724
+ selector = 'kendo-spreadsheet';
1725
+ registerTools(spreadsheet, config, modelContext, ngZone) {
1726
+ const handles = [];
1727
+ const prefix = config.dataName || 'kendo-spreadsheet';
1728
+ const label = config.dataName
1729
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' spreadsheet'
1730
+ : 'Kendo UI Spreadsheet';
1731
+ const rawOptions = this.buildToolOptions(config, label);
1732
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
1733
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
1734
+ if (enabled.has('set-cell')) {
1735
+ handles.push(modelContext.registerTool({
1736
+ name: `${prefix}-set-cell`,
1737
+ description: `Set a cell value in the ${label}.`,
1738
+ inputSchema: {
1739
+ type: 'object',
1740
+ properties: {
1741
+ cell: { type: 'string', description: 'Cell reference (e.g. "A1", "B3").' },
1742
+ value: { type: 'string', description: 'Value to set in the cell.' }
1743
+ },
1744
+ required: ['cell', 'value']
1745
+ },
1746
+ execute: (args) => {
1747
+ const cell = args['cell'];
1748
+ const value = args['value'];
1749
+ ngZone.run(() => {
1750
+ const instance = spreadsheet.spreadsheetWidget;
1751
+ if (instance) {
1752
+ const sheet = instance.activeSheet();
1753
+ sheet.range(cell).value(value);
1754
+ }
1755
+ });
1756
+ return { success: true, message: `Cell ${cell} set to "${value}".` };
1757
+ }
1758
+ }));
1759
+ }
1760
+ if (enabled.has('navigate-sheet')) {
1761
+ handles.push(modelContext.registerTool({
1762
+ name: `${prefix}-navigate-sheet`,
1763
+ description: `Switch to a specific sheet in the ${label} by name or index.`,
1764
+ inputSchema: {
1765
+ type: 'object',
1766
+ properties: {
1767
+ sheet: { type: 'string', description: 'Sheet name or 0-based index.' }
1768
+ },
1769
+ required: ['sheet']
1770
+ },
1771
+ execute: (args) => {
1772
+ const sheet = args['sheet'];
1773
+ let navigated = false;
1774
+ ngZone.run(() => {
1775
+ const instance = spreadsheet.spreadsheetWidget;
1776
+ if (instance) {
1777
+ const idx = Number.parseInt(sheet, 10);
1778
+ let targetName;
1779
+ if (Number.isNaN(idx)) {
1780
+ targetName = sheet;
1781
+ }
1782
+ else {
1783
+ const sheets = instance.sheets();
1784
+ if (sheets[idx]) {
1785
+ targetName = sheets[idx].name();
1786
+ }
1787
+ }
1788
+ if (targetName) {
1789
+ const targetSheet = instance.sheets().find((s) => s.name() === targetName);
1790
+ if (targetSheet) {
1791
+ instance.activeSheet(targetSheet);
1792
+ instance.view?.sheetsbar?.onSheetSelect(targetName);
1793
+ navigated = true;
1794
+ }
1795
+ }
1796
+ }
1797
+ });
1798
+ return navigated
1799
+ ? { success: true, message: `Navigated to sheet "${sheet}".` }
1800
+ : { success: false, message: `Sheet "${sheet}" not found.` };
1801
+ }
1802
+ }));
1803
+ }
1804
+ if (enabled.has('add-sheet')) {
1805
+ handles.push(modelContext.registerTool({
1806
+ name: `${prefix}-add-sheet`,
1807
+ description: `Add a new sheet to the ${label}.`,
1808
+ inputSchema: {
1809
+ type: 'object',
1810
+ properties: {
1811
+ name: { type: 'string', description: 'Optional name for the new sheet.' }
1812
+ },
1813
+ required: []
1814
+ },
1815
+ execute: (args) => {
1816
+ let sheetName = '';
1817
+ let sheetIndex = -1;
1818
+ ngZone.run(() => {
1819
+ const instance = spreadsheet.spreadsheetWidget;
1820
+ if (instance) {
1821
+ const sheetsbar = instance.view?.sheetsbar;
1822
+ if (args['name']) {
1823
+ instance.insertSheet({ data: { name: args['name'] } });
1824
+ }
1825
+ else if (sheetsbar) {
1826
+ sheetsbar.onAddSelect();
1827
+ }
1828
+ else {
1829
+ instance.insertSheet();
1830
+ }
1831
+ const sheets = instance.sheets();
1832
+ sheetIndex = sheets.length - 1;
1833
+ sheetName = sheets[sheetIndex]?.name() || 'Sheet' + (sheetIndex + 1);
1834
+ }
1835
+ });
1836
+ return { success: true, message: `Sheet added. Name: "${sheetName}", Index: ${sheetIndex}.` };
1837
+ }
1838
+ }));
1839
+ }
1840
+ if (enabled.has('rename-sheet')) {
1841
+ handles.push(modelContext.registerTool({
1842
+ name: `${prefix}-rename-sheet`,
1843
+ description: `Rename the active sheet in the ${label}.`,
1844
+ inputSchema: {
1845
+ type: 'object',
1846
+ properties: {
1847
+ name: { type: 'string', description: 'New name for the active sheet.' }
1848
+ },
1849
+ required: ['name']
1850
+ },
1851
+ execute: (args) => {
1852
+ const name = args['name'];
1853
+ let oldName = '';
1854
+ ngZone.run(() => {
1855
+ const instance = spreadsheet.spreadsheetWidget;
1856
+ if (instance) {
1857
+ const activeSheet = instance.activeSheet();
1858
+ oldName = activeSheet.name();
1859
+ const allSheets = instance.sheets();
1860
+ const sheetIndex = allSheets.findIndex((s) => s.name() === oldName);
1861
+ const sheetsbar = instance.view?.sheetsbar;
1862
+ if (sheetsbar && sheetIndex >= 0) {
1863
+ sheetsbar.onSheetRename(name, sheetIndex);
1864
+ }
1865
+ else {
1866
+ activeSheet.name(name);
1867
+ }
1868
+ }
1869
+ });
1870
+ return { success: true, message: `Sheet renamed from "${oldName}" to "${name}".` };
1871
+ }
1872
+ }));
1873
+ }
1874
+ if (enabled.has('export')) {
1875
+ handles.push(modelContext.registerTool({
1876
+ name: `${prefix}-export`,
1877
+ description: `Export the ${label} to a file.`,
1878
+ inputSchema: { type: 'object', properties: {}, required: [] },
1879
+ execute: () => {
1880
+ ngZone.run(() => {
1881
+ const instance = spreadsheet.spreadsheetWidget;
1882
+ if (instance) {
1883
+ instance.saveAsExcel({
1884
+ ...instance.options.excel,
1885
+ saveAs,
1886
+ Workbook
1887
+ });
1888
+ }
1889
+ });
1890
+ return { success: true, message: 'Export started.' };
1891
+ }
1892
+ }));
1893
+ }
1894
+ return handles;
1895
+ }
1896
+ buildToolOptions(config, label) {
1897
+ return [
1898
+ { name: 'set-cell', description: `Set a cell value in the ${label}.`, enabled: true },
1899
+ { name: 'navigate-sheet', description: `Switch to a specific sheet in the ${label} by name or index.`, enabled: true },
1900
+ { name: 'add-sheet', description: `Add a new sheet to the ${label}.`, enabled: true },
1901
+ { name: 'rename-sheet', description: `Rename the active sheet in the ${label}.`, enabled: true },
1902
+ { name: 'export', description: `Export the ${label} to a file.`, enabled: true }
1903
+ ];
1904
+ }
1905
+ }
1906
+
1907
+ function getGanttData(gantt) {
1908
+ if (Array.isArray(gantt.data) && gantt.data.length) {
1909
+ return gantt.data;
1910
+ }
1911
+ // When kendoGanttFlatBinding is used, data lives in the binding directive's originalData
1912
+ const treeList = gantt.treeList;
1913
+ const bindingData = treeList?.localEditService?.bindingDirective?.originalData;
1914
+ if (Array.isArray(bindingData) && bindingData.length) {
1915
+ return bindingData;
1916
+ }
1917
+ // Fallback to rendered items (visible rows only)
1918
+ return gantt.renderedTreeListItems || [];
1919
+ }
1920
+ /**
1921
+ * @hidden
1922
+ *
1923
+ * Web MCP tool adapter for the Kendo Angular Gantt component.
1924
+ * Supports: task CRUD, sort, filter, expand/collapse, export, view.
1925
+ */
1926
+ class GanttToolAdapter {
1927
+ selector = 'kendo-gantt';
1928
+ registerTools(gantt, config, modelContext, ngZone) {
1929
+ const handles = [];
1930
+ const prefix = config.dataName || 'kendo-gantt';
1931
+ const label = config.dataName
1932
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' gantt'
1933
+ : 'Kendo UI Gantt';
1934
+ const mc = modelContext;
1935
+ const zone = ngZone;
1936
+ const rawOptions = this.buildToolOptions(gantt, config, label);
1937
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
1938
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
1939
+ // Task CRUD
1940
+ if (enabled.has('add-task')) {
1941
+ handles.push(mc.registerTool({
1942
+ name: `${prefix}-add-task`,
1943
+ description: `Add a new task to the ${label}.`,
1944
+ inputSchema: {
1945
+ type: 'object',
1946
+ properties: {
1947
+ title: { type: 'string', description: 'Task title.' },
1948
+ start: { type: 'string', description: 'Start date (ISO 8601).' },
1949
+ end: { type: 'string', description: 'End date (ISO 8601).' }
1950
+ },
1951
+ required: ['title', 'start', 'end']
1952
+ },
1953
+ execute: (args) => {
1954
+ const data = gantt.data;
1955
+ if (!Array.isArray(data)) {
1956
+ return { success: false, message: 'Cannot add task: data is not an array.' };
1957
+ }
1958
+ const maxId = data.reduce((m, t) => Math.max(m, t.id || 0), 0);
1959
+ const newTask = {
1960
+ id: maxId + 1,
1961
+ parentId: null,
1962
+ title: args['title'],
1963
+ start: new Date(args['start']),
1964
+ end: new Date(args['end']),
1965
+ percentComplete: 0
1966
+ };
1967
+ zone.run(() => {
1968
+ gantt.data = [...data, newTask];
1969
+ });
1970
+ return { success: true, message: `Task added. ID: ${newTask.id}, Title: "${newTask.title}", Start: ${args['start']}, End: ${args['end']}, Complete: ${newTask.percentComplete}%.` };
1971
+ }
1972
+ }));
1973
+ }
1974
+ if (enabled.has('update-task')) {
1975
+ handles.push(mc.registerTool({
1976
+ name: `${prefix}-update-task`,
1977
+ description: `Update an existing task in the ${label}.`,
1978
+ inputSchema: {
1979
+ type: 'object',
1980
+ properties: {
1981
+ title: { type: 'string', description: 'Title of the task to update.' },
1982
+ start: { type: 'string', description: 'New start date.' },
1983
+ end: { type: 'string', description: 'New end date.' },
1984
+ percentComplete: { type: 'number', description: 'Completion percentage (0-1).' }
1985
+ },
1986
+ required: ['title']
1987
+ },
1988
+ execute: (args) => {
1989
+ const data = gantt.data;
1990
+ if (!Array.isArray(data)) {
1991
+ return { success: false, message: 'Cannot update task: data is not an array.' };
1992
+ }
1993
+ const task = data.find((t) => t.title === args['title']);
1994
+ if (!task) {
1995
+ return { success: false, message: `Task "${args['title']}" not found.` };
1996
+ }
1997
+ zone.run(() => {
1998
+ if (args['start'])
1999
+ task.start = new Date(args['start']);
2000
+ if (args['end'])
2001
+ task.end = new Date(args['end']);
2002
+ if (args['percentComplete'] !== undefined)
2003
+ task.percentComplete = args['percentComplete'];
2004
+ gantt.data = [...data];
2005
+ });
2006
+ return { success: true, message: `Task "${args['title']}" updated.` };
2007
+ }
2008
+ }));
2009
+ }
2010
+ if (enabled.has('delete-task')) {
2011
+ handles.push(mc.registerTool({
2012
+ name: `${prefix}-delete-task`,
2013
+ description: `Delete a task from the ${label}.`,
2014
+ inputSchema: {
2015
+ type: 'object',
2016
+ properties: {
2017
+ title: { type: 'string', description: 'Title of the task to delete.' }
2018
+ },
2019
+ required: ['title']
2020
+ },
2021
+ execute: (args) => {
2022
+ const data = gantt.data;
2023
+ if (!Array.isArray(data)) {
2024
+ return { success: false, message: 'Cannot delete task: data is not an array.' };
2025
+ }
2026
+ const idx = data.findIndex((t) => t.title === args['title']);
2027
+ if (idx < 0) {
2028
+ return { success: false, message: `Task "${args['title']}" not found.` };
2029
+ }
2030
+ zone.run(() => {
2031
+ data.splice(idx, 1);
2032
+ gantt.data = [...data];
2033
+ });
2034
+ return { success: true, message: `Task "${args['title']}" deleted.` };
2035
+ }
2036
+ }));
2037
+ }
2038
+ // View
2039
+ if (enabled.has('set-view')) {
2040
+ handles.push(mc.registerTool({
2041
+ name: `${prefix}-set-view`,
2042
+ description: `Change the timeline view of the ${label}.`,
2043
+ inputSchema: {
2044
+ type: 'object',
2045
+ properties: {
2046
+ view: { type: 'string', description: 'View name: day, week, month, year.' }
2047
+ },
2048
+ required: ['view']
2049
+ },
2050
+ execute: (args) => {
2051
+ zone.run(() => {
2052
+ gantt.activeView = args['view'];
2053
+ gantt.activeViewChange.emit(args['view']);
2054
+ });
2055
+ return { success: true, message: `View set to "${args['view']}".` };
2056
+ }
2057
+ }));
2058
+ }
2059
+ // Sort
2060
+ if (enabled.has('sort')) {
2061
+ handles.push(mc.registerTool({
2062
+ name: `${prefix}-sort`,
2063
+ description: `Sort the ${label} by a field.`,
2064
+ inputSchema: {
2065
+ type: 'object',
2066
+ properties: {
2067
+ field: { type: 'string', description: 'Field to sort by.' },
2068
+ dir: { type: 'string', enum: ['asc', 'desc'], description: 'Sort direction.' }
2069
+ },
2070
+ required: ['field', 'dir']
2071
+ },
2072
+ execute: (args) => {
2073
+ const sortDesc = [{ field: args['field'], dir: args['dir'] }];
2074
+ zone.run(() => {
2075
+ gantt.sort = sortDesc;
2076
+ gantt.sortChange.emit(sortDesc);
2077
+ });
2078
+ return { success: true, message: `Sorted by "${args['field']}" ${args['dir']}.` };
2079
+ }
2080
+ }));
2081
+ }
2082
+ if (enabled.has('clear-sort')) {
2083
+ handles.push(mc.registerTool({
2084
+ name: `${prefix}-clear-sort`,
2085
+ description: `Clear sorting from the ${label}.`,
2086
+ inputSchema: { type: 'object', properties: {}, required: [] },
2087
+ execute: () => {
2088
+ zone.run(() => {
2089
+ gantt.sort = [];
2090
+ gantt.sortChange.emit([]);
2091
+ });
2092
+ return { success: true, message: 'Sort cleared.' };
2093
+ }
2094
+ }));
2095
+ }
2096
+ // Filter
2097
+ if (enabled.has('filter')) {
2098
+ handles.push(mc.registerTool({
2099
+ name: `${prefix}-filter`,
2100
+ description: `Apply a filter to the ${label}.`,
2101
+ inputSchema: {
2102
+ type: 'object',
2103
+ properties: {
2104
+ field: { type: 'string', description: 'Field to filter.' },
2105
+ operator: { type: 'string', description: 'Filter operator.' },
2106
+ value: { type: 'string', description: 'Filter value.' }
2107
+ },
2108
+ required: ['field', 'operator', 'value']
2109
+ },
2110
+ execute: (args) => {
2111
+ const filterDesc = {
2112
+ logic: 'and',
2113
+ filters: [{ field: args['field'], operator: args['operator'], value: args['value'] }]
2114
+ };
2115
+ zone.run(() => {
2116
+ gantt.filter = filterDesc;
2117
+ gantt.filterChange.emit(filterDesc);
2118
+ });
2119
+ return { success: true, message: `Filtered "${args['field']}" ${args['operator']} "${args['value']}".` };
2120
+ }
2121
+ }));
2122
+ }
2123
+ if (enabled.has('clear-filters')) {
2124
+ handles.push(mc.registerTool({
2125
+ name: `${prefix}-clear-filters`,
2126
+ description: `Clear all filters from the ${label}.`,
2127
+ inputSchema: { type: 'object', properties: {}, required: [] },
2128
+ execute: () => {
2129
+ const emptyFilter = { logic: 'and', filters: [] };
2130
+ zone.run(() => {
2131
+ gantt.filter = emptyFilter;
2132
+ gantt.filterChange.emit(emptyFilter);
2133
+ });
2134
+ return { success: true, message: 'Filters cleared.' };
2135
+ }
2136
+ }));
2137
+ }
2138
+ // Expand / Collapse
2139
+ if (enabled.has('expand')) {
2140
+ handles.push(mc.registerTool({
2141
+ name: `${prefix}-expand`,
2142
+ description: `Expand a task row in the ${label}.`,
2143
+ inputSchema: {
2144
+ type: 'object',
2145
+ properties: { id: { type: 'number', description: 'Task id to expand.' } },
2146
+ required: ['id']
2147
+ },
2148
+ execute: (args) => {
2149
+ const task = getGanttData(gantt).find((t) => t.id === args['id']);
2150
+ if (!task) {
2151
+ return { success: false, message: `Task with id ${args['id']} not found.` };
2152
+ }
2153
+ zone.run(() => {
2154
+ gantt.rowExpand.emit({ dataItem: task });
2155
+ gantt.expandStateChange?.emit({ dataItem: task, expand: true });
2156
+ gantt.updateView();
2157
+ });
2158
+ return { success: true, message: `Task ${args['id']} expanded.` };
2159
+ }
2160
+ }));
2161
+ }
2162
+ if (enabled.has('collapse')) {
2163
+ handles.push(mc.registerTool({
2164
+ name: `${prefix}-collapse`,
2165
+ description: `Collapse a task row in the ${label}.`,
2166
+ inputSchema: {
2167
+ type: 'object',
2168
+ properties: { id: { type: 'number', description: 'Task id to collapse.' } },
2169
+ required: ['id']
2170
+ },
2171
+ execute: (args) => {
2172
+ const task = getGanttData(gantt).find((t) => t.id === args['id']);
2173
+ if (!task) {
2174
+ return { success: false, message: `Task with id ${args['id']} not found.` };
2175
+ }
2176
+ zone.run(() => {
2177
+ gantt.rowCollapse.emit({ dataItem: task });
2178
+ gantt.expandStateChange?.emit({ dataItem: task, expand: false });
2179
+ gantt.updateView();
2180
+ });
2181
+ return { success: true, message: `Task ${args['id']} collapsed.` };
2182
+ }
2183
+ }));
2184
+ }
2185
+ // Select
2186
+ if (enabled.has('select')) {
2187
+ handles.push(mc.registerTool({
2188
+ name: `${prefix}-select`,
2189
+ description: `Select a task in the ${label}.`,
2190
+ inputSchema: {
2191
+ type: 'object',
2192
+ properties: { id: { type: 'number', description: 'Task id to select.' } },
2193
+ required: ['id']
2194
+ },
2195
+ execute: (args) => {
2196
+ const task = getGanttData(gantt).find((t) => t.id === args['id']);
2197
+ if (!task) {
2198
+ return { success: false, message: `Task with id ${args['id']} not found.` };
2199
+ }
2200
+ zone.run(() => {
2201
+ gantt.notifySelectionChange?.(task, 'select');
2202
+ });
2203
+ return { success: true, message: `Task ${args['id']} selected.` };
2204
+ }
2205
+ }));
2206
+ }
2207
+ // Export
2208
+ if (enabled.has('export-pdf') && typeof gantt.saveAsPDF === 'function') {
2209
+ handles.push(mc.registerTool({
2210
+ name: `${prefix}-export-pdf`,
2211
+ description: `Export the ${label} to PDF.`,
2212
+ inputSchema: { type: 'object', properties: {}, required: [] },
2213
+ execute: () => {
2214
+ zone.run(() => gantt.saveAsPDF());
2215
+ return { success: true, message: 'PDF export started.' };
2216
+ }
2217
+ }));
2218
+ }
2219
+ if (enabled.has('export-excel') && typeof gantt.saveAsExcel === 'function') {
2220
+ handles.push(mc.registerTool({
2221
+ name: `${prefix}-export-excel`,
2222
+ description: `Export the ${label} to Excel.`,
2223
+ inputSchema: { type: 'object', properties: {}, required: [] },
2224
+ execute: () => {
2225
+ zone.run(() => gantt.saveAsExcel());
2226
+ return { success: true, message: 'Excel export started.' };
2227
+ }
2228
+ }));
2229
+ }
2230
+ return handles;
2231
+ }
2232
+ buildToolOptions(gantt, config, label) {
2233
+ return [
2234
+ { name: 'add-task', description: `Add a new task to the ${label}.`, enabled: true },
2235
+ { name: 'update-task', description: `Update an existing task in the ${label}.`, enabled: true },
2236
+ { name: 'delete-task', description: `Delete a task from the ${label}.`, enabled: true },
2237
+ { name: 'set-view', description: `Change the timeline view of the ${label}.`, enabled: true },
2238
+ { name: 'sort', description: `Sort the ${label} by a field.`, enabled: !!gantt.sortable },
2239
+ { name: 'clear-sort', description: `Clear sorting from the ${label}.`, enabled: !!gantt.sortable },
2240
+ { name: 'filter', description: `Apply a filter to the ${label}.`, enabled: !!gantt.filterable },
2241
+ { name: 'clear-filters', description: `Clear all filters from the ${label}.`, enabled: !!gantt.filterable },
2242
+ { name: 'expand', description: `Expand a task row in the ${label}.`, enabled: true },
2243
+ { name: 'collapse', description: `Collapse a task row in the ${label}.`, enabled: true },
2244
+ { name: 'select', description: `Select a task in the ${label}.`, enabled: true },
2245
+ { name: 'export-pdf', description: `Export the ${label} to PDF.`, enabled: true },
2246
+ { name: 'export-excel', description: `Export the ${label} to Excel.`, enabled: true }
2247
+ ];
2248
+ }
2249
+ }
2250
+
2251
+ /**
2252
+ * @hidden
2253
+ *
2254
+ * Web MCP tool adapter for the Kendo Angular PivotGrid component.
2255
+ * Supports: set rows, set columns, set measures, expand, collapse, export.
2256
+ */
2257
+ class PivotGridToolAdapter {
2258
+ selector = 'kendo-pivotgrid';
2259
+ registerTools(pivot, config, modelContext, ngZone) {
2260
+ const handles = [];
2261
+ const prefix = config.dataName || 'kendo-pivotgrid';
2262
+ const label = config.dataName
2263
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' pivot grid'
2264
+ : 'Kendo UI PivotGrid';
2265
+ const mc = modelContext;
2266
+ const zone = ngZone;
2267
+ const rawOptions = this.buildToolOptions(config, label);
2268
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2269
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2270
+ if (enabled.has('set-rows')) {
2271
+ handles.push(mc.registerTool({
2272
+ name: `${prefix}-set-rows`,
2273
+ description: `Set the row fields of the ${label}.`,
2274
+ inputSchema: {
2275
+ type: 'object',
2276
+ properties: {
2277
+ fields: { type: 'array', items: { type: 'string' }, description: 'Row field names.' }
2278
+ },
2279
+ required: ['fields']
2280
+ },
2281
+ execute: (args) => {
2282
+ zone.run(() => {
2283
+ const ds = pivot.dataService;
2284
+ const newAxes = args['fields'].map(f => ({ name: [f], expand: false }));
2285
+ const newState = { ...ds.state, rowAxes: newAxes };
2286
+ ds.configuratorFieldChange.emit(newState);
2287
+ });
2288
+ return { success: true, message: `Rows set to: ${args['fields'].join(', ')}.` };
2289
+ }
2290
+ }));
2291
+ }
2292
+ if (enabled.has('set-columns')) {
2293
+ handles.push(mc.registerTool({
2294
+ name: `${prefix}-set-columns`,
2295
+ description: `Set the column fields of the ${label}.`,
2296
+ inputSchema: {
2297
+ type: 'object',
2298
+ properties: {
2299
+ fields: { type: 'array', items: { type: 'string' }, description: 'Column field names.' }
2300
+ },
2301
+ required: ['fields']
2302
+ },
2303
+ execute: (args) => {
2304
+ zone.run(() => {
2305
+ const ds = pivot.dataService;
2306
+ const newAxes = args['fields'].map(f => ({ name: [f], expand: false }));
2307
+ const newState = { ...ds.state, columnAxes: newAxes };
2308
+ ds.configuratorFieldChange.emit(newState);
2309
+ });
2310
+ return { success: true, message: `Columns set to: ${args['fields'].join(', ')}.` };
2311
+ }
2312
+ }));
2313
+ }
2314
+ if (enabled.has('set-measures')) {
2315
+ handles.push(mc.registerTool({
2316
+ name: `${prefix}-set-measures`,
2317
+ description: `Set the measure fields of the ${label}.`,
2318
+ inputSchema: {
2319
+ type: 'object',
2320
+ properties: {
2321
+ fields: { type: 'array', items: { type: 'string' }, description: 'Measure field names.' }
2322
+ },
2323
+ required: ['fields']
2324
+ },
2325
+ execute: (args) => {
2326
+ zone.run(() => {
2327
+ const ds = pivot.dataService;
2328
+ const newAxes = args['fields'].map(f => ({ name: [f] }));
2329
+ const newState = { ...ds.state, measureAxes: newAxes };
2330
+ ds.configuratorFieldChange.emit(newState);
2331
+ });
2332
+ return { success: true, message: `Measures set to: ${args['fields'].join(', ')}.` };
2333
+ }
2334
+ }));
2335
+ }
2336
+ if (enabled.has('expand')) {
2337
+ handles.push(mc.registerTool({
2338
+ name: `${prefix}-expand`,
2339
+ description: `Expand a row or column header in the ${label}.`,
2340
+ inputSchema: {
2341
+ type: 'object',
2342
+ properties: {
2343
+ axis: { type: 'string', enum: ['rows', 'columns'], description: 'Axis to expand on.' },
2344
+ path: { type: 'array', items: { type: 'string' }, description: 'Header path to expand.' }
2345
+ },
2346
+ required: ['axis', 'path']
2347
+ },
2348
+ execute: (args) => {
2349
+ zone.run(() => {
2350
+ const ds = pivot.dataService;
2351
+ const tableType = args['axis'] === 'columns' ? 'columnHeader' : 'rowHeader';
2352
+ ds.expandedStateChange.emit({ tableType, cell: { path: args['path'] } });
2353
+ });
2354
+ return { success: true, message: `Expanded ${args['axis']} at path [${args['path'].join(', ')}].` };
2355
+ }
2356
+ }));
2357
+ }
2358
+ if (enabled.has('collapse')) {
2359
+ handles.push(mc.registerTool({
2360
+ name: `${prefix}-collapse`,
2361
+ description: `Collapse a row or column header in the ${label}.`,
2362
+ inputSchema: {
2363
+ type: 'object',
2364
+ properties: {
2365
+ axis: { type: 'string', enum: ['rows', 'columns'], description: 'Axis to collapse on.' },
2366
+ path: { type: 'array', items: { type: 'string' }, description: 'Header path to collapse.' }
2367
+ },
2368
+ required: ['axis', 'path']
2369
+ },
2370
+ execute: (args) => {
2371
+ zone.run(() => {
2372
+ const ds = pivot.dataService;
2373
+ const tableType = args['axis'] === 'columns' ? 'columnHeader' : 'rowHeader';
2374
+ ds.expandedStateChange.emit({ tableType, cell: { path: args['path'] } });
2375
+ });
2376
+ return { success: true, message: `Collapsed ${args['axis']} at path [${args['path'].join(', ')}].` };
2377
+ }
2378
+ }));
2379
+ }
2380
+ if (enabled.has('export-pdf')) {
2381
+ handles.push(mc.registerTool({
2382
+ name: `${prefix}-export-pdf`,
2383
+ description: `Export the ${label} to PDF.`,
2384
+ inputSchema: { type: 'object', properties: {}, required: [] },
2385
+ execute: () => {
2386
+ zone.run(() => pivot.saveAsPDF?.());
2387
+ return { success: true, message: 'PDF export started.' };
2388
+ }
2389
+ }));
2390
+ }
2391
+ return handles;
2392
+ }
2393
+ buildToolOptions(config, label) {
2394
+ return [
2395
+ { name: 'set-rows', description: `Set the row fields of the ${label}.`, enabled: true },
2396
+ { name: 'set-columns', description: `Set the column fields of the ${label}.`, enabled: true },
2397
+ { name: 'set-measures', description: `Set the measure fields of the ${label}.`, enabled: true },
2398
+ { name: 'expand', description: `Expand a row or column header in the ${label}.`, enabled: true },
2399
+ { name: 'collapse', description: `Collapse a row or column header in the ${label}.`, enabled: true },
2400
+ { name: 'export-pdf', description: `Export the ${label} to PDF.`, enabled: true }
2401
+ ];
2402
+ }
2403
+ }
2404
+
2405
+ // DropDownList
2406
+ /**
2407
+ * @hidden
2408
+ */
2409
+ class DropDownListToolAdapter {
2410
+ selector = 'kendo-dropdownlist';
2411
+ registerTools(ddl, config, mc, zone) {
2412
+ const prefix = config.dataName || 'kendo-dropdownlist';
2413
+ const label = config.dataName
2414
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' dropdown'
2415
+ : 'Kendo UI DropDownList';
2416
+ const rawOptions = this.buildToolOptions(config, label);
2417
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2418
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2419
+ const handles = [];
2420
+ if (enabled.has('select')) {
2421
+ handles.push(mc.registerTool({
2422
+ name: `${prefix}-select`,
2423
+ description: `Select an item in the ${label} by value or text.`,
2424
+ inputSchema: {
2425
+ type: 'object',
2426
+ properties: {
2427
+ value: { type: 'string', description: 'Value or text of the item to select.' }
2428
+ },
2429
+ required: ['value']
2430
+ },
2431
+ execute: (args) => {
2432
+ zone.run(() => {
2433
+ ddl.value = args['value'];
2434
+ ddl.valueChange.emit(args['value']);
2435
+ ddl.selectionChange.emit(args['value']);
2436
+ });
2437
+ return { success: true, message: `Selected "${args['value']}".` };
2438
+ }
2439
+ }));
2440
+ }
2441
+ if (enabled.has('open')) {
2442
+ handles.push(mc.registerTool({
2443
+ name: `${prefix}-open`,
2444
+ description: `Open the ${label} popup.`,
2445
+ inputSchema: { type: 'object', properties: {}, required: [] },
2446
+ execute: () => {
2447
+ zone.run(() => ddl.toggle(true));
2448
+ return { success: true, message: 'Dropdown opened.' };
2449
+ }
2450
+ }));
2451
+ }
2452
+ if (enabled.has('close')) {
2453
+ handles.push(mc.registerTool({
2454
+ name: `${prefix}-close`,
2455
+ description: `Close the ${label} popup.`,
2456
+ inputSchema: { type: 'object', properties: {}, required: [] },
2457
+ execute: () => {
2458
+ zone.run(() => ddl.toggle(false));
2459
+ return { success: true, message: 'Dropdown closed.' };
2460
+ }
2461
+ }));
2462
+ }
2463
+ return handles;
2464
+ }
2465
+ buildToolOptions(config, label) {
2466
+ return [
2467
+ { name: 'select', description: `Select an item in the ${label} by value or text.`, enabled: true },
2468
+ { name: 'open', description: `Open the ${label} popup.`, enabled: true },
2469
+ { name: 'close', description: `Close the ${label} popup.`, enabled: true }
2470
+ ];
2471
+ }
2472
+ }
2473
+ // ComboBox
2474
+ /**
2475
+ * @hidden
2476
+ */
2477
+ class ComboBoxToolAdapter {
2478
+ selector = 'kendo-combobox';
2479
+ registerTools(combo, config, mc, zone) {
2480
+ const prefix = config.dataName || 'kendo-combobox';
2481
+ const label = config.dataName
2482
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' combobox'
2483
+ : 'Kendo UI ComboBox';
2484
+ const rawOptions = this.buildToolOptions(config, label);
2485
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2486
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2487
+ const handles = [];
2488
+ if (enabled.has('select')) {
2489
+ handles.push(mc.registerTool({
2490
+ name: `${prefix}-select`,
2491
+ description: `Select an item in the ${label}.`,
2492
+ inputSchema: {
2493
+ type: 'object',
2494
+ properties: {
2495
+ value: { type: 'string', description: 'Value or text of the item to select.' }
2496
+ },
2497
+ required: ['value']
2498
+ },
2499
+ execute: (args) => {
2500
+ zone.run(() => {
2501
+ combo.value = args['value'];
2502
+ combo.valueChange.emit(args['value']);
2503
+ combo.selectionChange.emit(args['value']);
2504
+ });
2505
+ return { success: true, message: `Selected "${args['value']}".` };
2506
+ }
2507
+ }));
2508
+ }
2509
+ if (enabled.has('search')) {
2510
+ handles.push(mc.registerTool({
2511
+ name: `${prefix}-search`,
2512
+ description: `Search/filter items in the ${label}.`,
2513
+ inputSchema: {
2514
+ type: 'object',
2515
+ properties: {
2516
+ text: { type: 'string', description: 'Search text.' }
2517
+ },
2518
+ required: ['text']
2519
+ },
2520
+ execute: (args) => {
2521
+ zone.run(() => {
2522
+ combo.toggle(true);
2523
+ combo.filterChange.emit(args['text']);
2524
+ });
2525
+ return { success: true, message: `Searched for "${args['text']}".` };
2526
+ }
2527
+ }));
2528
+ }
2529
+ if (enabled.has('clear')) {
2530
+ handles.push(mc.registerTool({
2531
+ name: `${prefix}-clear`,
2532
+ description: `Clear the ${label} value.`,
2533
+ inputSchema: { type: 'object', properties: {}, required: [] },
2534
+ execute: () => {
2535
+ zone.run(() => {
2536
+ combo.value = null;
2537
+ combo.valueChange.emit(null);
2538
+ });
2539
+ return { success: true, message: 'Value cleared.' };
2540
+ }
2541
+ }));
2542
+ }
2543
+ if (enabled.has('open')) {
2544
+ handles.push(mc.registerTool({
2545
+ name: `${prefix}-open`,
2546
+ description: `Open the ${label} popup.`,
2547
+ inputSchema: { type: 'object', properties: {}, required: [] },
2548
+ execute: () => {
2549
+ zone.run(() => combo.toggle(true));
2550
+ return { success: true, message: 'Popup opened.' };
2551
+ }
2552
+ }));
2553
+ }
2554
+ if (enabled.has('close')) {
2555
+ handles.push(mc.registerTool({
2556
+ name: `${prefix}-close`,
2557
+ description: `Close the ${label} popup.`,
2558
+ inputSchema: { type: 'object', properties: {}, required: [] },
2559
+ execute: () => {
2560
+ zone.run(() => combo.toggle(false));
2561
+ return { success: true, message: 'Popup closed.' };
2562
+ }
2563
+ }));
2564
+ }
2565
+ return handles;
2566
+ }
2567
+ buildToolOptions(config, label) {
2568
+ return [
2569
+ { name: 'select', description: `Select an item in the ${label}.`, enabled: true },
2570
+ { name: 'search', description: `Search/filter items in the ${label}.`, enabled: true },
2571
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true },
2572
+ { name: 'open', description: `Open the ${label} popup.`, enabled: true },
2573
+ { name: 'close', description: `Close the ${label} popup.`, enabled: true }
2574
+ ];
2575
+ }
2576
+ }
2577
+ /**
2578
+ * @hidden
2579
+ */
2580
+ class AutoCompleteToolAdapter {
2581
+ selector = 'kendo-autocomplete';
2582
+ registerTools(ac, config, mc, zone) {
2583
+ const prefix = config.dataName || 'kendo-autocomplete';
2584
+ const label = config.dataName
2585
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' autocomplete'
2586
+ : 'Kendo UI AutoComplete';
2587
+ const rawOptions = this.buildToolOptions(config, label);
2588
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2589
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2590
+ const handles = [];
2591
+ if (enabled.has('search')) {
2592
+ handles.push(mc.registerTool({
2593
+ name: `${prefix}-search`,
2594
+ description: `Search for items in the ${label}.`,
2595
+ inputSchema: {
2596
+ type: 'object',
2597
+ properties: {
2598
+ text: { type: 'string', description: 'Search text.' }
2599
+ },
2600
+ required: ['text']
2601
+ },
2602
+ execute: (args) => {
2603
+ zone.run(() => {
2604
+ ac.value = args['text'];
2605
+ ac.toggle(true);
2606
+ ac.filterChange.emit(args['text']);
2607
+ });
2608
+ return { success: true, message: `Searched for "${args['text']}".` };
2609
+ }
2610
+ }));
2611
+ }
2612
+ if (enabled.has('select')) {
2613
+ handles.push(mc.registerTool({
2614
+ name: `${prefix}-select`,
2615
+ description: `Select a value in the ${label}.`,
2616
+ inputSchema: {
2617
+ type: 'object',
2618
+ properties: {
2619
+ value: { type: 'string', description: 'Value to select.' }
2620
+ },
2621
+ required: ['value']
2622
+ },
2623
+ execute: (args) => {
2624
+ zone.run(() => {
2625
+ ac.value = args['value'];
2626
+ ac.valueChange.emit(args['value']);
2627
+ });
2628
+ return { success: true, message: `Selected "${args['value']}".` };
2629
+ }
2630
+ }));
2631
+ }
2632
+ if (enabled.has('clear')) {
2633
+ handles.push(mc.registerTool({
2634
+ name: `${prefix}-clear`,
2635
+ description: `Clear the ${label} value.`,
2636
+ inputSchema: { type: 'object', properties: {}, required: [] },
2637
+ execute: () => {
2638
+ zone.run(() => {
2639
+ ac.value = '';
2640
+ ac.valueChange.emit('');
2641
+ });
2642
+ return { success: true, message: 'Value cleared.' };
2643
+ }
2644
+ }));
2645
+ }
2646
+ return handles;
2647
+ }
2648
+ buildToolOptions(config, label) {
2649
+ return [
2650
+ { name: 'search', description: `Search for items in the ${label}.`, enabled: true },
2651
+ { name: 'select', description: `Select a value in the ${label}.`, enabled: true },
2652
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true }
2653
+ ];
2654
+ }
2655
+ }
2656
+ // MultiSelect
2657
+ /**
2658
+ * @hidden
2659
+ */
2660
+ class MultiSelectToolAdapter {
2661
+ selector = 'kendo-multiselect';
2662
+ registerTools(ms, config, mc, zone) {
2663
+ const prefix = config.dataName || 'kendo-multiselect';
2664
+ const label = config.dataName
2665
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' multiselect'
2666
+ : 'Kendo UI MultiSelect';
2667
+ const rawOptions = this.buildToolOptions(config, label);
2668
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2669
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2670
+ const handles = [];
2671
+ if (enabled.has('select')) {
2672
+ handles.push(mc.registerTool({
2673
+ name: `${prefix}-select`,
2674
+ description: `Add items to the ${label} selection.`,
2675
+ inputSchema: {
2676
+ type: 'object',
2677
+ properties: {
2678
+ values: { type: 'array', items: { type: 'string' }, description: 'Values to add to selection.' }
2679
+ },
2680
+ required: ['values']
2681
+ },
2682
+ execute: (args) => {
2683
+ const toAdd = args['values'];
2684
+ const current = ms.value || [];
2685
+ const merged = [...current, ...toAdd.filter((v) => !current.includes(v))];
2686
+ zone.run(() => {
2687
+ ms.value = merged;
2688
+ ms.valueChange.emit(merged);
2689
+ });
2690
+ return { success: true, message: `Selected ${toAdd.length} item(s). Added: [${toAdd.join(', ')}]. Total selected: ${merged.length}.` };
2691
+ }
2692
+ }));
2693
+ }
2694
+ if (enabled.has('remove')) {
2695
+ handles.push(mc.registerTool({
2696
+ name: `${prefix}-remove`,
2697
+ description: `Remove an item from the ${label} selection.`,
2698
+ inputSchema: {
2699
+ type: 'object',
2700
+ properties: {
2701
+ value: { type: 'string', description: 'Value to remove from selection.' }
2702
+ },
2703
+ required: ['value']
2704
+ },
2705
+ execute: (args) => {
2706
+ zone.run(() => {
2707
+ ms.removeTag.emit(args['value']);
2708
+ });
2709
+ return { success: true, message: `Removed "${args['value']}".` };
2710
+ }
2711
+ }));
2712
+ }
2713
+ if (enabled.has('clear')) {
2714
+ handles.push(mc.registerTool({
2715
+ name: `${prefix}-clear`,
2716
+ description: `Clear all selections in the ${label}.`,
2717
+ inputSchema: { type: 'object', properties: {}, required: [] },
2718
+ execute: () => {
2719
+ zone.run(() => {
2720
+ ms.value = [];
2721
+ ms.valueChange.emit([]);
2722
+ });
2723
+ return { success: true, message: 'Selection cleared.' };
2724
+ }
2725
+ }));
2726
+ }
2727
+ return handles;
2728
+ }
2729
+ buildToolOptions(config, label) {
2730
+ return [
2731
+ { name: 'select', description: `Add items to the ${label} selection.`, enabled: true },
2732
+ { name: 'remove', description: `Remove an item from the ${label} selection.`, enabled: true },
2733
+ { name: 'clear', description: `Clear all selections in the ${label}.`, enabled: true }
2734
+ ];
2735
+ }
2736
+ }
2737
+ // DropDownTree
2738
+ /**
2739
+ * @hidden
2740
+ */
2741
+ class DropDownTreeToolAdapter {
2742
+ selector = 'kendo-dropdowntree';
2743
+ registerTools(ddt, config, mc, zone) {
2744
+ const prefix = config.dataName || 'kendo-dropdowntree';
2745
+ const label = config.dataName
2746
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' dropdown tree'
2747
+ : 'Kendo UI DropDownTree';
2748
+ const rawOptions = this.buildToolOptions(config, label);
2749
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2750
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2751
+ const handles = [];
2752
+ if (enabled.has('select')) {
2753
+ handles.push(mc.registerTool({
2754
+ name: `${prefix}-select`,
2755
+ description: `Select a node in the ${label}.`,
2756
+ inputSchema: {
2757
+ type: 'object',
2758
+ properties: {
2759
+ value: { type: 'string', description: 'Node value to select.' }
2760
+ },
2761
+ required: ['value']
2762
+ },
2763
+ execute: (args) => {
2764
+ zone.run(() => {
2765
+ ddt.value = args['value'];
2766
+ ddt.valueChange.emit(args['value']);
2767
+ });
2768
+ return { success: true, message: `Selected "${args['value']}".` };
2769
+ }
2770
+ }));
2771
+ }
2772
+ if (enabled.has('open')) {
2773
+ handles.push(mc.registerTool({
2774
+ name: `${prefix}-open`,
2775
+ description: `Open the ${label} popup.`,
2776
+ inputSchema: { type: 'object', properties: {}, required: [] },
2777
+ execute: () => {
2778
+ zone.run(() => ddt.toggle(true));
2779
+ return { success: true, message: 'Popup opened.' };
2780
+ }
2781
+ }));
2782
+ }
2783
+ if (enabled.has('close')) {
2784
+ handles.push(mc.registerTool({
2785
+ name: `${prefix}-close`,
2786
+ description: `Close the ${label} popup.`,
2787
+ inputSchema: { type: 'object', properties: {}, required: [] },
2788
+ execute: () => {
2789
+ zone.run(() => ddt.toggle(false));
2790
+ return { success: true, message: 'Popup closed.' };
2791
+ }
2792
+ }));
2793
+ }
2794
+ if (enabled.has('clear')) {
2795
+ handles.push(mc.registerTool({
2796
+ name: `${prefix}-clear`,
2797
+ description: `Clear the ${label} value.`,
2798
+ inputSchema: { type: 'object', properties: {}, required: [] },
2799
+ execute: () => {
2800
+ zone.run(() => {
2801
+ ddt.value = null;
2802
+ ddt.valueChange.emit(null);
2803
+ });
2804
+ return { success: true, message: 'Value cleared.' };
2805
+ }
2806
+ }));
2807
+ }
2808
+ return handles;
2809
+ }
2810
+ buildToolOptions(config, label) {
2811
+ return [
2812
+ { name: 'select', description: `Select a node in the ${label}.`, enabled: true },
2813
+ { name: 'open', description: `Open the ${label} popup.`, enabled: true },
2814
+ { name: 'close', description: `Close the ${label} popup.`, enabled: true },
2815
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true }
2816
+ ];
2817
+ }
2818
+ }
2819
+ // MultiColumnComboBox
2820
+ /**
2821
+ * @hidden
2822
+ */
2823
+ class MultiColumnComboBoxToolAdapter {
2824
+ selector = 'kendo-multicolumncombobox';
2825
+ registerTools(mccb, config, mc, zone) {
2826
+ const prefix = config.dataName || 'kendo-multicolumncombobox';
2827
+ const label = config.dataName
2828
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' multi-column combobox'
2829
+ : 'Kendo UI MultiColumnComboBox';
2830
+ const rawOptions = this.buildToolOptions(config, label);
2831
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2832
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2833
+ const handles = [];
2834
+ if (enabled.has('select')) {
2835
+ handles.push(mc.registerTool({
2836
+ name: `${prefix}-select`,
2837
+ description: `Select an item in the ${label}.`,
2838
+ inputSchema: {
2839
+ type: 'object',
2840
+ properties: {
2841
+ value: { type: 'string', description: 'Value to select.' }
2842
+ },
2843
+ required: ['value']
2844
+ },
2845
+ execute: (args) => {
2846
+ zone.run(() => {
2847
+ mccb.value = args['value'];
2848
+ mccb.valueChange.emit(args['value']);
2849
+ mccb.selectionChange.emit(args['value']);
2850
+ });
2851
+ return { success: true, message: `Selected "${args['value']}".` };
2852
+ }
2853
+ }));
2854
+ }
2855
+ if (enabled.has('search')) {
2856
+ handles.push(mc.registerTool({
2857
+ name: `${prefix}-search`,
2858
+ description: `Search/filter items in the ${label}.`,
2859
+ inputSchema: {
2860
+ type: 'object',
2861
+ properties: {
2862
+ text: { type: 'string', description: 'Search text.' }
2863
+ },
2864
+ required: ['text']
2865
+ },
2866
+ execute: (args) => {
2867
+ zone.run(() => {
2868
+ mccb.filterChange.emit(args['text']);
2869
+ });
2870
+ return { success: true, message: `Searched for "${args['text']}".` };
2871
+ }
2872
+ }));
2873
+ }
2874
+ if (enabled.has('clear')) {
2875
+ handles.push(mc.registerTool({
2876
+ name: `${prefix}-clear`,
2877
+ description: `Clear the ${label} value.`,
2878
+ inputSchema: { type: 'object', properties: {}, required: [] },
2879
+ execute: () => {
2880
+ zone.run(() => {
2881
+ mccb.value = null;
2882
+ mccb.valueChange.emit(null);
2883
+ });
2884
+ return { success: true, message: 'Value cleared.' };
2885
+ }
2886
+ }));
2887
+ }
2888
+ if (enabled.has('open')) {
2889
+ handles.push(mc.registerTool({
2890
+ name: `${prefix}-open`,
2891
+ description: `Open the ${label} popup.`,
2892
+ inputSchema: { type: 'object', properties: {}, required: [] },
2893
+ execute: () => {
2894
+ zone.run(() => mccb.toggle(true));
2895
+ return { success: true, message: 'Popup opened.' };
2896
+ }
2897
+ }));
2898
+ }
2899
+ return handles;
2900
+ }
2901
+ buildToolOptions(config, label) {
2902
+ return [
2903
+ { name: 'select', description: `Select an item in the ${label}.`, enabled: true },
2904
+ { name: 'search', description: `Search/filter items in the ${label}.`, enabled: true },
2905
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true },
2906
+ { name: 'open', description: `Open the ${label} popup.`, enabled: true }
2907
+ ];
2908
+ }
2909
+ }
2910
+
2911
+ // DatePicker ────────────────────────────────────────────────────────
2912
+ /**
2913
+ * @hidden
2914
+ */
2915
+ class DatePickerToolAdapter {
2916
+ selector = 'kendo-datepicker';
2917
+ registerTools(dp, config, mc, zone) {
2918
+ const prefix = config.dataName || 'kendo-datepicker';
2919
+ const label = config.dataName
2920
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' date picker'
2921
+ : 'Kendo UI DatePicker';
2922
+ const rawOptions = this.buildToolOptions(config, label);
2923
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2924
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2925
+ const handles = [];
2926
+ if (enabled.has('set-value')) {
2927
+ handles.push(mc.registerTool({
2928
+ name: `${prefix}-set-value`,
2929
+ description: `Set the date value of the ${label}.`,
2930
+ inputSchema: {
2931
+ type: 'object',
2932
+ properties: { date: { type: 'string', description: 'Date value (ISO 8601).' } },
2933
+ required: ['date']
2934
+ },
2935
+ execute: (args) => {
2936
+ const date = new Date(args['date']);
2937
+ zone.run(() => {
2938
+ dp.value = date;
2939
+ dp.valueChange.emit(date);
2940
+ dp.cdr.markForCheck();
2941
+ });
2942
+ return { success: true, message: `Date set to ${date.toISOString().split('T')[0]}.` };
2943
+ }
2944
+ }));
2945
+ }
2946
+ if (enabled.has('clear')) {
2947
+ handles.push(mc.registerTool({
2948
+ name: `${prefix}-clear`,
2949
+ description: `Clear the ${label} value.`,
2950
+ inputSchema: { type: 'object', properties: {}, required: [] },
2951
+ execute: () => {
2952
+ zone.run(() => {
2953
+ dp.value = null;
2954
+ dp.valueChange.emit(null);
2955
+ dp.cdr.markForCheck();
2956
+ });
2957
+ return { success: true, message: 'Date cleared.' };
2958
+ }
2959
+ }));
2960
+ }
2961
+ return handles;
2962
+ }
2963
+ buildToolOptions(config, label) {
2964
+ return [
2965
+ { name: 'set-value', description: `Set the date value of the ${label}.`, enabled: true },
2966
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true }
2967
+ ];
2968
+ }
2969
+ }
2970
+ // DateRangePicker
2971
+ /**
2972
+ * @hidden
2973
+ */
2974
+ class DateRangeToolAdapter {
2975
+ selector = 'kendo-daterange';
2976
+ registerTools(drp, config, mc, zone) {
2977
+ const prefix = config.dataName || 'kendo-daterangepicker';
2978
+ const label = config.dataName
2979
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' date range picker'
2980
+ : 'Kendo UI DateRangePicker';
2981
+ const rawOptions = this.buildToolOptions(config, label);
2982
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
2983
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
2984
+ const handles = [];
2985
+ if (enabled.has('set-range')) {
2986
+ handles.push(mc.registerTool({
2987
+ name: `${prefix}-set-range`,
2988
+ description: `Set the date range in the ${label}.`,
2989
+ inputSchema: {
2990
+ type: 'object',
2991
+ properties: {
2992
+ start: { type: 'string', description: 'Start date (ISO 8601).' },
2993
+ end: { type: 'string', description: 'End date (ISO 8601).' }
2994
+ },
2995
+ required: ['start', 'end']
2996
+ },
2997
+ execute: (args) => {
2998
+ const start = new Date(args['start']);
2999
+ const end = new Date(args['end']);
3000
+ zone.run(() => {
3001
+ drp.dateRangeService.range$.next({ start, end });
3002
+ });
3003
+ return { success: true, message: `Range set: ${start.toISOString().split('T')[0]} to ${end.toISOString().split('T')[0]}.` };
3004
+ }
3005
+ }));
3006
+ }
3007
+ if (enabled.has('clear')) {
3008
+ handles.push(mc.registerTool({
3009
+ name: `${prefix}-clear`,
3010
+ description: `Clear the ${label} value.`,
3011
+ inputSchema: { type: 'object', properties: {}, required: [] },
3012
+ execute: () => {
3013
+ zone.run(() => drp.dateRangeService.range$.next({ start: null, end: null }));
3014
+ return { success: true, message: 'Date range cleared.' };
3015
+ }
3016
+ }));
3017
+ }
3018
+ return handles;
3019
+ }
3020
+ buildToolOptions(config, label) {
3021
+ return [
3022
+ { name: 'set-range', description: `Set the date range in the ${label}.`, enabled: true },
3023
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true }
3024
+ ];
3025
+ }
3026
+ }
3027
+ // TimePicker
3028
+ /**
3029
+ * @hidden
3030
+ */
3031
+ class TimePickerToolAdapter {
3032
+ selector = 'kendo-timepicker';
3033
+ registerTools(tp, config, mc, zone) {
3034
+ const prefix = config.dataName || 'kendo-timepicker';
3035
+ const label = config.dataName
3036
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' time picker'
3037
+ : 'Kendo UI TimePicker';
3038
+ const rawOptions = this.buildToolOptions(config, label);
3039
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3040
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3041
+ const handles = [];
3042
+ if (enabled.has('set-value')) {
3043
+ handles.push(mc.registerTool({
3044
+ name: `${prefix}-set-value`,
3045
+ description: `Set the time value of the ${label}.`,
3046
+ inputSchema: {
3047
+ type: 'object',
3048
+ properties: { time: { type: 'string', description: 'Time value (ISO 8601 or HH:mm format).' } },
3049
+ required: ['time']
3050
+ },
3051
+ execute: (args) => {
3052
+ const time = new Date(args['time']);
3053
+ zone.run(() => {
3054
+ tp.value = time;
3055
+ tp.valueChange.emit(time);
3056
+ tp.cdr.markForCheck();
3057
+ });
3058
+ return { success: true, message: `Time set.` };
3059
+ }
3060
+ }));
3061
+ }
3062
+ if (enabled.has('clear')) {
3063
+ handles.push(mc.registerTool({
3064
+ name: `${prefix}-clear`,
3065
+ description: `Clear the ${label} value.`,
3066
+ inputSchema: { type: 'object', properties: {}, required: [] },
3067
+ execute: () => {
3068
+ zone.run(() => {
3069
+ tp.value = null;
3070
+ tp.valueChange.emit(null);
3071
+ tp.cdr.markForCheck();
3072
+ });
3073
+ return { success: true, message: 'Time cleared.' };
3074
+ }
3075
+ }));
3076
+ }
3077
+ return handles;
3078
+ }
3079
+ buildToolOptions(config, label) {
3080
+ return [
3081
+ { name: 'set-value', description: `Set the time value of the ${label}.`, enabled: true },
3082
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true }
3083
+ ];
3084
+ }
3085
+ }
3086
+ // DateTimePicker
3087
+ /**
3088
+ * @hidden
3089
+ */
3090
+ class DateTimePickerToolAdapter {
3091
+ selector = 'kendo-datetimepicker';
3092
+ registerTools(dtp, config, mc, zone) {
3093
+ const prefix = config.dataName || 'kendo-datetimepicker';
3094
+ const label = config.dataName
3095
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' date time picker'
3096
+ : 'Kendo UI DateTimePicker';
3097
+ const rawOptions = this.buildToolOptions(config, label);
3098
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3099
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3100
+ const handles = [];
3101
+ if (enabled.has('set-value')) {
3102
+ handles.push(mc.registerTool({
3103
+ name: `${prefix}-set-value`,
3104
+ description: `Set the date and time value of the ${label}.`,
3105
+ inputSchema: {
3106
+ type: 'object',
3107
+ properties: { datetime: { type: 'string', description: 'Date/time value (ISO 8601).' } },
3108
+ required: ['datetime']
3109
+ },
3110
+ execute: (args) => {
3111
+ const dt = new Date(args['datetime']);
3112
+ zone.run(() => {
3113
+ dtp.value = dt;
3114
+ dtp.valueChange.emit(dt);
3115
+ dtp.cdr.markForCheck();
3116
+ });
3117
+ return { success: true, message: `Date/time set.` };
3118
+ }
3119
+ }));
3120
+ }
3121
+ if (enabled.has('clear')) {
3122
+ handles.push(mc.registerTool({
3123
+ name: `${prefix}-clear`,
3124
+ description: `Clear the ${label} value.`,
3125
+ inputSchema: { type: 'object', properties: {}, required: [] },
3126
+ execute: () => {
3127
+ zone.run(() => {
3128
+ dtp.value = null;
3129
+ dtp.valueChange.emit(null);
3130
+ dtp.cdr.markForCheck();
3131
+ });
3132
+ return { success: true, message: 'Value cleared.' };
3133
+ }
3134
+ }));
3135
+ }
3136
+ return handles;
3137
+ }
3138
+ buildToolOptions(config, label) {
3139
+ return [
3140
+ { name: 'set-value', description: `Set the date and time value of the ${label}.`, enabled: true },
3141
+ { name: 'clear', description: `Clear the ${label} value.`, enabled: true }
3142
+ ];
3143
+ }
3144
+ }
3145
+ // Calendar
3146
+ /**
3147
+ * @hidden
3148
+ */
3149
+ class CalendarToolAdapter {
3150
+ selector = 'kendo-calendar';
3151
+ registerTools(cal, config, mc, zone) {
3152
+ const prefix = config.dataName || 'kendo-calendar';
3153
+ const label = config.dataName
3154
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' calendar'
3155
+ : 'Kendo UI Calendar';
3156
+ const rawOptions = this.buildToolOptions(config, label);
3157
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3158
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3159
+ const handles = [];
3160
+ if (enabled.has('select-date')) {
3161
+ handles.push(mc.registerTool({
3162
+ name: `${prefix}-select-date`,
3163
+ description: `Select a date in the ${label}.`,
3164
+ inputSchema: {
3165
+ type: 'object',
3166
+ properties: { date: { type: 'string', description: 'Date to select (ISO 8601).' } },
3167
+ required: ['date']
3168
+ },
3169
+ execute: (args) => {
3170
+ const date = new Date(args['date']);
3171
+ zone.run(() => {
3172
+ cal.value = date;
3173
+ cal.valueChange.emit(date);
3174
+ cal.cdr.markForCheck();
3175
+ });
3176
+ return { success: true, message: `Selected ${date.toISOString().split('T')[0]}.` };
3177
+ }
3178
+ }));
3179
+ }
3180
+ if (enabled.has('navigate')) {
3181
+ handles.push(mc.registerTool({
3182
+ name: `${prefix}-navigate`,
3183
+ description: `Navigate the ${label} to a specific date.`,
3184
+ inputSchema: {
3185
+ type: 'object',
3186
+ properties: { date: { type: 'string', description: 'Date to navigate to (ISO 8601).' } },
3187
+ required: ['date']
3188
+ },
3189
+ execute: (args) => {
3190
+ const date = new Date(args['date']);
3191
+ zone.run(() => {
3192
+ cal.focusedDate = date;
3193
+ cal.cdr.markForCheck();
3194
+ });
3195
+ return { success: true, message: `Navigated to ${date.toISOString().split('T')[0]}.` };
3196
+ }
3197
+ }));
3198
+ }
3199
+ return handles;
3200
+ }
3201
+ buildToolOptions(config, label) {
3202
+ return [
3203
+ { name: 'select-date', description: `Select a date in the ${label}.`, enabled: true },
3204
+ { name: 'navigate', description: `Navigate the ${label} to a specific date.`, enabled: true }
3205
+ ];
3206
+ }
3207
+ }
3208
+
3209
+ /**
3210
+ * @hidden
3211
+ *
3212
+ * Helper to build a simple set-value + clear adapter for input components.
3213
+ */
3214
+ function buildSimpleAdapter(selector, defaultPrefix, componentLabel, toolSuffixes, tools) {
3215
+ class Adapter {
3216
+ selector = selector;
3217
+ registerTools(component, config, mc, zone) {
3218
+ const prefix = config.dataName || defaultPrefix;
3219
+ const label = config.dataName
3220
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' ' + componentLabel
3221
+ : 'Kendo UI ' + componentLabel;
3222
+ const rawOptions = toolSuffixes.map(name => ({ name, description: name, enabled: true }));
3223
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3224
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3225
+ return tools(component, prefix, label, mc, zone, enabled);
3226
+ }
3227
+ }
3228
+ return Adapter;
3229
+ }
3230
+ // Slider
3231
+ /** @hidden */
3232
+ const SliderToolAdapter = buildSimpleAdapter('kendo-slider', 'kendo-slider', 'Slider', ['set-value'], (slider, prefix, label, mc, zone, enabled) => {
3233
+ const handles = [];
3234
+ if (enabled.has('set-value')) {
3235
+ handles.push(mc.registerTool({
3236
+ name: `${prefix}-set-value`,
3237
+ description: `Set the value of the ${label}.`,
3238
+ inputSchema: {
3239
+ type: 'object',
3240
+ properties: { value: { type: 'number', description: 'Slider value.' } },
3241
+ required: ['value']
3242
+ },
3243
+ execute: (args) => {
3244
+ zone.run(() => {
3245
+ slider.changeValue(args['value']);
3246
+ });
3247
+ return { success: true, message: `Value set to ${args['value']}.` };
3248
+ }
3249
+ }));
3250
+ }
3251
+ return handles;
3252
+ });
3253
+ // RangeSlider
3254
+ /** @hidden */
3255
+ const RangeSliderToolAdapter = buildSimpleAdapter('kendo-rangeslider', 'kendo-rangeslider', 'RangeSlider', ['set-value'], (rs, prefix, label, mc, zone, enabled) => {
3256
+ const handles = [];
3257
+ if (enabled.has('set-value')) {
3258
+ handles.push(mc.registerTool({
3259
+ name: `${prefix}-set-value`,
3260
+ description: `Set the range value of the ${label}.`,
3261
+ inputSchema: {
3262
+ type: 'object',
3263
+ properties: {
3264
+ start: { type: 'number', description: 'Start value.' },
3265
+ end: { type: 'number', description: 'End value.' }
3266
+ },
3267
+ required: ['start', 'end']
3268
+ },
3269
+ execute: (args) => {
3270
+ zone.run(() => {
3271
+ rs.value = { start: args['start'], end: args['end'] };
3272
+ rs.valueChange.emit({ start: args['start'], end: args['end'] });
3273
+ });
3274
+ return { success: true, message: `Range set to ${args['start']}-${args['end']}.` };
3275
+ }
3276
+ }));
3277
+ }
3278
+ return handles;
3279
+ });
3280
+ // Switch
3281
+ /** @hidden */
3282
+ const SwitchToolAdapter = buildSimpleAdapter('kendo-switch', 'kendo-switch', 'Switch', ['toggle'], (sw, prefix, label, mc, zone, enabled) => {
3283
+ const handles = [];
3284
+ if (enabled.has('toggle')) {
3285
+ handles.push(mc.registerTool({
3286
+ name: `${prefix}-toggle`,
3287
+ description: `Toggle the ${label} on/off.`,
3288
+ inputSchema: { type: 'object', properties: {}, required: [] },
3289
+ execute: () => {
3290
+ const newValue = !sw.checked;
3291
+ zone.run(() => {
3292
+ sw.checked = newValue;
3293
+ sw.changeDetector.markForCheck();
3294
+ sw.valueChange.emit(newValue);
3295
+ });
3296
+ return { success: true, message: `Switched ${newValue ? 'on' : 'off'}.` };
3297
+ }
3298
+ }));
3299
+ }
3300
+ return handles;
3301
+ });
3302
+ // NumericTextBox
3303
+ /** @hidden */
3304
+ const NumericTextBoxToolAdapter = buildSimpleAdapter('kendo-numerictextbox', 'kendo-numerictextbox', 'NumericTextBox', ['set-value', 'increment', 'decrement', 'clear'], (ntb, prefix, label, mc, zone, enabled) => {
3305
+ const handles = [];
3306
+ if (enabled.has('set-value')) {
3307
+ handles.push(mc.registerTool({
3308
+ name: `${prefix}-set-value`,
3309
+ description: `Set the numeric value of the ${label}.`,
3310
+ inputSchema: {
3311
+ type: 'object',
3312
+ properties: { value: { type: 'number', description: 'Numeric value.' } },
3313
+ required: ['value']
3314
+ },
3315
+ execute: (args) => {
3316
+ zone.run(() => {
3317
+ ntb.value = args['value'];
3318
+ ntb.setInputValue();
3319
+ ntb.valueChange.emit(args['value']);
3320
+ });
3321
+ return { success: true, message: `Value set to ${args['value']}.` };
3322
+ }
3323
+ }));
3324
+ }
3325
+ if (enabled.has('increment')) {
3326
+ handles.push(mc.registerTool({
3327
+ name: `${prefix}-increment`,
3328
+ description: `Increment the value of the ${label} by one step.`,
3329
+ inputSchema: { type: 'object', properties: {}, required: [] },
3330
+ execute: () => {
3331
+ const current = ntb.value || 0;
3332
+ const step = ntb.step || 1;
3333
+ const newValue = current + step;
3334
+ zone.run(() => {
3335
+ ntb.value = newValue;
3336
+ ntb.setInputValue();
3337
+ ntb.valueChange.emit(newValue);
3338
+ });
3339
+ return { success: true, message: `Incremented to ${newValue}.` };
3340
+ }
3341
+ }));
3342
+ }
3343
+ if (enabled.has('decrement')) {
3344
+ handles.push(mc.registerTool({
3345
+ name: `${prefix}-decrement`,
3346
+ description: `Decrement the value of the ${label} by one step.`,
3347
+ inputSchema: { type: 'object', properties: {}, required: [] },
3348
+ execute: () => {
3349
+ const current = ntb.value || 0;
3350
+ const step = ntb.step || 1;
3351
+ const newValue = current - step;
3352
+ zone.run(() => {
3353
+ ntb.value = newValue;
3354
+ ntb.setInputValue();
3355
+ ntb.valueChange.emit(newValue);
3356
+ });
3357
+ return { success: true, message: `Decremented to ${newValue}.` };
3358
+ }
3359
+ }));
3360
+ }
3361
+ if (enabled.has('clear')) {
3362
+ handles.push(mc.registerTool({
3363
+ name: `${prefix}-clear`,
3364
+ description: `Clear the ${label} value.`,
3365
+ inputSchema: { type: 'object', properties: {}, required: [] },
3366
+ execute: () => {
3367
+ zone.run(() => {
3368
+ ntb.value = null;
3369
+ ntb.setInputValue();
3370
+ ntb.valueChange.emit(null);
3371
+ });
3372
+ return { success: true, message: 'Value cleared.' };
3373
+ }
3374
+ }));
3375
+ }
3376
+ return handles;
3377
+ });
3378
+ // ColorPicker
3379
+ /** @hidden */
3380
+ const ColorPickerToolAdapter = buildSimpleAdapter('kendo-colorpicker', 'kendo-colorpicker', 'ColorPicker', ['set-value', 'clear'], (cp, prefix, label, mc, zone, enabled) => {
3381
+ const handles = [];
3382
+ if (enabled.has('set-value')) {
3383
+ handles.push(mc.registerTool({
3384
+ name: `${prefix}-set-value`,
3385
+ description: `Set the color value of the ${label}.`,
3386
+ inputSchema: {
3387
+ type: 'object',
3388
+ properties: { color: { type: 'string', description: 'Color value (hex, rgb, or named color).' } },
3389
+ required: ['color']
3390
+ },
3391
+ execute: (args) => {
3392
+ zone.run(() => {
3393
+ cp.value = args['color'];
3394
+ cp.valueChange.emit(args['color']);
3395
+ });
3396
+ return { success: true, message: `Color set to "${args['color']}".` };
3397
+ }
3398
+ }));
3399
+ }
3400
+ if (enabled.has('clear')) {
3401
+ handles.push(mc.registerTool({
3402
+ name: `${prefix}-clear`,
3403
+ description: `Clear the ${label} value.`,
3404
+ inputSchema: { type: 'object', properties: {}, required: [] },
3405
+ execute: () => {
3406
+ zone.run(() => {
3407
+ cp.value = '';
3408
+ cp.valueChange.emit('');
3409
+ });
3410
+ return { success: true, message: 'Color cleared.' };
3411
+ }
3412
+ }));
3413
+ }
3414
+ return handles;
3415
+ });
3416
+ // TextBox
3417
+ /** @hidden */
3418
+ const TextBoxToolAdapter = buildSimpleAdapter('kendo-textbox', 'kendo-textbox', 'TextBox', ['set-value', 'clear'], (tb, prefix, label, mc, zone, enabled) => {
3419
+ const handles = [];
3420
+ if (enabled.has('set-value')) {
3421
+ handles.push(mc.registerTool({
3422
+ name: `${prefix}-set-value`,
3423
+ description: `Set the text value of the ${label}.`,
3424
+ inputSchema: {
3425
+ type: 'object',
3426
+ properties: { text: { type: 'string', description: 'Text to set.' } },
3427
+ required: ['text']
3428
+ },
3429
+ execute: (args) => {
3430
+ zone.run(() => {
3431
+ tb.value = args['text'];
3432
+ tb.valueChange.emit(args['text']);
3433
+ });
3434
+ return { success: true, message: `Value set.` };
3435
+ }
3436
+ }));
3437
+ }
3438
+ if (enabled.has('clear')) {
3439
+ handles.push(mc.registerTool({
3440
+ name: `${prefix}-clear`,
3441
+ description: `Clear the ${label} value.`,
3442
+ inputSchema: { type: 'object', properties: {}, required: [] },
3443
+ execute: () => {
3444
+ zone.run(() => {
3445
+ tb.value = '';
3446
+ tb.valueChange.emit('');
3447
+ });
3448
+ return { success: true, message: 'Value cleared.' };
3449
+ }
3450
+ }));
3451
+ }
3452
+ return handles;
3453
+ });
3454
+ // TextArea
3455
+ /** @hidden */
3456
+ const TextAreaToolAdapter = buildSimpleAdapter('kendo-textarea', 'kendo-textarea', 'TextArea', ['set-value', 'clear'], (ta, prefix, label, mc, zone, enabled) => {
3457
+ const handles = [];
3458
+ if (enabled.has('set-value')) {
3459
+ handles.push(mc.registerTool({
3460
+ name: `${prefix}-set-value`,
3461
+ description: `Set the text value of the ${label}.`,
3462
+ inputSchema: {
3463
+ type: 'object',
3464
+ properties: { text: { type: 'string', description: 'Text to set.' } },
3465
+ required: ['text']
3466
+ },
3467
+ execute: (args) => {
3468
+ zone.run(() => {
3469
+ ta.value = args['text'];
3470
+ ta.valueChange.emit(args['text']);
3471
+ });
3472
+ return { success: true, message: `Value set.` };
3473
+ }
3474
+ }));
3475
+ }
3476
+ if (enabled.has('clear')) {
3477
+ handles.push(mc.registerTool({
3478
+ name: `${prefix}-clear`,
3479
+ description: `Clear the ${label} value.`,
3480
+ inputSchema: { type: 'object', properties: {}, required: [] },
3481
+ execute: () => {
3482
+ zone.run(() => {
3483
+ ta.value = '';
3484
+ ta.valueChange.emit('');
3485
+ });
3486
+ return { success: true, message: 'Value cleared.' };
3487
+ }
3488
+ }));
3489
+ }
3490
+ return handles;
3491
+ });
3492
+ // MaskedTextBox
3493
+ /** @hidden */
3494
+ const MaskedTextBoxToolAdapter = buildSimpleAdapter('kendo-maskedtextbox', 'kendo-maskedtextbox', 'MaskedTextBox', ['set-value', 'clear'], (mtb, prefix, label, mc, zone, enabled) => {
3495
+ const handles = [];
3496
+ if (enabled.has('set-value')) {
3497
+ handles.push(mc.registerTool({
3498
+ name: `${prefix}-set-value`,
3499
+ description: `Set the value of the ${label}.`,
3500
+ inputSchema: {
3501
+ type: 'object',
3502
+ properties: { value: { type: 'string', description: 'Masked value.' } },
3503
+ required: ['value']
3504
+ },
3505
+ execute: (args) => {
3506
+ zone.run(() => {
3507
+ mtb.value = args['value'];
3508
+ mtb.valueChange.emit(args['value']);
3509
+ });
3510
+ return { success: true, message: `Value set.` };
3511
+ }
3512
+ }));
3513
+ }
3514
+ if (enabled.has('clear')) {
3515
+ handles.push(mc.registerTool({
3516
+ name: `${prefix}-clear`,
3517
+ description: `Clear the ${label} value.`,
3518
+ inputSchema: { type: 'object', properties: {}, required: [] },
3519
+ execute: () => {
3520
+ zone.run(() => {
3521
+ mtb.value = '';
3522
+ mtb.valueChange.emit('');
3523
+ });
3524
+ return { success: true, message: 'Value cleared.' };
3525
+ }
3526
+ }));
3527
+ }
3528
+ return handles;
3529
+ });
3530
+ // Signature
3531
+ /** @hidden */
3532
+ const SignatureToolAdapter = buildSimpleAdapter('kendo-signature', 'kendo-signature', 'Signature', ['clear'], (sig, prefix, label, mc, zone, enabled) => {
3533
+ const handles = [];
3534
+ if (enabled.has('clear')) {
3535
+ handles.push(mc.registerTool({
3536
+ name: `${prefix}-clear`,
3537
+ description: `Clear the ${label}.`,
3538
+ inputSchema: { type: 'object', properties: {}, required: [] },
3539
+ execute: () => {
3540
+ zone.run(() => {
3541
+ sig.value = '';
3542
+ sig.valueChange.emit('');
3543
+ });
3544
+ return { success: true, message: 'Signature cleared.' };
3545
+ }
3546
+ }));
3547
+ }
3548
+ return handles;
3549
+ });
3550
+ // Rating
3551
+ /** @hidden */
3552
+ const RatingToolAdapter = buildSimpleAdapter('kendo-rating', 'kendo-rating', 'Rating', ['set-value', 'clear'], (rating, prefix, label, mc, zone, enabled) => {
3553
+ const handles = [];
3554
+ if (enabled.has('set-value')) {
3555
+ handles.push(mc.registerTool({
3556
+ name: `${prefix}-set-value`,
3557
+ description: `Set the rating value of the ${label}.`,
3558
+ inputSchema: {
3559
+ type: 'object',
3560
+ properties: { value: { type: 'number', description: 'Rating value (e.g. 1-5).' } },
3561
+ required: ['value']
3562
+ },
3563
+ execute: (args) => {
3564
+ zone.run(() => {
3565
+ rating.value = args['value'];
3566
+ rating.valueChange.emit(args['value']);
3567
+ });
3568
+ return { success: true, message: `Rating set to ${args['value']}.` };
3569
+ }
3570
+ }));
3571
+ }
3572
+ if (enabled.has('clear')) {
3573
+ handles.push(mc.registerTool({
3574
+ name: `${prefix}-clear`,
3575
+ description: `Clear the ${label} value.`,
3576
+ inputSchema: { type: 'object', properties: {}, required: [] },
3577
+ execute: () => {
3578
+ zone.run(() => {
3579
+ rating.value = null;
3580
+ rating.valueChange.emit(null);
3581
+ });
3582
+ return { success: true, message: 'Rating cleared.' };
3583
+ }
3584
+ }));
3585
+ }
3586
+ return handles;
3587
+ });
3588
+
3589
+ // TabStrip
3590
+ /**
3591
+ * @hidden
3592
+ */
3593
+ class TabStripToolAdapter {
3594
+ selector = 'kendo-tabstrip';
3595
+ registerTools(tabs, config, mc, zone) {
3596
+ const prefix = config.dataName || 'kendo-tabstrip';
3597
+ const label = config.dataName
3598
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' tab strip'
3599
+ : 'Kendo UI TabStrip';
3600
+ const rawOptions = this.buildToolOptions(config, label);
3601
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3602
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3603
+ const handles = [];
3604
+ if (enabled.has('select-tab')) {
3605
+ handles.push(mc.registerTool({
3606
+ name: `${prefix}-select-tab`,
3607
+ description: `Select a tab in the ${label} by title or index.`,
3608
+ inputSchema: {
3609
+ type: 'object',
3610
+ properties: {
3611
+ tab: { type: 'string', description: 'Tab title or 0-based index.' }
3612
+ },
3613
+ required: ['tab']
3614
+ },
3615
+ execute: (args) => {
3616
+ const tab = args['tab'];
3617
+ const idx = parseInt(tab, 10);
3618
+ zone.run(() => {
3619
+ if (!isNaN(idx)) {
3620
+ tabs.selectTab(idx);
3621
+ }
3622
+ else {
3623
+ const items = tabs.tabs?.toArray() || [];
3624
+ const found = items.findIndex((t) => t.title === tab);
3625
+ if (found >= 0) {
3626
+ tabs.selectTab(found);
3627
+ }
3628
+ }
3629
+ });
3630
+ return { success: true, message: `Selected tab "${tab}".` };
3631
+ }
3632
+ }));
3633
+ }
3634
+ return handles;
3635
+ }
3636
+ buildToolOptions(config, label) {
3637
+ return [{ name: 'select-tab', description: `Select a tab in the ${label} by title or index.`, enabled: true }];
3638
+ }
3639
+ }
3640
+ // PanelBar
3641
+ /**
3642
+ * @hidden
3643
+ */
3644
+ class PanelBarToolAdapter {
3645
+ selector = 'kendo-panelbar';
3646
+ registerTools(pb, config, mc, zone) {
3647
+ const prefix = config.dataName || 'kendo-panelbar';
3648
+ const label = config.dataName
3649
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' panel bar'
3650
+ : 'Kendo UI PanelBar';
3651
+ const rawOptions = this.buildToolOptions(config, label);
3652
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3653
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3654
+ const handles = [];
3655
+ if (enabled.has('expand')) {
3656
+ handles.push(mc.registerTool({
3657
+ name: `${prefix}-expand`,
3658
+ description: `Expand a panel in the ${label}.`,
3659
+ inputSchema: {
3660
+ type: 'object',
3661
+ properties: { id: { type: 'string', description: 'Panel identifier or title.' } },
3662
+ required: ['id']
3663
+ },
3664
+ execute: (args) => {
3665
+ const id = args['id'];
3666
+ zone.run(() => {
3667
+ const items = pb.allItems || [];
3668
+ const item = items.find((i) => i.id === id || i.title === id);
3669
+ if (item && !item.expanded) {
3670
+ pb.onItemAction(item);
3671
+ }
3672
+ });
3673
+ return { success: true, message: `Panel "${id}" expanded.` };
3674
+ }
3675
+ }));
3676
+ }
3677
+ if (enabled.has('collapse')) {
3678
+ handles.push(mc.registerTool({
3679
+ name: `${prefix}-collapse`,
3680
+ description: `Collapse a panel in the ${label}.`,
3681
+ inputSchema: {
3682
+ type: 'object',
3683
+ properties: { id: { type: 'string', description: 'Panel identifier or title.' } },
3684
+ required: ['id']
3685
+ },
3686
+ execute: (args) => {
3687
+ const id = args['id'];
3688
+ zone.run(() => {
3689
+ const items = pb.allItems || [];
3690
+ const item = items.find((i) => i.id === id || i.title === id);
3691
+ if (item?.expanded) {
3692
+ pb.onItemAction(item);
3693
+ }
3694
+ });
3695
+ return { success: true, message: `Panel "${id}" collapsed.` };
3696
+ }
3697
+ }));
3698
+ }
3699
+ return handles;
3700
+ }
3701
+ buildToolOptions(config, label) {
3702
+ return [
3703
+ { name: 'expand', description: `Expand a panel in the ${label}.`, enabled: true },
3704
+ { name: 'collapse', description: `Collapse a panel in the ${label}.`, enabled: true }
3705
+ ];
3706
+ }
3707
+ }
3708
+ // Drawer
3709
+ /**
3710
+ * @hidden
3711
+ */
3712
+ class DrawerToolAdapter {
3713
+ selector = 'kendo-drawer';
3714
+ registerTools(drawer, config, mc, zone) {
3715
+ const prefix = config.dataName || 'kendo-drawer';
3716
+ const label = config.dataName
3717
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' drawer'
3718
+ : 'Kendo UI Drawer';
3719
+ const rawOptions = this.buildToolOptions(config, label);
3720
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3721
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3722
+ const handles = [];
3723
+ if (enabled.has('open')) {
3724
+ handles.push(mc.registerTool({
3725
+ name: `${prefix}-open`,
3726
+ description: `Open the ${label}.`,
3727
+ inputSchema: { type: 'object', properties: {}, required: [] },
3728
+ execute: () => {
3729
+ zone.run(() => {
3730
+ drawer.expanded = true;
3731
+ drawer.expandedChange?.emit(true);
3732
+ });
3733
+ return { success: true, message: 'Drawer opened.' };
3734
+ }
3735
+ }));
3736
+ }
3737
+ if (enabled.has('close')) {
3738
+ handles.push(mc.registerTool({
3739
+ name: `${prefix}-close`,
3740
+ description: `Close the ${label}.`,
3741
+ inputSchema: { type: 'object', properties: {}, required: [] },
3742
+ execute: () => {
3743
+ zone.run(() => {
3744
+ drawer.expanded = false;
3745
+ drawer.expandedChange?.emit(false);
3746
+ });
3747
+ return { success: true, message: 'Drawer closed.' };
3748
+ }
3749
+ }));
3750
+ }
3751
+ return handles;
3752
+ }
3753
+ buildToolOptions(config, label) {
3754
+ return [
3755
+ { name: 'open', description: `Open the ${label}.`, enabled: true },
3756
+ { name: 'close', description: `Close the ${label}.`, enabled: true }
3757
+ ];
3758
+ }
3759
+ }
3760
+ // Stepper
3761
+ /**
3762
+ * @hidden
3763
+ */
3764
+ class StepperToolAdapter {
3765
+ selector = 'kendo-stepper';
3766
+ registerTools(stepper, config, mc, zone) {
3767
+ const prefix = config.dataName || 'kendo-stepper';
3768
+ const label = config.dataName
3769
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' stepper'
3770
+ : 'Kendo UI Stepper';
3771
+ const rawOptions = this.buildToolOptions(config, label);
3772
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3773
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3774
+ const handles = [];
3775
+ if (enabled.has('step')) {
3776
+ handles.push(mc.registerTool({
3777
+ name: `${prefix}-step`,
3778
+ description: `Navigate to a step in the ${label} by 0-based index.`,
3779
+ inputSchema: {
3780
+ type: 'object',
3781
+ properties: {
3782
+ step: { type: 'number', description: '0-based step index to navigate to.' }
3783
+ },
3784
+ required: ['step']
3785
+ },
3786
+ execute: (args) => {
3787
+ const step = args['step'];
3788
+ zone.run(() => {
3789
+ stepper.currentStep = step;
3790
+ stepper.activate.emit({ index: step });
3791
+ });
3792
+ return { success: true, message: `Navigated to step ${step}.` };
3793
+ }
3794
+ }));
3795
+ }
3796
+ return handles;
3797
+ }
3798
+ buildToolOptions(config, label) {
3799
+ return [{ name: 'step', description: `Navigate to a step in the ${label} by 0-based index.`, enabled: true }];
3800
+ }
3801
+ }
3802
+
3803
+ // Dialog
3804
+ /**
3805
+ * @hidden
3806
+ */
3807
+ class DialogToolAdapter {
3808
+ selector = 'kendo-dialog';
3809
+ registerTools(dialog, config, mc, zone) {
3810
+ const prefix = config.dataName || 'kendo-dialog';
3811
+ const label = config.dataName
3812
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' dialog'
3813
+ : 'Kendo UI Dialog';
3814
+ const rawOptions = this.buildToolOptions(config, label);
3815
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3816
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3817
+ const handles = [];
3818
+ if (enabled.has('confirm')) {
3819
+ handles.push(mc.registerTool({
3820
+ name: `${prefix}-confirm`,
3821
+ description: `Confirm/accept the ${label}.`,
3822
+ inputSchema: { type: 'object', properties: {}, required: [] },
3823
+ execute: () => {
3824
+ zone.run(() => dialog.action.emit({ text: 'confirm' }));
3825
+ return { success: true, message: 'Dialog confirmed.' };
3826
+ }
3827
+ }));
3828
+ }
3829
+ if (enabled.has('cancel')) {
3830
+ handles.push(mc.registerTool({
3831
+ name: `${prefix}-cancel`,
3832
+ description: `Cancel/close the ${label}.`,
3833
+ inputSchema: { type: 'object', properties: {}, required: [] },
3834
+ execute: () => {
3835
+ zone.run(() => dialog.close.emit());
3836
+ return { success: true, message: 'Dialog cancelled.' };
3837
+ }
3838
+ }));
3839
+ }
3840
+ return handles;
3841
+ }
3842
+ buildToolOptions(config, label) {
3843
+ return [
3844
+ { name: 'confirm', description: `Confirm/accept the ${label}.`, enabled: true },
3845
+ { name: 'cancel', description: `Cancel/close the ${label}.`, enabled: true }
3846
+ ];
3847
+ }
3848
+ }
3849
+ // Window
3850
+ /**
3851
+ * @hidden
3852
+ */
3853
+ class WindowToolAdapter {
3854
+ selector = 'kendo-window';
3855
+ registerTools(win, config, mc, zone) {
3856
+ const prefix = config.dataName || 'kendo-window';
3857
+ const label = config.dataName
3858
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' window'
3859
+ : 'Kendo UI Window';
3860
+ const rawOptions = this.buildToolOptions(config, label);
3861
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3862
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3863
+ const handles = [];
3864
+ if (enabled.has('open')) {
3865
+ handles.push(mc.registerTool({
3866
+ name: `${prefix}-open`,
3867
+ description: `Open/restore the ${label}.`,
3868
+ inputSchema: { type: 'object', properties: {}, required: [] },
3869
+ execute: () => {
3870
+ zone.run(() => {
3871
+ win.state = 'default';
3872
+ win.stateChange?.emit('default');
3873
+ });
3874
+ return { success: true, message: 'Window opened.' };
3875
+ }
3876
+ }));
3877
+ }
3878
+ if (enabled.has('close')) {
3879
+ handles.push(mc.registerTool({
3880
+ name: `${prefix}-close`,
3881
+ description: `Close the ${label}.`,
3882
+ inputSchema: { type: 'object', properties: {}, required: [] },
3883
+ execute: () => {
3884
+ zone.run(() => win.close.emit());
3885
+ return { success: true, message: 'Window closed.' };
3886
+ }
3887
+ }));
3888
+ }
3889
+ if (enabled.has('minimize')) {
3890
+ handles.push(mc.registerTool({
3891
+ name: `${prefix}-minimize`,
3892
+ description: `Minimize the ${label}.`,
3893
+ inputSchema: { type: 'object', properties: {}, required: [] },
3894
+ execute: () => {
3895
+ zone.run(() => {
3896
+ win.state = 'minimized';
3897
+ win.stateChange?.emit('minimized');
3898
+ });
3899
+ return { success: true, message: 'Window minimized.' };
3900
+ }
3901
+ }));
3902
+ }
3903
+ if (enabled.has('maximize')) {
3904
+ handles.push(mc.registerTool({
3905
+ name: `${prefix}-maximize`,
3906
+ description: `Maximize the ${label}.`,
3907
+ inputSchema: { type: 'object', properties: {}, required: [] },
3908
+ execute: () => {
3909
+ zone.run(() => {
3910
+ win.state = 'maximized';
3911
+ win.stateChange?.emit('maximized');
3912
+ });
3913
+ return { success: true, message: 'Window maximized.' };
3914
+ }
3915
+ }));
3916
+ }
3917
+ if (enabled.has('restore')) {
3918
+ handles.push(mc.registerTool({
3919
+ name: `${prefix}-restore`,
3920
+ description: `Restore the ${label} to its default size after being minimized or maximized.`,
3921
+ inputSchema: { type: 'object', properties: {}, required: [] },
3922
+ execute: () => {
3923
+ zone.run(() => {
3924
+ win.state = 'default';
3925
+ win.stateChange?.emit('default');
3926
+ });
3927
+ return { success: true, message: 'Window restored.' };
3928
+ }
3929
+ }));
3930
+ }
3931
+ return handles;
3932
+ }
3933
+ buildToolOptions(config, label) {
3934
+ return [
3935
+ { name: 'open', description: `Open/restore the ${label}.`, enabled: true },
3936
+ { name: 'close', description: `Close the ${label}.`, enabled: true },
3937
+ { name: 'minimize', description: `Minimize the ${label}.`, enabled: true },
3938
+ { name: 'maximize', description: `Maximize the ${label}.`, enabled: true },
3939
+ { name: 'restore', description: `Restore the ${label} to its default size after being minimized or maximized.`, enabled: true }
3940
+ ];
3941
+ }
3942
+ }
3943
+
3944
+ // Chat
3945
+ /**
3946
+ * @hidden
3947
+ */
3948
+ class ChatToolAdapter {
3949
+ selector = 'kendo-chat';
3950
+ registerTools(chat, config, mc, zone) {
3951
+ const prefix = config.dataName || 'kendo-chat';
3952
+ const label = config.dataName
3953
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' chat'
3954
+ : 'Kendo UI Chat';
3955
+ const rawOptions = this.buildToolOptions(config, label);
3956
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
3957
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
3958
+ const handles = [];
3959
+ if (enabled.has('send-message')) {
3960
+ handles.push(mc.registerTool({
3961
+ name: `${prefix}-send-message`,
3962
+ description: `Send a message in the ${label}.`,
3963
+ inputSchema: {
3964
+ type: 'object',
3965
+ properties: { text: { type: 'string', description: 'Message text.' } },
3966
+ required: ['text']
3967
+ },
3968
+ execute: (args) => {
3969
+ const timestamp = new Date();
3970
+ zone.run(() => {
3971
+ const msg = {
3972
+ author: { id: chat.authorId, name: 'User' },
3973
+ text: args['text'],
3974
+ timestamp
3975
+ };
3976
+ const updated = [...(chat.messages || []), msg];
3977
+ chat.messages = updated;
3978
+ if (chat.chatService) {
3979
+ chat.chatService.messages = chat.processedMessages;
3980
+ }
3981
+ chat.sendMessage.emit({ message: msg });
3982
+ });
3983
+ return { success: true, message: `Message sent. Author: "User" (id: ${chat.authorId}), Text: "${args['text']}", Timestamp: ${timestamp.toISOString()}.` };
3984
+ }
3985
+ }));
3986
+ }
3987
+ if (enabled.has('clear')) {
3988
+ handles.push(mc.registerTool({
3989
+ name: `${prefix}-clear`,
3990
+ description: `Clear the ${label} messages.`,
3991
+ inputSchema: { type: 'object', properties: {}, required: [] },
3992
+ execute: () => {
3993
+ zone.run(() => {
3994
+ chat.messages = [];
3995
+ if (chat.chatService) {
3996
+ chat.chatService.messages = [];
3997
+ }
3998
+ });
3999
+ return { success: true, message: 'Chat cleared.' };
4000
+ }
4001
+ }));
4002
+ }
4003
+ return handles;
4004
+ }
4005
+ buildToolOptions(config, label) {
4006
+ return [
4007
+ { name: 'send-message', description: `Send a message in the ${label}.`, enabled: true },
4008
+ { name: 'clear', description: `Clear the ${label} messages.`, enabled: true }
4009
+ ];
4010
+ }
4011
+ }
4012
+ // AIPrompt
4013
+ /**
4014
+ * @hidden
4015
+ */
4016
+ class AIPromptToolAdapter {
4017
+ selector = 'kendo-aiprompt';
4018
+ registerTools(prompt, config, mc, zone) {
4019
+ const prefix = config.dataName || 'kendo-aiprompt';
4020
+ const label = config.dataName
4021
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' AI prompt'
4022
+ : 'Kendo UI AIPrompt';
4023
+ const rawOptions = this.buildToolOptions(config, label);
4024
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4025
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4026
+ const handles = [];
4027
+ if (enabled.has('submit')) {
4028
+ handles.push(mc.registerTool({
4029
+ name: `${prefix}-submit`,
4030
+ description: `Submit a prompt to the ${label}.`,
4031
+ inputSchema: {
4032
+ type: 'object',
4033
+ properties: { text: { type: 'string', description: 'Prompt text.' } },
4034
+ required: ['text']
4035
+ },
4036
+ execute: (args) => {
4037
+ zone.run(() => prompt.promptRequest?.emit(args['text']));
4038
+ return { success: true, message: `Prompt submitted: "${args['text']}".` };
4039
+ }
4040
+ }));
4041
+ }
4042
+ if (enabled.has('clear')) {
4043
+ handles.push(mc.registerTool({
4044
+ name: `${prefix}-clear`,
4045
+ description: `Clear the ${label}.`,
4046
+ inputSchema: { type: 'object', properties: {}, required: [] },
4047
+ execute: () => {
4048
+ return { success: true, message: 'AI Prompt cleared.' };
4049
+ }
4050
+ }));
4051
+ }
4052
+ return handles;
4053
+ }
4054
+ buildToolOptions(config, label) {
4055
+ return [
4056
+ { name: 'submit', description: `Submit a prompt to the ${label}.`, enabled: true },
4057
+ { name: 'clear', description: `Clear the ${label}.`, enabled: true }
4058
+ ];
4059
+ }
4060
+ }
4061
+
4062
+ // TreeView
4063
+ /**
4064
+ * @hidden
4065
+ */
4066
+ class TreeViewToolAdapter {
4067
+ selector = 'kendo-treeview';
4068
+ registerTools(tv, config, mc, zone) {
4069
+ const prefix = config.dataName || 'kendo-treeview';
4070
+ const label = config.dataName
4071
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' tree view'
4072
+ : 'Kendo UI TreeView';
4073
+ const rawOptions = this.buildToolOptions(tv, config, label);
4074
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4075
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4076
+ const handles = [];
4077
+ if (enabled.has('expand')) {
4078
+ handles.push(mc.registerTool({
4079
+ name: `${prefix}-expand`,
4080
+ description: `Expand a node in the ${label}.`,
4081
+ inputSchema: {
4082
+ type: 'object',
4083
+ properties: { id: { type: 'string', description: 'Node identifier to expand.' } },
4084
+ required: ['id']
4085
+ },
4086
+ execute: (args) => {
4087
+ zone.run(() => tv.expand.emit({ dataItem: { id: args['id'] }, index: args['id'] }));
4088
+ return { success: true, message: `Node "${args['id']}" expanded.` };
4089
+ }
4090
+ }));
4091
+ }
4092
+ if (enabled.has('collapse')) {
4093
+ handles.push(mc.registerTool({
4094
+ name: `${prefix}-collapse`,
4095
+ description: `Collapse a node in the ${label}.`,
4096
+ inputSchema: {
4097
+ type: 'object',
4098
+ properties: { id: { type: 'string', description: 'Node identifier to collapse.' } },
4099
+ required: ['id']
4100
+ },
4101
+ execute: (args) => {
4102
+ zone.run(() => tv.collapse.emit({ dataItem: { id: args['id'] }, index: args['id'] }));
4103
+ return { success: true, message: `Node "${args['id']}" collapsed.` };
4104
+ }
4105
+ }));
4106
+ }
4107
+ if (enabled.has('select')) {
4108
+ handles.push(mc.registerTool({
4109
+ name: `${prefix}-select`,
4110
+ description: `Select a node in the ${label}.`,
4111
+ inputSchema: {
4112
+ type: 'object',
4113
+ properties: { id: { type: 'string', description: 'Node identifier to select.' } },
4114
+ required: ['id']
4115
+ },
4116
+ execute: (args) => {
4117
+ zone.run(() => tv.selectionChange.emit({ dataItem: { text: args['id'] }, index: args['id'] }));
4118
+ return { success: true, message: `Node "${args['id']}" selected.` };
4119
+ }
4120
+ }));
4121
+ }
4122
+ if (enabled.has('check') && tv.checkboxes) {
4123
+ handles.push(mc.registerTool({
4124
+ name: `${prefix}-check`,
4125
+ description: `Toggle the checkbox of a node in the ${label}.`,
4126
+ inputSchema: {
4127
+ type: 'object',
4128
+ properties: { id: { type: 'string', description: 'Node identifier to check/uncheck.' } },
4129
+ required: ['id']
4130
+ },
4131
+ execute: (args) => {
4132
+ zone.run(() => {
4133
+ const lookup = tv.itemLookup(args['id']);
4134
+ if (lookup) {
4135
+ tv.checkedChange.emit(lookup);
4136
+ tv.changeDetectorRef.markForCheck();
4137
+ }
4138
+ });
4139
+ return { success: true, message: `Node "${args['id']}" toggled.` };
4140
+ }
4141
+ }));
4142
+ }
4143
+ if (enabled.has('filter') && tv.filterable) {
4144
+ handles.push(mc.registerTool({
4145
+ name: `${prefix}-filter`,
4146
+ description: `Filter nodes in the ${label}.`,
4147
+ inputSchema: {
4148
+ type: 'object',
4149
+ properties: { text: { type: 'string', description: 'Filter text.' } },
4150
+ required: ['text']
4151
+ },
4152
+ execute: (args) => {
4153
+ zone.run(() => {
4154
+ tv.filter = args['text'];
4155
+ tv.filterChange.emit(args['text']);
4156
+ tv.changeDetectorRef.markForCheck();
4157
+ });
4158
+ return { success: true, message: `Filtered by "${args['text']}".` };
4159
+ }
4160
+ }));
4161
+ }
4162
+ return handles;
4163
+ }
4164
+ buildToolOptions(tv, config, label) {
4165
+ return [
4166
+ { name: 'expand', description: `Expand a node in the ${label}.`, enabled: true },
4167
+ { name: 'collapse', description: `Collapse a node in the ${label}.`, enabled: true },
4168
+ { name: 'select', description: `Select a node in the ${label}.`, enabled: true },
4169
+ { name: 'check', description: `Toggle the checkbox of a node in the ${label}.`, enabled: !!tv.checkboxes },
4170
+ { name: 'filter', description: `Filter nodes in the ${label}.`, enabled: !!tv.filterable }
4171
+ ];
4172
+ }
4173
+ }
4174
+ // ListBox
4175
+ /**
4176
+ * @hidden
4177
+ */
4178
+ class ListBoxToolAdapter {
4179
+ selector = 'kendo-listbox';
4180
+ registerTools(lb, config, mc, zone) {
4181
+ const prefix = config.dataName || 'kendo-listbox';
4182
+ const label = config.dataName
4183
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' list box'
4184
+ : 'Kendo UI ListBox';
4185
+ const rawOptions = this.buildToolOptions(config, label);
4186
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4187
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4188
+ const handles = [];
4189
+ if (enabled.has('transfer')) {
4190
+ handles.push(mc.registerTool({
4191
+ name: `${prefix}-transfer`,
4192
+ description: `Transfer items from the ${label} to the connected list box. Selects items by index first, then transfers.`,
4193
+ inputSchema: {
4194
+ type: 'object',
4195
+ properties: {
4196
+ indices: { type: 'array', items: { type: 'number' }, description: 'Zero-based indices of items to transfer.' },
4197
+ direction: { type: 'string', enum: ['to', 'from'], description: 'Transfer direction: "to" or "from".' }
4198
+ },
4199
+ required: ['indices']
4200
+ },
4201
+ execute: (args) => {
4202
+ zone.run(() => {
4203
+ const indices = args['indices'] || [0];
4204
+ lb.select(indices);
4205
+ const action = args['direction'] === 'from' ? 'transferFrom' : 'transferTo';
4206
+ lb.performAction(action);
4207
+ });
4208
+ return { success: true, message: `Transferred item(s) at indices ${args['indices']}.` };
4209
+ }
4210
+ }));
4211
+ }
4212
+ if (enabled.has('reorder')) {
4213
+ handles.push(mc.registerTool({
4214
+ name: `${prefix}-reorder`,
4215
+ description: `Reorder an item in the ${label}. Selects the item by index first, then moves it.`,
4216
+ inputSchema: {
4217
+ type: 'object',
4218
+ properties: {
4219
+ index: { type: 'number', description: 'Zero-based index of item to move.' },
4220
+ direction: { type: 'string', enum: ['up', 'down'], description: 'Direction to move.' }
4221
+ },
4222
+ required: ['index', 'direction']
4223
+ },
4224
+ execute: (args) => {
4225
+ zone.run(() => {
4226
+ lb.select([args['index']]);
4227
+ lb.performAction((args['direction'] === 'up' ? 'moveUp' : 'moveDown'));
4228
+ });
4229
+ return { success: true, message: `Item at index ${args['index']} moved ${args['direction']}.` };
4230
+ }
4231
+ }));
4232
+ }
4233
+ return handles;
4234
+ }
4235
+ buildToolOptions(config, label) {
4236
+ return [
4237
+ { name: 'transfer', description: `Transfer items in the ${label}.`, enabled: true },
4238
+ { name: 'reorder', description: `Reorder an item in the ${label}.`, enabled: true }
4239
+ ];
4240
+ }
4241
+ }
4242
+ // ListView
4243
+ /**
4244
+ * @hidden
4245
+ */
4246
+ class ListViewToolAdapter {
4247
+ selector = 'kendo-listview';
4248
+ registerTools(lv, config, mc, zone) {
4249
+ const prefix = config.dataName || 'kendo-listview';
4250
+ const label = config.dataName
4251
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' list view'
4252
+ : 'Kendo UI ListView';
4253
+ const rawOptions = this.buildToolOptions(lv, config, label);
4254
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4255
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4256
+ const handles = [];
4257
+ if (enabled.has('page') && lv.pageable) {
4258
+ handles.push(mc.registerTool({
4259
+ name: `${prefix}-page`,
4260
+ description: `Navigate to a page in the ${label}.`,
4261
+ inputSchema: {
4262
+ type: 'object',
4263
+ properties: { page: { type: 'number', description: '1-based page number.' } },
4264
+ required: ['page']
4265
+ },
4266
+ execute: (args) => {
4267
+ zone.run(() => lv.pageChange.emit({ skip: (args['page'] - 1) * (lv.pageSize || 10), take: lv.pageSize || 10 }));
4268
+ return { success: true, message: `Navigated to page ${args['page']}.` };
4269
+ }
4270
+ }));
4271
+ }
4272
+ return handles;
4273
+ }
4274
+ buildToolOptions(lv, config, label) {
4275
+ return [{ name: 'page', description: `Navigate to a page in the ${label}.`, enabled: !!lv.pageable }];
4276
+ }
4277
+ }
4278
+ // Menu
4279
+ /**
4280
+ * @hidden
4281
+ */
4282
+ class MenuToolAdapter {
4283
+ selector = 'kendo-menu';
4284
+ registerTools(menu, config, mc, zone) {
4285
+ const prefix = config.dataName || 'kendo-menu';
4286
+ const label = config.dataName
4287
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' menu'
4288
+ : 'Kendo UI Menu';
4289
+ const rawOptions = this.buildToolOptions(config, label);
4290
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4291
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4292
+ const handles = [];
4293
+ if (enabled.has('select-item')) {
4294
+ handles.push(mc.registerTool({
4295
+ name: `${prefix}-select-item`,
4296
+ description: `Select a menu item in the ${label} by path.`,
4297
+ inputSchema: {
4298
+ type: 'object',
4299
+ properties: { path: { type: 'string', description: "Menu item path (e.g. 'File > Export > PDF')." } },
4300
+ required: ['path']
4301
+ },
4302
+ execute: (args) => {
4303
+ zone.run(() => menu.select.emit({ item: { text: args['path'] } }));
4304
+ return { success: true, message: `Selected "${args['path']}".` };
4305
+ }
4306
+ }));
4307
+ }
4308
+ return handles;
4309
+ }
4310
+ buildToolOptions(config, label) {
4311
+ return [{ name: 'select-item', description: `Select a menu item in the ${label} by path.`, enabled: true }];
4312
+ }
4313
+ }
4314
+ // ScrollView
4315
+ /**
4316
+ * @hidden
4317
+ */
4318
+ class ScrollViewToolAdapter {
4319
+ selector = 'kendo-scrollview';
4320
+ registerTools(sv, config, mc, zone) {
4321
+ const prefix = config.dataName || 'kendo-scrollview';
4322
+ const label = config.dataName
4323
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' scroll view'
4324
+ : 'Kendo UI ScrollView';
4325
+ const rawOptions = this.buildToolOptions(config, label);
4326
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4327
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4328
+ const handles = [];
4329
+ if (enabled.has('next')) {
4330
+ handles.push(mc.registerTool({
4331
+ name: `${prefix}-next`,
4332
+ description: `Navigate to the next slide in the ${label}.`,
4333
+ inputSchema: { type: 'object', properties: {}, required: [] },
4334
+ execute: () => {
4335
+ zone.run(() => sv.next());
4336
+ return { success: true, message: 'Moved to next slide.' };
4337
+ }
4338
+ }));
4339
+ }
4340
+ if (enabled.has('previous')) {
4341
+ handles.push(mc.registerTool({
4342
+ name: `${prefix}-previous`,
4343
+ description: `Navigate to the previous slide in the ${label}.`,
4344
+ inputSchema: { type: 'object', properties: {}, required: [] },
4345
+ execute: () => {
4346
+ zone.run(() => sv.prev());
4347
+ return { success: true, message: 'Moved to previous slide.' };
4348
+ }
4349
+ }));
4350
+ }
4351
+ if (enabled.has('navigate')) {
4352
+ handles.push(mc.registerTool({
4353
+ name: `${prefix}-navigate`,
4354
+ description: `Navigate to a specific slide in the ${label}.`,
4355
+ inputSchema: {
4356
+ type: 'object',
4357
+ properties: { index: { type: 'number', description: '0-based slide index.' } },
4358
+ required: ['index']
4359
+ },
4360
+ execute: (args) => {
4361
+ zone.run(() => sv.activeIndex = args['index']);
4362
+ return { success: true, message: `Navigated to slide ${args['index']}.` };
4363
+ }
4364
+ }));
4365
+ }
4366
+ return handles;
4367
+ }
4368
+ buildToolOptions(config, label) {
4369
+ return [
4370
+ { name: 'next', description: `Navigate to the next slide in the ${label}.`, enabled: true },
4371
+ { name: 'previous', description: `Navigate to the previous slide in the ${label}.`, enabled: true },
4372
+ { name: 'navigate', description: `Navigate to a specific slide in the ${label}.`, enabled: true }
4373
+ ];
4374
+ }
4375
+ }
4376
+ // Sortable
4377
+ /**
4378
+ * @hidden
4379
+ */
4380
+ class SortableToolAdapter {
4381
+ selector = 'kendo-sortable';
4382
+ registerTools(sortable, config, mc, zone) {
4383
+ const prefix = config.dataName || 'kendo-sortable';
4384
+ const label = config.dataName
4385
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' sortable'
4386
+ : 'Kendo UI Sortable';
4387
+ const rawOptions = this.buildToolOptions(config, label);
4388
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4389
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4390
+ const handles = [];
4391
+ if (enabled.has('reorder')) {
4392
+ handles.push(mc.registerTool({
4393
+ name: `${prefix}-reorder`,
4394
+ description: `Reorder items in the ${label}.`,
4395
+ inputSchema: {
4396
+ type: 'object',
4397
+ properties: {
4398
+ fromIndex: { type: 'number', description: 'Source index.' },
4399
+ toIndex: { type: 'number', description: 'Destination index.' }
4400
+ },
4401
+ required: ['fromIndex', 'toIndex']
4402
+ },
4403
+ execute: (args) => {
4404
+ zone.run(() => sortable.moveItem(args['fromIndex'], args['toIndex']));
4405
+ return { success: true, message: `Moved item from ${args['fromIndex']} to ${args['toIndex']}.` };
4406
+ }
4407
+ }));
4408
+ }
4409
+ return handles;
4410
+ }
4411
+ buildToolOptions(config, label) {
4412
+ return [{ name: 'reorder', description: `Reorder items in the ${label}.`, enabled: true }];
4413
+ }
4414
+ }
4415
+ // Map
4416
+ /**
4417
+ * @hidden
4418
+ */
4419
+ class MapToolAdapter {
4420
+ selector = 'kendo-map';
4421
+ registerTools(map, config, mc, zone) {
4422
+ const prefix = config.dataName || 'kendo-map';
4423
+ const label = config.dataName
4424
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' map'
4425
+ : 'Kendo UI Map';
4426
+ const rawOptions = this.buildToolOptions(config, label);
4427
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4428
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4429
+ const handles = [];
4430
+ if (enabled.has('set-center')) {
4431
+ handles.push(mc.registerTool({
4432
+ name: `${prefix}-set-center`,
4433
+ description: `Set the center of the ${label}.`,
4434
+ inputSchema: {
4435
+ type: 'object',
4436
+ properties: {
4437
+ lat: { type: 'number', description: 'Latitude.' },
4438
+ lng: { type: 'number', description: 'Longitude.' }
4439
+ },
4440
+ required: ['lat', 'lng']
4441
+ },
4442
+ execute: (args) => {
4443
+ zone.run(() => {
4444
+ map.center = [args['lat'], args['lng']];
4445
+ if (map.instance) {
4446
+ map.instance.center([args['lat'], args['lng']]);
4447
+ }
4448
+ });
4449
+ return { success: true, message: `Center set to [${args['lat']}, ${args['lng']}].` };
4450
+ }
4451
+ }));
4452
+ }
4453
+ if (enabled.has('set-zoom')) {
4454
+ handles.push(mc.registerTool({
4455
+ name: `${prefix}-set-zoom`,
4456
+ description: `Set the zoom level of the ${label}.`,
4457
+ inputSchema: {
4458
+ type: 'object',
4459
+ properties: { zoom: { type: 'number', description: 'Zoom level (1-20).' } },
4460
+ required: ['zoom']
4461
+ },
4462
+ execute: (args) => {
4463
+ zone.run(() => {
4464
+ map.zoom = args['zoom'];
4465
+ if (map.instance) {
4466
+ map.instance.zoom(args['zoom']);
4467
+ }
4468
+ });
4469
+ return { success: true, message: `Zoom set to ${args['zoom']}.` };
4470
+ }
4471
+ }));
4472
+ }
4473
+ if (enabled.has('add-marker')) {
4474
+ handles.push(mc.registerTool({
4475
+ name: `${prefix}-add-marker`,
4476
+ description: `Add a marker to the ${label}.`,
4477
+ inputSchema: {
4478
+ type: 'object',
4479
+ properties: {
4480
+ lat: { type: 'number', description: 'Latitude.' },
4481
+ lng: { type: 'number', description: 'Longitude.' },
4482
+ title: { type: 'string', description: 'Marker tooltip (optional).' }
4483
+ },
4484
+ required: ['lat', 'lng']
4485
+ },
4486
+ execute: (args) => {
4487
+ zone.run(() => {
4488
+ map.instance?.markers?.add({ location: [args['lat'], args['lng']], tooltip: { content: args['title'] || '' } });
4489
+ });
4490
+ return { success: true, message: `Marker added at [${args['lat']}, ${args['lng']}].` };
4491
+ }
4492
+ }));
4493
+ }
4494
+ if (enabled.has('clear-markers')) {
4495
+ handles.push(mc.registerTool({
4496
+ name: `${prefix}-clear-markers`,
4497
+ description: `Clear all markers from the ${label}.`,
4498
+ inputSchema: { type: 'object', properties: {}, required: [] },
4499
+ execute: () => {
4500
+ zone.run(() => {
4501
+ map.instance?.markers?.clear();
4502
+ });
4503
+ return { success: true, message: 'Markers cleared.' };
4504
+ }
4505
+ }));
4506
+ }
4507
+ return handles;
4508
+ }
4509
+ buildToolOptions(config, label) {
4510
+ return [
4511
+ { name: 'set-center', description: `Set the center of the ${label}.`, enabled: true },
4512
+ { name: 'set-zoom', description: `Set the zoom level of the ${label}.`, enabled: true },
4513
+ { name: 'add-marker', description: `Add a marker to the ${label}.`, enabled: true },
4514
+ { name: 'clear-markers', description: `Clear all markers from the ${label}.`, enabled: true }
4515
+ ];
4516
+ }
4517
+ }
4518
+ // Notification
4519
+ /**
4520
+ * @hidden
4521
+ */
4522
+ class NotificationToolAdapter {
4523
+ selector = 'kendo-notification';
4524
+ registerTools(notif, config, mc, zone) {
4525
+ const prefix = config.dataName || 'kendo-notification';
4526
+ const label = config.dataName
4527
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' notification'
4528
+ : 'Kendo UI Notification';
4529
+ const rawOptions = this.buildToolOptions(config, label);
4530
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4531
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4532
+ const handles = [];
4533
+ if (enabled.has('show')) {
4534
+ handles.push(mc.registerTool({
4535
+ name: `${prefix}-show`,
4536
+ description: `Show a notification in the ${label}.`,
4537
+ inputSchema: {
4538
+ type: 'object',
4539
+ properties: {
4540
+ text: { type: 'string', description: 'Notification text.' },
4541
+ type: { type: 'string', description: 'Notification type (success, info, warning, error).' }
4542
+ },
4543
+ required: ['text']
4544
+ },
4545
+ execute: (args) => {
4546
+ zone.run(() => {
4547
+ notif.templateString = args['text'];
4548
+ notif.type = { style: args['type'] || 'info', icon: true };
4549
+ notif.cdr.markForCheck();
4550
+ });
4551
+ return { success: true, message: `Notification shown: "${args['text']}".` };
4552
+ }
4553
+ }));
4554
+ }
4555
+ if (enabled.has('dismiss')) {
4556
+ handles.push(mc.registerTool({
4557
+ name: `${prefix}-dismiss`,
4558
+ description: `Dismiss all notifications in the ${label}.`,
4559
+ inputSchema: { type: 'object', properties: {}, required: [] },
4560
+ execute: () => {
4561
+ zone.run(() => notif.hide());
4562
+ return { success: true, message: 'Notifications dismissed.' };
4563
+ }
4564
+ }));
4565
+ }
4566
+ return handles;
4567
+ }
4568
+ buildToolOptions(config, label) {
4569
+ return [
4570
+ { name: 'show', description: `Show a notification in the ${label}.`, enabled: true },
4571
+ { name: 'dismiss', description: `Dismiss all notifications in the ${label}.`, enabled: true }
4572
+ ];
4573
+ }
4574
+ }
4575
+ // Upload
4576
+ /**
4577
+ * @hidden
4578
+ */
4579
+ class UploadToolAdapter {
4580
+ selector = 'kendo-upload';
4581
+ registerTools(upload, config, mc, zone) {
4582
+ const prefix = config.dataName || 'kendo-upload';
4583
+ const label = config.dataName
4584
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' upload'
4585
+ : 'Kendo UI Upload';
4586
+ const rawOptions = this.buildToolOptions(config, label);
4587
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4588
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4589
+ const handles = [];
4590
+ if (enabled.has('clear')) {
4591
+ handles.push(mc.registerTool({
4592
+ name: `${prefix}-clear`,
4593
+ description: `Clear all files from the ${label}.`,
4594
+ inputSchema: { type: 'object', properties: {}, required: [] },
4595
+ execute: () => {
4596
+ zone.run(() => upload.clearFiles());
4597
+ return { success: true, message: 'Files cleared.' };
4598
+ }
4599
+ }));
4600
+ }
4601
+ return handles;
4602
+ }
4603
+ buildToolOptions(config, label) {
4604
+ return [{ name: 'clear', description: `Clear all files from the ${label}.`, enabled: true }];
4605
+ }
4606
+ }
4607
+ // Button
4608
+ /**
4609
+ * @hidden
4610
+ */
4611
+ class ButtonToolAdapter {
4612
+ selector = '[kendoButton]';
4613
+ registerTools(btn, config, mc, zone) {
4614
+ const prefix = config.dataName || 'kendo-button';
4615
+ const label = config.dataName
4616
+ ? config.dataName.charAt(0).toUpperCase() + config.dataName.slice(1) + ' button'
4617
+ : 'Kendo UI Button';
4618
+ const rawOptions = this.buildToolOptions(config, label);
4619
+ const finalOptions = config.tools ? config.tools(rawOptions) : rawOptions;
4620
+ const enabled = new Set(finalOptions.filter(t => t.enabled !== false).map(t => t.name));
4621
+ const handles = [];
4622
+ if (enabled.has('click')) {
4623
+ handles.push(mc.registerTool({
4624
+ name: `${prefix}-click`,
4625
+ description: `Click the ${label}.`,
4626
+ inputSchema: { type: 'object', properties: {}, required: [] },
4627
+ execute: () => {
4628
+ if (btn.disabled) {
4629
+ return { success: false, message: 'Button is disabled.' };
4630
+ }
4631
+ zone.run(() => btn.element.click());
4632
+ return { success: true, message: `Clicked the ${label}.` };
4633
+ }
4634
+ }));
4635
+ }
4636
+ return handles;
4637
+ }
4638
+ buildToolOptions(config, label) {
4639
+ return [{ name: 'click', description: `Click the ${label}.`, enabled: true }];
4640
+ }
4641
+ }
4642
+
4643
+ const ADAPTERS = [
4644
+ new GridToolAdapter(),
4645
+ new TreeListToolAdapter(),
4646
+ new SchedulerToolAdapter(),
4647
+ new ChartToolAdapter(),
4648
+ new EditorToolAdapter(),
4649
+ new SpreadsheetToolAdapter(),
4650
+ new GanttToolAdapter(),
4651
+ new PivotGridToolAdapter(),
4652
+ new DropDownListToolAdapter(),
4653
+ new ComboBoxToolAdapter(),
4654
+ new AutoCompleteToolAdapter(),
4655
+ new MultiSelectToolAdapter(),
4656
+ new DropDownTreeToolAdapter(),
4657
+ new MultiColumnComboBoxToolAdapter(),
4658
+ new DatePickerToolAdapter(),
4659
+ new DateRangeToolAdapter(),
4660
+ new TimePickerToolAdapter(),
4661
+ new DateTimePickerToolAdapter(),
4662
+ new CalendarToolAdapter(),
4663
+ new SliderToolAdapter(),
4664
+ new RangeSliderToolAdapter(),
4665
+ new SwitchToolAdapter(),
4666
+ new NumericTextBoxToolAdapter(),
4667
+ new ColorPickerToolAdapter(),
4668
+ new TextBoxToolAdapter(),
4669
+ new TextAreaToolAdapter(),
4670
+ new MaskedTextBoxToolAdapter(),
4671
+ new SignatureToolAdapter(),
4672
+ new RatingToolAdapter(),
4673
+ new TabStripToolAdapter(),
4674
+ new PanelBarToolAdapter(),
4675
+ new DrawerToolAdapter(),
4676
+ new StepperToolAdapter(),
4677
+ new DialogToolAdapter(),
4678
+ new WindowToolAdapter(),
4679
+ new ChatToolAdapter(),
4680
+ new AIPromptToolAdapter(),
4681
+ new TreeViewToolAdapter(),
4682
+ new ListBoxToolAdapter(),
4683
+ new ListViewToolAdapter(),
4684
+ new MenuToolAdapter(),
4685
+ new ScrollViewToolAdapter(),
4686
+ new SortableToolAdapter(),
4687
+ new MapToolAdapter(),
4688
+ new NotificationToolAdapter(),
4689
+ new UploadToolAdapter(),
4690
+ new ButtonToolAdapter()
4691
+ ];
4692
+ /**
4693
+ * Registers Web MCP tools for the host Kendo Angular component.
4694
+ *
4695
+ * Apply this attribute directive to any supported Kendo component to expose
4696
+ * AI-discoverable tools through the browser's Web MCP protocol (Chrome 146+).
4697
+ *
4698
+ * @example
4699
+ * ```html
4700
+ * <kendo-grid [data]="data" [kendoWebMcp]="true"></kendo-grid>
4701
+ * ```
4702
+ *
4703
+ * @example
4704
+ * ```html
4705
+ * <kendo-grid [data]="data" [kendoWebMcp]="{ dataName: 'orders' }"></kendo-grid>
4706
+ * ```
4707
+ */
4708
+ class WebMcpDirective {
4709
+ el;
4710
+ ngZone;
4711
+ hostComponent;
4712
+ /**
4713
+ * Enables or configures the Web MCP tools for the host component.
4714
+ *
4715
+ * - `true` — registers all supported tools with defaults.
4716
+ * - `WebMcpConfig` — fine-grained control over which tool groups are enabled.
4717
+ * - `false` — disables all tools.
4718
+ */
4719
+ config = true;
4720
+ registrations = [];
4721
+ modelContext = null;
4722
+ constructor(el, ngZone, hostComponent) {
4723
+ this.el = el;
4724
+ this.ngZone = ngZone;
4725
+ this.hostComponent = hostComponent;
4726
+ validatePackage(packageMetadata);
4727
+ }
4728
+ ngOnInit() {
4729
+ if (!this.isBrowserWithMcp()) {
4730
+ return;
4731
+ }
4732
+ this.modelContext = navigator.modelContext;
4733
+ this.registerAll();
4734
+ }
4735
+ ngOnChanges(changes) {
4736
+ if (changes['config'] && !changes['config'].firstChange) {
4737
+ this.unregisterAll();
4738
+ this.registerAll();
4739
+ }
4740
+ }
4741
+ ngOnDestroy() {
4742
+ this.unregisterAll();
4743
+ }
4744
+ registerAll() {
4745
+ const resolved = resolveConfig(this.config);
4746
+ if (!resolved || !this.modelContext || !this.hostComponent) {
4747
+ return;
4748
+ }
4749
+ const mc = this.modelContext;
4750
+ const adapter = this.findAdapter();
4751
+ if (!adapter) {
4752
+ return;
4753
+ }
4754
+ this.ngZone.runOutsideAngular(() => {
4755
+ const handles = adapter.registerTools(this.hostComponent, resolved, mc, this.ngZone);
4756
+ this.registrations.push(...handles);
4757
+ });
4758
+ }
4759
+ findAdapter() {
4760
+ const el = this.el.nativeElement;
4761
+ const tag = el.tagName.toLowerCase();
4762
+ for (const adapter of ADAPTERS) {
4763
+ const sel = adapter.selector;
4764
+ if (sel.startsWith('[')) {
4765
+ // Attribute selector — check for the attribute on the host element
4766
+ const attr = sel.slice(1, -1);
4767
+ if (el.hasAttribute(attr) || el.hasAttribute(attr.toLowerCase())) {
4768
+ return adapter;
4769
+ }
4770
+ }
4771
+ else if (sel === tag) {
4772
+ return adapter;
4773
+ }
4774
+ }
4775
+ return null;
4776
+ }
4777
+ unregisterAll() {
4778
+ for (const reg of this.registrations) {
4779
+ reg.unregister();
4780
+ }
4781
+ this.registrations = [];
4782
+ }
4783
+ isBrowserWithMcp() {
4784
+ return isDocumentAvailable()
4785
+ && typeof navigator !== 'undefined'
4786
+ && !!navigator.modelContext;
4787
+ }
4788
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: WebMcpDirective, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: KENDO_WEBMCP_HOST, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
4789
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.20", type: WebMcpDirective, isStandalone: true, selector: "[kendoWebMcp]", inputs: { config: ["kendoWebMcp", "config"] }, usesOnChanges: true, ngImport: i0 });
4790
+ }
4791
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: WebMcpDirective, decorators: [{
4792
+ type: Directive,
4793
+ args: [{
4794
+ selector: '[kendoWebMcp]',
4795
+ standalone: true
4796
+ }]
4797
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: undefined, decorators: [{
4798
+ type: Host
4799
+ }, {
4800
+ type: Optional
4801
+ }, {
4802
+ type: Inject,
4803
+ args: [KENDO_WEBMCP_HOST]
4804
+ }] }], propDecorators: { config: [{
4805
+ type: Input,
4806
+ args: ['kendoWebMcp']
4807
+ }] } });
4808
+
4809
+ /**
4810
+ * Use the `KENDO_WEBMCP` utility array to add Web MCP support to a standalone Angular component.
4811
+ *
4812
+ * @example
4813
+ * ```typescript
4814
+ * import { Component } from '@angular/core';
4815
+ * import { KENDO_WEBMCP } from '@progress/kendo-angular-webmcp';
4816
+ * import { KENDO_GRID } from '@progress/kendo-angular-grid';
4817
+ *
4818
+ * @Component({
4819
+ * standalone: true,
4820
+ * imports: [KENDO_WEBMCP, KENDO_GRID],
4821
+ * template: `
4822
+ * <kendo-grid [data]="data" [kendoWebMcp]="true"></kendo-grid>
4823
+ * `,
4824
+ * })
4825
+ * export class AppComponent {
4826
+ * data = [...];
4827
+ * }
4828
+ * ```
4829
+ */
4830
+ const KENDO_WEBMCP = [
4831
+ WebMcpDirective
4832
+ ];
4833
+
4834
+ //IMPORTANT: NgModule export kept for backwards compatibility
4835
+ /**
4836
+ * Represents the [NgModule](link:site.data.urls.angular['ngmoduleapi'])
4837
+ * definition for the Web MCP directive.
4838
+ *
4839
+ * @example
4840
+ * ```typescript
4841
+ * import { NgModule } from '@angular/core';
4842
+ * import { BrowserModule } from '@angular/platform-browser';
4843
+ * import { WebMcpModule } from '@progress/kendo-angular-webmcp';
4844
+ * import { GridModule } from '@progress/kendo-angular-grid';
4845
+ * import { AppComponent } from './app.component';
4846
+ *
4847
+ * @NgModule({
4848
+ * declarations: [AppComponent],
4849
+ * imports: [BrowserModule, GridModule, WebMcpModule],
4850
+ * bootstrap: [AppComponent]
4851
+ * })
4852
+ * export class AppModule {}
4853
+ * ```
4854
+ */
4855
+ class WebMcpModule {
4856
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: WebMcpModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
4857
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.20", ngImport: i0, type: WebMcpModule, imports: [WebMcpDirective], exports: [WebMcpDirective] });
4858
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: WebMcpModule });
4859
+ }
4860
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: WebMcpModule, decorators: [{
4861
+ type: NgModule,
4862
+ args: [{
4863
+ imports: [...KENDO_WEBMCP],
4864
+ exports: [...KENDO_WEBMCP]
4865
+ }]
4866
+ }] });
4867
+
4868
+ /**
4869
+ * Generated bundle index. Do not edit.
4870
+ */
4871
+
4872
+ export { KENDO_WEBMCP, WebMcpDirective, WebMcpModule };
4873
+