@aceshooting/lyra-ui 2.0.0 → 2.1.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 (224) hide show
  1. package/README.md +87 -14
  2. package/custom-elements.json +11555 -5539
  3. package/dist/components/app-rail/app-rail-item.d.ts +30 -0
  4. package/dist/components/app-rail/app-rail-item.js +68 -0
  5. package/dist/components/app-rail/app-rail-item.styles.d.ts +1 -0
  6. package/dist/components/app-rail/app-rail-item.styles.js +65 -0
  7. package/dist/components/app-rail/app-rail.d.ts +21 -24
  8. package/dist/components/app-rail/app-rail.js +68 -132
  9. package/dist/components/app-rail/app-rail.styles.js +5 -2
  10. package/dist/components/attachment-chip/attachment-chip.d.ts +38 -11
  11. package/dist/components/attachment-chip/attachment-chip.js +73 -29
  12. package/dist/components/attachment-trigger/attachment-trigger.d.ts +13 -0
  13. package/dist/components/attachment-trigger/attachment-trigger.js +13 -1
  14. package/dist/components/attachment-trigger/attachment-trigger.styles.js +28 -6
  15. package/dist/components/chart/bar-chart.js +2 -18
  16. package/dist/components/chart/box-plot.d.ts +25 -1
  17. package/dist/components/chart/box-plot.js +136 -6
  18. package/dist/components/chart/box-plot.styles.js +13 -0
  19. package/dist/components/chart/bubble-chart.js +2 -18
  20. package/dist/components/chart/chart.d.ts +54 -10
  21. package/dist/components/chart/chart.js +182 -39
  22. package/dist/components/chart/doughnut-chart.js +2 -18
  23. package/dist/components/chart/histogram-bin.d.ts +5 -2
  24. package/dist/components/chart/histogram-bin.js +12 -4
  25. package/dist/components/chart/histogram.js +8 -16
  26. package/dist/components/chart/line-chart.js +2 -18
  27. package/dist/components/chart/lite-chart.d.ts +80 -3
  28. package/dist/components/chart/lite-chart.js +250 -53
  29. package/dist/components/chart/pie-chart.js +2 -18
  30. package/dist/components/chart/polar-area-chart.js +2 -18
  31. package/dist/components/chart/radar-chart.js +2 -18
  32. package/dist/components/chart/scatter-chart.js +2 -18
  33. package/dist/components/chat-composer/chat-composer.d.ts +42 -1
  34. package/dist/components/chat-composer/chat-composer.js +84 -3
  35. package/dist/components/chat-composer/chat-composer.styles.js +11 -1
  36. package/dist/components/chat-message/chat-message.d.ts +32 -4
  37. package/dist/components/chat-message/chat-message.js +57 -17
  38. package/dist/components/checkbox/checkbox.d.ts +66 -8
  39. package/dist/components/checkbox/checkbox.js +173 -50
  40. package/dist/components/checkbox/checkbox.styles.js +9 -2
  41. package/dist/components/chip/chip-group.js +8 -2
  42. package/dist/components/chip/chip.js +9 -2
  43. package/dist/components/citation-badge/citation-badge.js +21 -3
  44. package/dist/components/code-block/code-block.d.ts +38 -0
  45. package/dist/components/code-block/code-block.js +72 -7
  46. package/dist/components/code-block/code-loader.d.ts +44 -1
  47. package/dist/components/code-block/code-loader.js +63 -0
  48. package/dist/components/combobox/combobox.d.ts +46 -5
  49. package/dist/components/combobox/combobox.js +175 -35
  50. package/dist/components/combobox/combobox.styles.js +3 -3
  51. package/dist/components/context-meter/context-meter.js +17 -8
  52. package/dist/components/context-meter/context-meter.styles.js +0 -6
  53. package/dist/components/conversation-item/conversation-item.d.ts +13 -16
  54. package/dist/components/conversation-item/conversation-item.js +29 -20
  55. package/dist/components/date-picker/calendar-core.d.ts +24 -0
  56. package/dist/components/date-picker/calendar-core.js +51 -4
  57. package/dist/components/date-picker/date-input.d.ts +77 -10
  58. package/dist/components/date-picker/date-input.js +278 -73
  59. package/dist/components/date-picker/date-picker.d.ts +28 -4
  60. package/dist/components/date-picker/date-picker.js +95 -37
  61. package/dist/components/dialog/confirm.js +3 -6
  62. package/dist/components/dialog/dialog.d.ts +75 -31
  63. package/dist/components/dialog/dialog.js +147 -131
  64. package/dist/components/dialog/dialog.styles.js +46 -2
  65. package/dist/components/dock-panel/dock-panel.d.ts +8 -0
  66. package/dist/components/dock-panel/dock-panel.js +15 -1
  67. package/dist/components/dock-panel/dock-panel.styles.js +15 -1
  68. package/dist/components/document-preview/document-preview.d.ts +15 -7
  69. package/dist/components/document-preview/document-preview.js +29 -13
  70. package/dist/components/empty/empty.d.ts +1 -2
  71. package/dist/components/empty/empty.js +18 -12
  72. package/dist/components/empty/empty.styles.js +0 -3
  73. package/dist/components/export-button/export-button.d.ts +12 -0
  74. package/dist/components/export-button/export-button.js +33 -5
  75. package/dist/components/export-button/export-button.styles.js +5 -1
  76. package/dist/components/file-input/accept.js +2 -0
  77. package/dist/components/file-input/file-input.d.ts +10 -1
  78. package/dist/components/file-input/file-input.js +36 -5
  79. package/dist/components/flag/flag.d.ts +1 -1
  80. package/dist/components/flag/flag.js +15 -1
  81. package/dist/components/flag/flag.styles.js +1 -1
  82. package/dist/components/flag/language-map.d.ts +4 -1
  83. package/dist/components/flag/language-map.js +6 -3
  84. package/dist/components/gauge/gauge.d.ts +10 -1
  85. package/dist/components/gauge/gauge.js +28 -9
  86. package/dist/components/generation-status/generation-status.d.ts +13 -5
  87. package/dist/components/generation-status/generation-status.js +59 -5
  88. package/dist/components/graph/graph.d.ts +37 -3
  89. package/dist/components/graph/graph.js +132 -20
  90. package/dist/components/heatmap/calendar-grid.d.ts +15 -6
  91. package/dist/components/heatmap/calendar-grid.js +17 -6
  92. package/dist/components/heatmap/heatmap-scale.d.ts +5 -1
  93. package/dist/components/heatmap/heatmap-scale.js +8 -2
  94. package/dist/components/heatmap/heatmap.d.ts +166 -25
  95. package/dist/components/heatmap/heatmap.js +372 -89
  96. package/dist/components/json-viewer/json-viewer.d.ts +22 -5
  97. package/dist/components/json-viewer/json-viewer.js +115 -37
  98. package/dist/components/json-viewer/json-viewer.styles.js +2 -1
  99. package/dist/components/kbd/kbd.js +24 -7
  100. package/dist/components/live-region/live-region.d.ts +10 -1
  101. package/dist/components/live-region/live-region.js +42 -6
  102. package/dist/components/map/map.d.ts +24 -1
  103. package/dist/components/map/map.js +89 -24
  104. package/dist/components/markdown/markdown-loader.d.ts +24 -0
  105. package/dist/components/markdown/markdown-loader.js +35 -1
  106. package/dist/components/markdown/markdown.d.ts +46 -8
  107. package/dist/components/markdown/markdown.js +136 -31
  108. package/dist/components/markdown/markdown.styles.js +7 -1
  109. package/dist/components/media-card/media-card.d.ts +25 -17
  110. package/dist/components/media-card/media-card.js +43 -65
  111. package/dist/components/mention-popover/mention-popover.d.ts +4 -1
  112. package/dist/components/mention-popover/mention-popover.js +23 -5
  113. package/dist/components/menu/menu-item.d.ts +32 -3
  114. package/dist/components/menu/menu-item.js +94 -7
  115. package/dist/components/menu/menu-item.styles.js +8 -0
  116. package/dist/components/menu/menu.d.ts +25 -1
  117. package/dist/components/menu/menu.js +99 -3
  118. package/dist/components/menu/menu.styles.js +3 -2
  119. package/dist/components/model-select/model-select.d.ts +48 -3
  120. package/dist/components/model-select/model-select.js +120 -22
  121. package/dist/components/model-select/model-select.styles.js +17 -3
  122. package/dist/components/model-settings-panel/model-settings-panel.d.ts +25 -1
  123. package/dist/components/model-settings-panel/model-settings-panel.js +65 -1
  124. package/dist/components/playback/playback.d.ts +6 -2
  125. package/dist/components/playback/playback.js +72 -29
  126. package/dist/components/playback/playback.styles.js +6 -1
  127. package/dist/components/responsive-panel/responsive-panel.d.ts +29 -16
  128. package/dist/components/responsive-panel/responsive-panel.js +130 -132
  129. package/dist/components/responsive-panel/responsive-panel.styles.js +1 -1
  130. package/dist/components/result-card/result-card.d.ts +8 -2
  131. package/dist/components/result-card/result-card.js +9 -3
  132. package/dist/components/result-card/result-card.styles.js +0 -1
  133. package/dist/components/select/select.d.ts +59 -20
  134. package/dist/components/select/select.js +125 -43
  135. package/dist/components/skeleton/skeleton.d.ts +3 -0
  136. package/dist/components/skeleton/skeleton.js +7 -1
  137. package/dist/components/skeleton/skeleton.styles.js +5 -4
  138. package/dist/components/slider/slider.d.ts +57 -5
  139. package/dist/components/slider/slider.js +195 -80
  140. package/dist/components/source-card/source-card.d.ts +17 -5
  141. package/dist/components/source-card/source-card.js +55 -8
  142. package/dist/components/source-card/source-card.styles.js +1 -1
  143. package/dist/components/source-list/source-list.js +9 -1
  144. package/dist/components/sparkline/sparkline.js +34 -10
  145. package/dist/components/split/split.d.ts +141 -14
  146. package/dist/components/split/split.js +357 -52
  147. package/dist/components/split/split.styles.js +18 -1
  148. package/dist/components/stat/stat.d.ts +10 -4
  149. package/dist/components/stat/stat.js +46 -11
  150. package/dist/components/stream-status/stream-status.d.ts +27 -11
  151. package/dist/components/stream-status/stream-status.js +95 -13
  152. package/dist/components/streaming-text/streaming-text.d.ts +9 -3
  153. package/dist/components/streaming-text/streaming-text.js +25 -10
  154. package/dist/components/streaming-text/streaming-text.styles.js +3 -2
  155. package/dist/components/switch/switch.d.ts +52 -7
  156. package/dist/components/switch/switch.js +111 -40
  157. package/dist/components/switch/switch.styles.js +1 -1
  158. package/dist/components/table/table.d.ts +18 -3
  159. package/dist/components/table/table.js +88 -20
  160. package/dist/components/tabs/tabs.d.ts +23 -0
  161. package/dist/components/tabs/tabs.js +76 -12
  162. package/dist/components/tabs/tabs.styles.js +11 -0
  163. package/dist/components/thinking-panel/thinking-panel.d.ts +3 -1
  164. package/dist/components/thinking-panel/thinking-panel.js +34 -9
  165. package/dist/components/thinking-panel/thinking-panel.styles.js +6 -0
  166. package/dist/components/time-range/time-range.d.ts +59 -2
  167. package/dist/components/time-range/time-range.js +163 -67
  168. package/dist/components/toast/toast-item.d.ts +12 -3
  169. package/dist/components/toast/toast-item.js +90 -53
  170. package/dist/components/toast/toast-item.styles.js +59 -28
  171. package/dist/components/toast/toast.js +17 -5
  172. package/dist/components/toast/toaster.js +10 -1
  173. package/dist/components/tool-approval-dialog/tool-approval-dialog.d.ts +11 -10
  174. package/dist/components/tool-approval-dialog/tool-approval-dialog.js +50 -124
  175. package/dist/components/tool-approval-dialog/tool-approval-dialog.styles.js +1 -1
  176. package/dist/components/tool-call-chip/tool-call-chip.d.ts +18 -2
  177. package/dist/components/tool-call-chip/tool-call-chip.js +86 -25
  178. package/dist/components/tool-param-form/tool-param-form.d.ts +89 -28
  179. package/dist/components/tool-param-form/tool-param-form.js +350 -106
  180. package/dist/components/tool-result-dialog/tool-result-dialog.d.ts +27 -7
  181. package/dist/components/tool-result-dialog/tool-result-dialog.js +80 -115
  182. package/dist/components/tool-result-dialog/tool-result-dialog.styles.js +1 -1
  183. package/dist/components/tool-result-view/registry.d.ts +0 -8
  184. package/dist/components/tool-result-view/registry.js +7 -2
  185. package/dist/components/tool-result-view/tool-result-view.d.ts +6 -3
  186. package/dist/components/tool-result-view/tool-result-view.js +18 -4
  187. package/dist/components/tool-select-dialog/tool-select-dialog.d.ts +11 -11
  188. package/dist/components/tool-select-dialog/tool-select-dialog.js +44 -133
  189. package/dist/components/tool-select-dialog/tool-select-dialog.styles.js +13 -4
  190. package/dist/components/tree/tree-node.d.ts +4 -1
  191. package/dist/components/tree/tree-node.js +4 -1
  192. package/dist/components/tree/tree.d.ts +17 -8
  193. package/dist/components/tree/tree.js +21 -20
  194. package/dist/components/typing-indicator/typing-indicator.d.ts +12 -2
  195. package/dist/components/typing-indicator/typing-indicator.js +16 -4
  196. package/dist/components/typing-indicator/typing-indicator.styles.js +2 -2
  197. package/dist/components/virtual-list/virtual-list.d.ts +56 -19
  198. package/dist/components/virtual-list/virtual-list.js +184 -38
  199. package/dist/components/virtual-list/virtual-list.styles.js +12 -0
  200. package/dist/components/widget/widget.d.ts +5 -4
  201. package/dist/components/widget/widget.js +54 -120
  202. package/dist/components/widget/widget.styles.js +8 -2
  203. package/dist/components/word-cloud/word-cloud-layout.d.ts +7 -0
  204. package/dist/components/word-cloud/word-cloud-layout.js +20 -6
  205. package/dist/components/word-cloud/word-cloud.d.ts +22 -2
  206. package/dist/components/word-cloud/word-cloud.js +45 -6
  207. package/dist/components/word-cloud/word-cloud.styles.js +13 -1
  208. package/dist/internal/anchored-validity.d.ts +1 -0
  209. package/dist/internal/anchored-validity.js +86 -0
  210. package/dist/internal/form-associated.d.ts +7 -0
  211. package/dist/internal/form-associated.js +43 -4
  212. package/dist/internal/numbers.d.ts +12 -0
  213. package/dist/internal/numbers.js +26 -0
  214. package/dist/internal/overlay-manager.d.ts +51 -0
  215. package/dist/internal/overlay-manager.js +523 -0
  216. package/dist/internal/positioner.js +27 -3
  217. package/dist/internal/safe-url.d.ts +12 -0
  218. package/dist/internal/safe-url.js +41 -0
  219. package/dist/internal/tokens.styles.js +17 -11
  220. package/dist/lyra.d.ts +5 -2
  221. package/dist/lyra.js +3 -1
  222. package/llms-full.txt +970 -426
  223. package/llms.txt +5 -4
  224. package/package.json +19 -2
@@ -5,34 +5,16 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { html, nothing } from 'lit';
8
- import { property, state } from 'lit/decorators.js';
8
+ import { state } from 'lit/decorators.js';
9
9
  import { LyraElement } from '../../internal/lyra-element.js';
10
10
  import { defineElement } from '../../internal/prefix.js';
11
11
  import { nextId } from '../../internal/a11y.js';
12
+ import { AnchoredValidityController, resolveValidityAnchor, VALIDITY_ANCHOR, } from '../../internal/anchored-validity.js';
12
13
  import { styles } from './tool-param-form.styles.js';
13
14
  import '../select/select.js';
14
15
  import '../combobox/option.js';
15
16
  import '../checkbox/checkbox.js';
16
17
  const EMPTY_SCHEMA = { type: 'object', properties: {} };
17
- /** `true` when `v` counts as "empty" for `prop`'s type, for the one validation rule this phase implements. */
18
- function isFieldEmpty(prop, v) {
19
- if (prop.type === 'boolean') {
20
- // A checkbox always holds a concrete true/false once rendered — there's
21
- // no third "unset" visual state to represent JSON Schema's "key absent"
22
- // meaning. Reusing <lyra-checkbox>'s own required semantics (must be
23
- // checked) gives `required` on a boolean field an actually useful
24
- // meaning here — e.g. a `confirmDestroy: boolean, required` param — and
25
- // matches the control it's composed from instead of inventing a second,
26
- // inconsistent definition of "required checkbox".
27
- return v !== true;
28
- }
29
- if (v === undefined || v === null)
30
- return true;
31
- if (prop.type === 'string')
32
- return typeof v !== 'string' || v.trim() === '';
33
- // number / integer
34
- return typeof v !== 'number' || Number.isNaN(v);
35
- }
36
18
  /**
37
19
  * `<lyra-tool-param-form>` — renders one form control per top-level property
38
20
  * of a JSON Schema object, for ad hoc tool invocation or approval-editing UIs
@@ -41,18 +23,21 @@ function isFieldEmpty(prop, v) {
41
23
  *
42
24
  * **Scope limitation (intentional, not accidental):** this phase only
43
25
  * understands a *flat* object schema — every `properties` entry must be
44
- * `'string'`, `'number'`, `'integer'`, `'boolean'`, or a string `enum`.
45
- * Nested objects, arrays, `oneOf`/`anyOf`/`allOf`, `$ref`, and any other
46
- * JSON Schema keyword are not read. A full JSON-Schema-to-form renderer is
26
+ * `'string'`, `'number'`, `'integer'`, `'boolean'`, or a string `enum`;
27
+ * primitive `const` is also enforced. Nested objects, arrays,
28
+ * `oneOf`/`anyOf`/`allOf`, `$ref`, constraints such as `minLength`/`minimum`,
29
+ * and schema-valued `additionalProperties` are not read. A full
30
+ * JSON-Schema-to-form renderer is
47
31
  * out of scope for this component; a property whose `type` isn't one of the
48
- * four above renders a visible "Unsupported field type" note instead of
49
- * silently dropping it or throwing.
32
+ * four above renders a visible "Unsupported field type" note and marks the
33
+ * form invalid instead of silently dropping it or throwing.
50
34
  *
51
35
  * Fields render in `Object.keys(schema.properties)` order (insertion order,
52
36
  * which is reliable for a plain object's string keys). A field's label is
53
37
  * `title ?? ` the property key; `description` renders as helper text below
54
- * the control; a key listed in `required` gets a visible `*` and the
55
- * underlying control's own `required` wiring.
38
+ * the control; a key listed in `required` gets a visible `*`. The outer
39
+ * component owns validation because JSON Schema `required` means property
40
+ * presence, unlike HTML controls' nonempty/must-check semantics.
56
41
  *
57
42
  * This component owns no Submit/Cancel/Approve chrome — a consumer composes
58
43
  * it inside their own dialog (e.g. a tool-approval dialog) and reads
@@ -67,7 +52,9 @@ function isFieldEmpty(prop, v) {
67
52
  * uncontrolled `<input placeholder>` not writing to `.value`, and means the
68
53
  * very first `lyra-input` a consumer receives already carries every default
69
54
  * resolved, so round-tripping `e.detail.value` back into `.value` converges
70
- * after one edit.
55
+ * after one edit. JSON Schema defines `default` as an annotation; this form
56
+ * renderer deliberately materializes it before validation and submission,
57
+ * so a valid default can satisfy `required` here.
71
58
  *
72
59
  * Optional native `<form>` participation is implemented via `ElementInternals`
73
60
  * attached directly (this component's value is a whole object, not a plain
@@ -81,37 +68,85 @@ function isFieldEmpty(prop, v) {
81
68
  * @event lyra-input - A field's value changed. `detail: { value }` — the
82
69
  * full current value object (every property, defaults resolved), not just
83
70
  * the field that changed.
84
- * @event lyra-validity-change - Overall validity changed (a required field
85
- * became empty/non-empty). `detail: { valid: boolean; errors: Record<string, string> }`.
71
+ * @event lyra-validity-change - Overall validity or field errors changed.
72
+ * `detail: { valid: boolean; errors: Record<string, string> }`.
86
73
  * @csspart base - The outer wrapper around all fields.
87
74
  * @csspart field - One property's wrapper (label + control + description + error).
88
75
  * @csspart label - A field's label.
89
76
  * @csspart description - A field's helper text, from `schema.description`.
90
- * @csspart error - A field's inline validation message (only rendered once the field has been visited).
77
+ * @csspart error - A field-level or form-level validation message.
91
78
  */
92
79
  export class LyraToolParamForm extends LyraElement {
93
80
  static { this.formAssociated = true; }
94
81
  static { this.styles = [LyraElement.styles, styles]; }
82
+ static { this.properties = {
83
+ name: { reflect: true, noAccessor: true },
84
+ schema: { attribute: false, noAccessor: true },
85
+ value: { attribute: false, noAccessor: true },
86
+ disabled: { type: Boolean, reflect: true, noAccessor: true },
87
+ }; }
95
88
  constructor() {
96
89
  super();
97
- this.schema = EMPTY_SCHEMA;
98
- this.value = {};
99
- /** Submission key for the optional native `<form>` participation see the class doc. */
100
- this.name = '';
101
- this.disabled = false;
102
- // The true (touched-independent) per-field error set, recomputed in
103
- // willUpdate() so it's available to the very same render() pass rather
104
- // than lagging a frame behind in updated(). Exposed publicly (read-only)
105
- // via the `errors` getter below, under a leading underscore here so the
106
- // getter itself can be named plain `errors` without a collision.
90
+ // The true (touched-independent) per-field error set, recomputed by the
91
+ // synchronous schema/value accessors so native validity APIs and the next
92
+ // render observe the same state. Exposed publicly (read-only) via the
93
+ // `errors` getter below, under a leading underscore so the getter itself
94
+ // can be named plain `errors` without a collision.
107
95
  this._errors = {};
96
+ this._formError = '';
97
+ this.showFormError = false;
108
98
  // Which fields have been visited (focusout'd) at least once — gates only
109
99
  // the *visual* error/aria-invalid presentation, matching every other
110
100
  // form control in this library (lyra-select/lyra-combobox/lyra-model-select
111
101
  // all avoid flashing red before the user has touched anything).
112
102
  this.touchedFields = new Set();
113
103
  this.baseId = nextId('tool-param-form');
104
+ this._fieldsetDisabled = false;
105
+ this._name = '';
106
+ this._schema = EMPTY_SCHEMA;
107
+ this._value = {};
108
+ this._effectiveValue = {};
109
+ this._validityFlags = {};
110
+ this._disabled = false;
114
111
  this.internals = this.attachInternals();
112
+ this.validityController = new AnchoredValidityController(this, this.internals, () => this[VALIDITY_ANCHOR]());
113
+ this.syncFormState();
114
+ }
115
+ get form() {
116
+ return this.internals.form;
117
+ }
118
+ get labels() {
119
+ return this.internals.labels;
120
+ }
121
+ get validity() {
122
+ return this.internals.validity;
123
+ }
124
+ get validationMessage() {
125
+ return this.internals.validationMessage;
126
+ }
127
+ get willValidate() {
128
+ return this.internals.willValidate;
129
+ }
130
+ /** @internal */
131
+ [VALIDITY_ANCHOR]() {
132
+ const field = this.firstInvalidField();
133
+ if (!field)
134
+ return undefined;
135
+ const input = field.querySelector('input.control');
136
+ if (input)
137
+ return input;
138
+ for (const child of field.children) {
139
+ const anchor = resolveValidityAnchor(child);
140
+ if (anchor)
141
+ return anchor;
142
+ }
143
+ return undefined;
144
+ }
145
+ firstInvalidField() {
146
+ const firstInvalidKey = Object.keys(this._errors)[0];
147
+ if (!firstInvalidKey || !this.renderRoot)
148
+ return undefined;
149
+ return Array.from(this.renderRoot.querySelectorAll('[part="field"]')).find((candidate) => candidate.dataset.key === firstInvalidKey);
115
150
  }
116
151
  connectedCallback() {
117
152
  super.connectedCallback();
@@ -120,12 +155,24 @@ export class LyraToolParamForm extends LyraElement {
120
155
  // see the correct answer even if called before Lit's first (async)
121
156
  // update has run, mirroring lyra-select's/lyra-checkbox's identical
122
157
  // connectedCallback -> updateValidity() call.
123
- this._errors = this.computeErrors();
124
- this.syncInternals();
158
+ this.syncFormState();
125
159
  }
126
160
  /** `value`, with any property missing from it filled in from `schema`'s own `default` — see the class doc. */
127
161
  get effectiveValue() {
128
- const props = this.schema?.properties ?? {};
162
+ return { ...this._effectiveValue };
163
+ }
164
+ get schemaProperties() {
165
+ const properties = this.schema?.properties;
166
+ return properties !== null && typeof properties === 'object' && !Array.isArray(properties)
167
+ ? properties
168
+ : {};
169
+ }
170
+ get requiredKeys() {
171
+ const required = this.schema?.required;
172
+ return Array.isArray(required) ? required.filter((key) => typeof key === 'string') : [];
173
+ }
174
+ resolveEffectiveValue() {
175
+ const props = this.schemaProperties;
129
176
  const out = { ...this.value };
130
177
  for (const key of Object.keys(props)) {
131
178
  // `hasOwnProperty`, not `out[key] === undefined` — a number field the
@@ -136,88 +183,282 @@ export class LyraToolParamForm extends LyraElement {
136
183
  // happens to be `undefined`. Only a key genuinely absent from `value`
137
184
  // (never touched, or reset back to `{}`) falls back to the default.
138
185
  if (!Object.prototype.hasOwnProperty.call(this.value, key) && props[key].default !== undefined) {
139
- out[key] = props[key].default;
186
+ Object.defineProperty(out, key, {
187
+ configurable: true,
188
+ enumerable: true,
189
+ writable: true,
190
+ value: props[key].default,
191
+ });
140
192
  }
141
193
  }
142
194
  return out;
143
195
  }
196
+ get schema() {
197
+ return this._schema;
198
+ }
199
+ set schema(next) {
200
+ const old = this._schema;
201
+ this._schema = next ?? EMPTY_SCHEMA;
202
+ this.syncFormState();
203
+ this.requestUpdate('schema', old);
204
+ }
205
+ get value() {
206
+ return this._value;
207
+ }
208
+ set value(next) {
209
+ const old = this._value;
210
+ this._value = next ?? {};
211
+ this.syncFormState();
212
+ this.requestUpdate('value', old);
213
+ }
214
+ /** Submission key for the optional native `<form>` participation, reflected synchronously. */
215
+ get name() {
216
+ return this._name;
217
+ }
218
+ set name(next) {
219
+ const old = this._name;
220
+ this._name = next ?? '';
221
+ if (this._name) {
222
+ this.setAttribute('name', this._name);
223
+ }
224
+ else {
225
+ this.removeAttribute('name');
226
+ }
227
+ this.requestUpdate('name', old);
228
+ }
229
+ get disabled() {
230
+ return this._disabled;
231
+ }
232
+ set disabled(next) {
233
+ const old = this._disabled;
234
+ this._disabled = Boolean(next);
235
+ this.toggleAttribute('disabled', this._disabled);
236
+ this.requestUpdate('disabled', old);
237
+ }
238
+ /** Whether the form is disabled explicitly or by an ancestor fieldset. */
239
+ get effectiveDisabled() {
240
+ return this.disabled || this._fieldsetDisabled;
241
+ }
144
242
  /**
145
- * The current per-field validation errors (`{ [propertyKey]: message }`),
146
- * required-and-empty only mirrors the last `lyra-validity-change`
147
- * event's `errors`. Independent of which fields have been visited; a
148
- * consumer wanting the visited-only, screen-reader-announced subset
149
- * should read the rendered `[part="error"]` elements instead.
243
+ * The current per-field validation errors (`{ [propertyKey]: message }`)
244
+ * required presence, primitive type, integer, enum, const, or unsupported
245
+ * type — mirrors the last `lyra-validity-change` event's `errors`.
246
+ * Independent of which fields have been visited; a consumer wanting the
247
+ * visited-only, screen-reader-announced subset should read the rendered
248
+ * `[part="error"]` elements instead.
150
249
  */
151
250
  get errors() {
152
251
  return { ...this._errors };
153
252
  }
154
- /** The current per-field validation errors (required-and-empty only), independent of `touchedFields`. */
155
- computeErrors() {
156
- const props = this.schema?.properties ?? {};
157
- const required = new Set(this.schema?.required ?? []);
158
- const effective = this.effectiveValue;
253
+ /** A schema-wide or JSON-serialization error that cannot be assigned to one field. */
254
+ get formError() {
255
+ return this._formError;
256
+ }
257
+ /**
258
+ * Computes the supported flat JSON Schema subset against the exact value
259
+ * that will be submitted. Defaults are already materialized in `effective`.
260
+ */
261
+ computeValidation(effective) {
262
+ const props = this.schemaProperties;
263
+ const required = new Set(this.requiredKeys);
159
264
  const out = {};
265
+ const flags = {};
266
+ const addError = (key, message) => {
267
+ Object.defineProperty(out, key, {
268
+ configurable: true,
269
+ enumerable: true,
270
+ writable: true,
271
+ value: message,
272
+ });
273
+ };
160
274
  for (const key of Object.keys(props)) {
161
- if (!required.has(key))
275
+ const prop = props[key];
276
+ const type = prop?.type;
277
+ if (type !== 'string' && type !== 'number' && type !== 'integer' && type !== 'boolean') {
278
+ addError(key, `Unsupported field type "${String(type)}".`);
279
+ flags.customError = true;
280
+ continue;
281
+ }
282
+ const present = Object.prototype.hasOwnProperty.call(effective, key) && effective[key] !== undefined;
283
+ if (!present) {
284
+ if (required.has(key)) {
285
+ addError(key, 'This field is required.');
286
+ flags.valueMissing = true;
287
+ }
288
+ continue;
289
+ }
290
+ const v = effective[key];
291
+ if (type === 'string' && typeof v !== 'string') {
292
+ addError(key, 'Must be a string.');
293
+ flags.typeMismatch = true;
294
+ continue;
295
+ }
296
+ if (type === 'number' && (typeof v !== 'number' || !Number.isFinite(v))) {
297
+ addError(key, 'Must be a finite number.');
298
+ flags.typeMismatch = true;
162
299
  continue;
163
- if (isFieldEmpty(props[key], effective[key])) {
164
- out[key] = 'This field is required.';
300
+ }
301
+ if (type === 'integer' && (typeof v !== 'number' || !Number.isFinite(v))) {
302
+ addError(key, 'Must be a whole number.');
303
+ flags.typeMismatch = true;
304
+ continue;
305
+ }
306
+ if (type === 'integer' && !Number.isInteger(v)) {
307
+ addError(key, 'Must be a whole number.');
308
+ flags.stepMismatch = true;
309
+ continue;
310
+ }
311
+ if (type === 'boolean' && typeof v !== 'boolean') {
312
+ addError(key, 'Must be a boolean.');
313
+ flags.typeMismatch = true;
314
+ continue;
315
+ }
316
+ if (type === 'string' && Array.isArray(prop.enum) && !prop.enum.includes(v)) {
317
+ addError(key, `Must be one of: ${prop.enum.join(', ')}.`);
318
+ flags.customError = true;
319
+ continue;
320
+ }
321
+ if (prop.const !== undefined && v !== prop.const) {
322
+ addError(key, `Must equal ${JSON.stringify(prop.const)}.`);
323
+ flags.customError = true;
165
324
  }
166
325
  }
167
- return out;
326
+ for (const key of required) {
327
+ if (Object.prototype.hasOwnProperty.call(props, key))
328
+ continue;
329
+ if (!Object.prototype.hasOwnProperty.call(effective, key) || effective[key] === undefined) {
330
+ addError(key, 'This field is required.');
331
+ flags.valueMissing = true;
332
+ }
333
+ }
334
+ return { errors: out, flags };
168
335
  }
169
- /** Non-mutating validity check unlike `reportValidity()`, never reveals inline errors. */
336
+ /** Resynchronizes validity without revealing inline errors. */
170
337
  checkValidity() {
338
+ this.syncFormState();
171
339
  return this.internals.checkValidity();
172
340
  }
173
341
  /**
174
- * Reveals inline errors for every currently-invalid required field (as if
175
- * each had been visited) and returns overall validity — the hook a
342
+ * Reveals every current field/root error (as if each field had been
343
+ * visited) and returns overall validity — the hook a
176
344
  * consumer's own Submit/Approve button should call right before acting,
177
345
  * mirroring a native `<form>`'s `reportValidity()`.
178
346
  */
179
347
  reportValidity() {
180
- const errors = this.computeErrors();
181
- if (Object.keys(errors).length > 0) {
182
- this.touchedFields = new Set([...this.touchedFields, ...Object.keys(errors)]);
348
+ this.syncFormState();
349
+ if (Object.keys(this._errors).length > 0) {
350
+ this.touchedFields = new Set([...this.touchedFields, ...Object.keys(this._errors)]);
183
351
  }
184
- return this.internals.checkValidity();
352
+ if (this._formError)
353
+ this.showFormError = true;
354
+ return this.internals.reportValidity();
185
355
  }
186
356
  formResetCallback() {
187
357
  this.value = {};
188
358
  this.touchedFields = new Set();
359
+ this.showFormError = false;
360
+ }
361
+ formStateRestoreCallback(state, _mode) {
362
+ let restored = {};
363
+ if (typeof state === 'string') {
364
+ try {
365
+ const parsed = JSON.parse(state);
366
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
367
+ restored = parsed;
368
+ }
369
+ }
370
+ catch {
371
+ // Invalid persisted state restores the safe empty object.
372
+ }
373
+ }
374
+ this.value = restored;
189
375
  }
190
376
  formDisabledCallback(disabled) {
191
- this.disabled = disabled;
377
+ this._fieldsetDisabled = disabled;
378
+ this.requestUpdate();
192
379
  }
193
- willUpdate(changed) {
194
- if (changed.has('value') || changed.has('schema') || !this.hasUpdated) {
195
- this._errors = this.computeErrors();
380
+ schemaShapeError() {
381
+ const schema = this.schema;
382
+ if (schema === null || typeof schema !== 'object' || schema.type !== 'object') {
383
+ return 'Schema must describe an object.';
384
+ }
385
+ const properties = schema.properties;
386
+ if (properties === null || typeof properties !== 'object' || Array.isArray(properties)) {
387
+ return 'Schema properties must be a flat object.';
196
388
  }
389
+ return '';
197
390
  }
198
- /** Pushes `effectiveValue`/`errors` into `ElementInternals` — see the class doc's form-participation note. */
199
- syncInternals() {
200
- this.internals.setFormValue(JSON.stringify(this.effectiveValue));
201
- if (Object.keys(this._errors).length === 0) {
202
- this.internals.setValidity({});
391
+ serializeEffectiveValue(effective) {
392
+ const serialized = JSON.stringify(effective, (_key, value) => {
393
+ if (typeof value === 'number' && !Number.isFinite(value)) {
394
+ throw new TypeError('Non-finite numbers are not JSON values.');
395
+ }
396
+ return value;
397
+ });
398
+ if (typeof serialized !== 'string')
399
+ throw new TypeError('Value did not serialize to JSON.');
400
+ return serialized;
401
+ }
402
+ /** Pushes the already-computed snapshot into `ElementInternals`. */
403
+ syncInternals(formValue) {
404
+ this.internals.setFormValue(formValue, formValue);
405
+ if (Object.keys(this._validityFlags).length === 0) {
406
+ this.validityController.setValidity({});
203
407
  }
204
408
  else {
205
- const firstMessage = Object.values(this._errors)[0] ?? 'Some required fields are empty.';
206
- this.internals.setValidity({ valueMissing: true }, firstMessage);
409
+ const firstMessage = Object.values(this._errors)[0] ?? this._formError ?? 'The value is invalid.';
410
+ this.validityController.setValidity(this._validityFlags, firstMessage);
207
411
  }
208
412
  }
413
+ syncFormState() {
414
+ let effective = {};
415
+ let errors = {};
416
+ let flags = {};
417
+ let formError = '';
418
+ let formValue = null;
419
+ try {
420
+ formError = this.schemaShapeError();
421
+ effective = this.resolveEffectiveValue();
422
+ const validation = this.computeValidation(effective);
423
+ errors = validation.errors;
424
+ flags = validation.flags;
425
+ formValue = this.serializeEffectiveValue(effective);
426
+ }
427
+ catch {
428
+ formError = 'Value must be JSON-serializable.';
429
+ formValue = null;
430
+ }
431
+ if (formError) {
432
+ flags.customError = true;
433
+ formValue = null;
434
+ }
435
+ this._effectiveValue = effective;
436
+ if (JSON.stringify(this._errors) !== JSON.stringify(errors))
437
+ this._errors = errors;
438
+ this._formError = formError;
439
+ this._validityFlags = flags;
440
+ this.syncInternals(formValue);
441
+ }
209
442
  updated(changed) {
210
- if (changed.has('value') || changed.has('schema')) {
211
- this.syncInternals();
212
- const valid = Object.keys(this._errors).length === 0;
213
- const key = JSON.stringify({ valid, errors: this._errors });
443
+ if (changed.has('value') || changed.has('schema') || changed.has('_errors') || changed.has('_formError')) {
444
+ const valid = Object.keys(this._errors).length === 0 && this._formError === '';
445
+ const key = JSON.stringify({ valid, errors: this._errors, formError: this._formError });
214
446
  if (key !== this.lastValidityKey) {
215
447
  this.lastValidityKey = key;
216
448
  this.emit('lyra-validity-change', { valid, errors: { ...this._errors } });
217
449
  }
450
+ const nestedUpdates = Array.from(this.firstInvalidField()?.children ?? [])
451
+ .filter((element) => typeof element[VALIDITY_ANCHOR] === 'function')
452
+ .map((element) => element.updateComplete)
453
+ .filter((update) => update !== undefined);
454
+ if (nestedUpdates.length > 0) {
455
+ void Promise.all(nestedUpdates).then(() => this.validityController.refreshAnchor());
456
+ }
218
457
  }
219
458
  }
220
459
  setFieldValue(key, val) {
460
+ if (this.effectiveDisabled)
461
+ return;
221
462
  this.value = { ...this.value, [key]: val };
222
463
  this.emit('lyra-input', { value: this.effectiveValue });
223
464
  }
@@ -254,9 +495,9 @@ export class LyraToolParamForm extends LyraElement {
254
495
  return html `<lyra-select
255
496
  id=${fieldId}
256
497
  aria-label=${errorMessage ? `${label}. ${errorMessage}` : label}
498
+ aria-required=${required ? 'true' : nothing}
257
499
  .value=${typeof effective === 'string' ? effective : ''}
258
- ?required=${required}
259
- ?disabled=${this.disabled}
500
+ ?disabled=${this.effectiveDisabled}
260
501
  @change=${(e) => this.onSelectChange(key, e)}
261
502
  >
262
503
  ${prop.enum.map((v) => html `<lyra-option value=${v}>${v}</lyra-option>`)}
@@ -268,10 +509,10 @@ export class LyraToolParamForm extends LyraElement {
268
509
  type="text"
269
510
  id=${fieldId}
270
511
  aria-describedby=${describedBy || nothing}
512
+ aria-required=${required ? 'true' : nothing}
271
513
  aria-invalid=${errorMessage ? 'true' : nothing}
272
514
  .value=${typeof effective === 'string' ? effective : ''}
273
- ?required=${required}
274
- ?disabled=${this.disabled}
515
+ ?disabled=${this.effectiveDisabled}
275
516
  @input=${(e) => this.onTextInput(key, e)}
276
517
  />`;
277
518
  }
@@ -283,10 +524,10 @@ export class LyraToolParamForm extends LyraElement {
283
524
  id=${fieldId}
284
525
  step=${prop.type === 'integer' ? '1' : 'any'}
285
526
  aria-describedby=${describedBy || nothing}
527
+ aria-required=${required ? 'true' : nothing}
286
528
  aria-invalid=${errorMessage ? 'true' : nothing}
287
529
  .value=${numValue}
288
- ?required=${required}
289
- ?disabled=${this.disabled}
530
+ ?disabled=${this.effectiveDisabled}
290
531
  @input=${(e) => this.onNumberInput(key, e)}
291
532
  />`;
292
533
  }
@@ -295,12 +536,16 @@ export class LyraToolParamForm extends LyraElement {
295
536
  // that slot *is* lyra-checkbox's documented way to give it an
296
537
  // accessible name (see checkbox.ts's own @slot doc), and unlike
297
538
  // aria-describedby, this genuinely works since it's real slotted
298
- // content, not an inert host attribute.
539
+ // content, not an inert host attribute. Once there's an error, fold it
540
+ // into aria-label the same way the enum/select branch above does --
541
+ // an explicit aria-label overrides the slotted accessible name, so the
542
+ // normal (no error) case is left alone to keep deriving its name from
543
+ // the slot per lyra-checkbox's own documented contract.
299
544
  return html `<lyra-checkbox
300
545
  id=${fieldId}
546
+ aria-label=${errorMessage ? `${label}. ${errorMessage}` : nothing}
301
547
  ?checked=${effective === true}
302
- ?required=${required}
303
- ?disabled=${this.disabled}
548
+ ?disabled=${this.effectiveDisabled}
304
549
  @lyra-change=${(e) => this.onCheckboxChange(key, e)}
305
550
  >
306
551
  <span part="label">${label}</span>
@@ -308,11 +553,13 @@ export class LyraToolParamForm extends LyraElement {
308
553
  }
309
554
  // Defensive fallback for a schema property outside this phase's scope
310
555
  // (see the class doc) — render a visible note instead of silently
311
- // dropping the field or throwing.
312
- return html `<p class="unsupported">Unsupported field type "${prop.type}".</p>`;
556
+ // dropping the field or throwing. Still carries id=fieldId like every
557
+ // other control-slot render, since renderField's shared, non-boolean
558
+ // branch above always renders a <label for=fieldId> pointing at it.
559
+ return html `<p class="unsupported" id=${fieldId}>Unsupported field type "${prop.type}".</p>`;
313
560
  }
314
561
  renderField(key, prop, index) {
315
- const required = (this.schema.required ?? []).includes(key);
562
+ const required = this.requiredKeys.includes(key);
316
563
  const label = prop.title ?? key;
317
564
  const fieldId = `${this.baseId}-f${index}`;
318
565
  const descId = prop.description ? `${fieldId}-desc` : '';
@@ -320,7 +567,7 @@ export class LyraToolParamForm extends LyraElement {
320
567
  const describedBy = [descId, this.touchedFields.has(key) ? errId : ''].filter(Boolean).join(' ');
321
568
  const hasError = this.touchedFields.has(key) && Boolean(this._errors[key]);
322
569
  const errorMessage = hasError ? this._errors[key] : '';
323
- const effective = this.effectiveValue[key];
570
+ const effective = this._effectiveValue[key];
324
571
  const isBoolean = prop.type === 'boolean';
325
572
  return html `
326
573
  <div part="field" class="field" data-key=${key} data-type=${prop.type} ?data-required=${required}
@@ -338,28 +585,25 @@ export class LyraToolParamForm extends LyraElement {
338
585
  `;
339
586
  }
340
587
  render() {
341
- const props = this.schema?.properties ?? {};
588
+ const props = this.schemaProperties;
342
589
  const keys = Object.keys(props);
343
590
  return html `<div part="base">
344
591
  ${keys.map((key, i) => this.renderField(key, props[key], i))}
592
+ ${this.showFormError && this._formError
593
+ ? html `<p part="error" class="form-error" role="alert">${this._formError}</p>`
594
+ : nothing}
345
595
  </div>`;
346
596
  }
347
597
  }
348
598
  __decorate([
349
- property({ attribute: false })
350
- ], LyraToolParamForm.prototype, "schema", void 0);
351
- __decorate([
352
- property({ attribute: false })
353
- ], LyraToolParamForm.prototype, "value", void 0);
354
- __decorate([
355
- property()
356
- ], LyraToolParamForm.prototype, "name", void 0);
599
+ state()
600
+ ], LyraToolParamForm.prototype, "_errors", void 0);
357
601
  __decorate([
358
- property({ type: Boolean, reflect: true })
359
- ], LyraToolParamForm.prototype, "disabled", void 0);
602
+ state()
603
+ ], LyraToolParamForm.prototype, "_formError", void 0);
360
604
  __decorate([
361
605
  state()
362
- ], LyraToolParamForm.prototype, "_errors", void 0);
606
+ ], LyraToolParamForm.prototype, "showFormError", void 0);
363
607
  __decorate([
364
608
  state()
365
609
  ], LyraToolParamForm.prototype, "touchedFields", void 0);