@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.
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/ai-ui.js +3218 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/lib/has-slot-controller.d.ts +21 -0
- package/dist/types/lib/tool-tone.d.ts +2 -0
- package/dist/types/native-styles.d.ts +5 -0
- package/dist/types/semantic/ai-conversation.d.ts +28 -0
- package/dist/types/semantic/ai-event.d.ts +91 -0
- package/dist/types/semantic/ai-message.d.ts +45 -0
- package/dist/types/semantic/ai-thinking.d.ts +41 -0
- package/dist/types/semantic/ai-tool-call.d.ts +59 -0
- package/dist/types/semantic/ai-tool-result.d.ts +44 -0
- package/dist/types/semantic/index.d.ts +6 -0
- package/dist/types/visual/avatar.d.ts +34 -0
- package/dist/types/visual/badge.d.ts +32 -0
- package/dist/types/visual/divider.d.ts +26 -0
- package/dist/types/visual/icon.d.ts +24 -0
- package/dist/types/visual/index.d.ts +9 -0
- package/dist/types/visual/markdown.d.ts +52 -0
- package/dist/types/visual/stack.d.ts +32 -0
- package/dist/types/visual/status.d.ts +33 -0
- package/dist/types/visual/surface.d.ts +34 -0
- package/dist/types/visual/text.d.ts +37 -0
- package/package.json +67 -0
- package/src/custom-elements.json +3741 -0
- package/src/index.ts +8 -0
- package/src/lib/has-slot-controller.ts +61 -0
- package/src/lib/tool-tone.ts +18 -0
- package/src/native-styles.ts +29 -0
- package/src/semantic/ai-conversation.ts +84 -0
- package/src/semantic/ai-event.ts +452 -0
- package/src/semantic/ai-message.ts +235 -0
- package/src/semantic/ai-thinking.ts +190 -0
- package/src/semantic/ai-tool-call.ts +513 -0
- package/src/semantic/ai-tool-result.ts +239 -0
- package/src/semantic/index.ts +6 -0
- package/src/visual/avatar.ts +163 -0
- package/src/visual/badge.ts +141 -0
- package/src/visual/divider.ts +97 -0
- package/src/visual/icon.ts +97 -0
- package/src/visual/index.ts +9 -0
- package/src/visual/markdown.ts +888 -0
- package/src/visual/stack.ts +115 -0
- package/src/visual/status.ts +170 -0
- package/src/visual/surface.ts +150 -0
- package/src/visual/text.ts +141 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
|
|
4
|
+
const GAP_MAP: Record<string, string> = {
|
|
5
|
+
none: "0",
|
|
6
|
+
"2xs": "0.125rem",
|
|
7
|
+
xs: "0.25rem",
|
|
8
|
+
sm: "0.5rem",
|
|
9
|
+
md: "0.75rem",
|
|
10
|
+
lg: "1rem",
|
|
11
|
+
xl: "1.5rem",
|
|
12
|
+
"2xl": "2rem",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const JUSTIFY_MAP: Record<string, string> = {
|
|
16
|
+
start: "flex-start",
|
|
17
|
+
center: "center",
|
|
18
|
+
end: "flex-end",
|
|
19
|
+
between: "space-between",
|
|
20
|
+
around: "space-around",
|
|
21
|
+
evenly: "space-evenly",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Flex layout primitive.
|
|
26
|
+
*
|
|
27
|
+
* @slot - Stack children.
|
|
28
|
+
*
|
|
29
|
+
* @cssprop --gap - Overrides computed gap.
|
|
30
|
+
* @cssprop --align-items - Overrides align-items.
|
|
31
|
+
* @cssprop --justify-content - Overrides justify-content.
|
|
32
|
+
*/
|
|
33
|
+
@customElement("ai-stack")
|
|
34
|
+
export class AiStack extends LitElement {
|
|
35
|
+
/** Flex direction. */
|
|
36
|
+
@property({ reflect: true }) direction: "row" | "column" = "column";
|
|
37
|
+
/** Gap between children. */
|
|
38
|
+
@property({ reflect: true }) gap: "none" | "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" =
|
|
39
|
+
"none";
|
|
40
|
+
/** Cross-axis alignment. */
|
|
41
|
+
@property({ reflect: true }) align: "start" | "center" | "end" | "stretch" | "baseline" =
|
|
42
|
+
"stretch";
|
|
43
|
+
/** Main-axis justification. */
|
|
44
|
+
@property({ reflect: true }) justify:
|
|
45
|
+
| "start"
|
|
46
|
+
| "center"
|
|
47
|
+
| "end"
|
|
48
|
+
| "between"
|
|
49
|
+
| "around"
|
|
50
|
+
| "evenly" = "start";
|
|
51
|
+
/** Whether children wrap. */
|
|
52
|
+
@property({ reflect: true, type: Boolean }) wrap = false;
|
|
53
|
+
/** Whether to use inline-flex instead of flex. */
|
|
54
|
+
@property({ reflect: true, type: Boolean }) inline = false;
|
|
55
|
+
|
|
56
|
+
static override styles = css`
|
|
57
|
+
:host {
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: var(--gap, 0);
|
|
62
|
+
align-items: var(--align-items, stretch);
|
|
63
|
+
justify-content: var(--justify-content, flex-start);
|
|
64
|
+
margin: 0;
|
|
65
|
+
padding: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:host([inline]) {
|
|
69
|
+
display: inline-flex;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:host([direction="row"]) {
|
|
73
|
+
flex-direction: row;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:host([wrap]) {
|
|
77
|
+
flex-wrap: wrap;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
*,
|
|
81
|
+
*::before,
|
|
82
|
+
*::after {
|
|
83
|
+
box-sizing: inherit;
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
override render() {
|
|
88
|
+
return html`
|
|
89
|
+
<slot></slot>
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
protected override updated(): void {
|
|
94
|
+
const gap = GAP_MAP[this.gap] ?? "0";
|
|
95
|
+
this.style.setProperty("--gap", `var(--gap, ${gap})`);
|
|
96
|
+
|
|
97
|
+
const alignItems = this.align;
|
|
98
|
+
this.style.setProperty("--align-items", `var(--align-items, ${alignItems})`);
|
|
99
|
+
|
|
100
|
+
const justifyContent = JUSTIFY_MAP[this.justify] ?? "flex-start";
|
|
101
|
+
this.style.setProperty("--justify-content", `var(--justify-content, ${justifyContent})`);
|
|
102
|
+
|
|
103
|
+
// force reflow — gap needs explicit value
|
|
104
|
+
void this.style.direction;
|
|
105
|
+
this.style.gap = `var(--gap, ${gap})`;
|
|
106
|
+
this.style.alignItems = `var(--align-items, ${alignItems})`;
|
|
107
|
+
this.style.justifyContent = `var(--justify-content, ${justifyContent})`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
declare global {
|
|
112
|
+
interface HTMLElementTagNameMap {
|
|
113
|
+
"ai-stack": AiStack;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
|
|
4
|
+
const SIZE_MAP: Record<string, string> = {
|
|
5
|
+
xs: "6px",
|
|
6
|
+
sm: "8px",
|
|
7
|
+
md: "12px",
|
|
8
|
+
lg: "16px",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Status dot/icon indicator.
|
|
13
|
+
*
|
|
14
|
+
* @slot - Optional custom icon/content.
|
|
15
|
+
*
|
|
16
|
+
* @cssprop --size - Indicator size.
|
|
17
|
+
* @cssprop --color - Indicator color.
|
|
18
|
+
* @cssprop --idle-color - Idle color.
|
|
19
|
+
* @cssprop --running-color - Running color.
|
|
20
|
+
* @cssprop --success-color - Success color.
|
|
21
|
+
* @cssprop --error-color - Error color.
|
|
22
|
+
* @cssprop --cancelled-color - Cancelled color.
|
|
23
|
+
* @cssprop --pulse-duration - Running pulse duration. Default: 1.5s
|
|
24
|
+
*/
|
|
25
|
+
@customElement("ai-status")
|
|
26
|
+
export class AiStatus extends LitElement {
|
|
27
|
+
/** Current status state. */
|
|
28
|
+
@property({ reflect: true }) state:
|
|
29
|
+
| "idle"
|
|
30
|
+
| "running"
|
|
31
|
+
| "success"
|
|
32
|
+
| "error"
|
|
33
|
+
| "cancelled"
|
|
34
|
+
| "unknown" = "unknown";
|
|
35
|
+
/** Size preset. */
|
|
36
|
+
@property({ reflect: true }) size: "xs" | "sm" | "md" | "lg" = "md";
|
|
37
|
+
/** Display variant — dot or icon. */
|
|
38
|
+
@property({ reflect: true }) variant: "dot" | "icon" = "dot";
|
|
39
|
+
|
|
40
|
+
static override styles = css`
|
|
41
|
+
:host {
|
|
42
|
+
box-sizing: border-box;
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
margin: 0;
|
|
47
|
+
padding: 0;
|
|
48
|
+
color: var(--color, var(--ai-color-text-muted, rgba(128, 128, 128, 0.7)));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.dot {
|
|
52
|
+
width: var(--size, 12px);
|
|
53
|
+
height: var(--size, 12px);
|
|
54
|
+
border-radius: 50%;
|
|
55
|
+
background: var(--color, var(--ai-color-text-muted, rgba(128, 128, 128, 0.7)));
|
|
56
|
+
flex-shrink: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:host([state="running"]) .dot {
|
|
60
|
+
animation: pulse var(--pulse-duration, 1.5s) ease-in-out infinite;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@keyframes pulse {
|
|
64
|
+
0%,
|
|
65
|
+
100% {
|
|
66
|
+
transform: scale(1);
|
|
67
|
+
opacity: 1;
|
|
68
|
+
}
|
|
69
|
+
50% {
|
|
70
|
+
transform: scale(1.2);
|
|
71
|
+
opacity: 0.7;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.icon-wrapper {
|
|
76
|
+
width: var(--size, 12px);
|
|
77
|
+
height: var(--size, 12px);
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.fallback-icon {
|
|
84
|
+
width: 100%;
|
|
85
|
+
height: 100%;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
*,
|
|
89
|
+
*::before,
|
|
90
|
+
*::after {
|
|
91
|
+
box-sizing: inherit;
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
override render() {
|
|
96
|
+
if (this.variant === "icon") {
|
|
97
|
+
return html`
|
|
98
|
+
<span class="icon-wrapper" part="icon">
|
|
99
|
+
<slot>
|
|
100
|
+
<svg class="fallback-icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
101
|
+
${this.getFallbackIconPath()}
|
|
102
|
+
</svg>
|
|
103
|
+
</slot>
|
|
104
|
+
</span>
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return html`
|
|
109
|
+
<span class="dot" part="dot"></span>
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
protected override updated(): void {
|
|
114
|
+
const size = SIZE_MAP[this.size] ?? "12px";
|
|
115
|
+
this.style.setProperty("--size", `var(--size, ${size})`);
|
|
116
|
+
|
|
117
|
+
const stateColor = this.getStateColor();
|
|
118
|
+
this.style.setProperty("--color", `var(--color, ${stateColor})`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private getStateColor(): string {
|
|
122
|
+
switch (this.state) {
|
|
123
|
+
case "idle":
|
|
124
|
+
return "var(--idle-color, var(--ai-color-text-muted, rgba(128, 128, 128, 0.4)))";
|
|
125
|
+
case "running":
|
|
126
|
+
return "var(--running-color, var(--ai-color-accent, #4a90d9))";
|
|
127
|
+
case "success":
|
|
128
|
+
return "var(--success-color, var(--ai-color-success, #2ea043))";
|
|
129
|
+
case "error":
|
|
130
|
+
return "var(--error-color, var(--ai-color-error, #e33e33))";
|
|
131
|
+
case "cancelled":
|
|
132
|
+
return "var(--cancelled-color, var(--ai-color-text-muted, rgba(128, 128, 128, 0.5)))";
|
|
133
|
+
default:
|
|
134
|
+
return "var(--ai-color-text-muted, rgba(128, 128, 128, 0.7))";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private getFallbackIconPath(): string {
|
|
139
|
+
switch (this.state) {
|
|
140
|
+
case "success":
|
|
141
|
+
return html`
|
|
142
|
+
<path
|
|
143
|
+
d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
|
|
144
|
+
/>
|
|
145
|
+
`.strings.join("");
|
|
146
|
+
case "error":
|
|
147
|
+
return html`
|
|
148
|
+
<path
|
|
149
|
+
d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"
|
|
150
|
+
/>
|
|
151
|
+
`.strings.join("");
|
|
152
|
+
case "running":
|
|
153
|
+
return html`
|
|
154
|
+
<path
|
|
155
|
+
d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9-3.25v2.5h2.5a.75.75 0 010 1.5H9v2.5a.75.75 0 01-1.5 0v-2.5H5a.75.75 0 010-1.5h2.5v-2.5a.75.75 0 011.5 0z"
|
|
156
|
+
/>
|
|
157
|
+
`.strings.join("");
|
|
158
|
+
default:
|
|
159
|
+
return html`
|
|
160
|
+
<circle cx="8" cy="8" r="4" />
|
|
161
|
+
`.strings.join("");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
declare global {
|
|
167
|
+
interface HTMLElementTagNameMap {
|
|
168
|
+
"ai-status": AiStatus;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
|
|
4
|
+
const RADIUS_MAP: Record<string, string> = {
|
|
5
|
+
none: "0",
|
|
6
|
+
sm: "0.375rem",
|
|
7
|
+
md: "0.625rem",
|
|
8
|
+
lg: "0.875rem",
|
|
9
|
+
pill: "999px",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Themed container/surface.
|
|
14
|
+
*
|
|
15
|
+
* @slot - Surface content.
|
|
16
|
+
*
|
|
17
|
+
* @cssprop --background-color - Surface background.
|
|
18
|
+
* @cssprop --text-color - Surface text color.
|
|
19
|
+
* @cssprop --border-color - Border color.
|
|
20
|
+
* @cssprop --border-width - Border width.
|
|
21
|
+
* @cssprop --border-radius - Border radius.
|
|
22
|
+
* @cssprop --shadow - Box shadow.
|
|
23
|
+
* @cssprop --hover-background-color - Hover background when interactive.
|
|
24
|
+
* @cssprop --focus-ring - Focus ring for slotted/native focus styling.
|
|
25
|
+
*/
|
|
26
|
+
@customElement("ai-surface")
|
|
27
|
+
export class AiSurface extends LitElement {
|
|
28
|
+
/** Visual variant. */
|
|
29
|
+
@property({ reflect: true }) variant: "flat" | "outlined" | "raised" | "sunken" = "flat";
|
|
30
|
+
/** Semantic color tone. */
|
|
31
|
+
@property({ reflect: true }) tone:
|
|
32
|
+
| "neutral"
|
|
33
|
+
| "accent"
|
|
34
|
+
| "success"
|
|
35
|
+
| "warning"
|
|
36
|
+
| "error"
|
|
37
|
+
| "info" = "neutral";
|
|
38
|
+
/** Border radius preset. */
|
|
39
|
+
@property({ reflect: true }) radius: "none" | "sm" | "md" | "lg" | "pill" = "md";
|
|
40
|
+
/** Whether the surface is clickable/hoverable. */
|
|
41
|
+
@property({ reflect: true, type: Boolean }) interactive = false;
|
|
42
|
+
|
|
43
|
+
static override styles = css`
|
|
44
|
+
:host {
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
display: block;
|
|
47
|
+
margin: 0;
|
|
48
|
+
padding: 0;
|
|
49
|
+
border-radius: var(--border-radius, 0.625rem);
|
|
50
|
+
background: var(--background-color, transparent);
|
|
51
|
+
color: var(--text-color, inherit);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:host([variant="outlined"]) {
|
|
55
|
+
border: var(--border-width, 1px) solid
|
|
56
|
+
var(--border-color, var(--ai-color-border, rgba(128, 128, 128, 0.2)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
:host([variant="raised"]) {
|
|
60
|
+
box-shadow: var(--shadow, 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.08));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:host([variant="sunken"]) {
|
|
64
|
+
box-shadow: var(--shadow, inset 0 1px 3px rgba(0, 0, 0, 0.12));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:host([interactive]) {
|
|
68
|
+
cursor: pointer;
|
|
69
|
+
transition: background 0.15s ease;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:host([interactive]:hover) {
|
|
73
|
+
background: var(--hover-background-color, var(--background-color, rgba(128, 128, 128, 0.05)));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:host([interactive]:focus-visible) {
|
|
77
|
+
outline: var(--focus-ring, 2px solid var(--ai-color-accent, #4a90d9));
|
|
78
|
+
outline-offset: 2px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
*,
|
|
82
|
+
*::before,
|
|
83
|
+
*::after {
|
|
84
|
+
box-sizing: inherit;
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
override render() {
|
|
89
|
+
return html`
|
|
90
|
+
<slot></slot>
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
protected override updated(): void {
|
|
95
|
+
const radius = RADIUS_MAP[this.radius] ?? "0.625rem";
|
|
96
|
+
this.style.setProperty("--border-radius", `var(--border-radius, ${radius})`);
|
|
97
|
+
|
|
98
|
+
const toneColors = this.getToneColors();
|
|
99
|
+
this.style.setProperty("--background-color", `var(--background-color, ${toneColors.bg})`);
|
|
100
|
+
this.style.setProperty("--text-color", `var(--text-color, ${toneColors.text})`);
|
|
101
|
+
this.style.setProperty("--border-color", `var(--border-color, ${toneColors.border})`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private getToneColors(): { bg: string; text: string; border: string } {
|
|
105
|
+
switch (this.tone) {
|
|
106
|
+
case "accent":
|
|
107
|
+
return {
|
|
108
|
+
bg: "rgba(74, 144, 217, 0.1)",
|
|
109
|
+
text: "var(--ai-color-accent, #4a90d9)",
|
|
110
|
+
border: "rgba(74, 144, 217, 0.3)",
|
|
111
|
+
};
|
|
112
|
+
case "success":
|
|
113
|
+
return {
|
|
114
|
+
bg: "rgba(46, 160, 67, 0.1)",
|
|
115
|
+
text: "var(--ai-color-success, #2ea043)",
|
|
116
|
+
border: "rgba(46, 160, 67, 0.3)",
|
|
117
|
+
};
|
|
118
|
+
case "warning":
|
|
119
|
+
return {
|
|
120
|
+
bg: "rgba(210, 153, 34, 0.1)",
|
|
121
|
+
text: "var(--ai-color-warning, #d29922)",
|
|
122
|
+
border: "rgba(210, 153, 34, 0.3)",
|
|
123
|
+
};
|
|
124
|
+
case "error":
|
|
125
|
+
return {
|
|
126
|
+
bg: "rgba(227, 62, 51, 0.1)",
|
|
127
|
+
text: "var(--ai-color-error, #e33e33)",
|
|
128
|
+
border: "rgba(227, 62, 51, 0.3)",
|
|
129
|
+
};
|
|
130
|
+
case "info":
|
|
131
|
+
return {
|
|
132
|
+
bg: "rgba(80, 160, 220, 0.1)",
|
|
133
|
+
text: "var(--ai-color-info, #50a0dc)",
|
|
134
|
+
border: "rgba(80, 160, 220, 0.3)",
|
|
135
|
+
};
|
|
136
|
+
default:
|
|
137
|
+
return {
|
|
138
|
+
bg: "transparent",
|
|
139
|
+
text: "inherit",
|
|
140
|
+
border: "var(--ai-color-border, rgba(128, 128, 128, 0.2))",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
declare global {
|
|
147
|
+
interface HTMLElementTagNameMap {
|
|
148
|
+
"ai-surface": AiSurface;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
|
|
4
|
+
const SIZE_MAP: Record<string, string> = {
|
|
5
|
+
caption: "0.75rem",
|
|
6
|
+
meta: "0.8125rem",
|
|
7
|
+
ui: "0.875rem",
|
|
8
|
+
body: "1rem",
|
|
9
|
+
title: "1.125rem",
|
|
10
|
+
display: "1.375rem",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const WEIGHT_MAP: Record<string, number> = {
|
|
14
|
+
normal: 400,
|
|
15
|
+
medium: 500,
|
|
16
|
+
semibold: 600,
|
|
17
|
+
bold: 700,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Typography primitive.
|
|
22
|
+
*
|
|
23
|
+
* @slot - Text/inline content.
|
|
24
|
+
*
|
|
25
|
+
* @cssprop --text-color - Text color.
|
|
26
|
+
* @cssprop --font-family - Font family.
|
|
27
|
+
* @cssprop --font-family-mono - Monospace font family.
|
|
28
|
+
* @cssprop --font-size - Font size.
|
|
29
|
+
* @cssprop --font-weight - Font weight.
|
|
30
|
+
* @cssprop --line-height - Line height.
|
|
31
|
+
* @cssprop --letter-spacing - Letter spacing.
|
|
32
|
+
*/
|
|
33
|
+
@customElement("ai-text")
|
|
34
|
+
export class AiText extends LitElement {
|
|
35
|
+
/** Size preset. */
|
|
36
|
+
@property({ reflect: true }) size: "caption" | "meta" | "ui" | "body" | "title" | "display" =
|
|
37
|
+
"body";
|
|
38
|
+
/** Font weight preset. */
|
|
39
|
+
@property({ reflect: true }) weight: "normal" | "medium" | "semibold" | "bold" = "normal";
|
|
40
|
+
/** Semantic color tone. */
|
|
41
|
+
@property({ reflect: true }) tone:
|
|
42
|
+
| "default"
|
|
43
|
+
| "muted"
|
|
44
|
+
| "accent"
|
|
45
|
+
| "success"
|
|
46
|
+
| "warning"
|
|
47
|
+
| "error" = "default";
|
|
48
|
+
/** Whether to use monospace font. */
|
|
49
|
+
@property({ reflect: true, type: Boolean }) mono = false;
|
|
50
|
+
/** Whether to truncate with ellipsis. */
|
|
51
|
+
@property({ reflect: true, type: Boolean }) truncate = false;
|
|
52
|
+
/** Whether to use inline display. */
|
|
53
|
+
@property({ reflect: true, type: Boolean }) inline = false;
|
|
54
|
+
|
|
55
|
+
static override styles = css`
|
|
56
|
+
:host {
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
display: block;
|
|
59
|
+
margin: 0;
|
|
60
|
+
padding: 0;
|
|
61
|
+
font-size: var(--font-size, 1rem);
|
|
62
|
+
font-weight: var(--font-weight, 400);
|
|
63
|
+
line-height: var(--line-height, 1.5);
|
|
64
|
+
letter-spacing: var(--letter-spacing, normal);
|
|
65
|
+
color: var(--text-color, inherit);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:host([inline]) {
|
|
69
|
+
display: inline-flex;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
:host([mono]) {
|
|
73
|
+
font-family: var(
|
|
74
|
+
--font-family-mono,
|
|
75
|
+
ui-monospace,
|
|
76
|
+
"Cascadia Code",
|
|
77
|
+
"Source Code Pro",
|
|
78
|
+
Menlo,
|
|
79
|
+
Consolas,
|
|
80
|
+
"DejaVu Sans Mono",
|
|
81
|
+
monospace
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
:host(:not([mono])) {
|
|
86
|
+
font-family: var(--font-family, inherit);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
:host([truncate]) {
|
|
90
|
+
text-overflow: ellipsis;
|
|
91
|
+
overflow: hidden;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
*,
|
|
96
|
+
*::before,
|
|
97
|
+
*::after {
|
|
98
|
+
box-sizing: inherit;
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
override render() {
|
|
103
|
+
return html`
|
|
104
|
+
<slot></slot>
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected override updated(): void {
|
|
109
|
+
const size = SIZE_MAP[this.size] ?? "1rem";
|
|
110
|
+
const weight = WEIGHT_MAP[this.weight] ?? 400;
|
|
111
|
+
|
|
112
|
+
this.style.setProperty("--font-size", `var(--font-size, ${size})`);
|
|
113
|
+
this.style.setProperty("--font-weight", `var(--font-weight, ${weight})`);
|
|
114
|
+
|
|
115
|
+
const toneColor = this.getToneColor();
|
|
116
|
+
this.style.setProperty("--text-color", `var(--text-color, ${toneColor})`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private getToneColor(): string {
|
|
120
|
+
switch (this.tone) {
|
|
121
|
+
case "muted":
|
|
122
|
+
return "var(--ai-color-text-muted, rgba(128, 128, 128, 0.7))";
|
|
123
|
+
case "accent":
|
|
124
|
+
return "var(--ai-color-accent, #4a90d9)";
|
|
125
|
+
case "success":
|
|
126
|
+
return "var(--ai-color-success, #2ea043)";
|
|
127
|
+
case "warning":
|
|
128
|
+
return "var(--ai-color-warning, #d29922)";
|
|
129
|
+
case "error":
|
|
130
|
+
return "var(--ai-color-error, #e33e33)";
|
|
131
|
+
default:
|
|
132
|
+
return "inherit";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
declare global {
|
|
138
|
+
interface HTMLElementTagNameMap {
|
|
139
|
+
"ai-text": AiText;
|
|
140
|
+
}
|
|
141
|
+
}
|