gbs-add-block 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gbs/skills/gbs-components/SKILL.md +190 -134
- package/.gbs/skills/gbs-components/references/install.md +25 -3
- package/.gbs/skills/gbs-components/references/styling.md +246 -207
- package/CHANGELOG.md +74 -0
- package/README.md +154 -23
- package/index.cjs +212 -3
- package/package.json +41 -10
- package/schema/passport-v1.schema.json +204 -0
- package/source/beta-components/accordion/passport.json +259 -0
- package/source/beta-components/accordion/styles.css +207 -208
- package/source/beta-components/alert/passport.json +250 -0
- package/source/beta-components/alert/styles.css +154 -155
- package/source/beta-components/avatar/passport.json +294 -0
- package/source/beta-components/avatar/styles.css +225 -226
- package/source/beta-components/badge/passport.json +332 -0
- package/source/beta-components/badge/styles.css +203 -204
- package/source/beta-components/breadcrumb/passport.json +243 -0
- package/source/beta-components/breadcrumb/styles.css +138 -140
- package/source/beta-components/button/passport.json +402 -0
- package/source/beta-components/button/passport.manual.json +31 -0
- package/source/beta-components/button/styles.css +232 -234
- package/source/beta-components/card/passport.json +337 -0
- package/source/beta-components/card/styles.css +230 -232
- package/source/beta-components/checkbox/passport.json +456 -0
- package/source/beta-components/checkbox/styles.css +211 -213
- package/source/beta-components/combobox/passport.json +456 -0
- package/source/beta-components/combobox/styles.css +419 -417
- package/source/beta-components/data-grid/agent/coerce.ts +368 -0
- package/source/beta-components/data-grid/agent/contract.ts +410 -0
- package/source/beta-components/data-grid/agent/dataset.ts +92 -0
- package/source/beta-components/data-grid/agent/engine.ts +470 -0
- package/source/beta-components/data-grid/agent/executors.ts +155 -0
- package/source/beta-components/data-grid/agent/index.ts +79 -0
- package/source/beta-components/data-grid/agent/intent.ts +324 -0
- package/source/beta-components/data-grid/agent/operations.ts +335 -0
- package/source/beta-components/data-grid/agent/validate.ts +630 -0
- package/source/beta-components/data-grid/agent/webmcp.ts +107 -0
- package/source/beta-components/data-grid/index.ts +14 -7
- package/source/beta-components/data-grid/passport.json +1051 -0
- package/source/beta-components/data-grid/passport.manual.json +255 -0
- package/source/beta-components/data-grid/react/AskGrid.tsx +164 -0
- package/source/beta-components/data-grid/react/DataGrid.tsx +39 -0
- package/source/beta-components/data-grid/styles.css +874 -716
- package/source/beta-components/date-picker/passport.json +407 -0
- package/source/beta-components/date-picker/styles.css +445 -446
- package/source/beta-components/dialog/passport.json +344 -0
- package/source/beta-components/dialog/styles.css +280 -279
- package/source/beta-components/file-uploader/passport.json +518 -0
- package/source/beta-components/file-uploader/styles.css +394 -396
- package/source/beta-components/input/passport.json +536 -0
- package/source/beta-components/input/styles.css +295 -297
- package/source/beta-components/menu/passport.json +322 -0
- package/source/beta-components/menu/styles.css +224 -223
- package/source/beta-components/modal/passport.json +289 -0
- package/source/beta-components/modal/styles.css +241 -240
- package/source/beta-components/number-input/passport.json +541 -0
- package/source/beta-components/number-input/styles.css +230 -231
- package/source/beta-components/popover/passport.json +238 -0
- package/source/beta-components/popover/styles.css +148 -147
- package/source/beta-components/progress/passport.json +270 -0
- package/source/beta-components/progress/styles.css +200 -201
- package/source/beta-components/radio-group/passport.json +477 -0
- package/source/beta-components/radio-group/styles.css +269 -270
- package/source/beta-components/shared/core/agent/adapter.ts +65 -0
- package/source/beta-components/shared/core/agent/history.ts +120 -0
- package/source/beta-components/shared/core/agent/index.ts +46 -0
- package/source/beta-components/shared/core/agent/numbers.ts +217 -0
- package/source/beta-components/shared/core/agent/schema.ts +180 -0
- package/source/beta-components/shared/core/agent/types.ts +169 -0
- package/source/beta-components/shared/core/agent/webmcp.ts +328 -0
- package/source/beta-components/shared/index.ts +9 -0
- package/source/beta-components/shared/react/GramproAIProvider.tsx +50 -0
- package/source/beta-components/shared/react/useAskAgent.ts +217 -0
- package/source/beta-components/shared/styles.css +79 -0
- package/source/beta-components/shared/version.json +4 -4
- package/source/beta-components/shared/version.ts +6 -6
- package/source/beta-components/skeleton/passport.json +251 -0
- package/source/beta-components/skeleton/styles.css +185 -187
- package/source/beta-components/spinner/passport.json +245 -0
- package/source/beta-components/spinner/styles.css +173 -174
- package/source/beta-components/switch/passport.json +421 -0
- package/source/beta-components/switch/styles.css +227 -229
- package/source/beta-components/tabs/passport.json +315 -0
- package/source/beta-components/tabs/styles.css +263 -264
- package/source/beta-components/textarea/passport.json +382 -0
- package/source/beta-components/textarea/styles.css +158 -160
- package/source/beta-components/toaster/passport.json +221 -0
- package/source/beta-components/toaster/styles.css +282 -282
- package/source/beta-components/tooltip/passport.json +170 -0
- package/source/beta-components/tooltip/styles.css +71 -73
- package/tools/env.cjs +61 -0
- package/tools/passport/cli.cjs +79 -0
- package/tools/passport/extract.cjs +493 -0
- package/tools/passport/index.cjs +185 -0
- package/tools/passport/merge.cjs +131 -0
- package/tools/passport/policy.cjs +65 -0
- package/tools/passport/validate.cjs +277 -0
- package/tools/ts-require.cjs +79 -0
- package/source/beta-components/accordion/__tests__/core.test.ts +0 -58
- package/source/beta-components/alert/__tests__/core.test.ts +0 -17
- package/source/beta-components/avatar/__tests__/core.test.ts +0 -88
- package/source/beta-components/badge/__tests__/core.test.ts +0 -46
- package/source/beta-components/breadcrumb/__tests__/core.test.ts +0 -58
- package/source/beta-components/button/__tests__/core.test.ts +0 -31
- package/source/beta-components/card/__tests__/core.test.ts +0 -57
- package/source/beta-components/checkbox/__tests__/core.test.ts +0 -40
- package/source/beta-components/combobox/__tests__/core.test.ts +0 -134
- package/source/beta-components/data-grid/__tests__/core.test.ts +0 -356
- package/source/beta-components/data-grid/__tests__/export.test.ts +0 -70
- package/source/beta-components/data-grid/__tests__/pdf.test.ts +0 -209
- package/source/beta-components/date-picker/__tests__/core.test.ts +0 -273
- package/source/beta-components/dialog/__tests__/core.test.ts +0 -86
- package/source/beta-components/file-uploader/__tests__/core.test.ts +0 -395
- package/source/beta-components/input/__tests__/core.test.ts +0 -75
- package/source/beta-components/menu/__tests__/core.test.ts +0 -120
- package/source/beta-components/modal/__tests__/core.test.ts +0 -55
- package/source/beta-components/number-input/__tests__/core.test.ts +0 -151
- package/source/beta-components/progress/__tests__/core.test.ts +0 -56
- package/source/beta-components/radio-group/__tests__/core.test.ts +0 -64
- package/source/beta-components/shared/__tests__/boundaries.test.ts +0 -95
- package/source/beta-components/shared/__tests__/core.test.ts +0 -55
- package/source/beta-components/shared/__tests__/position.test.ts +0 -143
- package/source/beta-components/skeleton/__tests__/core.test.ts +0 -41
- package/source/beta-components/spinner/__tests__/core.test.ts +0 -48
- package/source/beta-components/switch/__tests__/core.test.ts +0 -64
- package/source/beta-components/tabs/__tests__/core.test.ts +0 -51
- package/source/beta-components/textarea/__tests__/core.test.ts +0 -38
- package/source/beta-components/toaster/__tests__/core.test.ts +0 -256
- package/source/beta-components/tooltip/__tests__/core.test.ts +0 -42
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The intent schema, generated per instance.
|
|
3
|
+
*
|
|
4
|
+
* A hand-written GridIntent schema would say "operator is one of sixteen
|
|
5
|
+
* strings" and leave `startsWith` on a revenue column perfectly legal. The
|
|
6
|
+
* generated one carries a branch per filterable column, each with the operator
|
|
7
|
+
* set that column's type actually supports, so the illegal pair cannot be
|
|
8
|
+
* expressed — not merely rejected afterwards. Feed it to a constrained decoder
|
|
9
|
+
* and a model is physically unable to emit it; feed it to the validator and
|
|
10
|
+
* the same rule is enforced a second time, because a schema that only lives
|
|
11
|
+
* in the decoder is a schema you are trusting someone else to run.
|
|
12
|
+
*
|
|
13
|
+
* What is strict and what is not:
|
|
14
|
+
*
|
|
15
|
+
* strict action names, column ids, operator sets per column,
|
|
16
|
+
* export formats, densities, placements, page bounds
|
|
17
|
+
* permissive the *values* people write — "1 lakh", "20%", "kerala"
|
|
18
|
+
*
|
|
19
|
+
* Values stay loose here on purpose. Narrowing them in the schema would reject
|
|
20
|
+
* the input before the coercion layer had a chance to read it, and coercion is
|
|
21
|
+
* where "1 lakh" becomes 100000 deterministically instead of a model guessing.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { Density, ExportScope, FilterOperator, FilterValue, RowId } from "../core/types";
|
|
25
|
+
import type { JsonSchema } from "../../shared/core/agent";
|
|
26
|
+
import type { GridRuntimeContract } from "./contract";
|
|
27
|
+
import type { GridOperationName } from "./operations";
|
|
28
|
+
|
|
29
|
+
export type GridIntent =
|
|
30
|
+
| { action: "search"; text: string }
|
|
31
|
+
| {
|
|
32
|
+
action: "filter";
|
|
33
|
+
column: string;
|
|
34
|
+
operator: FilterOperator;
|
|
35
|
+
value?: FilterValue;
|
|
36
|
+
value2?: FilterValue;
|
|
37
|
+
}
|
|
38
|
+
| { action: "clearFilters" }
|
|
39
|
+
| { action: "sort"; column: string; direction: "asc" | "desc"; append?: boolean }
|
|
40
|
+
| { action: "clearSort" }
|
|
41
|
+
| { action: "selectRows"; rowIds: RowId[]; value?: boolean }
|
|
42
|
+
| { action: "selectAll"; value?: boolean }
|
|
43
|
+
| { action: "clearSelection" }
|
|
44
|
+
| { action: "setColumnVisibility"; column: string; visible: boolean }
|
|
45
|
+
| { action: "pinColumn"; column: string; side: "left" | "right" | null }
|
|
46
|
+
| { action: "moveColumn"; column: string; target: string; placement: "before" | "after" }
|
|
47
|
+
| { action: "setColumnWidth"; column: string; width: number | null }
|
|
48
|
+
| { action: "resetColumns" }
|
|
49
|
+
| { action: "setPage"; index: number }
|
|
50
|
+
| { action: "setPageSize"; size: number }
|
|
51
|
+
| { action: "setDensity"; density: Density }
|
|
52
|
+
| { action: "export"; format: "csv" | "excel" | "pdf"; scope?: ExportScope; fileName?: string }
|
|
53
|
+
| { action: "print"; scope?: ExportScope; title?: string }
|
|
54
|
+
| { action: "copy" }
|
|
55
|
+
| { action: "undo" }
|
|
56
|
+
| { action: "redo" };
|
|
57
|
+
|
|
58
|
+
/** Which column flag an action needs, so a refusal can say why. */
|
|
59
|
+
export const COLUMN_REQUIREMENT: Partial<
|
|
60
|
+
Record<GridOperationName, "filterable" | "sortable" | "hideable" | "pinnable" | "reorderable" | "resizable">
|
|
61
|
+
> = {
|
|
62
|
+
filter: "filterable",
|
|
63
|
+
sort: "sortable",
|
|
64
|
+
setColumnVisibility: "hideable",
|
|
65
|
+
pinColumn: "pinnable",
|
|
66
|
+
moveColumn: "reorderable",
|
|
67
|
+
setColumnWidth: "resizable",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export interface GeneratedIntentSchema {
|
|
71
|
+
/** The whole union. Hand this to a constrained decoder. */
|
|
72
|
+
schema: JsonSchema;
|
|
73
|
+
/** One entry per action, for targeted error messages. */
|
|
74
|
+
byAction: Map<GridOperationName, JsonSchema>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ANY_VALUE: JsonSchema = {
|
|
78
|
+
anyOf: [
|
|
79
|
+
{ type: "string" },
|
|
80
|
+
{ type: "number" },
|
|
81
|
+
{ type: "boolean" },
|
|
82
|
+
{ type: "null" },
|
|
83
|
+
{ type: "array", items: { type: ["string", "number"] } },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const SCOPE = (): JsonSchema => ({ enum: ["filtered", "all", "selected", "page"] });
|
|
88
|
+
|
|
89
|
+
const branch = (
|
|
90
|
+
action: GridOperationName,
|
|
91
|
+
properties: Record<string, JsonSchema> = {},
|
|
92
|
+
required: string[] = [],
|
|
93
|
+
description?: string,
|
|
94
|
+
): JsonSchema => ({
|
|
95
|
+
type: "object",
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
...(description ? { description } : {}),
|
|
98
|
+
required: ["action", ...required],
|
|
99
|
+
properties: { action: { const: action }, ...properties },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Builds the intent schema for one live contract.
|
|
104
|
+
*
|
|
105
|
+
* Deterministic: the only inputs are the contract's columns, capabilities and
|
|
106
|
+
* page count, and they are walked in order. The same contract always produces
|
|
107
|
+
* byte-identical JSON.
|
|
108
|
+
*/
|
|
109
|
+
export function buildIntentSchema(contract: GridRuntimeContract): GeneratedIntentSchema {
|
|
110
|
+
const has = new Set(contract.operations);
|
|
111
|
+
const byAction = new Map<GridOperationName, JsonSchema>();
|
|
112
|
+
const ids = (predicate: (column: GridRuntimeContract["columns"][number]) => boolean) =>
|
|
113
|
+
contract.columns.filter(predicate).map((column) => column.id);
|
|
114
|
+
|
|
115
|
+
const add = (action: GridOperationName, schema: JsonSchema | JsonSchema[]) => {
|
|
116
|
+
if (!has.has(action)) return;
|
|
117
|
+
const list = Array.isArray(schema) ? schema : [schema];
|
|
118
|
+
if (list.length === 0) return;
|
|
119
|
+
byAction.set(action, list.length === 1 ? list[0] : { oneOf: list });
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
add("search", branch("search", { text: { type: "string", maxLength: 200 } }, ["text"]));
|
|
123
|
+
|
|
124
|
+
// One branch per filterable column: this is what couples operator to type.
|
|
125
|
+
add(
|
|
126
|
+
"filter",
|
|
127
|
+
contract.columns
|
|
128
|
+
.filter((column) => column.filterable && column.operators.length > 0)
|
|
129
|
+
.map((column) =>
|
|
130
|
+
branch(
|
|
131
|
+
"filter",
|
|
132
|
+
{
|
|
133
|
+
column: { const: column.id },
|
|
134
|
+
operator: { enum: column.operators as FilterOperator[] },
|
|
135
|
+
value: ANY_VALUE,
|
|
136
|
+
value2: ANY_VALUE,
|
|
137
|
+
},
|
|
138
|
+
["column", "operator"],
|
|
139
|
+
column.description ?? `Filter ${column.label}${column.unit ? ` (${column.unit})` : ""}.`,
|
|
140
|
+
),
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
add("clearFilters", branch("clearFilters"));
|
|
145
|
+
|
|
146
|
+
add(
|
|
147
|
+
"sort",
|
|
148
|
+
branch(
|
|
149
|
+
"sort",
|
|
150
|
+
{
|
|
151
|
+
column: { enum: ids((column) => column.sortable) },
|
|
152
|
+
direction: { enum: ["asc", "desc"] },
|
|
153
|
+
append: { type: "boolean" },
|
|
154
|
+
},
|
|
155
|
+
["column", "direction"],
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
add("clearSort", branch("clearSort"));
|
|
159
|
+
|
|
160
|
+
add(
|
|
161
|
+
"selectRows",
|
|
162
|
+
branch(
|
|
163
|
+
"selectRows",
|
|
164
|
+
{
|
|
165
|
+
rowIds: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
166
|
+
value: { type: "boolean" },
|
|
167
|
+
},
|
|
168
|
+
["rowIds"],
|
|
169
|
+
),
|
|
170
|
+
);
|
|
171
|
+
add("selectAll", branch("selectAll", { value: { type: "boolean" } }));
|
|
172
|
+
add("clearSelection", branch("clearSelection"));
|
|
173
|
+
|
|
174
|
+
add(
|
|
175
|
+
"setColumnVisibility",
|
|
176
|
+
branch(
|
|
177
|
+
"setColumnVisibility",
|
|
178
|
+
{ column: { enum: ids((column) => column.hideable) }, visible: { type: "boolean" } },
|
|
179
|
+
["column", "visible"],
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
add(
|
|
183
|
+
"pinColumn",
|
|
184
|
+
branch(
|
|
185
|
+
"pinColumn",
|
|
186
|
+
{ column: { enum: ids((column) => column.pinnable) }, side: { enum: ["left", "right", null] } },
|
|
187
|
+
["column", "side"],
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
add(
|
|
191
|
+
"moveColumn",
|
|
192
|
+
branch(
|
|
193
|
+
"moveColumn",
|
|
194
|
+
{
|
|
195
|
+
column: { enum: ids((column) => column.reorderable) },
|
|
196
|
+
target: { enum: ids(() => true) },
|
|
197
|
+
placement: { enum: ["before", "after"] },
|
|
198
|
+
},
|
|
199
|
+
["column", "target", "placement"],
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
add(
|
|
203
|
+
"setColumnWidth",
|
|
204
|
+
branch(
|
|
205
|
+
"setColumnWidth",
|
|
206
|
+
{
|
|
207
|
+
column: { enum: ids((column) => column.resizable) },
|
|
208
|
+
width: { type: ["integer", "null"], minimum: 1, maximum: 2000 },
|
|
209
|
+
},
|
|
210
|
+
["column", "width"],
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
add("resetColumns", branch("resetColumns"));
|
|
214
|
+
|
|
215
|
+
add(
|
|
216
|
+
"setPage",
|
|
217
|
+
branch(
|
|
218
|
+
"setPage",
|
|
219
|
+
{ index: { type: "integer", minimum: 0, maximum: Math.max(0, contract.state.page.count - 1) } },
|
|
220
|
+
["index"],
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
add("setPageSize", branch("setPageSize", { size: { type: "integer", minimum: 1, maximum: 1000 } }, ["size"]));
|
|
224
|
+
add(
|
|
225
|
+
"setDensity",
|
|
226
|
+
branch("setDensity", { density: { enum: ["compact", "standard", "comfortable"] } }, ["density"]),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
add(
|
|
230
|
+
"export",
|
|
231
|
+
branch(
|
|
232
|
+
"export",
|
|
233
|
+
{
|
|
234
|
+
format: { enum: [...contract.capabilities.exportFormats] },
|
|
235
|
+
scope: SCOPE(),
|
|
236
|
+
fileName: { type: "string", maxLength: 120 },
|
|
237
|
+
},
|
|
238
|
+
["format"],
|
|
239
|
+
),
|
|
240
|
+
);
|
|
241
|
+
add("print", branch("print", { scope: SCOPE(), title: { type: "string", maxLength: 120 } }));
|
|
242
|
+
add("copy", branch("copy"));
|
|
243
|
+
add("undo", branch("undo"));
|
|
244
|
+
add("redo", branch("redo"));
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
schema: {
|
|
248
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
249
|
+
title: `GridIntent (${contract.instanceId})`,
|
|
250
|
+
description:
|
|
251
|
+
"One operation on this DataGrid instance. Column ids, operators and formats are " +
|
|
252
|
+
"fixed to what this instance supports; values are read by the validator's coercion layer.",
|
|
253
|
+
oneOf: [...byAction.values()].flatMap((entry) => (entry.oneOf ? [...entry.oneOf] : [entry])),
|
|
254
|
+
},
|
|
255
|
+
byAction,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* ------------------------------------------------------------- the envelope */
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* What a producer of intents — a model, a form, a macro recorder — is allowed
|
|
263
|
+
* to answer with.
|
|
264
|
+
*
|
|
265
|
+
* Three outcomes, not one. A producer that can only return a command has no
|
|
266
|
+
* way to say "that is ambiguous" or "this grid cannot do that", so it will
|
|
267
|
+
* guess, and a confident wrong filter is the failure mode the whole validator
|
|
268
|
+
* exists to prevent. Making refusal and clarification *first-class answers*
|
|
269
|
+
* rather than error paths is what lets them be measured — and rewarded.
|
|
270
|
+
*/
|
|
271
|
+
export type GridResponse =
|
|
272
|
+
| { result: "command"; intents: GridIntent[] }
|
|
273
|
+
| { result: "clarify"; question: string; options?: string[] }
|
|
274
|
+
| { result: "declined"; reason: string };
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The schema a producer is held to, including the two non-command answers.
|
|
278
|
+
*
|
|
279
|
+
* Clarifying and declining are validated like anything else: a `clarify` with
|
|
280
|
+
* no question is not a clarification, it is malformed output wearing a label.
|
|
281
|
+
*/
|
|
282
|
+
export function buildResponseSchema(contract: GridRuntimeContract): JsonSchema {
|
|
283
|
+
const { schema: intent } = buildIntentSchema(contract);
|
|
284
|
+
return {
|
|
285
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
286
|
+
title: `GridResponse (${contract.instanceId})`,
|
|
287
|
+
description:
|
|
288
|
+
"Answer with a command when the request maps onto this grid, clarify when more " +
|
|
289
|
+
"than one reading is defensible, and decline when this grid cannot do it.",
|
|
290
|
+
oneOf: [
|
|
291
|
+
{
|
|
292
|
+
type: "object",
|
|
293
|
+
additionalProperties: false,
|
|
294
|
+
required: ["result", "intents"],
|
|
295
|
+
description: "One or more operations to apply, in order.",
|
|
296
|
+
properties: {
|
|
297
|
+
result: { const: "command" },
|
|
298
|
+
intents: { type: "array", minItems: 1, maxItems: 8, items: intent },
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
type: "object",
|
|
303
|
+
additionalProperties: false,
|
|
304
|
+
required: ["result", "question"],
|
|
305
|
+
description: "The request has more than one defensible reading on this grid.",
|
|
306
|
+
properties: {
|
|
307
|
+
result: { const: "clarify" },
|
|
308
|
+
question: { type: "string", minLength: 8, maxLength: 300 },
|
|
309
|
+
options: { type: "array", maxItems: 5, items: { type: "string", maxLength: 120 } },
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
type: "object",
|
|
314
|
+
additionalProperties: false,
|
|
315
|
+
required: ["result", "reason"],
|
|
316
|
+
description: "This grid cannot do what was asked.",
|
|
317
|
+
properties: {
|
|
318
|
+
result: { const: "declined" },
|
|
319
|
+
reason: { type: "string", minLength: 4, maxLength: 300 },
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* What a DataGrid knows how to do.
|
|
3
|
+
*
|
|
4
|
+
* This is the static half of the contract — the same set the component's
|
|
5
|
+
* `passport.json` publishes, kept here in TypeScript so the runtime and the
|
|
6
|
+
* passport cannot drift apart (a test asserts they match). The input schemas
|
|
7
|
+
* are the generic ones; `intent.ts` narrows them against a live instance, so
|
|
8
|
+
* that a filter on a number column cannot even be expressed with `startsWith`.
|
|
9
|
+
*
|
|
10
|
+
* Two rules decide what is in this list:
|
|
11
|
+
*
|
|
12
|
+
* 1. It is backed by something the grid really implements. Grouping and
|
|
13
|
+
* aggregation are absent because the grid has neither.
|
|
14
|
+
* 2. `apiMethod` is recorded when, and only when, one `GridApi` method
|
|
15
|
+
* performs it. `export` spans three methods, and `undo`/`redo` are the
|
|
16
|
+
* agent's own history rather than the grid's — those have none, and that
|
|
17
|
+
* is normal, not a gap.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { JsonSchema, OperationDefinition } from "../../shared/core/agent";
|
|
21
|
+
|
|
22
|
+
export type GridOperationName =
|
|
23
|
+
| "search"
|
|
24
|
+
| "filter"
|
|
25
|
+
| "clearFilters"
|
|
26
|
+
| "sort"
|
|
27
|
+
| "clearSort"
|
|
28
|
+
| "selectRows"
|
|
29
|
+
| "selectAll"
|
|
30
|
+
| "clearSelection"
|
|
31
|
+
| "setColumnVisibility"
|
|
32
|
+
| "pinColumn"
|
|
33
|
+
| "moveColumn"
|
|
34
|
+
| "setColumnWidth"
|
|
35
|
+
| "resetColumns"
|
|
36
|
+
| "setPage"
|
|
37
|
+
| "setPageSize"
|
|
38
|
+
| "setDensity"
|
|
39
|
+
| "export"
|
|
40
|
+
| "print"
|
|
41
|
+
| "copy"
|
|
42
|
+
| "undo"
|
|
43
|
+
| "redo";
|
|
44
|
+
|
|
45
|
+
export interface GridOperationDefinition extends OperationDefinition {
|
|
46
|
+
readonly name: GridOperationName;
|
|
47
|
+
/**
|
|
48
|
+
* Which grid feature flag has to be on for this instance to offer it.
|
|
49
|
+
* Undefined means always available.
|
|
50
|
+
*/
|
|
51
|
+
readonly requires?:
|
|
52
|
+
| "sorting"
|
|
53
|
+
| "filtering"
|
|
54
|
+
| "selection"
|
|
55
|
+
| "pagination"
|
|
56
|
+
| "columnHiding"
|
|
57
|
+
| "columnPinning"
|
|
58
|
+
| "columnReordering"
|
|
59
|
+
| "columnResizing";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const COLUMN: JsonSchema = { type: "string", description: "Column id from the runtime contract." };
|
|
63
|
+
|
|
64
|
+
const SCOPE: JsonSchema = {
|
|
65
|
+
enum: ["filtered", "all", "selected", "page"],
|
|
66
|
+
default: "filtered",
|
|
67
|
+
description: "Which rows to take. `filtered` is everything matching the current query.",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const GRID_OPERATIONS: Readonly<Record<GridOperationName, GridOperationDefinition>> = {
|
|
71
|
+
search: {
|
|
72
|
+
name: "search",
|
|
73
|
+
summary: "Free-text match across every searchable column.",
|
|
74
|
+
apiMethod: "setGlobalFilter",
|
|
75
|
+
requires: "filtering",
|
|
76
|
+
input: {
|
|
77
|
+
type: "object",
|
|
78
|
+
required: ["text"],
|
|
79
|
+
properties: { text: { type: "string", maxLength: 200 } },
|
|
80
|
+
},
|
|
81
|
+
effects: ["filters visible rows", "resets to page 0"],
|
|
82
|
+
reversible: true,
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
filter: {
|
|
86
|
+
name: "filter",
|
|
87
|
+
summary: "Apply a typed filter to one column. Operators are constrained by the column's type.",
|
|
88
|
+
apiMethod: "setFilter",
|
|
89
|
+
requires: "filtering",
|
|
90
|
+
input: {
|
|
91
|
+
type: "object",
|
|
92
|
+
required: ["column", "operator"],
|
|
93
|
+
properties: {
|
|
94
|
+
column: COLUMN,
|
|
95
|
+
operator: {
|
|
96
|
+
enum: [
|
|
97
|
+
"contains", "notContains", "equals", "notEquals", "startsWith", "endsWith",
|
|
98
|
+
"gt", "gte", "lt", "lte", "between", "before", "after", "in",
|
|
99
|
+
"isEmpty", "isNotEmpty",
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
value: {},
|
|
103
|
+
value2: { description: "Upper bound; only for the `between` operator." },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
effects: ["filters visible rows", "resets to page 0"],
|
|
107
|
+
reversible: true,
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
clearFilters: {
|
|
111
|
+
name: "clearFilters",
|
|
112
|
+
summary: "Remove every column filter and the search text.",
|
|
113
|
+
apiMethod: "clearFilters",
|
|
114
|
+
requires: "filtering",
|
|
115
|
+
effects: ["restores every row"],
|
|
116
|
+
reversible: true,
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
sort: {
|
|
120
|
+
name: "sort",
|
|
121
|
+
summary: "Sort by one column, replacing the current sort unless `append` is set.",
|
|
122
|
+
apiMethod: "setSorting",
|
|
123
|
+
requires: "sorting",
|
|
124
|
+
input: {
|
|
125
|
+
type: "object",
|
|
126
|
+
required: ["column", "direction"],
|
|
127
|
+
properties: {
|
|
128
|
+
column: COLUMN,
|
|
129
|
+
direction: { enum: ["asc", "desc"] },
|
|
130
|
+
append: { type: "boolean", default: false, description: "Add to the existing sort instead of replacing it." },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
effects: ["reorders visible rows"],
|
|
134
|
+
reversible: true,
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
clearSort: {
|
|
138
|
+
name: "clearSort",
|
|
139
|
+
summary: "Return to the data's original order.",
|
|
140
|
+
apiMethod: "setSorting",
|
|
141
|
+
requires: "sorting",
|
|
142
|
+
effects: ["reorders visible rows"],
|
|
143
|
+
reversible: true,
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
selectRows: {
|
|
147
|
+
name: "selectRows",
|
|
148
|
+
summary: "Select or deselect rows by id.",
|
|
149
|
+
apiMethod: "toggleRowSelected",
|
|
150
|
+
requires: "selection",
|
|
151
|
+
input: {
|
|
152
|
+
type: "object",
|
|
153
|
+
required: ["rowIds"],
|
|
154
|
+
properties: {
|
|
155
|
+
rowIds: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
156
|
+
value: { type: "boolean", default: true },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
effects: ["changes rowSelection"],
|
|
160
|
+
reversible: true,
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
selectAll: {
|
|
164
|
+
name: "selectAll",
|
|
165
|
+
summary: "Select, or clear, every row matching the current filters.",
|
|
166
|
+
apiMethod: "toggleAllRowsSelected",
|
|
167
|
+
requires: "selection",
|
|
168
|
+
input: { type: "object", properties: { value: { type: "boolean", default: true } } },
|
|
169
|
+
effects: ["changes rowSelection"],
|
|
170
|
+
reversible: true,
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
clearSelection: {
|
|
174
|
+
name: "clearSelection",
|
|
175
|
+
summary: "Deselect every row.",
|
|
176
|
+
apiMethod: "clearSelection",
|
|
177
|
+
requires: "selection",
|
|
178
|
+
effects: ["changes rowSelection"],
|
|
179
|
+
reversible: true,
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
setColumnVisibility: {
|
|
183
|
+
name: "setColumnVisibility",
|
|
184
|
+
summary: "Show or hide one column.",
|
|
185
|
+
apiMethod: "setColumnVisibility",
|
|
186
|
+
requires: "columnHiding",
|
|
187
|
+
input: {
|
|
188
|
+
type: "object",
|
|
189
|
+
required: ["column", "visible"],
|
|
190
|
+
properties: { column: COLUMN, visible: { type: "boolean" } },
|
|
191
|
+
},
|
|
192
|
+
effects: ["changes columnVisibility"],
|
|
193
|
+
reversible: true,
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
pinColumn: {
|
|
197
|
+
name: "pinColumn",
|
|
198
|
+
summary: "Pin a column to an edge, or unpin it with a null side.",
|
|
199
|
+
apiMethod: "pinColumn",
|
|
200
|
+
requires: "columnPinning",
|
|
201
|
+
input: {
|
|
202
|
+
type: "object",
|
|
203
|
+
required: ["column", "side"],
|
|
204
|
+
properties: { column: COLUMN, side: { enum: ["left", "right", null] } },
|
|
205
|
+
},
|
|
206
|
+
effects: ["changes columnPinning"],
|
|
207
|
+
reversible: true,
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
moveColumn: {
|
|
211
|
+
name: "moveColumn",
|
|
212
|
+
summary: "Move a column before or after another one.",
|
|
213
|
+
apiMethod: "moveColumn",
|
|
214
|
+
requires: "columnReordering",
|
|
215
|
+
input: {
|
|
216
|
+
type: "object",
|
|
217
|
+
required: ["column", "target", "placement"],
|
|
218
|
+
properties: { column: COLUMN, target: COLUMN, placement: { enum: ["before", "after"] } },
|
|
219
|
+
},
|
|
220
|
+
effects: ["changes columnOrder"],
|
|
221
|
+
reversible: true,
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
setColumnWidth: {
|
|
225
|
+
name: "setColumnWidth",
|
|
226
|
+
summary: "Set a column's width in pixels, or null to restore its default.",
|
|
227
|
+
apiMethod: "setColumnWidth",
|
|
228
|
+
requires: "columnResizing",
|
|
229
|
+
input: {
|
|
230
|
+
type: "object",
|
|
231
|
+
required: ["column", "width"],
|
|
232
|
+
properties: { column: COLUMN, width: { type: ["integer", "null"], minimum: 1 } },
|
|
233
|
+
},
|
|
234
|
+
effects: ["changes columnSizing"],
|
|
235
|
+
reversible: true,
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
resetColumns: {
|
|
239
|
+
name: "resetColumns",
|
|
240
|
+
summary: "Restore column order, width, visibility and pinning to their defaults.",
|
|
241
|
+
apiMethod: "resetColumns",
|
|
242
|
+
effects: ["changes columnOrder", "changes columnVisibility", "changes columnSizing", "changes columnPinning"],
|
|
243
|
+
reversible: true,
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
setPage: {
|
|
247
|
+
name: "setPage",
|
|
248
|
+
summary: "Jump to a page, counting from zero.",
|
|
249
|
+
apiMethod: "setPageIndex",
|
|
250
|
+
requires: "pagination",
|
|
251
|
+
input: { type: "object", required: ["index"], properties: { index: { type: "integer", minimum: 0 } } },
|
|
252
|
+
effects: ["changes the visible page"],
|
|
253
|
+
reversible: true,
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
setPageSize: {
|
|
257
|
+
name: "setPageSize",
|
|
258
|
+
summary: "Change how many rows a page holds.",
|
|
259
|
+
apiMethod: "setPageSize",
|
|
260
|
+
requires: "pagination",
|
|
261
|
+
input: { type: "object", required: ["size"], properties: { size: { type: "integer", minimum: 1 } } },
|
|
262
|
+
effects: ["changes the visible page"],
|
|
263
|
+
reversible: true,
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
setDensity: {
|
|
267
|
+
name: "setDensity",
|
|
268
|
+
summary: "Change row height.",
|
|
269
|
+
apiMethod: "setDensity",
|
|
270
|
+
input: {
|
|
271
|
+
type: "object",
|
|
272
|
+
required: ["density"],
|
|
273
|
+
properties: { density: { enum: ["compact", "standard", "comfortable"] } },
|
|
274
|
+
},
|
|
275
|
+
effects: ["changes row height"],
|
|
276
|
+
reversible: true,
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
export: {
|
|
280
|
+
name: "export",
|
|
281
|
+
summary: "Download the chosen rows as a file.",
|
|
282
|
+
// Three formats, three GridApi methods — so no single apiMethod applies.
|
|
283
|
+
input: {
|
|
284
|
+
type: "object",
|
|
285
|
+
required: ["format"],
|
|
286
|
+
properties: {
|
|
287
|
+
format: { enum: ["csv", "excel", "pdf"] },
|
|
288
|
+
scope: SCOPE,
|
|
289
|
+
fileName: { type: "string", maxLength: 120 },
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
effects: ["downloads a file"],
|
|
293
|
+
reversible: false,
|
|
294
|
+
requiresConfirmation: true,
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
print: {
|
|
298
|
+
name: "print",
|
|
299
|
+
summary: "Open the browser's print dialog with the table laid out for paper.",
|
|
300
|
+
apiMethod: "print",
|
|
301
|
+
input: { type: "object", properties: { scope: SCOPE, title: { type: "string", maxLength: 120 } } },
|
|
302
|
+
effects: ["opens the print dialog"],
|
|
303
|
+
reversible: false,
|
|
304
|
+
requiresConfirmation: true,
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
copy: {
|
|
308
|
+
name: "copy",
|
|
309
|
+
summary: "Copy the selected rows to the clipboard as tab-separated text.",
|
|
310
|
+
apiMethod: "copyToClipboard",
|
|
311
|
+
effects: ["writes to the clipboard"],
|
|
312
|
+
reversible: false,
|
|
313
|
+
requiresConfirmation: true,
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
undo: {
|
|
317
|
+
name: "undo",
|
|
318
|
+
// Performed by the agent's snapshot history, not by the grid.
|
|
319
|
+
summary: "Restore the state from before the last operation.",
|
|
320
|
+
effects: ["restores a previous state"],
|
|
321
|
+
reversible: false,
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
redo: {
|
|
325
|
+
name: "redo",
|
|
326
|
+
summary: "Re-apply the operation that was last undone.",
|
|
327
|
+
effects: ["restores a later state"],
|
|
328
|
+
reversible: false,
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export const GRID_OPERATION_NAMES = Object.keys(GRID_OPERATIONS) as GridOperationName[];
|
|
333
|
+
|
|
334
|
+
export const isGridOperation = (name: string): name is GridOperationName =>
|
|
335
|
+
Object.prototype.hasOwnProperty.call(GRID_OPERATIONS, name);
|