@vaadin/field-highlighter 25.3.0-alpha11 → 25.3.0-alpha13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vaadin/field-highlighter",
|
|
3
|
-
"version": "25.3.0-
|
|
3
|
+
"version": "25.3.0-alpha13",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -32,33 +32,33 @@
|
|
|
32
32
|
"field"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@vaadin/a11y-base": "25.3.0-
|
|
36
|
-
"@vaadin/component-base": "25.3.0-
|
|
37
|
-
"@vaadin/overlay": "25.3.0-
|
|
38
|
-
"@vaadin/popover": "25.3.0-
|
|
39
|
-
"@vaadin/tooltip": "25.3.0-
|
|
40
|
-
"@vaadin/vaadin-themable-mixin": "25.3.0-
|
|
35
|
+
"@vaadin/a11y-base": "25.3.0-alpha13",
|
|
36
|
+
"@vaadin/component-base": "25.3.0-alpha13",
|
|
37
|
+
"@vaadin/overlay": "25.3.0-alpha13",
|
|
38
|
+
"@vaadin/popover": "25.3.0-alpha13",
|
|
39
|
+
"@vaadin/tooltip": "25.3.0-alpha13",
|
|
40
|
+
"@vaadin/vaadin-themable-mixin": "25.3.0-alpha13",
|
|
41
41
|
"lit": "^3.0.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@vaadin/aura": "25.3.0-
|
|
45
|
-
"@vaadin/chai-plugins": "25.3.0-
|
|
46
|
-
"@vaadin/checkbox": "25.3.0-
|
|
47
|
-
"@vaadin/checkbox-group": "25.3.0-
|
|
48
|
-
"@vaadin/custom-field": "25.3.0-
|
|
49
|
-
"@vaadin/date-picker": "25.3.0-
|
|
50
|
-
"@vaadin/date-time-picker": "25.3.0-
|
|
51
|
-
"@vaadin/item": "25.3.0-
|
|
52
|
-
"@vaadin/list-box": "25.3.0-
|
|
53
|
-
"@vaadin/radio-group": "25.3.0-
|
|
54
|
-
"@vaadin/select": "25.3.0-
|
|
55
|
-
"@vaadin/test-runner-commands": "25.3.0-
|
|
44
|
+
"@vaadin/aura": "25.3.0-alpha13",
|
|
45
|
+
"@vaadin/chai-plugins": "25.3.0-alpha13",
|
|
46
|
+
"@vaadin/checkbox": "25.3.0-alpha13",
|
|
47
|
+
"@vaadin/checkbox-group": "25.3.0-alpha13",
|
|
48
|
+
"@vaadin/custom-field": "25.3.0-alpha13",
|
|
49
|
+
"@vaadin/date-picker": "25.3.0-alpha13",
|
|
50
|
+
"@vaadin/date-time-picker": "25.3.0-alpha13",
|
|
51
|
+
"@vaadin/item": "25.3.0-alpha13",
|
|
52
|
+
"@vaadin/list-box": "25.3.0-alpha13",
|
|
53
|
+
"@vaadin/radio-group": "25.3.0-alpha13",
|
|
54
|
+
"@vaadin/select": "25.3.0-alpha13",
|
|
55
|
+
"@vaadin/test-runner-commands": "25.3.0-alpha13",
|
|
56
56
|
"@vaadin/testing-helpers": "^2.0.0",
|
|
57
|
-
"@vaadin/text-area": "25.3.0-
|
|
58
|
-
"@vaadin/text-field": "25.3.0-
|
|
59
|
-
"@vaadin/time-picker": "25.3.0-
|
|
60
|
-
"@vaadin/vaadin-lumo-styles": "25.3.0-
|
|
57
|
+
"@vaadin/text-area": "25.3.0-alpha13",
|
|
58
|
+
"@vaadin/text-field": "25.3.0-alpha13",
|
|
59
|
+
"@vaadin/time-picker": "25.3.0-alpha13",
|
|
60
|
+
"@vaadin/vaadin-lumo-styles": "25.3.0-alpha13",
|
|
61
61
|
"sinon": "^22.0.0"
|
|
62
62
|
},
|
|
63
|
-
"gitHead": "
|
|
63
|
+
"gitHead": "e23e44d29e6bc1e47245d5e2cc0e4b022ec61818"
|
|
64
64
|
}
|
|
@@ -181,6 +181,65 @@ export const aiFieldMarkerStyles = css`
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/* The confidence indicator: a sibling of the marker slotted into the
|
|
185
|
+
field's helper text section. The level class name picks the color and how
|
|
186
|
+
much of the pie icon is filled. */
|
|
187
|
+
:has(> vaadin-ai-field-marker) > [slot='helper'].ai-confidence {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
gap: var(--vaadin-gap-s);
|
|
191
|
+
color: var(--_vaadin-ai-field-marker-confidence-color);
|
|
192
|
+
|
|
193
|
+
&::before {
|
|
194
|
+
content: '';
|
|
195
|
+
flex: none;
|
|
196
|
+
box-sizing: border-box;
|
|
197
|
+
width: var(--vaadin-icon-size, 1lh);
|
|
198
|
+
height: var(--vaadin-icon-size, 1lh);
|
|
199
|
+
/* The padding insets the pie from the ring: the wedge is clipped to the
|
|
200
|
+
content box, while the tint fills the whole circle behind it. */
|
|
201
|
+
padding: calc(var(--vaadin-icon-size, 1lh) / 12);
|
|
202
|
+
border: 1px solid color-mix(in srgb, currentColor 50%, transparent);
|
|
203
|
+
border-radius: 50%;
|
|
204
|
+
background-color: color-mix(in srgb, currentColor 15%, transparent);
|
|
205
|
+
background-image: conic-gradient(currentColor var(--_vaadin-ai-field-marker-confidence-fill, 0%), #0000 0%);
|
|
206
|
+
background-clip: content-box, border-box;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* The levels are told apart by how much of the pie is filled, so the base
|
|
210
|
+
styles keep them in the same neutral color. The themes map the custom
|
|
211
|
+
properties to their own semantic colors. */
|
|
212
|
+
&.ai-confidence-low {
|
|
213
|
+
--_vaadin-ai-field-marker-confidence-color: var(
|
|
214
|
+
--vaadin-ai-field-marker-confidence-low-color,
|
|
215
|
+
var(--vaadin-text-color-secondary)
|
|
216
|
+
);
|
|
217
|
+
--_vaadin-ai-field-marker-confidence-fill: 25%;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
&.ai-confidence-medium {
|
|
221
|
+
--_vaadin-ai-field-marker-confidence-color: var(
|
|
222
|
+
--vaadin-ai-field-marker-confidence-medium-color,
|
|
223
|
+
var(--vaadin-text-color-secondary)
|
|
224
|
+
);
|
|
225
|
+
--_vaadin-ai-field-marker-confidence-fill: 50%;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
&.ai-confidence-high {
|
|
229
|
+
--_vaadin-ai-field-marker-confidence-color: var(
|
|
230
|
+
--vaadin-ai-field-marker-confidence-high-color,
|
|
231
|
+
var(--vaadin-text-color-secondary)
|
|
232
|
+
);
|
|
233
|
+
--_vaadin-ai-field-marker-confidence-fill: 75%;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* While the AI is working, the confidence describes a value that is about
|
|
238
|
+
to be replaced, so hide it along with the marker. */
|
|
239
|
+
[ai-working] > [slot='helper'].ai-confidence {
|
|
240
|
+
display: none;
|
|
241
|
+
}
|
|
242
|
+
|
|
184
243
|
[ai-working],
|
|
185
244
|
:has(> vaadin-ai-field-marker)::part(input-field),
|
|
186
245
|
:has(> vaadin-ai-field-marker)::part(input-fields),
|
|
@@ -28,6 +28,15 @@ export interface AiFieldMarkerI18n {
|
|
|
28
28
|
* The tooltip text of the badge button.
|
|
29
29
|
*/
|
|
30
30
|
badgeTooltip?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The texts of the confidence indicator.
|
|
34
|
+
*/
|
|
35
|
+
confidence?: {
|
|
36
|
+
low?: string;
|
|
37
|
+
medium?: string;
|
|
38
|
+
high?: string;
|
|
39
|
+
};
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
/**
|
|
@@ -53,20 +62,36 @@ export type AiFieldRevertEvent = CustomEvent<{ value: unknown }>;
|
|
|
53
62
|
* in progress, set the `working` property to show an "AI is working" shimmer
|
|
54
63
|
* on the field along with a client-side read-only guard.
|
|
55
64
|
*
|
|
65
|
+
* The popover can show custom content — such as a summary of what the AI
|
|
66
|
+
* based the value on — below the explanation, given as a DOM node through
|
|
67
|
+
* the `content` property.
|
|
68
|
+
*
|
|
69
|
+
* Set the `confidence` property to show the confidence level of the filled
|
|
70
|
+
* value (`low`, `medium` or `high`) as an indicator in the field's helper
|
|
71
|
+
* text section, ahead of a helper the field itself may have. While the
|
|
72
|
+
* indicator is shown, the field is marked with `has-helper`, so that the
|
|
73
|
+
* helper text section is laid out the same as for a helper of its own.
|
|
74
|
+
*
|
|
56
75
|
* ### Styling
|
|
57
76
|
*
|
|
58
|
-
* The following state
|
|
77
|
+
* The following state attributes are set on the field element for styling:
|
|
78
|
+
*
|
|
79
|
+
* Attribute | Description
|
|
80
|
+
* ----------------|-------------
|
|
81
|
+
* `ai-working` | Set while an AI is working on the field.
|
|
59
82
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* `ai-
|
|
83
|
+
* The confidence indicator is rendered into the field's light DOM as a
|
|
84
|
+
* `<span>` with the `ai-confidence` class name and the level as an additional
|
|
85
|
+
* `ai-confidence-low`, `ai-confidence-medium` or `ai-confidence-high` one.
|
|
63
86
|
*
|
|
64
87
|
* The following custom CSS properties are available for styling:
|
|
65
88
|
*
|
|
66
|
-
* Custom CSS property
|
|
67
|
-
*
|
|
68
|
-
* `--vaadin-ai-field-marker-badge-icon-color`
|
|
69
|
-
* `--vaadin-ai-field-marker-
|
|
89
|
+
* Custom CSS property |
|
|
90
|
+
* :----------------------------------------------------|
|
|
91
|
+
* `--vaadin-ai-field-marker-badge-icon-color` |
|
|
92
|
+
* `--vaadin-ai-field-marker-confidence-high-color` |
|
|
93
|
+
* `--vaadin-ai-field-marker-confidence-low-color` |
|
|
94
|
+
* `--vaadin-ai-field-marker-confidence-medium-color` |
|
|
70
95
|
*
|
|
71
96
|
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
72
97
|
*
|
|
@@ -89,12 +114,35 @@ declare class AiFieldMarker extends I18nMixin<typeof HTMLElement, AiFieldMarkerI
|
|
|
89
114
|
* // The accessible label of the badge button and the popover dialog.
|
|
90
115
|
* badgeLabel: 'AI-provided value',
|
|
91
116
|
* // The tooltip text of the badge button.
|
|
92
|
-
* badgeTooltip: 'Field value modified by AI.\nClick for details'
|
|
117
|
+
* badgeTooltip: 'Field value modified by AI.\nClick for details',
|
|
118
|
+
* // The texts of the confidence indicator.
|
|
119
|
+
* confidence: {
|
|
120
|
+
* low: 'Low confidence',
|
|
121
|
+
* medium: 'Medium confidence',
|
|
122
|
+
* high: 'High confidence'
|
|
123
|
+
* }
|
|
93
124
|
* }
|
|
94
125
|
* ```
|
|
95
126
|
*/
|
|
96
127
|
i18n: AiFieldMarkerI18n;
|
|
97
128
|
|
|
129
|
+
/**
|
|
130
|
+
* A DOM node to show in the popover, between the message and the revert
|
|
131
|
+
* control — for example a summary of what the AI based the value on.
|
|
132
|
+
*
|
|
133
|
+
* The node is rendered as given, and moved into the marker's own light DOM.
|
|
134
|
+
* The host owns it: setting the property to another node or to `null`
|
|
135
|
+
* removes the previous node from the popover.
|
|
136
|
+
*
|
|
137
|
+
* ```js
|
|
138
|
+
* const source = document.createElement('a');
|
|
139
|
+
* source.href = '/documents/invoice.pdf';
|
|
140
|
+
* source.textContent = 'invoice.pdf';
|
|
141
|
+
* marker.content = source;
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
content: Node | null | undefined;
|
|
145
|
+
|
|
98
146
|
/**
|
|
99
147
|
* Whether an AI is currently working on the field. While `true`, the field
|
|
100
148
|
* shows an "AI is working" shimmer and is made read-only on the client so
|
|
@@ -105,6 +153,14 @@ declare class AiFieldMarker extends I18nMixin<typeof HTMLElement, AiFieldMarkerI
|
|
|
105
153
|
* the field is marked with `aria-busy`.
|
|
106
154
|
*/
|
|
107
155
|
working: boolean;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The confidence level of the AI-filled value, shown as an indicator
|
|
159
|
+
* in the field's helper text section. Possible values are `low`,
|
|
160
|
+
* `medium` and `high`; when not set, no indicator is shown. The
|
|
161
|
+
* indicator texts can be localized with the `i18n` property.
|
|
162
|
+
*/
|
|
163
|
+
confidence: 'high' | 'low' | 'medium' | null;
|
|
108
164
|
}
|
|
109
165
|
|
|
110
166
|
declare global {
|
|
@@ -11,7 +11,11 @@ import { getDeepActiveElement, getTabbableElements, isKeyboardActive } from '@va
|
|
|
11
11
|
import { registerCSSProperty } from '@vaadin/component-base/src/css-utils.js';
|
|
12
12
|
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
|
|
13
13
|
import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
addValuesToAttribute,
|
|
16
|
+
hasNodeContent,
|
|
17
|
+
removeValuesFromAttribute,
|
|
18
|
+
} from '@vaadin/component-base/src/dom-utils.js';
|
|
15
19
|
import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js';
|
|
16
20
|
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
|
|
17
21
|
import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
|
|
@@ -23,6 +27,11 @@ const DEFAULT_I18N = {
|
|
|
23
27
|
revert: 'Revert Value',
|
|
24
28
|
badgeLabel: 'AI-provided value',
|
|
25
29
|
badgeTooltip: 'Field value modified by AI.\nClick for details',
|
|
30
|
+
confidence: {
|
|
31
|
+
low: 'Low confidence',
|
|
32
|
+
medium: 'Medium confidence',
|
|
33
|
+
high: 'High confidence',
|
|
34
|
+
},
|
|
26
35
|
};
|
|
27
36
|
|
|
28
37
|
// Half of the 1s working shimmer slide (`--vaadin-ai-field-marker-slide` in
|
|
@@ -35,6 +44,14 @@ const MARKER_SLOT = 'ai-field-marker';
|
|
|
35
44
|
/** Marks the `<style>` element the marker injects into a field's shadow root. */
|
|
36
45
|
const MARKER_STYLE_ATTRIBUTE = 'ai-field-marker-styles';
|
|
37
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The class name of the confidence indicator the marker adds to the field's
|
|
49
|
+
* light DOM; the level goes on a suffixed class name of its own. Prefixed
|
|
50
|
+
* with `ai-`, since the indicator sits among the application's own children
|
|
51
|
+
* of the field, where a plain `confidence` or `low` would be ambiguous.
|
|
52
|
+
*/
|
|
53
|
+
const CONFIDENCE_CLASS = 'ai-confidence';
|
|
54
|
+
|
|
38
55
|
// The position the shimmer's mask is at, animated by the marker's keyframes.
|
|
39
56
|
// Registered here rather than with an @property rule in the marker stylesheet,
|
|
40
57
|
// which is injected into the field's root node: a registration only takes effect
|
|
@@ -205,25 +222,41 @@ class DelayedFieldValue {
|
|
|
205
222
|
* annotates is about to be replaced; setting `working` back to `false`
|
|
206
223
|
* brings it back, so a cancelled or failed fill leaves the mark intact.
|
|
207
224
|
*
|
|
225
|
+
* The popover can show custom content — such as a summary of what the AI
|
|
226
|
+
* based the value on — below the explanation, given as a DOM node through
|
|
227
|
+
* the `content` property.
|
|
228
|
+
*
|
|
208
229
|
* The pieces that construct the marker — the badge, its tooltip and the
|
|
209
230
|
* popover with the explanation and the revert control — are rendered
|
|
210
231
|
* directly into the marker's light DOM, so that document-level themes
|
|
211
232
|
* and user stylesheets can reach them.
|
|
212
233
|
*
|
|
234
|
+
* Set the `confidence` property to show the confidence level of the filled
|
|
235
|
+
* value (`low`, `medium` or `high`) as an indicator in the field's helper
|
|
236
|
+
* text section, ahead of a helper the field itself may have. While the
|
|
237
|
+
* indicator is shown, the field is marked with `has-helper`, so that the
|
|
238
|
+
* helper text section is laid out the same as for a helper of its own.
|
|
239
|
+
*
|
|
213
240
|
* ### Styling
|
|
214
241
|
*
|
|
215
|
-
* The following state
|
|
242
|
+
* The following state attributes are set on the field element for styling:
|
|
216
243
|
*
|
|
217
|
-
* Attribute
|
|
218
|
-
*
|
|
219
|
-
* `ai-working`
|
|
244
|
+
* Attribute | Description
|
|
245
|
+
* ----------------|-------------
|
|
246
|
+
* `ai-working` | Set while an AI is working on the field.
|
|
247
|
+
*
|
|
248
|
+
* The confidence indicator is rendered into the field's light DOM as a
|
|
249
|
+
* `<span>` with the `ai-confidence` class name and the level as an additional
|
|
250
|
+
* `ai-confidence-low`, `ai-confidence-medium` or `ai-confidence-high` one.
|
|
220
251
|
*
|
|
221
252
|
* The following custom CSS properties are available for styling:
|
|
222
253
|
*
|
|
223
|
-
* Custom CSS property
|
|
224
|
-
*
|
|
225
|
-
* `--vaadin-ai-field-marker-badge-icon-color`
|
|
226
|
-
* `--vaadin-ai-field-marker-
|
|
254
|
+
* Custom CSS property |
|
|
255
|
+
* :----------------------------------------------------|
|
|
256
|
+
* `--vaadin-ai-field-marker-badge-icon-color` |
|
|
257
|
+
* `--vaadin-ai-field-marker-confidence-high-color` |
|
|
258
|
+
* `--vaadin-ai-field-marker-confidence-low-color` |
|
|
259
|
+
* `--vaadin-ai-field-marker-confidence-medium-color` |
|
|
227
260
|
*
|
|
228
261
|
* See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
|
|
229
262
|
*
|
|
@@ -244,6 +277,25 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
244
277
|
|
|
245
278
|
static get properties() {
|
|
246
279
|
return {
|
|
280
|
+
/**
|
|
281
|
+
* A DOM node to show in the popover, between the message and the revert
|
|
282
|
+
* control — for example a summary of what the AI based the value on.
|
|
283
|
+
*
|
|
284
|
+
* The node is rendered as given, and moved into the marker's own light
|
|
285
|
+
* DOM. The host owns it: setting the property to another node or to
|
|
286
|
+
* `null` removes the previous node from the popover.
|
|
287
|
+
*
|
|
288
|
+
* ```js
|
|
289
|
+
* const source = document.createElement('a');
|
|
290
|
+
* source.href = '/documents/invoice.pdf';
|
|
291
|
+
* source.textContent = 'invoice.pdf';
|
|
292
|
+
* marker.content = source;
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
content: {
|
|
296
|
+
type: Object,
|
|
297
|
+
},
|
|
298
|
+
|
|
247
299
|
/**
|
|
248
300
|
* Whether an AI is currently working on the field. While `true`, the
|
|
249
301
|
* field shows an "AI is working" shimmer and is made read-only on the
|
|
@@ -257,6 +309,17 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
257
309
|
type: Boolean,
|
|
258
310
|
value: false,
|
|
259
311
|
},
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* The confidence level of the AI-filled value, shown as an indicator
|
|
315
|
+
* in the field's helper text section. Possible values are `low`,
|
|
316
|
+
* `medium` and `high`; when not set, no indicator is shown. The
|
|
317
|
+
* indicator texts can be localized with the `i18n` property.
|
|
318
|
+
*/
|
|
319
|
+
confidence: {
|
|
320
|
+
type: String,
|
|
321
|
+
value: null,
|
|
322
|
+
},
|
|
260
323
|
};
|
|
261
324
|
}
|
|
262
325
|
|
|
@@ -278,6 +341,20 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
278
341
|
/** The field value captured for the revert event detail. */
|
|
279
342
|
#capturedValue;
|
|
280
343
|
|
|
344
|
+
/**
|
|
345
|
+
* The confidence indicator added to the field's light DOM and rendered
|
|
346
|
+
* in the field's helper text section. Set while `confidence` is set on
|
|
347
|
+
* a marked field.
|
|
348
|
+
*/
|
|
349
|
+
#confidenceNode = null;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Observes the field's `has-helper` attribute while the confidence
|
|
353
|
+
* indicator is shown, so that the marker can re-assert it if the field
|
|
354
|
+
* recomputes it from its own helper content. Created on first use.
|
|
355
|
+
*/
|
|
356
|
+
#helperStateObserver = null;
|
|
357
|
+
|
|
281
358
|
/**
|
|
282
359
|
* While in the working state, the elements whose client-side `readonly`
|
|
283
360
|
* state was overridden — the field itself and, for a `vaadin-custom-field`,
|
|
@@ -367,7 +444,13 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
367
444
|
* // The accessible label of the badge button and the popover dialog.
|
|
368
445
|
* badgeLabel: 'AI-provided value',
|
|
369
446
|
* // The tooltip text of the badge button.
|
|
370
|
-
* badgeTooltip: 'Field value modified by AI.\nClick for details'
|
|
447
|
+
* badgeTooltip: 'Field value modified by AI.\nClick for details',
|
|
448
|
+
* // The texts of the confidence indicator.
|
|
449
|
+
* confidence: {
|
|
450
|
+
* low: 'Low confidence',
|
|
451
|
+
* medium: 'Medium confidence',
|
|
452
|
+
* high: 'High confidence'
|
|
453
|
+
* }
|
|
371
454
|
* }
|
|
372
455
|
* ```
|
|
373
456
|
*
|
|
@@ -449,6 +532,8 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
449
532
|
|
|
450
533
|
this.#stopWorking(true);
|
|
451
534
|
|
|
535
|
+
this.#removeConfidenceNode();
|
|
536
|
+
|
|
452
537
|
if (this.#descNode) {
|
|
453
538
|
removeValuesFromAttribute(this.#describedElement, 'aria-describedby', this.#descNode.id);
|
|
454
539
|
this.#descNode.remove();
|
|
@@ -483,6 +568,10 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
483
568
|
return;
|
|
484
569
|
}
|
|
485
570
|
|
|
571
|
+
if (props.has('confidence') || props.has('__effectiveI18n')) {
|
|
572
|
+
this.#updateConfidence();
|
|
573
|
+
}
|
|
574
|
+
|
|
486
575
|
if (props.has('working')) {
|
|
487
576
|
if (this.working) {
|
|
488
577
|
this.#startWorking();
|
|
@@ -493,6 +582,12 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
493
582
|
this.#capturedValue = this.#annotatedValue();
|
|
494
583
|
this.#announcePending = true;
|
|
495
584
|
}
|
|
585
|
+
|
|
586
|
+
// The indicator is hidden while working, so the helper text section is
|
|
587
|
+
// only claimed for it — and the indicator described — once the working
|
|
588
|
+
// state ends.
|
|
589
|
+
this.#updateConfidenceDescription();
|
|
590
|
+
this.#updateFieldHelperState();
|
|
496
591
|
}
|
|
497
592
|
|
|
498
593
|
// Announce after the update so the announcement reflects a message set in
|
|
@@ -518,6 +613,7 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
518
613
|
<vaadin-tooltip for="${this.#badgeId}" text="${badgeTooltip}"></vaadin-tooltip>
|
|
519
614
|
<vaadin-popover for="${this.#badgeId}" aria-label="${badgeLabel}" autofocus theme="arrow" position="end-top">
|
|
520
615
|
<p class="message">${message}</p>
|
|
616
|
+
${this.content ?? nothing}
|
|
521
617
|
<div class="actions">
|
|
522
618
|
<button type="button" tabindex="0" @click="${this.#onRevert}">${revert}</button>
|
|
523
619
|
</div>
|
|
@@ -594,6 +690,10 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
594
690
|
this.#describedElement = describedElement;
|
|
595
691
|
}
|
|
596
692
|
|
|
693
|
+
// Apply the confidence indicator directly: on a reconnect no property
|
|
694
|
+
// change triggers updated(), which handles the first connect.
|
|
695
|
+
this.#updateConfidence();
|
|
696
|
+
|
|
597
697
|
// Capture the AI-filled value so the revert event can carry it.
|
|
598
698
|
this.#capturedValue = this.#annotatedValue();
|
|
599
699
|
|
|
@@ -619,6 +719,133 @@ class AiFieldMarker extends SlotStylesMixin(I18nMixin(DirMixin(PolylitMixin(LitE
|
|
|
619
719
|
return this.#valueDelay ? this.#valueDelay.latestValue : field.value;
|
|
620
720
|
}
|
|
621
721
|
|
|
722
|
+
/**
|
|
723
|
+
* Syncs the confidence indicator in the field's helper text section with
|
|
724
|
+
* the `confidence` property: a `<span>` slotted into the field's helper
|
|
725
|
+
* slot, with the `ai-confidence` and `ai-confidence-<level>` class names
|
|
726
|
+
* and the localized level text as content.
|
|
727
|
+
*/
|
|
728
|
+
#updateConfidence() {
|
|
729
|
+
const field = this.#field;
|
|
730
|
+
if (!field) {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const level = this.confidence;
|
|
735
|
+
if (!level) {
|
|
736
|
+
this.#removeConfidenceNode();
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (!this.#confidenceNode) {
|
|
741
|
+
const node = document.createElement('span');
|
|
742
|
+
node.setAttribute('slot', 'helper');
|
|
743
|
+
// Hide the indicator from the field's helper slot controller, which
|
|
744
|
+
// would otherwise evict the field's own helper element in favor of
|
|
745
|
+
// the indicator. The browser still renders it in the helper slot.
|
|
746
|
+
node.setAttribute('data-slot-ignore', '');
|
|
747
|
+
node.id = `ai-field-marker-confidence-${generateUniqueId()}`;
|
|
748
|
+
// Insert ahead of a helper the field already has, so that the indicator
|
|
749
|
+
// comes first in the helper text section. A helper added later ends up
|
|
750
|
+
// after the indicator, since the field appends it.
|
|
751
|
+
field.insertBefore(node, field.querySelector(':scope > [slot="helper"]'));
|
|
752
|
+
this.#confidenceNode = node;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
this.#confidenceNode.className = `${CONFIDENCE_CLASS} ${CONFIDENCE_CLASS}-${level}`;
|
|
756
|
+
this.#confidenceNode.textContent = this.__effectiveI18n.confidence[level] ?? '';
|
|
757
|
+
this.#updateConfidenceDescription();
|
|
758
|
+
this.#updateFieldHelperState();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Keeps the indicator's id in the described element's `aria-describedby`
|
|
763
|
+
* only while the indicator is shown: a visually hidden indicator would
|
|
764
|
+
* still get read as part of the field's description, although it describes
|
|
765
|
+
* a value the AI is about to replace.
|
|
766
|
+
*/
|
|
767
|
+
#updateConfidenceDescription() {
|
|
768
|
+
const node = this.#confidenceNode;
|
|
769
|
+
if (!node || !this.#describedElement) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (this.working) {
|
|
774
|
+
removeValuesFromAttribute(this.#describedElement, 'aria-describedby', node.id);
|
|
775
|
+
} else {
|
|
776
|
+
addValuesToAttribute(this.#describedElement, 'aria-describedby', node.id);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Keeps the field's `has-helper` attribute set while the indicator is
|
|
782
|
+
* shown, since it is content in the field's helper text section although
|
|
783
|
+
* the field's own helper is not what provides it. The attribute is what
|
|
784
|
+
* both the field and the themes key their helper text section styles on —
|
|
785
|
+
* from showing the section at all to placing it above the field for the
|
|
786
|
+
* `helper-above-field` theme.
|
|
787
|
+
*
|
|
788
|
+
* The field recomputes the attribute from its own helper content, which
|
|
789
|
+
* never includes the indicator, so a recomputation can drop it while the
|
|
790
|
+
* indicator is still shown. An observer re-asserts it in that case.
|
|
791
|
+
*/
|
|
792
|
+
#updateFieldHelperState() {
|
|
793
|
+
const field = this.#field;
|
|
794
|
+
|
|
795
|
+
// While the AI is working the indicator is hidden, so the field should
|
|
796
|
+
// only reserve the helper text section for a helper of its own.
|
|
797
|
+
if (this.#confidenceNode && !this.working) {
|
|
798
|
+
field.toggleAttribute('has-helper', true);
|
|
799
|
+
|
|
800
|
+
this.#helperStateObserver ??= new MutationObserver(() => {
|
|
801
|
+
// Read the field live: the observer is reused when the marker moves
|
|
802
|
+
// to another field, so a captured one could be a previous field.
|
|
803
|
+
const observedField = this.#field;
|
|
804
|
+
if (observedField && this.#confidenceNode && !this.working && !observedField.hasAttribute('has-helper')) {
|
|
805
|
+
observedField.toggleAttribute('has-helper', true);
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
this.#helperStateObserver.observe(field, { attributes: true, attributeFilter: ['has-helper'] });
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
this.#helperStateObserver?.disconnect();
|
|
813
|
+
|
|
814
|
+
// The field keeps the attribute when its own helper provides content,
|
|
815
|
+
// which it may have gained while the indicator was shown.
|
|
816
|
+
if (!this.#hasFieldHelper()) {
|
|
817
|
+
field.removeAttribute('has-helper');
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Whether the field has helper content of its own, i.e. helper slot content
|
|
823
|
+
* other than the indicator. Judged with the same content check the field
|
|
824
|
+
* itself uses for its `has-helper` attribute.
|
|
825
|
+
*
|
|
826
|
+
* @return {boolean}
|
|
827
|
+
*/
|
|
828
|
+
#hasFieldHelper() {
|
|
829
|
+
return [...this.#field.querySelectorAll(':scope > [slot="helper"]')].some(
|
|
830
|
+
(node) => node !== this.#confidenceNode && hasNodeContent(node),
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** Removes the confidence indicator. */
|
|
835
|
+
#removeConfidenceNode() {
|
|
836
|
+
const node = this.#confidenceNode;
|
|
837
|
+
if (!node) {
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (this.#describedElement) {
|
|
842
|
+
removeValuesFromAttribute(this.#describedElement, 'aria-describedby', node.id);
|
|
843
|
+
}
|
|
844
|
+
node.remove();
|
|
845
|
+
this.#confidenceNode = null;
|
|
846
|
+
this.#updateFieldHelperState();
|
|
847
|
+
}
|
|
848
|
+
|
|
622
849
|
/**
|
|
623
850
|
* Enters the "AI is working" state: shows the shimmer and makes the field
|
|
624
851
|
* read-only on the client so the user cannot edit a value the AI is about
|