gbs-add-block 2.0.4 → 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.
Files changed (129) hide show
  1. package/.gbs/skills/gbs-components/SKILL.md +190 -134
  2. package/.gbs/skills/gbs-components/references/install.md +25 -3
  3. package/.gbs/skills/gbs-components/references/styling.md +246 -201
  4. package/CHANGELOG.md +74 -0
  5. package/README.md +157 -11
  6. package/index.cjs +212 -3
  7. package/package.json +41 -10
  8. package/schema/passport-v1.schema.json +204 -0
  9. package/source/beta-components/accordion/passport.json +259 -0
  10. package/source/beta-components/accordion/styles.css +207 -208
  11. package/source/beta-components/alert/passport.json +250 -0
  12. package/source/beta-components/alert/styles.css +154 -155
  13. package/source/beta-components/avatar/passport.json +294 -0
  14. package/source/beta-components/avatar/styles.css +201 -203
  15. package/source/beta-components/badge/passport.json +332 -0
  16. package/source/beta-components/badge/styles.css +203 -204
  17. package/source/beta-components/breadcrumb/passport.json +243 -0
  18. package/source/beta-components/breadcrumb/styles.css +138 -139
  19. package/source/beta-components/button/passport.json +402 -0
  20. package/source/beta-components/button/passport.manual.json +31 -0
  21. package/source/beta-components/button/styles.css +232 -233
  22. package/source/beta-components/card/passport.json +337 -0
  23. package/source/beta-components/card/styles.css +230 -231
  24. package/source/beta-components/checkbox/passport.json +456 -0
  25. package/source/beta-components/checkbox/styles.css +211 -212
  26. package/source/beta-components/combobox/passport.json +456 -0
  27. package/source/beta-components/combobox/styles.css +419 -417
  28. package/source/beta-components/data-grid/agent/coerce.ts +368 -0
  29. package/source/beta-components/data-grid/agent/contract.ts +410 -0
  30. package/source/beta-components/data-grid/agent/dataset.ts +92 -0
  31. package/source/beta-components/data-grid/agent/engine.ts +470 -0
  32. package/source/beta-components/data-grid/agent/executors.ts +155 -0
  33. package/source/beta-components/data-grid/agent/index.ts +79 -0
  34. package/source/beta-components/data-grid/agent/intent.ts +324 -0
  35. package/source/beta-components/data-grid/agent/operations.ts +335 -0
  36. package/source/beta-components/data-grid/agent/validate.ts +630 -0
  37. package/source/beta-components/data-grid/agent/webmcp.ts +107 -0
  38. package/source/beta-components/data-grid/index.ts +14 -7
  39. package/source/beta-components/data-grid/passport.json +1051 -0
  40. package/source/beta-components/data-grid/passport.manual.json +255 -0
  41. package/source/beta-components/data-grid/react/AskGrid.tsx +164 -0
  42. package/source/beta-components/data-grid/react/DataGrid.tsx +39 -0
  43. package/source/beta-components/data-grid/styles.css +874 -717
  44. package/source/beta-components/date-picker/passport.json +407 -0
  45. package/source/beta-components/date-picker/styles.css +445 -446
  46. package/source/beta-components/dialog/passport.json +344 -0
  47. package/source/beta-components/dialog/styles.css +280 -278
  48. package/source/beta-components/file-uploader/passport.json +518 -0
  49. package/source/beta-components/file-uploader/styles.css +394 -395
  50. package/source/beta-components/input/passport.json +536 -0
  51. package/source/beta-components/input/styles.css +295 -296
  52. package/source/beta-components/menu/passport.json +322 -0
  53. package/source/beta-components/menu/styles.css +224 -222
  54. package/source/beta-components/modal/passport.json +289 -0
  55. package/source/beta-components/modal/styles.css +241 -239
  56. package/source/beta-components/number-input/passport.json +541 -0
  57. package/source/beta-components/number-input/styles.css +230 -231
  58. package/source/beta-components/popover/passport.json +238 -0
  59. package/source/beta-components/popover/styles.css +148 -146
  60. package/source/beta-components/progress/passport.json +270 -0
  61. package/source/beta-components/progress/styles.css +200 -202
  62. package/source/beta-components/radio-group/passport.json +477 -0
  63. package/source/beta-components/radio-group/styles.css +241 -242
  64. package/source/beta-components/shared/core/agent/adapter.ts +65 -0
  65. package/source/beta-components/shared/core/agent/history.ts +120 -0
  66. package/source/beta-components/shared/core/agent/index.ts +46 -0
  67. package/source/beta-components/shared/core/agent/numbers.ts +217 -0
  68. package/source/beta-components/shared/core/agent/schema.ts +180 -0
  69. package/source/beta-components/shared/core/agent/types.ts +169 -0
  70. package/source/beta-components/shared/core/agent/webmcp.ts +328 -0
  71. package/source/beta-components/shared/index.ts +9 -0
  72. package/source/beta-components/shared/react/GramproAIProvider.tsx +50 -0
  73. package/source/beta-components/shared/react/useAskAgent.ts +217 -0
  74. package/source/beta-components/shared/styles.css +79 -0
  75. package/source/beta-components/shared/version.json +4 -4
  76. package/source/beta-components/shared/version.ts +6 -6
  77. package/source/beta-components/skeleton/passport.json +251 -0
  78. package/source/beta-components/skeleton/styles.css +185 -186
  79. package/source/beta-components/spinner/passport.json +245 -0
  80. package/source/beta-components/spinner/styles.css +145 -146
  81. package/source/beta-components/switch/passport.json +421 -0
  82. package/source/beta-components/switch/styles.css +194 -196
  83. package/source/beta-components/tabs/passport.json +315 -0
  84. package/source/beta-components/tabs/styles.css +234 -235
  85. package/source/beta-components/textarea/passport.json +382 -0
  86. package/source/beta-components/textarea/styles.css +158 -159
  87. package/source/beta-components/toaster/passport.json +221 -0
  88. package/source/beta-components/toaster/styles.css +282 -283
  89. package/source/beta-components/tooltip/passport.json +170 -0
  90. package/source/beta-components/tooltip/styles.css +71 -72
  91. package/tools/env.cjs +61 -0
  92. package/tools/passport/cli.cjs +79 -0
  93. package/tools/passport/extract.cjs +493 -0
  94. package/tools/passport/index.cjs +185 -0
  95. package/tools/passport/merge.cjs +131 -0
  96. package/tools/passport/policy.cjs +65 -0
  97. package/tools/passport/validate.cjs +277 -0
  98. package/tools/ts-require.cjs +79 -0
  99. package/source/beta-components/accordion/__tests__/core.test.ts +0 -58
  100. package/source/beta-components/alert/__tests__/core.test.ts +0 -17
  101. package/source/beta-components/avatar/__tests__/core.test.ts +0 -88
  102. package/source/beta-components/badge/__tests__/core.test.ts +0 -46
  103. package/source/beta-components/breadcrumb/__tests__/core.test.ts +0 -58
  104. package/source/beta-components/button/__tests__/core.test.ts +0 -31
  105. package/source/beta-components/card/__tests__/core.test.ts +0 -57
  106. package/source/beta-components/checkbox/__tests__/core.test.ts +0 -40
  107. package/source/beta-components/combobox/__tests__/core.test.ts +0 -134
  108. package/source/beta-components/data-grid/__tests__/core.test.ts +0 -356
  109. package/source/beta-components/data-grid/__tests__/export.test.ts +0 -70
  110. package/source/beta-components/data-grid/__tests__/pdf.test.ts +0 -209
  111. package/source/beta-components/date-picker/__tests__/core.test.ts +0 -273
  112. package/source/beta-components/dialog/__tests__/core.test.ts +0 -86
  113. package/source/beta-components/file-uploader/__tests__/core.test.ts +0 -395
  114. package/source/beta-components/input/__tests__/core.test.ts +0 -75
  115. package/source/beta-components/menu/__tests__/core.test.ts +0 -120
  116. package/source/beta-components/modal/__tests__/core.test.ts +0 -55
  117. package/source/beta-components/number-input/__tests__/core.test.ts +0 -151
  118. package/source/beta-components/progress/__tests__/core.test.ts +0 -56
  119. package/source/beta-components/radio-group/__tests__/core.test.ts +0 -64
  120. package/source/beta-components/shared/__tests__/boundaries.test.ts +0 -95
  121. package/source/beta-components/shared/__tests__/core.test.ts +0 -55
  122. package/source/beta-components/shared/__tests__/position.test.ts +0 -143
  123. package/source/beta-components/skeleton/__tests__/core.test.ts +0 -41
  124. package/source/beta-components/spinner/__tests__/core.test.ts +0 -48
  125. package/source/beta-components/switch/__tests__/core.test.ts +0 -64
  126. package/source/beta-components/tabs/__tests__/core.test.ts +0 -51
  127. package/source/beta-components/textarea/__tests__/core.test.ts +0 -38
  128. package/source/beta-components/toaster/__tests__/core.test.ts +0 -256
  129. 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);