@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
@@ -0,0 +1,239 @@
1
+ import { LitElement, css, html, nothing } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import { HasSlotController } from "../lib/has-slot-controller";
4
+
5
+ export type ToolResultStatus = "success" | "error" | "cancelled" | "unknown";
6
+ export type ToolResultChannel = "result" | "stdout" | "stderr" | "log" | "unknown";
7
+
8
+ function isStructuredValue(value: unknown): value is string | unknown[] | Record<string, unknown> {
9
+ return (
10
+ typeof value === "string" ||
11
+ Array.isArray(value) ||
12
+ (Boolean(value) && typeof value === "object")
13
+ );
14
+ }
15
+
16
+ function extractFirstStructuredField(
17
+ record: Record<string, unknown>,
18
+ keys: string[],
19
+ ): string[] | undefined {
20
+ for (const key of keys) {
21
+ const candidate = record[key];
22
+ if (!isStructuredValue(candidate)) {
23
+ continue;
24
+ }
25
+ const parts = extractStructuredText(candidate);
26
+ if (parts.length > 0) {
27
+ return parts;
28
+ }
29
+ }
30
+ }
31
+
32
+ function extractStructuredText(value: unknown): string[] {
33
+ if (typeof value === "string") {
34
+ const trimmed = value.trim();
35
+ return trimmed ? [trimmed] : [];
36
+ }
37
+
38
+ if (Array.isArray(value)) {
39
+ return value.flatMap((item) => extractStructuredText(item));
40
+ }
41
+
42
+ if (value && typeof value === "object") {
43
+ const record = value as Record<string, unknown>;
44
+ const extracted = extractFirstStructuredField(record, [
45
+ "text",
46
+ "message",
47
+ "content",
48
+ "result",
49
+ "output",
50
+ "error",
51
+ ]);
52
+ return extracted ?? [JSON.stringify(value, undefined, 2)];
53
+ }
54
+
55
+ if (value === null || value === undefined) {
56
+ return [];
57
+ }
58
+ return [String(value)];
59
+ }
60
+
61
+ export function normalizeToolOutput(content: string): string {
62
+ const trimmed = content.trim();
63
+ if (!trimmed) {
64
+ return "";
65
+ }
66
+
67
+ try {
68
+ const parsed = JSON.parse(trimmed) as unknown;
69
+ const lines = extractStructuredText(parsed).filter(Boolean);
70
+ if (lines.length > 0) {
71
+ return lines.join("\n\n");
72
+ }
73
+ } catch {
74
+ // fall through to raw content
75
+ }
76
+
77
+ return trimmed;
78
+ }
79
+
80
+ /**
81
+ * Specialized runtime/tool output content block.
82
+ *
83
+ * @slot - Output content. If absent and content is set, renders normalized output in <pre>.
84
+ * @slot meta - Optional source/channel/truncation metadata.
85
+ *
86
+ * @cssprop --ai-tool-result-color - Output text color.
87
+ * @cssprop --ai-tool-result-background - Output background.
88
+ * @cssprop --ai-tool-result-meta-color - Meta slot text color.
89
+ * @cssprop --ai-tool-result-error-color - Error text color.
90
+ * @cssprop --ai-tool-result-code-color - Code/output text color.
91
+ * @cssprop --ai-tool-result-code-font - Code/output font family.
92
+ * @cssprop --ai-tool-result-max-height - Maximum output height. Default: 240px
93
+ */
94
+ @customElement("ai-tool-result")
95
+ export class AiToolResult extends LitElement {
96
+ /** Associated ai-tool-call[id]. Missing/mismatched ids are allowed. */
97
+ @property({ reflect: true, attribute: "for" })
98
+ htmlFor = "";
99
+
100
+ /** Tool/result source name. */
101
+ @property({ reflect: true })
102
+ name = "";
103
+
104
+ /** Result status. */
105
+ @property({ reflect: true })
106
+ status: "success" | "error" | "cancelled" | "unknown" = "unknown";
107
+
108
+ /** Output channel. */
109
+ @property({ reflect: true })
110
+ channel: "result" | "stdout" | "stderr" | "log" | "unknown" = "unknown";
111
+
112
+ /** MIME/content type hint. */
113
+ @property({ reflect: true, attribute: "content-type" })
114
+ contentType = "text/plain";
115
+
116
+ /** Raw output content. Prefer property binding for large content. */
117
+ @property({ type: String })
118
+ content = "";
119
+
120
+ /** Whether the rendered output was truncated upstream. */
121
+ @property({ reflect: true, type: Boolean })
122
+ truncated = false;
123
+
124
+ private readonly hasSlot = new HasSlotController(this, "[default]", "meta");
125
+
126
+ static override styles = css`
127
+ :host {
128
+ box-sizing: border-box;
129
+ display: block;
130
+ margin: 0;
131
+ padding: 0;
132
+ min-width: 0;
133
+ max-width: 100%;
134
+ background: var(--ai-tool-result-background, transparent);
135
+ color: var(--ai-tool-result-color, var(--text, var(--ai-color-text, inherit)));
136
+ }
137
+
138
+ *,
139
+ *::before,
140
+ *::after {
141
+ box-sizing: inherit;
142
+ }
143
+
144
+ .container {
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: var(--ai-space-sm, 8px);
148
+ min-width: 0;
149
+ max-width: 100%;
150
+ }
151
+
152
+ .meta {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: var(--ai-space-xs, 4px);
156
+ color: var(--ai-tool-result-meta-color, var(--text-muted, var(--ai-color-text-muted, #888)));
157
+ font-size: var(--font-size-caption, var(--ai-font-size-xs, 0.75rem));
158
+ }
159
+
160
+ .content {
161
+ min-width: 0;
162
+ max-width: 100%;
163
+ color: var(--ai-tool-result-color, var(--text, var(--ai-color-text, inherit)));
164
+ }
165
+
166
+ pre {
167
+ margin: 0;
168
+ max-width: 100%;
169
+ max-height: var(--ai-tool-result-max-height, 240px);
170
+ overflow-x: auto;
171
+ overflow-y: auto;
172
+ -webkit-overflow-scrolling: touch;
173
+ color: var(
174
+ --ai-tool-result-code-color,
175
+ color-mix(in oklch, var(--text, currentColor) 82%, var(--text-muted, currentColor))
176
+ );
177
+ font-family: var(--ai-tool-result-code-font, var(--font-family-mono, monospace));
178
+ font-size: var(--font-size-caption, 0.75rem);
179
+ line-height: var(--line-height-body, 1.5);
180
+ white-space: pre-wrap;
181
+ word-break: break-word;
182
+ }
183
+
184
+ :host([channel="stderr"]) .content,
185
+ :host([status="error"]) .content {
186
+ color: var(--ai-tool-result-error-color, var(--error, var(--ai-color-error, #dc2626)));
187
+ }
188
+
189
+ :host([channel="log"]) .content {
190
+ color: var(--ai-tool-result-meta-color, var(--text-muted, var(--ai-color-text-muted, #888)));
191
+ }
192
+
193
+ .truncation-indicator::after {
194
+ content: " (truncated)";
195
+ color: var(--ai-tool-result-meta-color, var(--text-muted, var(--ai-color-text-muted, #888)));
196
+ font-style: italic;
197
+ }
198
+ `;
199
+
200
+ private get hasDefaultContent(): boolean {
201
+ return this.hasSlot.test("[default]");
202
+ }
203
+
204
+ private get hasMetaContent(): boolean {
205
+ return this.hasSlot.test("meta");
206
+ }
207
+
208
+ override render() {
209
+ const body = normalizeToolOutput(this.content);
210
+ return html`
211
+ <div class="container">
212
+ ${
213
+ this.hasMetaContent
214
+ ? html`
215
+ <div class="meta"><slot name="meta"></slot></div>
216
+ `
217
+ : nothing
218
+ }
219
+ <div class="content ${this.truncated ? "truncation-indicator" : ""}">
220
+ ${
221
+ this.hasDefaultContent
222
+ ? html`
223
+ <slot></slot>
224
+ `
225
+ : body
226
+ ? html`<pre>${body}</pre>`
227
+ : nothing
228
+ }
229
+ </div>
230
+ </div>
231
+ `;
232
+ }
233
+ }
234
+
235
+ declare global {
236
+ interface HTMLElementTagNameMap {
237
+ "ai-tool-result": AiToolResult;
238
+ }
239
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./ai-conversation";
2
+ export * from "./ai-message";
3
+ export * from "./ai-tool-call";
4
+ export * from "./ai-tool-result";
5
+ export * from "./ai-thinking";
6
+ export * from "./ai-event";
@@ -0,0 +1,163 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ const SIZE_MAP: Record<string, string> = {
5
+ sm: "1.5rem",
6
+ md: "2rem",
7
+ lg: "2.5rem",
8
+ };
9
+
10
+ const FONT_SIZE_MAP: Record<string, string> = {
11
+ sm: "0.625rem",
12
+ md: "0.75rem",
13
+ lg: "0.875rem",
14
+ };
15
+
16
+ /**
17
+ * Actor/agent identity marker.
18
+ *
19
+ * @slot - Custom avatar content/icon.
20
+ *
21
+ * @cssprop --size - Avatar width/height.
22
+ * @cssprop --background-color - Fallback background.
23
+ * @cssprop --text-color - Initials/icon color.
24
+ * @cssprop --border-color - Border color.
25
+ * @cssprop --border-radius - Avatar radius.
26
+ * @cssprop --font-size - Initials font size.
27
+ * @cssprop --font-weight - Initials font weight. Default: 700
28
+ */
29
+ @customElement("ai-avatar")
30
+ export class AiAvatar extends LitElement {
31
+ /** Image source URL. When set, renders an <img>. */
32
+ @property({ reflect: true }) src = "";
33
+ /** Display name. Used for initials fallback and img alt text. */
34
+ @property({ reflect: true }) name = "";
35
+ /** Size preset. */
36
+ @property({ reflect: true }) size: "sm" | "md" | "lg" = "md";
37
+ /** Semantic color tone. */
38
+ @property({ reflect: true }) tone: "neutral" | "accent" | "user" | "assistant" | "agent" =
39
+ "neutral";
40
+
41
+ static override styles = css`
42
+ :host {
43
+ box-sizing: border-box;
44
+ display: inline-flex;
45
+ align-items: center;
46
+ justify-content: center;
47
+ width: var(--size, 2rem);
48
+ height: var(--size, 2rem);
49
+ border-radius: var(--border-radius, 50%);
50
+ background: var(--background-color, rgba(128, 128, 128, 0.15));
51
+ color: var(--text-color, inherit);
52
+ border: 1px solid var(--border-color, transparent);
53
+ overflow: hidden;
54
+ flex-shrink: 0;
55
+ margin: 0;
56
+ padding: 0;
57
+ }
58
+
59
+ img {
60
+ width: 100%;
61
+ height: 100%;
62
+ object-fit: cover;
63
+ display: block;
64
+ }
65
+
66
+ .initials {
67
+ font-size: var(--font-size, 0.75rem);
68
+ font-weight: var(--font-weight, 700);
69
+ line-height: 1;
70
+ text-transform: uppercase;
71
+ letter-spacing: 0.02em;
72
+ user-select: none;
73
+ }
74
+
75
+ *,
76
+ *::before,
77
+ *::after {
78
+ box-sizing: inherit;
79
+ }
80
+ `;
81
+
82
+ override render() {
83
+ if (this.src) {
84
+ return html`<img src="${this.src}" alt="${this.name || ""}" part="image" />`;
85
+ }
86
+
87
+ const initials = this.getInitials();
88
+ if (initials) {
89
+ return html`<span class="initials" part="initials">${initials}</span>`;
90
+ }
91
+
92
+ return html`
93
+ <slot></slot>
94
+ `;
95
+ }
96
+
97
+ protected override updated(): void {
98
+ const size = SIZE_MAP[this.size] ?? "2rem";
99
+ const fontSize = FONT_SIZE_MAP[this.size] ?? "0.75rem";
100
+
101
+ this.style.setProperty("--size", `var(--size, ${size})`);
102
+ this.style.setProperty("--font-size", `var(--font-size, ${fontSize})`);
103
+
104
+ const toneColors = this.getToneColors();
105
+ this.style.setProperty("--background-color", `var(--background-color, ${toneColors.bg})`);
106
+ this.style.setProperty("--text-color", `var(--text-color, ${toneColors.text})`);
107
+ this.style.setProperty("--border-color", `var(--border-color, ${toneColors.border})`);
108
+ }
109
+
110
+ private getInitials(): string {
111
+ if (!this.name) {
112
+ return "";
113
+ }
114
+ const words = this.name.trim().split(/\s+/);
115
+ if (words.length >= 2) {
116
+ const first = words[0]?.[0] ?? "";
117
+ const last = words[words.length - 1]?.[0] ?? "";
118
+ return (first + last).toUpperCase();
119
+ }
120
+ return (words[0]?.[0] ?? "").toUpperCase();
121
+ }
122
+
123
+ private getToneColors(): { bg: string; text: string; border: string } {
124
+ switch (this.tone) {
125
+ case "accent":
126
+ return {
127
+ bg: "rgba(74, 144, 217, 0.15)",
128
+ text: "var(--ai-color-accent, #4a90d9)",
129
+ border: "rgba(74, 144, 217, 0.25)",
130
+ };
131
+ case "user":
132
+ return {
133
+ bg: "rgba(74, 144, 217, 0.12)",
134
+ text: "var(--ai-color-accent, #4a90d9)",
135
+ border: "rgba(74, 144, 217, 0.2)",
136
+ };
137
+ case "assistant":
138
+ return {
139
+ bg: "rgba(128, 128, 128, 0.12)",
140
+ text: "var(--ai-color-text-muted, rgba(128, 128, 128, 0.8))",
141
+ border: "rgba(128, 128, 128, 0.2)",
142
+ };
143
+ case "agent":
144
+ return {
145
+ bg: "rgba(46, 160, 67, 0.12)",
146
+ text: "var(--ai-color-success, #2ea043)",
147
+ border: "rgba(46, 160, 67, 0.2)",
148
+ };
149
+ default:
150
+ return {
151
+ bg: "rgba(128, 128, 128, 0.15)",
152
+ text: "inherit",
153
+ border: "transparent",
154
+ };
155
+ }
156
+ }
157
+ }
158
+
159
+ declare global {
160
+ interface HTMLElementTagNameMap {
161
+ "ai-avatar": AiAvatar;
162
+ }
163
+ }
@@ -0,0 +1,141 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ /**
5
+ * Compact semantic label/status chip.
6
+ *
7
+ * @slot - Badge label.
8
+ *
9
+ * @cssprop --background-color - Badge background.
10
+ * @cssprop --text-color - Badge text color.
11
+ * @cssprop --border-color - Badge border color.
12
+ * @cssprop --border-radius - Badge radius.
13
+ * @cssprop --dot-color - Leading dot color.
14
+ * @cssprop --font-size - Font size.
15
+ * @cssprop --font-weight - Font weight. Default: 600
16
+ * @cssprop --inline-gap - Gap between dot/content.
17
+ */
18
+ @customElement("ai-badge")
19
+ export class AiBadge extends LitElement {
20
+ /** Semantic color tone. */
21
+ @property({ reflect: true }) tone:
22
+ | "neutral"
23
+ | "accent"
24
+ | "success"
25
+ | "warning"
26
+ | "error"
27
+ | "info" = "neutral";
28
+ /** Size preset. */
29
+ @property({ reflect: true }) size: "sm" | "md" = "md";
30
+ /** Whether to show a leading dot indicator. */
31
+ @property({ reflect: true, type: Boolean }) dot = false;
32
+
33
+ static override styles = css`
34
+ :host {
35
+ box-sizing: border-box;
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: var(--inline-gap, 4px);
39
+ margin: 0;
40
+ padding: var(--_padding, 3px 8px);
41
+ font-size: var(--font-size, 0.75rem);
42
+ font-weight: var(--font-weight, 600);
43
+ line-height: 1;
44
+ color: var(--text-color, inherit);
45
+ background: var(--background-color, rgba(128, 128, 128, 0.1));
46
+ border: 1px solid var(--border-color, transparent);
47
+ border-radius: var(--border-radius, 999px);
48
+ white-space: nowrap;
49
+ }
50
+
51
+ :host([size="sm"]) {
52
+ --_padding: 2px 6px;
53
+ }
54
+
55
+ :host([size="md"]) {
56
+ --_padding: 3px 8px;
57
+ }
58
+
59
+ .dot {
60
+ width: 6px;
61
+ height: 6px;
62
+ border-radius: 50%;
63
+ background: var(--dot-color, var(--text-color, inherit));
64
+ flex-shrink: 0;
65
+ }
66
+
67
+ *,
68
+ *::before,
69
+ *::after {
70
+ box-sizing: inherit;
71
+ }
72
+ `;
73
+
74
+ override render() {
75
+ return html`
76
+ ${
77
+ this.dot
78
+ ? html`
79
+ <span class="dot" part="dot"></span>
80
+ `
81
+ : ""
82
+ }
83
+ <slot></slot>
84
+ `;
85
+ }
86
+
87
+ protected override updated(): void {
88
+ const colors = this.getToneColors();
89
+ this.style.setProperty("--background-color", `var(--background-color, ${colors.bg})`);
90
+ this.style.setProperty("--text-color", `var(--text-color, ${colors.text})`);
91
+ this.style.setProperty("--border-color", `var(--border-color, ${colors.border})`);
92
+ this.style.setProperty("--dot-color", `var(--dot-color, ${colors.text})`);
93
+ }
94
+
95
+ private getToneColors(): { bg: string; text: string; border: string } {
96
+ switch (this.tone) {
97
+ case "accent":
98
+ return {
99
+ bg: "rgba(74, 144, 217, 0.12)",
100
+ text: "var(--ai-color-accent, #4a90d9)",
101
+ border: "rgba(74, 144, 217, 0.2)",
102
+ };
103
+ case "success":
104
+ return {
105
+ bg: "rgba(46, 160, 67, 0.12)",
106
+ text: "var(--ai-color-success, #2ea043)",
107
+ border: "rgba(46, 160, 67, 0.2)",
108
+ };
109
+ case "warning":
110
+ return {
111
+ bg: "rgba(210, 153, 34, 0.12)",
112
+ text: "var(--ai-color-warning, #d29922)",
113
+ border: "rgba(210, 153, 34, 0.2)",
114
+ };
115
+ case "error":
116
+ return {
117
+ bg: "rgba(227, 62, 51, 0.12)",
118
+ text: "var(--ai-color-error, #e33e33)",
119
+ border: "rgba(227, 62, 51, 0.2)",
120
+ };
121
+ case "info":
122
+ return {
123
+ bg: "rgba(80, 160, 220, 0.12)",
124
+ text: "var(--ai-color-info, #50a0dc)",
125
+ border: "rgba(80, 160, 220, 0.2)",
126
+ };
127
+ default:
128
+ return {
129
+ bg: "rgba(128, 128, 128, 0.1)",
130
+ text: "inherit",
131
+ border: "rgba(128, 128, 128, 0.15)",
132
+ };
133
+ }
134
+ }
135
+ }
136
+
137
+ declare global {
138
+ interface HTMLElementTagNameMap {
139
+ "ai-badge": AiBadge;
140
+ }
141
+ }
@@ -0,0 +1,97 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+
4
+ const TONE_OPACITY: Record<string, number> = {
5
+ subtle: 0.08,
6
+ default: 0.16,
7
+ strong: 0.32,
8
+ };
9
+
10
+ /**
11
+ * Separator/section marker.
12
+ *
13
+ * @slot - Optional label/content centered in divider.
14
+ *
15
+ * @cssprop --line-color - Divider line color.
16
+ * @cssprop --line-width - Divider thickness.
17
+ * @cssprop --text-color - Label text color.
18
+ * @cssprop --label-background-color - Label background to mask line.
19
+ * @cssprop --label-gap - Gap between line and label.
20
+ */
21
+ @customElement("ai-divider")
22
+ export class AiDivider extends LitElement {
23
+ /** Axis of the divider line. */
24
+ @property({ reflect: true }) orientation: "horizontal" | "vertical" = "horizontal";
25
+ /** Visual prominence of the divider. */
26
+ @property({ reflect: true }) tone: "subtle" | "default" | "strong" = "default";
27
+
28
+ static override styles = css`
29
+ :host {
30
+ box-sizing: border-box;
31
+ display: flex;
32
+ margin: 0;
33
+ padding: 0;
34
+ color: var(--text-color, inherit);
35
+ }
36
+
37
+ :host([orientation="horizontal"]) {
38
+ flex-direction: row;
39
+ align-items: center;
40
+ width: 100%;
41
+ }
42
+
43
+ :host([orientation="vertical"]) {
44
+ flex-direction: column;
45
+ justify-content: center;
46
+ height: 100%;
47
+ align-self: stretch;
48
+ }
49
+
50
+ .line {
51
+ flex: 1;
52
+ background: var(--line-color, rgba(128, 128, 128, 0.16));
53
+ }
54
+
55
+ :host([orientation="horizontal"]) .line {
56
+ height: var(--line-width, 1px);
57
+ }
58
+
59
+ :host([orientation="vertical"]) .line {
60
+ width: var(--line-width, 1px);
61
+ }
62
+
63
+ ::slotted(*) {
64
+ padding: 0 var(--label-gap, 0.5rem);
65
+ white-space: nowrap;
66
+ }
67
+
68
+ :host([orientation="vertical"]) ::slotted(*) {
69
+ padding: var(--label-gap, 0.5rem) 0;
70
+ }
71
+
72
+ *,
73
+ *::before,
74
+ *::after {
75
+ box-sizing: inherit;
76
+ }
77
+ `;
78
+
79
+ override render() {
80
+ return html`
81
+ <span class="line" part="line-before"></span>
82
+ <slot></slot>
83
+ <span class="line" part="line-after"></span>
84
+ `;
85
+ }
86
+
87
+ protected override updated(): void {
88
+ const opacity = TONE_OPACITY[this.tone] ?? 0.16;
89
+ this.style.setProperty("--line-color", `var(--line-color, rgba(128, 128, 128, ${opacity}))`);
90
+ }
91
+ }
92
+
93
+ declare global {
94
+ interface HTMLElementTagNameMap {
95
+ "ai-divider": AiDivider;
96
+ }
97
+ }