@cianfrani/ai-ui 0.1.0-alpha.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -0
  3. package/dist/ai-ui.js +3218 -0
  4. package/dist/types/index.d.ts +7 -0
  5. package/dist/types/lib/has-slot-controller.d.ts +21 -0
  6. package/dist/types/lib/tool-tone.d.ts +2 -0
  7. package/dist/types/native-styles.d.ts +5 -0
  8. package/dist/types/semantic/ai-conversation.d.ts +28 -0
  9. package/dist/types/semantic/ai-event.d.ts +91 -0
  10. package/dist/types/semantic/ai-message.d.ts +45 -0
  11. package/dist/types/semantic/ai-thinking.d.ts +41 -0
  12. package/dist/types/semantic/ai-tool-call.d.ts +59 -0
  13. package/dist/types/semantic/ai-tool-result.d.ts +44 -0
  14. package/dist/types/semantic/index.d.ts +6 -0
  15. package/dist/types/visual/avatar.d.ts +34 -0
  16. package/dist/types/visual/badge.d.ts +32 -0
  17. package/dist/types/visual/divider.d.ts +26 -0
  18. package/dist/types/visual/icon.d.ts +24 -0
  19. package/dist/types/visual/index.d.ts +9 -0
  20. package/dist/types/visual/markdown.d.ts +52 -0
  21. package/dist/types/visual/stack.d.ts +32 -0
  22. package/dist/types/visual/status.d.ts +33 -0
  23. package/dist/types/visual/surface.d.ts +34 -0
  24. package/dist/types/visual/text.d.ts +37 -0
  25. package/package.json +67 -0
  26. package/src/custom-elements.json +3741 -0
  27. package/src/index.ts +8 -0
  28. package/src/lib/has-slot-controller.ts +61 -0
  29. package/src/lib/tool-tone.ts +18 -0
  30. package/src/native-styles.ts +29 -0
  31. package/src/semantic/ai-conversation.ts +84 -0
  32. package/src/semantic/ai-event.ts +452 -0
  33. package/src/semantic/ai-message.ts +235 -0
  34. package/src/semantic/ai-thinking.ts +190 -0
  35. package/src/semantic/ai-tool-call.ts +513 -0
  36. package/src/semantic/ai-tool-result.ts +239 -0
  37. package/src/semantic/index.ts +6 -0
  38. package/src/visual/avatar.ts +163 -0
  39. package/src/visual/badge.ts +141 -0
  40. package/src/visual/divider.ts +97 -0
  41. package/src/visual/icon.ts +97 -0
  42. package/src/visual/index.ts +9 -0
  43. package/src/visual/markdown.ts +888 -0
  44. package/src/visual/stack.ts +115 -0
  45. package/src/visual/status.ts +170 -0
  46. package/src/visual/surface.ts +150 -0
  47. package/src/visual/text.ts +141 -0
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ import "./semantic";
2
+ import "./visual";
3
+ import "./native-styles";
4
+
5
+ export * from "./semantic/index";
6
+ export * from "./visual/index";
7
+ export * from "./lib/has-slot-controller";
8
+ export * from "./lib/tool-tone";
@@ -0,0 +1,61 @@
1
+ import type { ReactiveController, ReactiveControllerHost } from "lit";
2
+
3
+ /**
4
+ * Reactive controller that tracks whether named/default slots have content.
5
+ *
6
+ * Usage:
7
+ * private readonly hasSlot = new HasSlotController(this, "[default]");
8
+ * // then in render():
9
+ * this.hasSlot.test("[default]") // → boolean
10
+ */
11
+ export class HasSlotController implements ReactiveController {
12
+ host: ReactiveControllerHost & Element;
13
+ slotNames: string[] = [];
14
+
15
+ constructor(host: ReactiveControllerHost & Element, ...slotNames: string[]) {
16
+ (this.host = host).addController(this);
17
+ this.slotNames = slotNames;
18
+ }
19
+
20
+ private hasDefaultSlot(): boolean {
21
+ return [...this.host.childNodes].some((node) => {
22
+ if (node.nodeType === node.TEXT_NODE && node.textContent?.trim() !== "") {
23
+ return true;
24
+ }
25
+ if (node.nodeType === node.ELEMENT_NODE) {
26
+ const el = node as HTMLElement;
27
+ if (!el.hasAttribute("slot")) {
28
+ return true;
29
+ }
30
+ }
31
+ return false;
32
+ });
33
+ }
34
+
35
+ private hasNamedSlot(name: string): boolean {
36
+ return this.host.querySelector(`:scope > [slot="${name}"]`) !== null;
37
+ }
38
+
39
+ /** Test whether a slot has content. Use `"[default]"` for the default slot. */
40
+ test(slotName: string): boolean {
41
+ return slotName === "[default]" ? this.hasDefaultSlot() : this.hasNamedSlot(slotName);
42
+ }
43
+
44
+ hostConnected(): void {
45
+ this.host.shadowRoot?.addEventListener("slotchange", this.handleSlotChange);
46
+ }
47
+
48
+ hostDisconnected(): void {
49
+ this.host.shadowRoot?.removeEventListener("slotchange", this.handleSlotChange);
50
+ }
51
+
52
+ private handleSlotChange = (event: Event): void => {
53
+ const slot = event.target as HTMLSlotElement;
54
+ if (
55
+ (this.slotNames.includes("[default]") && !slot.name) ||
56
+ (slot.name && this.slotNames.includes(slot.name))
57
+ ) {
58
+ this.host.requestUpdate();
59
+ }
60
+ };
61
+ }
@@ -0,0 +1,18 @@
1
+ export type ToolTone = "read" | "write" | "edit" | "bash" | "generic";
2
+
3
+ export function getToolTone(name: string): ToolTone {
4
+ const normalized = name.toLowerCase();
5
+ if (normalized.includes("read")) {
6
+ return "read";
7
+ }
8
+ if (normalized.includes("write")) {
9
+ return "write";
10
+ }
11
+ if (normalized.includes("edit")) {
12
+ return "edit";
13
+ }
14
+ if (normalized.includes("bash")) {
15
+ return "bash";
16
+ }
17
+ return "generic";
18
+ }
@@ -0,0 +1,29 @@
1
+ import { css } from "lit";
2
+
3
+ /**
4
+ * Baseline styles for native elements within ai-* shadow roots.
5
+ * Import and include in any ai component that renders native elements.
6
+ */
7
+ export const nativeStyles = css`
8
+ pre {
9
+ margin: 0;
10
+ max-height: 240px;
11
+ overflow: auto;
12
+ font-family: var(--ai-font-family-mono, monospace);
13
+ font-size: var(--ai-font-size-caption, 0.75rem);
14
+ white-space: pre-wrap;
15
+ }
16
+
17
+ a {
18
+ color: var(--ai-color-accent, #0066cc);
19
+ text-decoration: underline;
20
+ }
21
+
22
+ time {
23
+ font-size: inherit;
24
+ }
25
+
26
+ button {
27
+ cursor: pointer;
28
+ }
29
+ `;
@@ -0,0 +1,84 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ /**
5
+ * Root ordered transcript container for message-first AI conversations.
6
+ *
7
+ * @slot - Ordered records: ai-message, ai-tool-call, ai-tool-result, ai-thinking, ai-event, or native elements.
8
+ *
9
+ * @cssprop --ai-conversation-background - Conversation background. Default: transparent
10
+ * @cssprop --ai-conversation-color - Default text color. Default: var(--ai-color-text)
11
+ * @cssprop --ai-conversation-gap - Vertical gap between direct child records. Default: var(--ai-space-lg)
12
+ * @cssprop --ai-conversation-compact-gap - Gap when density="compact". Default: var(--ai-space-sm)
13
+ * @cssprop --ai-conversation-live-border-color - Optional visual marker for live conversations. Default: transparent
14
+ */
15
+ @customElement("ai-conversation")
16
+ export class AiConversation extends LitElement {
17
+ /** Spacing density for transcript rows. Invalid values fall back to comfortable. */
18
+ @property({ reflect: true })
19
+ density: "comfortable" | "compact" = "comfortable";
20
+
21
+ /** Whether this is a live/streaming region. Adds aria-live="polite" by default. */
22
+ @property({ type: Boolean, reflect: true })
23
+ live = false;
24
+
25
+ /** Accessible label for the conversation region. */
26
+ @property({ reflect: true })
27
+ label = "Conversation";
28
+
29
+ static override styles = css`
30
+ :host {
31
+ box-sizing: border-box;
32
+ display: block;
33
+ margin: 0;
34
+ padding: 0;
35
+ }
36
+
37
+ *,
38
+ *::before,
39
+ *::after {
40
+ box-sizing: inherit;
41
+ }
42
+
43
+ .conversation {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: var(--ai-conversation-gap, var(--ai-space-lg, 16px));
47
+ background: var(--ai-conversation-background, transparent);
48
+ color: var(--ai-conversation-color, var(--ai-color-text, inherit));
49
+ }
50
+
51
+ .conversation[data-density="compact"] {
52
+ gap: var(--ai-conversation-compact-gap, var(--ai-space-sm, 8px));
53
+ }
54
+
55
+ :host([live]) .conversation {
56
+ border-left: 2px solid var(--ai-conversation-live-border-color, transparent);
57
+ }
58
+ `;
59
+
60
+ private get normalizedDensity(): "comfortable" | "compact" {
61
+ return this.density === "compact" ? "compact" : "comfortable";
62
+ }
63
+
64
+ override render() {
65
+ const ariaLive = this.live ? "polite" : "off";
66
+ return html`
67
+ <section
68
+ class="conversation"
69
+ data-density=${this.normalizedDensity}
70
+ aria-label=${this.getAttribute("aria-label") ?? this.label}
71
+ aria-live=${ariaLive}
72
+ aria-relevant="additions text"
73
+ >
74
+ <slot></slot>
75
+ </section>
76
+ `;
77
+ }
78
+ }
79
+
80
+ declare global {
81
+ interface HTMLElementTagNameMap {
82
+ "ai-conversation": AiConversation;
83
+ }
84
+ }
@@ -0,0 +1,452 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { customElement, property, query } from "lit/decorators.js";
3
+ import type { TemplateResult } from "lit";
4
+
5
+ const KIND_LABELS: Record<string, string> = {
6
+ status: "Status",
7
+ "model-change": "Model Change",
8
+ checkpoint: "Checkpoint",
9
+ note: "Note",
10
+ system: "System",
11
+ error: "Error",
12
+ custom: "Event",
13
+ };
14
+
15
+ const SEVERITY_COLORS: Record<string, string> = {
16
+ info: "var(--ai-event-info-color, var(--ai-event-info, var(--ai-color-info, #3b82f6)))",
17
+ warning:
18
+ "var(--ai-event-warning-color, var(--ai-event-warning, var(--ai-color-warning, #f59e0b)))",
19
+ error: "var(--ai-event-error-color, var(--ai-event-error, var(--ai-color-error, #ef4444)))",
20
+ };
21
+
22
+ /**
23
+ * Payload of the `ai-show` / `ai-hide` events.
24
+ *
25
+ * These events share a name but are emitted by three different components with
26
+ * distinct payloads. The `source` field discriminates which component fired, so
27
+ * listeners can narrow safely:
28
+ *
29
+ * ```ts
30
+ * el.addEventListener("ai-show", (e) => {
31
+ * if (e.detail.source === "tool-call") {
32
+ * console.log(e.detail.id, e.detail.name); // typed
33
+ * }
34
+ * });
35
+ * ```
36
+ */
37
+ export type AiShowHideDetail =
38
+ | { source: "event"; kind: string }
39
+ | { source: "thinking"; redacted: boolean }
40
+ | { source: "tool-call"; open: boolean; id: string; name: string };
41
+
42
+ /**
43
+ * Non-message, non-operation transcript event.
44
+ *
45
+ * Represents status changes, model switches, checkpoints, notes,
46
+ * system messages, errors, or custom events within an AI transcript.
47
+ *
48
+ * @slot summary - Custom summary/label override
49
+ * @slot meta - Timestamps, source labels
50
+ * @slot - Default: event content
51
+ *
52
+ * @fires ai-show - Dispatched when opened. Bubbles, composed.
53
+ * @fires ai-hide - Dispatched when closed. Bubbles, composed.
54
+ *
55
+ * @cssprop --ai-event-background-color - Event background. Default: transparent
56
+ * @cssprop --ai-event-text-color - Event text color. Default: var(--ai-color-text-muted)
57
+ * @cssprop --ai-event-border-color - Event border color. Default: var(--ai-color-border)
58
+ * @cssprop --ai-event-border-width - Event border width. Default: 0
59
+ * @cssprop --ai-event-border-radius - Event border radius. Default: 0
60
+ * @cssprop --ai-event-content-gap - Content gap. Default: var(--ai-space-sm)
61
+ * @cssprop --ai-event-content-indent - Expanded content indentation. Default: clamp(20px, 7vw, 58px)
62
+ * @cssprop --ai-event-rule-color - Divider rule color. Default: subtle muted text color
63
+ * @cssprop --ai-event-open-rule-color - Divider rule color when expanded. Default: stronger muted text color
64
+ * @cssprop --ai-event-meta-color - Meta slot text color. Default: var(--ai-color-text-muted)
65
+ * @cssprop --ai-event-info-color - Info severity color. Default: var(--ai-color-info)
66
+ * @cssprop --ai-event-warning-color - Warning severity color. Default: var(--ai-color-warning)
67
+ * @cssprop --ai-event-error-color - Error severity color. Default: var(--ai-color-error)
68
+ */
69
+ @customElement("ai-event")
70
+ export class AiEvent extends LitElement {
71
+ //#region Properties
72
+
73
+ /** Event kind. */
74
+ @property({ reflect: true })
75
+ kind: "status" | "model-change" | "checkpoint" | "note" | "system" | "error" | "custom" =
76
+ "custom";
77
+
78
+ /** Severity level — controls accent color. */
79
+ @property({ reflect: true })
80
+ severity: "info" | "warning" | "error" = "info";
81
+
82
+ /** Origin source identifier. */
83
+ @property({ reflect: true })
84
+ source: string = "";
85
+
86
+ /** ID of the element this event pertains to (maps to `for` attribute). */
87
+ @property({ reflect: true, attribute: "for" })
88
+ htmlFor: string = "";
89
+
90
+ /** Whether the event details are expanded. */
91
+ @property({ reflect: true, type: Boolean })
92
+ open: boolean = false;
93
+
94
+ //#endregion
95
+
96
+ //#region Queries
97
+
98
+ @query("details")
99
+ private _details!: HTMLDetailsElement | null;
100
+
101
+ //#endregion
102
+
103
+ //#region Public API
104
+
105
+ /** Expand the event details. */
106
+ show(): void {
107
+ this.open = true;
108
+ }
109
+
110
+ /** Collapse the event details. */
111
+ hide(): void {
112
+ this.open = false;
113
+ }
114
+
115
+ /** Toggle open state. Optionally force open/closed. */
116
+ toggle(force?: boolean): void {
117
+ this.open = force !== undefined ? force : !this.open;
118
+ }
119
+
120
+ //#endregion
121
+
122
+ //#region Lifecycle
123
+
124
+ private _prevOpen: boolean = false;
125
+
126
+ override updated(): void {
127
+ // Sync the native <details> element with the reflected `open` property
128
+ if (this._details && this._details.open !== this.open) {
129
+ this._details.open = this.open;
130
+ }
131
+
132
+ // Dispatch toggle event on change
133
+ if (this._prevOpen !== this.open) {
134
+ this._prevOpen = this.open;
135
+ this.dispatchEvent(
136
+ new CustomEvent<AiShowHideDetail>(this.open ? "ai-show" : "ai-hide", {
137
+ detail: { source: "event", kind: this.kind },
138
+ bubbles: true,
139
+ composed: true,
140
+ }),
141
+ );
142
+ }
143
+ }
144
+
145
+ //#endregion
146
+
147
+ //#region Render
148
+
149
+ private _onToggle(): void {
150
+ const details = this._details;
151
+ if (!details) {
152
+ return;
153
+ }
154
+ this.open = details.open;
155
+ }
156
+
157
+ private get _severityColor(): string {
158
+ return SEVERITY_COLORS[this.severity] ?? SEVERITY_COLORS.info ?? "#888";
159
+ }
160
+
161
+ private get _kindLabel(): string {
162
+ return KIND_LABELS[this.kind] ?? KIND_LABELS.custom ?? "Custom";
163
+ }
164
+
165
+ override render(): TemplateResult {
166
+ const accentColor = this._severityColor;
167
+
168
+ return html`
169
+ <details
170
+ .open=${this.open}
171
+ @toggle=${this._onToggle}
172
+ part="details"
173
+ style="--_accent: ${accentColor}"
174
+ >
175
+ <summary part="summary">
176
+ <span class="summary-text" part="summary-text">
177
+ <slot name="summary">
178
+ <span class="kind-label" part="kind-label">${this._kindLabel}</span>
179
+ </slot>
180
+ </span>
181
+ <span class="summary-rule" aria-hidden="true"></span>
182
+ <span class="summary-meta" part="meta">
183
+ <slot name="meta"></slot>
184
+ </span>
185
+ <span class="chevron" part="marker" aria-hidden="true">
186
+ <svg viewBox="0 0 10 10" focusable="false">
187
+ <path d="M3.25 2.25 6.25 5 3.25 7.75"></path>
188
+ </svg>
189
+ </span>
190
+ </summary>
191
+
192
+ <div class="content-shell" part="content-shell">
193
+ <div class="content" part="content">
194
+ <slot></slot>
195
+ </div>
196
+ </div>
197
+ </details>
198
+ `;
199
+ }
200
+
201
+ //#endregion
202
+
203
+ //#region Styles
204
+
205
+ static override styles = css`
206
+ :host {
207
+ box-sizing: border-box;
208
+ display: block;
209
+ width: 100%;
210
+ max-width: 100%;
211
+ min-width: 0;
212
+ margin: 0;
213
+ padding: 0;
214
+ color: var(--ai-event-color, var(--ai-event-text-color, var(--text-muted, #8d867a)));
215
+ }
216
+
217
+ *,
218
+ *::before,
219
+ *::after {
220
+ box-sizing: inherit;
221
+ }
222
+
223
+ details {
224
+ display: block;
225
+ min-width: 0;
226
+ max-width: 100%;
227
+ margin: 0;
228
+ background: var(--ai-event-background, var(--ai-event-background-color, transparent));
229
+ border: var(--ai-event-border-width, 0) solid
230
+ var(--ai-event-border-color, var(--ai-color-border, transparent));
231
+ border-radius: var(--ai-event-radius, var(--ai-event-border-radius, 0));
232
+ }
233
+
234
+ summary {
235
+ display: grid;
236
+ grid-template-columns: auto minmax(24px, 1fr) auto 12px;
237
+ align-items: center;
238
+ column-gap: 8px;
239
+ min-width: 0;
240
+ list-style: none;
241
+ cursor: pointer;
242
+ color: var(
243
+ --ai-event-summary-color,
244
+ color-mix(in oklch, var(--text-muted, currentColor) 82%, var(--text, currentColor))
245
+ );
246
+ padding: 3px 4px;
247
+ border-radius: var(--radius-sm, 5px);
248
+ background: transparent;
249
+ user-select: none;
250
+ transition:
251
+ background 0.16s ease,
252
+ color 0.16s ease;
253
+ }
254
+
255
+ summary:hover {
256
+ background: color-mix(in oklch, var(--_accent) 5%, transparent);
257
+ color: color-mix(in oklch, var(--text, currentColor) 72%, var(--text-muted, currentColor));
258
+ }
259
+
260
+ summary:focus-visible {
261
+ outline: 2px solid var(--focus, var(--_accent));
262
+ outline-offset: 2px;
263
+ }
264
+
265
+ details[open] > summary {
266
+ color: color-mix(in oklch, var(--text, currentColor) 76%, var(--text-muted, currentColor));
267
+ }
268
+
269
+ summary::marker,
270
+ summary::-webkit-details-marker {
271
+ display: none;
272
+ }
273
+
274
+ .summary-text {
275
+ min-width: 0;
276
+ overflow: hidden;
277
+ text-overflow: ellipsis;
278
+ white-space: nowrap;
279
+ font-size: var(--font-size-caption, 0.75rem);
280
+ font-weight: var(--font-weight-semibold, 600);
281
+ letter-spacing: var(--tracking-label, 0.04em);
282
+ line-height: var(--line-height-tight, 1.1);
283
+ text-transform: uppercase;
284
+ }
285
+
286
+ .summary-rule {
287
+ min-width: 24px;
288
+ height: 1px;
289
+ background: var(
290
+ --ai-event-rule-color,
291
+ color-mix(in oklch, var(--text-muted, currentColor) 16%, transparent)
292
+ );
293
+ }
294
+
295
+ details[open] .summary-rule {
296
+ background: var(
297
+ --ai-event-open-rule-color,
298
+ color-mix(in oklch, var(--text-muted, currentColor) 24%, transparent)
299
+ );
300
+ }
301
+
302
+ .kind-label {
303
+ color: inherit;
304
+ font-weight: inherit;
305
+ }
306
+
307
+ .summary-meta {
308
+ display: inline-flex;
309
+ align-items: baseline;
310
+ justify-content: flex-end;
311
+ min-width: 0;
312
+ max-width: min(46vw, 360px);
313
+ color: var(
314
+ --ai-event-meta-color,
315
+ var(--ai-event-meta, color-mix(in oklch, var(--text-muted, currentColor) 76%, transparent))
316
+ );
317
+ font-size: var(--font-size-caption, 0.75rem);
318
+ line-height: var(--line-height-tight, 1.1);
319
+ white-space: nowrap;
320
+ overflow: hidden;
321
+ text-overflow: ellipsis;
322
+ }
323
+
324
+ .chevron {
325
+ display: inline-flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ width: 12px;
329
+ height: 16px;
330
+ justify-self: end;
331
+ color: color-mix(in oklch, var(--text-muted, currentColor) 88%, transparent);
332
+ }
333
+
334
+ .chevron svg {
335
+ width: 10px;
336
+ height: 10px;
337
+ stroke: currentColor;
338
+ fill: none;
339
+ stroke-width: 1.8;
340
+ stroke-linecap: round;
341
+ stroke-linejoin: round;
342
+ transition: transform 0.16s ease;
343
+ }
344
+
345
+ details[open] .chevron svg {
346
+ transform: rotate(90deg);
347
+ }
348
+
349
+ .content-shell {
350
+ margin: 2px 0 1px;
351
+ padding: 6px 4px 3px var(--ai-event-content-indent, clamp(20px, 7vw, 58px));
352
+ }
353
+
354
+ .content {
355
+ display: block;
356
+ min-width: 0;
357
+ max-width: 100%;
358
+ color: var(
359
+ --ai-event-content-color,
360
+ color-mix(in oklch, var(--text, currentColor) 82%, var(--text-muted, currentColor))
361
+ );
362
+ font-size: var(--font-size-meta, 0.8125rem);
363
+ line-height: var(--line-height-relaxed, 1.45);
364
+ }
365
+
366
+ .content ::slotted(*) {
367
+ max-width: 100%;
368
+ }
369
+
370
+ .content ::slotted(:first-child) {
371
+ margin-top: 0;
372
+ }
373
+
374
+ .content ::slotted(:last-child) {
375
+ margin-bottom: 0;
376
+ }
377
+
378
+ .content ::slotted(pre) {
379
+ overflow-x: auto;
380
+ padding: 8px 10px;
381
+ border-radius: var(--radius-sm, 5px);
382
+ background: color-mix(in oklch, var(--text, currentColor) 5%, transparent);
383
+ font-size: var(--font-size-caption, 0.75rem);
384
+ line-height: var(--line-height-snug, 1.25);
385
+ }
386
+
387
+ .content ::slotted(code) {
388
+ border-radius: 4px;
389
+ padding: 0.08em 0.35em;
390
+ background: color-mix(in oklch, var(--text, currentColor) 7%, transparent);
391
+ color: color-mix(in oklch, var(--text, currentColor) 88%, var(--_accent));
392
+ font-size: 0.94em;
393
+ }
394
+
395
+ :host([severity="warning"]) .summary-text {
396
+ color: color-mix(in oklch, var(--_accent) 44%, var(--text, currentColor));
397
+ }
398
+
399
+ :host([severity="warning"]) .summary-rule {
400
+ background: color-mix(in oklch, var(--_accent) 24%, transparent);
401
+ }
402
+
403
+ :host([severity="error"]) .summary-text {
404
+ color: color-mix(in oklch, var(--_accent) 52%, var(--text, currentColor));
405
+ }
406
+
407
+ :host([severity="error"]) .summary-rule {
408
+ background: color-mix(in oklch, var(--_accent) 30%, transparent);
409
+ }
410
+
411
+ :host([severity="error"]) summary:hover {
412
+ background: color-mix(in oklch, var(--_accent) 9%, transparent);
413
+ }
414
+
415
+ @media (max-width: 520px) {
416
+ summary {
417
+ grid-template-columns: auto minmax(16px, 1fr) 12px;
418
+ padding-right: 4px;
419
+ }
420
+
421
+ .summary-meta {
422
+ grid-column: 1 / 3;
423
+ grid-row: 2;
424
+ justify-content: flex-start;
425
+ max-width: 100%;
426
+ margin-top: 1px;
427
+ }
428
+
429
+ .chevron {
430
+ grid-column: 3;
431
+ grid-row: 1 / span 2;
432
+ }
433
+
434
+ .content-shell {
435
+ padding-left: 18px;
436
+ }
437
+ }
438
+ `;
439
+
440
+ //#endregion
441
+ }
442
+
443
+ declare global {
444
+ interface HTMLElementTagNameMap {
445
+ "ai-event": AiEvent;
446
+ }
447
+
448
+ interface HTMLElementEventMap {
449
+ "ai-show": CustomEvent<AiShowHideDetail>;
450
+ "ai-hide": CustomEvent<AiShowHideDetail>;
451
+ }
452
+ }