@grampro/kit 2.6.3 → 2.7.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 (93) hide show
  1. package/.gbs/skills/gbs-components/SKILL.md +5 -4
  2. package/.gbs/skills/gbs-components/references/install.md +17 -14
  3. package/CHANGELOG.md +164 -0
  4. package/README.md +19 -6
  5. package/index.cjs +59 -59
  6. package/package.json +2 -2
  7. package/schema/passport-overlay-v1.schema.json +67 -0
  8. package/source/components/accordion/passport.json +45 -8
  9. package/source/components/accordion/passport.manual.json +46 -0
  10. package/source/components/alert/passport.json +41 -8
  11. package/source/components/alert/passport.manual.json +42 -0
  12. package/source/components/avatar/passport.json +40 -7
  13. package/source/components/avatar/passport.manual.json +43 -0
  14. package/source/components/badge/passport.json +43 -7
  15. package/source/components/badge/passport.manual.json +46 -0
  16. package/source/components/breadcrumb/passport.json +36 -7
  17. package/source/components/breadcrumb/passport.manual.json +39 -0
  18. package/source/components/button/passport.json +1 -1
  19. package/source/components/button/passport.manual.json +1 -1
  20. package/source/components/card/passport.json +45 -8
  21. package/source/components/card/passport.manual.json +47 -0
  22. package/source/components/checkbox/core/agent.ts +33 -0
  23. package/source/components/checkbox/core/index.ts +2 -0
  24. package/source/components/checkbox/passport.json +83 -10
  25. package/source/components/checkbox/passport.manual.json +82 -0
  26. package/source/components/combobox/core/agent.ts +38 -0
  27. package/source/components/combobox/core/index.ts +2 -0
  28. package/source/components/combobox/passport.json +103 -11
  29. package/source/components/combobox/passport.manual.json +101 -0
  30. package/source/components/combobox/react/props.ts +14 -0
  31. package/source/components/combobox/react/useCombobox.ts +7 -1
  32. package/source/components/data-grid/export/xlsx.ts +9 -2
  33. package/source/components/data-grid/passport.json +2 -2
  34. package/source/components/data-grid/passport.manual.json +1 -1
  35. package/source/components/date-picker/core/agent.ts +42 -0
  36. package/source/components/date-picker/core/index.ts +2 -0
  37. package/source/components/date-picker/passport.json +102 -11
  38. package/source/components/date-picker/passport.manual.json +100 -0
  39. package/source/components/date-picker/react/DatePicker.tsx +6 -0
  40. package/source/components/date-picker/react/DateRangePicker.tsx +4 -0
  41. package/source/components/date-picker/react/props.ts +17 -0
  42. package/source/components/dialog/passport.json +54 -9
  43. package/source/components/dialog/passport.manual.json +54 -0
  44. package/source/components/file-uploader/passport.json +65 -10
  45. package/source/components/file-uploader/passport.manual.json +64 -0
  46. package/source/components/input/core/agent.ts +36 -0
  47. package/source/components/input/core/index.ts +2 -0
  48. package/source/components/input/passport.json +89 -12
  49. package/source/components/input/passport.manual.json +86 -0
  50. package/source/components/menu/passport.json +64 -9
  51. package/source/components/menu/passport.manual.json +64 -0
  52. package/source/components/modal/passport.json +52 -9
  53. package/source/components/modal/passport.manual.json +52 -0
  54. package/source/components/number-input/core/agent.ts +38 -0
  55. package/source/components/number-input/core/index.ts +2 -0
  56. package/source/components/number-input/passport.json +92 -11
  57. package/source/components/number-input/passport.manual.json +90 -0
  58. package/source/components/popover/passport.json +47 -8
  59. package/source/components/popover/passport.manual.json +48 -0
  60. package/source/components/progress/passport.json +37 -8
  61. package/source/components/progress/passport.manual.json +39 -0
  62. package/source/components/radio-group/core/agent.ts +34 -0
  63. package/source/components/radio-group/core/index.ts +2 -0
  64. package/source/components/radio-group/passport.json +77 -10
  65. package/source/components/radio-group/passport.manual.json +76 -0
  66. package/source/components/shared/core/agent/field-agent.ts +101 -0
  67. package/source/components/shared/core/agent/field-executors.ts +137 -0
  68. package/source/components/shared/core/agent/field.ts +649 -0
  69. package/source/components/shared/core/agent/index.ts +30 -0
  70. package/source/components/shared/core/agent/write.ts +58 -0
  71. package/source/components/shared/react/useFieldAgent.ts +88 -0
  72. package/source/components/skeleton/passport.json +37 -6
  73. package/source/components/skeleton/passport.manual.json +39 -0
  74. package/source/components/spinner/passport.json +34 -7
  75. package/source/components/spinner/passport.manual.json +37 -0
  76. package/source/components/switch/core/agent.ts +31 -0
  77. package/source/components/switch/core/index.ts +2 -0
  78. package/source/components/switch/passport.json +68 -10
  79. package/source/components/switch/passport.manual.json +67 -0
  80. package/source/components/tabs/passport.json +50 -9
  81. package/source/components/tabs/passport.manual.json +50 -0
  82. package/source/components/textarea/core/agent.ts +34 -0
  83. package/source/components/textarea/core/index.ts +2 -0
  84. package/source/components/textarea/passport.json +83 -10
  85. package/source/components/textarea/passport.manual.json +82 -0
  86. package/source/components/toaster/passport.json +41 -8
  87. package/source/components/toaster/passport.manual.json +42 -0
  88. package/source/components/tooltip/passport.json +39 -6
  89. package/source/components/tooltip/passport.manual.json +41 -0
  90. package/tools/mcp/design.cjs +6 -1
  91. package/tools/mcp/figma.cjs +49 -9
  92. package/tools/mcp/install.cjs +6 -1
  93. package/tools/mcp/server.cjs +14 -2
@@ -10,8 +10,9 @@
10
10
  ]
11
11
  },
12
12
  "source": {
13
- "libraryVersion": "2.6.3",
13
+ "libraryVersion": "2.7.0",
14
14
  "files": [
15
+ "core/agent.ts",
15
16
  "core/group.ts",
16
17
  "core/index.ts",
17
18
  "core/types.ts",
@@ -22,8 +23,9 @@
22
23
  "react/locale.ts",
23
24
  "react/props.ts"
24
25
  ],
25
- "sourceHash": "sha256:ddf0907b2b6377c5d134ed140d8a4f47b0dc5ef39ec984c21ec33370c6d9815f"
26
+ "sourceHash": "sha256:864a782462120954cea168efe6df111474541556f8518107677c1098cceaa035"
26
27
  },
28
+ "purpose": "A single native checkbox, and CheckboxGroup for several that share a name, with mixed state, a select-all and group-level validation.",
27
29
  "props": [
28
30
  {
29
31
  "name": "aria-describedby",
@@ -260,7 +262,8 @@
260
262
  "required": false,
261
263
  "origin": "derived",
262
264
  "description": "Render these options, or pass `<Checkbox value=\"…\">` children.",
263
- "descriptionOrigin": "jsdoc"
265
+ "descriptionOrigin": "jsdoc",
266
+ "note": "The declarative form of the group's children. Supply this or Checkbox children, never both."
264
267
  },
265
268
  {
266
269
  "name": "orientation",
@@ -455,20 +458,90 @@
455
458
  }
456
459
  ],
457
460
  "accessibility": {
458
- "supplied": [],
459
- "required": []
461
+ "supplied": [
462
+ "native <input type=checkbox> semantics, including Space to toggle",
463
+ "the indeterminate state is set as a DOM property, which is the only way it exists",
464
+ "CheckboxGroup renders a fieldset with the group label as its legend",
465
+ "description and error linked by aria-describedby"
466
+ ],
467
+ "required": [
468
+ "a `label` on every checkbox — an unlabelled one is unusable by anything but sight",
469
+ "`name` when the value is submitted with a form"
470
+ ]
471
+ },
472
+ "composition": {
473
+ "allowedChildren": [
474
+ "Checkbox"
475
+ ],
476
+ "mustNotContain": [
477
+ "label"
478
+ ],
479
+ "notes": "CheckboxGroup expects Checkbox children or an `options` array, not both. The component supplies its own label element."
480
+ },
481
+ "operations": {
482
+ "setValue": {
483
+ "summary": "Write a value into the field, as though a person had entered it.",
484
+ "effects": [
485
+ "replaces the field's current value",
486
+ "fires the host's change handler"
487
+ ],
488
+ "reversible": true
489
+ },
490
+ "clear": {
491
+ "summary": "Empty the field.",
492
+ "effects": [
493
+ "discards the field's current value",
494
+ "fires the host's change handler"
495
+ ],
496
+ "reversible": true
497
+ },
498
+ "focus": {
499
+ "summary": "Move keyboard focus to the field.",
500
+ "effects": [
501
+ "moves focus"
502
+ ],
503
+ "reversible": false
504
+ }
505
+ },
506
+ "intentDomains": [
507
+ "ActionIntent"
508
+ ],
509
+ "safeMutations": {
510
+ "allowed": [
511
+ "size",
512
+ "orientation",
513
+ "description",
514
+ "selectAll"
515
+ ],
516
+ "forbidden": [
517
+ "checked",
518
+ "defaultChecked",
519
+ "value",
520
+ "defaultValue",
521
+ "name",
522
+ "options",
523
+ "required",
524
+ "disabled",
525
+ "readOnly",
526
+ "onChange",
527
+ "onCheckedChange",
528
+ "onValueChange",
529
+ "error"
530
+ ],
531
+ "rationale": "Layout is safe. The checked state is the user's answer and `options` is the question; an agent that edits either is answering on their behalf."
460
532
  },
461
- "composition": {},
462
- "operations": {},
463
- "intentDomains": [],
464
- "safeMutations": {},
465
533
  "examples": [],
466
534
  "coverage": {
467
535
  "propsTotal": 19,
468
536
  "propsDescribed": 15,
469
537
  "describedPct": 79,
470
538
  "authoredSections": [
471
- "accessibility"
539
+ "accessibility",
540
+ "composition",
541
+ "intentDomains",
542
+ "operations",
543
+ "purpose",
544
+ "safeMutations"
472
545
  ]
473
546
  }
474
547
  }
@@ -0,0 +1,82 @@
1
+ {
2
+ "$schema": "../../../schema/passport-overlay-v1.schema.json",
3
+ "component": "Checkbox",
4
+ "purpose": "A single native checkbox, and CheckboxGroup for several that share a name, with mixed state, a select-all and group-level validation.",
5
+ "intentDomains": [
6
+ "ActionIntent"
7
+ ],
8
+ "$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
9
+ "operations": {
10
+ "setValue": {
11
+ "summary": "Write a value into the field, as though a person had entered it.",
12
+ "effects": [
13
+ "replaces the field's current value",
14
+ "fires the host's change handler"
15
+ ],
16
+ "reversible": true
17
+ },
18
+ "clear": {
19
+ "summary": "Empty the field.",
20
+ "effects": [
21
+ "discards the field's current value",
22
+ "fires the host's change handler"
23
+ ],
24
+ "reversible": true
25
+ },
26
+ "focus": {
27
+ "summary": "Move keyboard focus to the field.",
28
+ "effects": [
29
+ "moves focus"
30
+ ],
31
+ "reversible": false
32
+ }
33
+ },
34
+ "accessibility": {
35
+ "supplied": [
36
+ "native <input type=checkbox> semantics, including Space to toggle",
37
+ "the indeterminate state is set as a DOM property, which is the only way it exists",
38
+ "CheckboxGroup renders a fieldset with the group label as its legend",
39
+ "description and error linked by aria-describedby"
40
+ ],
41
+ "required": [
42
+ "a `label` on every checkbox — an unlabelled one is unusable by anything but sight",
43
+ "`name` when the value is submitted with a form"
44
+ ]
45
+ },
46
+ "composition": {
47
+ "allowedChildren": [
48
+ "Checkbox"
49
+ ],
50
+ "mustNotContain": [
51
+ "label"
52
+ ],
53
+ "notes": "CheckboxGroup expects Checkbox children or an `options` array, not both. The component supplies its own label element."
54
+ },
55
+ "safeMutations": {
56
+ "allowed": [
57
+ "size",
58
+ "orientation",
59
+ "description",
60
+ "selectAll"
61
+ ],
62
+ "forbidden": [
63
+ "checked",
64
+ "defaultChecked",
65
+ "value",
66
+ "defaultValue",
67
+ "name",
68
+ "options",
69
+ "required",
70
+ "disabled",
71
+ "readOnly",
72
+ "onChange",
73
+ "onCheckedChange",
74
+ "onValueChange",
75
+ "error"
76
+ ],
77
+ "rationale": "Layout is safe. The checked state is the user's answer and `options` is the question; an agent that edits either is answering on their behalf."
78
+ },
79
+ "propNotes": {
80
+ "options": "The declarative form of the group's children. Supply this or Checkbox children, never both."
81
+ }
82
+ }
@@ -0,0 +1,38 @@
1
+ /*
2
+ * Combobox's agent contract.
3
+ *
4
+ * The first of the two components whose ref is a handle rather than an
5
+ * element. `open` and `close` join the operation set because it has a list to
6
+ * disclose, and `mode` decides whether the value is one option or several.
7
+ *
8
+ * Pure: no React, no DOM. The contract is what this instance will accept, and
9
+ * the validator in shared/core/agent decides everything from it. Build it from
10
+ * props on every render so that a field which has just become read-only stops
11
+ * accepting writes immediately.
12
+ */
13
+
14
+ import type { FieldContract } from "../../shared/core/agent";
15
+
16
+ export interface ComboboxAgentProps {
17
+ label?: string;
18
+ disabled?: boolean;
19
+ readOnly?: boolean;
20
+ required?: boolean;
21
+ multiple?: boolean;
22
+ max?: number;
23
+ options?: readonly { value: string; label: string; disabled?: boolean }[];
24
+ }
25
+
26
+ export function comboboxContract(props: ComboboxAgentProps = {}): FieldContract {
27
+ return {
28
+ component: props.multiple ? "MultiSelect" : "Select",
29
+ kind: props.multiple ? "multiChoice" : "choice",
30
+ operations: ["setValue", "clear", "focus", "open", "close"],
31
+ label: props.label,
32
+ disabled: props.disabled,
33
+ readOnly: props.readOnly,
34
+ required: props.required,
35
+ options: props.options,
36
+ maxSelected: props.multiple ? props.max : undefined,
37
+ };
38
+ }
@@ -14,3 +14,5 @@ export {
14
14
  export { getVisibleRange, measureItems, scrollToItem } from "./virtual";
15
15
  export type { ItemSizes, ListMetrics, Range } from "./virtual";
16
16
  export type * from "./types";
17
+ // The agent contract: what a model may ask this field to do.
18
+ export { comboboxContract, type ComboboxAgentProps } from "./agent";
@@ -9,8 +9,9 @@
9
9
  ]
10
10
  },
11
11
  "source": {
12
- "libraryVersion": "2.6.3",
12
+ "libraryVersion": "2.7.0",
13
13
  "files": [
14
+ "core/agent.ts",
14
15
  "core/filter.ts",
15
16
  "core/index.ts",
16
17
  "core/types.ts",
@@ -25,8 +26,9 @@
25
26
  "react/props.ts",
26
27
  "react/useCombobox.ts"
27
28
  ],
28
- "sourceHash": "sha256:9cd7b0e88a1ee4b9bd83cab6ad6d55613476a35e4d7ea70be9fc7ac9fe2a58e7"
29
+ "sourceHash": "sha256:5243c2cc6863f8c2dcd248c2fd46417390fec66a8d5b0d405177a372e9c0566f"
29
30
  },
31
+ "purpose": "Select and MultiSelect: a searchable combobox for one or many values, with options in the browser's top layer, optional virtualisation and create-on-type.",
30
32
  "props": [
31
33
  {
32
34
  "name": "allowCreate",
@@ -213,7 +215,8 @@
213
215
  "required": false,
214
216
  "origin": "derived",
215
217
  "description": "`client` filters `options` locally; `server` expects `options` to be the current results.",
216
- "descriptionOrigin": "jsdoc"
218
+ "descriptionOrigin": "jsdoc",
219
+ "note": "Single or multiple. Changing it changes the shape of `value`, so it cannot be toggled on a live field."
217
220
  },
218
221
  {
219
222
  "name": "name",
@@ -376,7 +379,8 @@
376
379
  "default": true,
377
380
  "origin": "derived",
378
381
  "description": "`true` / `false` to force, or a count above which virtualization turns on (default 80).",
379
- "descriptionOrigin": "jsdoc"
382
+ "descriptionOrigin": "jsdoc",
383
+ "note": "Worth turning on past a few hundred options; it changes nothing a user can observe."
380
384
  }
381
385
  ],
382
386
  "events": [
@@ -457,20 +461,108 @@
457
461
  }
458
462
  ],
459
463
  "accessibility": {
460
- "supplied": [],
461
- "required": []
464
+ "supplied": [
465
+ "the combobox pattern: aria-expanded, aria-controls, aria-activedescendant on the control",
466
+ "arrow keys move the active option, Enter selects, Escape closes and returns focus",
467
+ "the listbox renders in the top layer, so it is never clipped by an ancestor's overflow",
468
+ "the empty and loading states are announced rather than leaving silence"
469
+ ],
470
+ "required": [
471
+ "a `label`, or `aria-label` when the control is visually unlabelled",
472
+ "an `emptyMessage` that says what to do next, not just that nothing matched"
473
+ ]
474
+ },
475
+ "composition": {
476
+ "notes": "Options come from the `options` array, not from children. `renderOption` changes how one is drawn but must not introduce its own focusable elements — the listbox owns focus."
477
+ },
478
+ "operations": {
479
+ "setValue": {
480
+ "summary": "Write a value into the field, as though a person had entered it.",
481
+ "effects": [
482
+ "replaces the field's current value",
483
+ "fires the host's change handler"
484
+ ],
485
+ "reversible": true
486
+ },
487
+ "clear": {
488
+ "summary": "Empty the field.",
489
+ "effects": [
490
+ "discards the field's current value",
491
+ "fires the host's change handler"
492
+ ],
493
+ "reversible": true
494
+ },
495
+ "focus": {
496
+ "summary": "Move keyboard focus to the field.",
497
+ "effects": [
498
+ "moves focus"
499
+ ],
500
+ "reversible": false
501
+ },
502
+ "open": {
503
+ "summary": "Open the field's calendar or option list.",
504
+ "effects": [
505
+ "shows the overlay"
506
+ ],
507
+ "reversible": true
508
+ },
509
+ "close": {
510
+ "summary": "Close the field's calendar or option list.",
511
+ "effects": [
512
+ "hides the overlay"
513
+ ],
514
+ "reversible": true
515
+ }
516
+ },
517
+ "intentDomains": [
518
+ "ActionIntent",
519
+ "Recommendation",
520
+ "Classification"
521
+ ],
522
+ "safeMutations": {
523
+ "allowed": [
524
+ "size",
525
+ "placeholder",
526
+ "description",
527
+ "emptyMessage",
528
+ "maxHeight",
529
+ "maxVisibleTags",
530
+ "searchable",
531
+ "clearable",
532
+ "closeOnSelect",
533
+ "showSelectAll",
534
+ "virtualize"
535
+ ],
536
+ "forbidden": [
537
+ "value",
538
+ "defaultValue",
539
+ "name",
540
+ "options",
541
+ "mode",
542
+ "max",
543
+ "required",
544
+ "disabled",
545
+ "allowCreate",
546
+ "onChange",
547
+ "onCreate",
548
+ "onLoadMore",
549
+ "filterFn",
550
+ "error"
551
+ ],
552
+ "rationale": "Presentation and affordances are safe. `options` is the domain, `mode` decides single or multiple, and `allowCreate` lets a user invent values the backend may not accept."
462
553
  },
463
- "composition": {},
464
- "operations": {},
465
- "intentDomains": [],
466
- "safeMutations": {},
467
554
  "examples": [],
468
555
  "coverage": {
469
556
  "propsTotal": 34,
470
557
  "propsDescribed": 28,
471
558
  "describedPct": 82,
472
559
  "authoredSections": [
473
- "accessibility"
560
+ "accessibility",
561
+ "composition",
562
+ "intentDomains",
563
+ "operations",
564
+ "purpose",
565
+ "safeMutations"
474
566
  ]
475
567
  }
476
568
  }
@@ -0,0 +1,101 @@
1
+ {
2
+ "$schema": "../../../schema/passport-overlay-v1.schema.json",
3
+ "component": "Combobox",
4
+ "purpose": "Select and MultiSelect: a searchable combobox for one or many values, with options in the browser's top layer, optional virtualisation and create-on-type.",
5
+ "intentDomains": [
6
+ "ActionIntent",
7
+ "Recommendation",
8
+ "Classification"
9
+ ],
10
+ "$operationsNote": "Mirrors shared/core/agent/field.ts, which is what the runtime dispatches on. source/components/shared/core/agent/__tests__/field-passport.test.ts fails if the two drift. No `apiMethod`: a field is written through its ref — the element for most of these, a handle for Combobox and DatePicker — and that is a normal case, not a gap.",
11
+ "operations": {
12
+ "setValue": {
13
+ "summary": "Write a value into the field, as though a person had entered it.",
14
+ "effects": [
15
+ "replaces the field's current value",
16
+ "fires the host's change handler"
17
+ ],
18
+ "reversible": true
19
+ },
20
+ "clear": {
21
+ "summary": "Empty the field.",
22
+ "effects": [
23
+ "discards the field's current value",
24
+ "fires the host's change handler"
25
+ ],
26
+ "reversible": true
27
+ },
28
+ "focus": {
29
+ "summary": "Move keyboard focus to the field.",
30
+ "effects": [
31
+ "moves focus"
32
+ ],
33
+ "reversible": false
34
+ },
35
+ "open": {
36
+ "summary": "Open the field's calendar or option list.",
37
+ "effects": [
38
+ "shows the overlay"
39
+ ],
40
+ "reversible": true
41
+ },
42
+ "close": {
43
+ "summary": "Close the field's calendar or option list.",
44
+ "effects": [
45
+ "hides the overlay"
46
+ ],
47
+ "reversible": true
48
+ }
49
+ },
50
+ "accessibility": {
51
+ "supplied": [
52
+ "the combobox pattern: aria-expanded, aria-controls, aria-activedescendant on the control",
53
+ "arrow keys move the active option, Enter selects, Escape closes and returns focus",
54
+ "the listbox renders in the top layer, so it is never clipped by an ancestor's overflow",
55
+ "the empty and loading states are announced rather than leaving silence"
56
+ ],
57
+ "required": [
58
+ "a `label`, or `aria-label` when the control is visually unlabelled",
59
+ "an `emptyMessage` that says what to do next, not just that nothing matched"
60
+ ]
61
+ },
62
+ "composition": {
63
+ "notes": "Options come from the `options` array, not from children. `renderOption` changes how one is drawn but must not introduce its own focusable elements — the listbox owns focus."
64
+ },
65
+ "safeMutations": {
66
+ "allowed": [
67
+ "size",
68
+ "placeholder",
69
+ "description",
70
+ "emptyMessage",
71
+ "maxHeight",
72
+ "maxVisibleTags",
73
+ "searchable",
74
+ "clearable",
75
+ "closeOnSelect",
76
+ "showSelectAll",
77
+ "virtualize"
78
+ ],
79
+ "forbidden": [
80
+ "value",
81
+ "defaultValue",
82
+ "name",
83
+ "options",
84
+ "mode",
85
+ "max",
86
+ "required",
87
+ "disabled",
88
+ "allowCreate",
89
+ "onChange",
90
+ "onCreate",
91
+ "onLoadMore",
92
+ "filterFn",
93
+ "error"
94
+ ],
95
+ "rationale": "Presentation and affordances are safe. `options` is the domain, `mode` decides single or multiple, and `allowCreate` lets a user invent values the backend may not accept."
96
+ },
97
+ "propNotes": {
98
+ "mode": "Single or multiple. Changing it changes the shape of `value`, so it cannot be toggled on a live field.",
99
+ "virtualize": "Worth turning on past a few hundred options; it changes nothing a user can observe."
100
+ }
101
+ }
@@ -24,6 +24,20 @@ export interface ComboboxHandle<V extends OptionValue = string> {
24
24
  toggle(): void;
25
25
  focus(): void;
26
26
  clear(): void;
27
+ /**
28
+ * Replace the selection, as choosing from the list would: the change
29
+ * handler fires, and an uncontrolled combobox updates itself.
30
+ *
31
+ * `null` clears. A single value is accepted in either mode; a list given to
32
+ * a single-select combobox keeps the first entry, and one longer than `max`
33
+ * is truncated rather than silently rejected. Values that are not in
34
+ * `options` are still committed — the component does not own the option set
35
+ * and a `server`-mode combobox legitimately holds a value whose option is
36
+ * not currently loaded. Code that needs values checked against the list
37
+ * should go through the agent runtime, whose reference layer does exactly
38
+ * that.
39
+ */
40
+ setValue(next: V | readonly V[] | null): void;
27
41
  getValue(): V[];
28
42
  getSelectedOptions(): ComboboxOption<V>[];
29
43
  }
@@ -373,10 +373,16 @@ export function useCombobox<V extends OptionValue>(params: UseComboboxParams<V>)
373
373
  toggle: () => setOpen(!open),
374
374
  focus: () => controlRef.current?.focus(),
375
375
  clear: () => commit([]),
376
+ // `clear` was always a write; this is the same write with a value in it.
377
+ setValue: (next) => {
378
+ const list = next == null ? [] : Array.isArray(next) ? [...next] : [next as V];
379
+ if (!multiple) return commit(list.slice(0, 1));
380
+ commit(max === undefined ? list : list.slice(0, max));
381
+ },
376
382
  getValue: () => [...values],
377
383
  getSelectedOptions: () => selectedOptions,
378
384
  }),
379
- [setOpen, open, commit, values, selectedOptions, controlRef],
385
+ [setOpen, open, commit, values, selectedOptions, controlRef, multiple, max],
380
386
  );
381
387
 
382
388
  return {
@@ -7,8 +7,15 @@ const MAIN_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
7
7
  const REL_NS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
8
8
  const PKG_REL_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
9
9
 
10
- /** Characters XML 1.0 does not allow at all. */
11
- const INVALID_XML_CHARS = /[^\t\n\r -퟿-�\u{10000}-\u{10FFFF}]/gu;
10
+ /**
11
+ * Characters XML 1.0 does not allow at all.
12
+ *
13
+ * Written as escapes rather than as the characters themselves. The bounds
14
+ * here are U+D7FF, U+E000 and U+FFFD, which are invisible in an editor, do
15
+ * not survive every copy and paste, and made this line depend on the file
16
+ * being read back as UTF-8. TypeScript 7 also rejected the literal form.
17
+ */
18
+ const INVALID_XML_CHARS = /[^\t\n\r\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/gu;
12
19
 
13
20
  const XML_ENTITIES: Record<string, string> = {
14
21
  "&": "&amp;",
@@ -14,7 +14,7 @@
14
14
  ]
15
15
  },
16
16
  "source": {
17
- "libraryVersion": "2.6.3",
17
+ "libraryVersion": "2.7.0",
18
18
  "files": [
19
19
  "agent/coerce.ts",
20
20
  "agent/contract.ts",
@@ -64,7 +64,7 @@
64
64
  "react/keyboard.ts",
65
65
  "react/locale.ts"
66
66
  ],
67
- "sourceHash": "sha256:b3fa605b7c7ae2fbb4d9ccd3900b69472665508c0c92b5a218767d4c387d20a3"
67
+ "sourceHash": "sha256:66dfc75f60791c6e4397d3438ec3ad19ce47f9e5ea51677024200d1f75bd5d44"
68
68
  },
69
69
  "purpose": "Display and operate on tabular data. Virtualized; holds its own sort, filter, search, selection, column and pagination state in an external store exposed through GridApi.",
70
70
  "props": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "../../../schema/passport-v1.schema.json",
2
+ "$schema": "../../../schema/passport-overlay-v1.schema.json",
3
3
  "component": "DataGrid",
4
4
  "purpose": "Display and operate on tabular data. Virtualized; holds its own sort, filter, search, selection, column and pagination state in an external store exposed through GridApi.",
5
5
  "intentDomains": ["Query", "ActionIntent", "Extraction"],
@@ -0,0 +1,42 @@
1
+ /*
2
+ * DatePicker's agent contract.
3
+ *
4
+ * Dates arrive as ISO yyyy-mm-dd and are checked against `min` and `max`
5
+ * before anything is written. Anything relative — "last Friday", "end of the
6
+ * quarter" — is the adapter's to resolve: this layer refuses what it cannot read
7
+ * rather than guessing, because guessing a date wrong is indistinguishable from
8
+ * being told the wrong date.
9
+ *
10
+ * Pure: no React, no DOM. The contract is what this instance will accept, and
11
+ * the validator in shared/core/agent decides everything from it. Build it from
12
+ * props on every render so that a field which has just become read-only stops
13
+ * accepting writes immediately.
14
+ */
15
+
16
+ import type { FieldContract } from "../../shared/core/agent";
17
+
18
+ export interface DatePickerAgentProps {
19
+ label?: string;
20
+ disabled?: boolean;
21
+ readOnly?: boolean;
22
+ required?: boolean;
23
+ /** ISO yyyy-mm-dd. */
24
+ min?: string;
25
+ /** ISO yyyy-mm-dd. */
26
+ max?: string;
27
+ range?: boolean;
28
+ }
29
+
30
+ export function datePickerContract(props: DatePickerAgentProps = {}): FieldContract {
31
+ return {
32
+ component: props.range ? "DateRangePicker" : "DatePicker",
33
+ kind: props.range ? "dateRange" : "date",
34
+ operations: ["setValue", "clear", "focus", "open", "close"],
35
+ label: props.label,
36
+ disabled: props.disabled,
37
+ readOnly: props.readOnly,
38
+ required: props.required,
39
+ min: props.min,
40
+ max: props.max,
41
+ };
42
+ }
@@ -39,3 +39,5 @@ export {
39
39
  export type { DateField, WeekdayName } from "./format";
40
40
  export { getServerToday, getToday, subscribeToToday } from "./today";
41
41
  export type * from "./types";
42
+ // The agent contract: what a model may ask this field to do.
43
+ export { datePickerContract, type DatePickerAgentProps } from "./agent";