@xmesh/system-design 0.0.1
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/README.md +472 -0
- package/assets/brand-lockup-dark.svg +9 -0
- package/assets/brand-lockup-light.svg +9 -0
- package/assets/brand-mark.svg +9 -0
- package/colors_and_type.css +11 -0
- package/dist/lit/components/alert/index.css +201 -0
- package/dist/lit/components/alert/index.d.ts +25 -0
- package/dist/lit/components/alert/index.js +191 -0
- package/dist/lit/components/app-bar/index.css +80 -0
- package/dist/lit/components/app-bar/index.d.ts +19 -0
- package/dist/lit/components/app-bar/index.js +120 -0
- package/dist/lit/components/artifact/index.css +166 -0
- package/dist/lit/components/artifact/index.d.ts +37 -0
- package/dist/lit/components/artifact/index.js +294 -0
- package/dist/lit/components/autocomplete/index.css +171 -0
- package/dist/lit/components/autocomplete/index.d.ts +47 -0
- package/dist/lit/components/autocomplete/index.js +404 -0
- package/dist/lit/components/avatar/index.css +62 -0
- package/dist/lit/components/avatar/index.d.ts +19 -0
- package/dist/lit/components/avatar/index.js +112 -0
- package/dist/lit/components/avatar-group/index.css +60 -0
- package/dist/lit/components/avatar-group/index.d.ts +19 -0
- package/dist/lit/components/avatar-group/index.js +97 -0
- package/dist/lit/components/badge/index.css +72 -0
- package/dist/lit/components/badge/index.d.ts +18 -0
- package/dist/lit/components/badge/index.js +115 -0
- package/dist/lit/components/brand-mark/index.css +109 -0
- package/dist/lit/components/brand-mark/index.d.ts +24 -0
- package/dist/lit/components/brand-mark/index.js +116 -0
- package/dist/lit/components/breadcrumbs/index.css +91 -0
- package/dist/lit/components/breadcrumbs/index.d.ts +19 -0
- package/dist/lit/components/breadcrumbs/index.js +104 -0
- package/dist/lit/components/bubble/index.css +182 -0
- package/dist/lit/components/bubble/index.d.ts +72 -0
- package/dist/lit/components/bubble/index.js +617 -0
- package/dist/lit/components/button/index.css +342 -0
- package/dist/lit/components/button/index.d.ts +32 -0
- package/dist/lit/components/button/index.js +202 -0
- package/dist/lit/components/card/index.css +99 -0
- package/dist/lit/components/card/index.d.ts +20 -0
- package/dist/lit/components/card/index.js +133 -0
- package/dist/lit/components/chat/index.css +292 -0
- package/dist/lit/components/chat/index.d.ts +74 -0
- package/dist/lit/components/chat/index.js +589 -0
- package/dist/lit/components/checkbox/index.css +126 -0
- package/dist/lit/components/checkbox/index.d.ts +21 -0
- package/dist/lit/components/checkbox/index.js +138 -0
- package/dist/lit/components/chip/index.css +145 -0
- package/dist/lit/components/chip/index.d.ts +30 -0
- package/dist/lit/components/chip/index.js +230 -0
- package/dist/lit/components/chip-group/index.css +19 -0
- package/dist/lit/components/chip-group/index.d.ts +24 -0
- package/dist/lit/components/chip-group/index.js +171 -0
- package/dist/lit/components/code/index.css +42 -0
- package/dist/lit/components/code/index.d.ts +12 -0
- package/dist/lit/components/code/index.js +68 -0
- package/dist/lit/components/composer/index.css +548 -0
- package/dist/lit/components/composer/index.d.ts +67 -0
- package/dist/lit/components/composer/index.js +713 -0
- package/dist/lit/components/data-table/index.css +166 -0
- package/dist/lit/components/data-table/index.d.ts +55 -0
- package/dist/lit/components/data-table/index.js +390 -0
- package/dist/lit/components/dialog/index.css +124 -0
- package/dist/lit/components/dialog/index.d.ts +24 -0
- package/dist/lit/components/dialog/index.js +199 -0
- package/dist/lit/components/divider/index.css +27 -0
- package/dist/lit/components/divider/index.d.ts +13 -0
- package/dist/lit/components/divider/index.js +67 -0
- package/dist/lit/components/empty-state/index.css +69 -0
- package/dist/lit/components/empty-state/index.d.ts +21 -0
- package/dist/lit/components/empty-state/index.js +123 -0
- package/dist/lit/components/expansion-panel/index.css +120 -0
- package/dist/lit/components/expansion-panel/index.d.ts +22 -0
- package/dist/lit/components/expansion-panel/index.js +174 -0
- package/dist/lit/components/field/index.css +223 -0
- package/dist/lit/components/field/index.d.ts +106 -0
- package/dist/lit/components/field/index.js +388 -0
- package/dist/lit/components/file-input/index.css +257 -0
- package/dist/lit/components/file-input/index.d.ts +30 -0
- package/dist/lit/components/file-input/index.js +298 -0
- package/dist/lit/components/form/index.css +29 -0
- package/dist/lit/components/form/index.d.ts +38 -0
- package/dist/lit/components/form/index.js +192 -0
- package/dist/lit/components/grid/index.css +53 -0
- package/dist/lit/components/grid/index.d.ts +14 -0
- package/dist/lit/components/grid/index.js +82 -0
- package/dist/lit/components/kbd/index.css +35 -0
- package/dist/lit/components/kbd/index.d.ts +11 -0
- package/dist/lit/components/kbd/index.js +43 -0
- package/dist/lit/components/list/index.css +15 -0
- package/dist/lit/components/list/index.d.ts +28 -0
- package/dist/lit/components/list/index.js +188 -0
- package/dist/lit/components/list-item/index.css +119 -0
- package/dist/lit/components/list-item/index.d.ts +20 -0
- package/dist/lit/components/list-item/index.js +127 -0
- package/dist/lit/components/menu/index.css +94 -0
- package/dist/lit/components/menu/index.d.ts +47 -0
- package/dist/lit/components/menu/index.js +386 -0
- package/dist/lit/components/navigation-drawer/index.css +114 -0
- package/dist/lit/components/navigation-drawer/index.d.ts +29 -0
- package/dist/lit/components/navigation-drawer/index.js +218 -0
- package/dist/lit/components/overlay/index.css +171 -0
- package/dist/lit/components/overlay/index.d.ts +65 -0
- package/dist/lit/components/overlay/index.js +566 -0
- package/dist/lit/components/pagination/index.css +102 -0
- package/dist/lit/components/pagination/index.d.ts +22 -0
- package/dist/lit/components/pagination/index.js +184 -0
- package/dist/lit/components/primitives/index.css +504 -0
- package/dist/lit/components/primitives/index.d.ts +25 -0
- package/dist/lit/components/primitives/index.js +283 -0
- package/dist/lit/components/progress/index.css +143 -0
- package/dist/lit/components/progress/index.d.ts +23 -0
- package/dist/lit/components/progress/index.js +180 -0
- package/dist/lit/components/radio-group/index.css +178 -0
- package/dist/lit/components/radio-group/index.d.ts +35 -0
- package/dist/lit/components/radio-group/index.js +292 -0
- package/dist/lit/components/select/index.css +151 -0
- package/dist/lit/components/select/index.d.ts +50 -0
- package/dist/lit/components/select/index.js +390 -0
- package/dist/lit/components/sidebar-item/index.css +133 -0
- package/dist/lit/components/sidebar-item/index.d.ts +20 -0
- package/dist/lit/components/sidebar-item/index.js +105 -0
- package/dist/lit/components/skeleton/index.css +81 -0
- package/dist/lit/components/skeleton/index.d.ts +19 -0
- package/dist/lit/components/skeleton/index.js +119 -0
- package/dist/lit/components/slider/index.css +171 -0
- package/dist/lit/components/slider/index.d.ts +36 -0
- package/dist/lit/components/slider/index.js +302 -0
- package/dist/lit/components/snackbar/index.css +279 -0
- package/dist/lit/components/snackbar/index.d.ts +33 -0
- package/dist/lit/components/snackbar/index.js +195 -0
- package/dist/lit/components/stack/index.css +41 -0
- package/dist/lit/components/stack/index.d.ts +20 -0
- package/dist/lit/components/stack/index.js +103 -0
- package/dist/lit/components/switch/index.css +126 -0
- package/dist/lit/components/switch/index.d.ts +17 -0
- package/dist/lit/components/switch/index.js +116 -0
- package/dist/lit/components/table/index.css +85 -0
- package/dist/lit/components/table/index.d.ts +25 -0
- package/dist/lit/components/table/index.js +139 -0
- package/dist/lit/components/tabs/index.css +116 -0
- package/dist/lit/components/tabs/index.d.ts +49 -0
- package/dist/lit/components/tabs/index.js +320 -0
- package/dist/lit/components/text-field/index.css +90 -0
- package/dist/lit/components/text-field/index.d.ts +17 -0
- package/dist/lit/components/text-field/index.js +101 -0
- package/dist/lit/components/textarea/index.css +55 -0
- package/dist/lit/components/textarea/index.d.ts +26 -0
- package/dist/lit/components/textarea/index.js +124 -0
- package/dist/lit/components/tooltip/index.css +37 -0
- package/dist/lit/components/tooltip/index.d.ts +31 -0
- package/dist/lit/components/tooltip/index.js +196 -0
- package/dist/lit/components/validation/index.css +386 -0
- package/dist/lit/components/validation/index.d.ts +45 -0
- package/dist/lit/components/validation/index.js +318 -0
- package/dist/lit/index.d.ts +50 -0
- package/dist/lit/index.js +59 -0
- package/package.json +81 -0
- package/styles/README.md +346 -0
- package/styles/_elevation.css +24 -0
- package/styles/_fonts.css +6 -0
- package/styles/_layout.css +37 -0
- package/styles/_primitives.css +154 -0
- package/styles/_scroll.css +75 -0
- package/styles/_semantic.css +146 -0
- package/styles/_space.css +61 -0
- package/styles/_type.css +139 -0
- package/styles/_xmesh-extensions.css +232 -0
- package/styles/index.css +44 -0
- package/styles/md3/_color.css +102 -0
- package/styles/md3/_elevation.css +26 -0
- package/styles/md3/_motion.css +35 -0
- package/styles/md3/_shape.css +22 -0
- package/styles/md3/_state.css +22 -0
- package/styles/md3/_type.css +111 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Artifact — Claude-style side-panel viewer.
|
|
3
|
+
|
|
4
|
+
Canvas-surface panel that lives in the artifact
|
|
5
|
+
column of chat.html, or standalone in
|
|
6
|
+
preview/artifact.html. Header (provider chip +
|
|
7
|
+
segmented eye/code toggle + title/sub +
|
|
8
|
+
action cluster) over a scrollable body.
|
|
9
|
+
|
|
10
|
+
The panel rides `surface-container-high` in both
|
|
11
|
+
themes — same family as the chat shell's sidebar/
|
|
12
|
+
topbar. Surface-rooted means text tokens come
|
|
13
|
+
from `on-surface*` (POLICIES rule 7a). The
|
|
14
|
+
primitives flip in light theme via
|
|
15
|
+
_primitives.css, so a single set of rules covers
|
|
16
|
+
both modes. Mirrors the .bubble.bot pattern.
|
|
17
|
+
============================================ */
|
|
18
|
+
|
|
19
|
+
.artifact {
|
|
20
|
+
background: var(--md-sys-color-surface-container-high);
|
|
21
|
+
border: 0;
|
|
22
|
+
border-left: 1px solid var(--md-sys-color-outline);
|
|
23
|
+
border-radius: 0;
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
min-height: 0;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
color: var(--md-sys-color-on-surface);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* ---------- Head ----------
|
|
32
|
+
Padding + min-height match preview/chat.html `.top` so the chat
|
|
33
|
+
topbar and artifact head render at identical heights when both
|
|
34
|
+
are visible. */
|
|
35
|
+
.artifact__head {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: var(--s-3);
|
|
39
|
+
padding: var(--s-3) var(--s-5);
|
|
40
|
+
min-height: 56px;
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
/* Hairline seam so the header reads as a band over the body instead of
|
|
44
|
+
floating; outline-variant is the soft internal divider weight. */
|
|
45
|
+
border-bottom: 1px solid var(--md-sys-color-outline-variant);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.artifact__head-left {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
gap: var(--s-2);
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.artifact__head-mid {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
gap: var(--s-1);
|
|
59
|
+
min-width: 0;
|
|
60
|
+
flex: 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.artifact__head-right {
|
|
64
|
+
display: inline-flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: var(--s-1-5);
|
|
67
|
+
flex-shrink: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.artifact__title {
|
|
71
|
+
font: 600 15px/1.2 var(--md-sys-typescale-body-large-font);
|
|
72
|
+
color: var(--md-sys-color-on-surface);
|
|
73
|
+
letter-spacing: -0.01em;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
text-overflow: ellipsis;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.artifact__sub {
|
|
80
|
+
font: 400 11px/1 var(--xm-typescale-mono-font);
|
|
81
|
+
color: var(--xm-color-on-surface-soft);
|
|
82
|
+
font-feature-settings: "tnum";
|
|
83
|
+
display: inline-flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: var(--s-1-5);
|
|
86
|
+
flex-wrap: nowrap;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
}
|
|
89
|
+
.artifact__sub > span { white-space: nowrap; }
|
|
90
|
+
/* Inline middot dividers come from the .ds-sep primitive (primitives/index.css);
|
|
91
|
+
no per-component override needed. */
|
|
92
|
+
|
|
93
|
+
/* ---------- View toggle (eye / code) ---------- */
|
|
94
|
+
.artifact__view-toggle {
|
|
95
|
+
display: inline-flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
padding: var(--s-0-5);
|
|
98
|
+
background: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
|
|
99
|
+
border-radius: 8px;
|
|
100
|
+
gap: var(--s-0-5);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ---------- Body ---------- */
|
|
104
|
+
.artifact__body {
|
|
105
|
+
flex: 1;
|
|
106
|
+
min-height: 0;
|
|
107
|
+
overflow-y: auto;
|
|
108
|
+
scrollbar-gutter: stable;
|
|
109
|
+
/* Horizontal inset matches .artifact__head (--s-5) so the title and the
|
|
110
|
+
first line of body copy share one left edge. */
|
|
111
|
+
padding: var(--s-5) var(--s-5) var(--s-6);
|
|
112
|
+
font:
|
|
113
|
+
var(--md-sys-typescale-body-large-weight)
|
|
114
|
+
var(--md-sys-typescale-body-large-size) /
|
|
115
|
+
var(--md-sys-typescale-body-large-line-height)
|
|
116
|
+
var(--md-sys-typescale-body-large-font);
|
|
117
|
+
color: var(--md-sys-color-on-surface);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.artifact__body :where(h1, h2, h3, h4) {
|
|
121
|
+
color: var(--md-sys-color-on-surface);
|
|
122
|
+
letter-spacing: -0.01em;
|
|
123
|
+
margin: var(--s-4-5) 0 var(--s-2);
|
|
124
|
+
}
|
|
125
|
+
.artifact__body h1 { font: 600 20px/1.25 var(--md-sys-typescale-body-large-font); }
|
|
126
|
+
.artifact__body h2 { font: 600 16px/1.3 var(--md-sys-typescale-body-large-font); }
|
|
127
|
+
.artifact__body h3 { font: 600 14px/1.3 var(--md-sys-typescale-body-large-font); }
|
|
128
|
+
.artifact__body :where(h1, h2, h3, h4):first-child { margin-top: 0; }
|
|
129
|
+
|
|
130
|
+
.artifact__body p { margin: 0 0 var(--s-3); }
|
|
131
|
+
.artifact__body ul,
|
|
132
|
+
.artifact__body ol { margin: 0 0 var(--s-3); padding-left: var(--s-5-5); }
|
|
133
|
+
.artifact__body li { margin: var(--s-1) 0; }
|
|
134
|
+
.artifact__body strong { color: var(--md-sys-color-on-surface); font-weight: 600; }
|
|
135
|
+
.artifact__body em { font-style: italic; color: var(--md-sys-color-on-surface); }
|
|
136
|
+
.artifact__body code {
|
|
137
|
+
font: 500 12.5px/1.4 var(--xm-typescale-mono-font);
|
|
138
|
+
padding: 0;
|
|
139
|
+
background: transparent;
|
|
140
|
+
color: var(--md-sys-color-on-surface);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.artifact__body pre {
|
|
144
|
+
font: 500 12.5px/1.55 var(--xm-typescale-mono-font);
|
|
145
|
+
color: var(--md-sys-color-on-surface);
|
|
146
|
+
background: color-mix(in oklab, var(--md-sys-color-on-surface) 6%, transparent);
|
|
147
|
+
border: 1px solid color-mix(in oklab, var(--md-sys-color-on-surface) 10%, transparent);
|
|
148
|
+
border-radius: var(--md-sys-shape-corner-extra-small);
|
|
149
|
+
padding: var(--s-3) var(--s-4);
|
|
150
|
+
margin: var(--s-3) 0;
|
|
151
|
+
white-space: pre-wrap;
|
|
152
|
+
overflow-x: auto;
|
|
153
|
+
font-feature-settings: "tnum";
|
|
154
|
+
}
|
|
155
|
+
.artifact__body pre code {
|
|
156
|
+
font: inherit;
|
|
157
|
+
color: inherit;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.artifact__body blockquote {
|
|
161
|
+
margin: var(--s-3) 0;
|
|
162
|
+
padding-left: var(--s-3);
|
|
163
|
+
border-left: 2px solid color-mix(in oklab, var(--md-sys-color-on-surface) 30%, transparent);
|
|
164
|
+
color: var(--xm-color-on-surface-soft);
|
|
165
|
+
font-style: italic;
|
|
166
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
import "../button/index.js";
|
|
4
|
+
type ArtifactView = "preview" | "code";
|
|
5
|
+
declare class XmArtifact extends LitElement {
|
|
6
|
+
title: string;
|
|
7
|
+
sub: string[] | null;
|
|
8
|
+
subCsv: string;
|
|
9
|
+
view: ArtifactView;
|
|
10
|
+
showRefresh: boolean;
|
|
11
|
+
showClose: boolean;
|
|
12
|
+
private _bodies;
|
|
13
|
+
private _uid;
|
|
14
|
+
createRenderRoot(): HTMLElement | DocumentFragment;
|
|
15
|
+
connectedCallback(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Imperatively set the preview/code body nodes from a host. Replaces any
|
|
18
|
+
* authored or previously-set bodies. Used by <xm-chat-shell> to hand the
|
|
19
|
+
* panel its slot content; the chat shell can't author them as direct
|
|
20
|
+
* children of the lit-rendered <xm-artifact> tag without the snapshot
|
|
21
|
+
* timing race that this method sidesteps.
|
|
22
|
+
*/
|
|
23
|
+
setBodies(preview: ChildNode[], code: ChildNode[]): void;
|
|
24
|
+
private _setView;
|
|
25
|
+
private _onTabKeydown;
|
|
26
|
+
private _onRefresh;
|
|
27
|
+
private _onClose;
|
|
28
|
+
private _subItems;
|
|
29
|
+
render(): TemplateResult;
|
|
30
|
+
updated(): void;
|
|
31
|
+
}
|
|
32
|
+
declare global {
|
|
33
|
+
interface HTMLElementTagNameMap {
|
|
34
|
+
"xm-artifact": XmArtifact;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/*
|
|
2
|
+
artifact/index.ts — Lit port of components/artifact/index.jsx.
|
|
3
|
+
|
|
4
|
+
<xm-artifact> — Claude-style side-panel viewer.
|
|
5
|
+
|
|
6
|
+
Authoring:
|
|
7
|
+
<xm-artifact title="..." sub-csv="Document,MD" show-refresh show-close>
|
|
8
|
+
<div data-view="preview"> ...html body for preview... </div>
|
|
9
|
+
<div data-view="code"> ...html body for code... </div>
|
|
10
|
+
</xm-artifact>
|
|
11
|
+
|
|
12
|
+
The component shows whichever child matches the active `view`.
|
|
13
|
+
Children without a data-view attribute are treated as `preview`
|
|
14
|
+
(single-body case).
|
|
15
|
+
|
|
16
|
+
Properties:
|
|
17
|
+
title string
|
|
18
|
+
sub string[] — preferred (set via JS)
|
|
19
|
+
sub-csv string — comma-separated convenience
|
|
20
|
+
attribute for static HTML
|
|
21
|
+
view "preview" | "code" — controlled when host listens
|
|
22
|
+
for `view-change`; otherwise
|
|
23
|
+
local state from initial value
|
|
24
|
+
showRefresh boolean — render the refresh button
|
|
25
|
+
showClose boolean — render the close button
|
|
26
|
+
|
|
27
|
+
Events:
|
|
28
|
+
view-change detail: { view: "preview" | "code" }
|
|
29
|
+
refresh
|
|
30
|
+
close
|
|
31
|
+
|
|
32
|
+
Light DOM. components/artifact/index.css + components/button/index.css +
|
|
33
|
+
components/primitives/index.css must be loaded in the host page.
|
|
34
|
+
*/
|
|
35
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
36
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
37
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
38
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
39
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
40
|
+
};
|
|
41
|
+
import { LitElement, html, svg, nothing } from "lit";
|
|
42
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
43
|
+
// Side-effect import: registers <xm-button> for use in the head toolbar.
|
|
44
|
+
import "../button/index.js";
|
|
45
|
+
const ICON_EYE = (size = 14) => svg `
|
|
46
|
+
<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none"
|
|
47
|
+
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
|
|
48
|
+
stroke-linejoin="round" class="ds-icon">
|
|
49
|
+
<path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z" />
|
|
50
|
+
<circle cx="12" cy="12" r="3" />
|
|
51
|
+
</svg>
|
|
52
|
+
`;
|
|
53
|
+
const ICON_CODE = (size = 14) => svg `
|
|
54
|
+
<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none"
|
|
55
|
+
stroke="currentColor" stroke-width="1.9" stroke-linecap="round"
|
|
56
|
+
stroke-linejoin="round" class="ds-icon">
|
|
57
|
+
<polyline points="16 18 22 12 16 6" />
|
|
58
|
+
<polyline points="8 6 2 12 8 18" />
|
|
59
|
+
</svg>
|
|
60
|
+
`;
|
|
61
|
+
const ICON_REFRESH = () => svg `
|
|
62
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
63
|
+
stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
|
|
64
|
+
stroke-linejoin="round" class="ds-icon">
|
|
65
|
+
<path d="M21 12a9 9 0 1 1-3-6.7" />
|
|
66
|
+
<path d="M21 4v5h-5" />
|
|
67
|
+
</svg>
|
|
68
|
+
`;
|
|
69
|
+
const ICON_CLOSE = () => svg `
|
|
70
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
|
71
|
+
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
72
|
+
stroke-linejoin="round" class="ds-icon">
|
|
73
|
+
<path d="M6 6l12 12M18 6L6 18" />
|
|
74
|
+
</svg>
|
|
75
|
+
`;
|
|
76
|
+
let artifactInstance = 0;
|
|
77
|
+
let XmArtifact = class XmArtifact extends LitElement {
|
|
78
|
+
constructor() {
|
|
79
|
+
super(...arguments);
|
|
80
|
+
this.title = "";
|
|
81
|
+
this.sub = null;
|
|
82
|
+
this.subCsv = "";
|
|
83
|
+
this.view = "preview";
|
|
84
|
+
this.showRefresh = false;
|
|
85
|
+
this.showClose = false;
|
|
86
|
+
this._bodies = { preview: [], code: [] };
|
|
87
|
+
this._uid = `artifact-${++artifactInstance}`;
|
|
88
|
+
this._setView = (v) => {
|
|
89
|
+
if (this.view === v)
|
|
90
|
+
return;
|
|
91
|
+
// Always update internal state; consumers that prefer "controlled" mode
|
|
92
|
+
// can still listen for view-change to react.
|
|
93
|
+
this.view = v;
|
|
94
|
+
this.dispatchEvent(new CustomEvent("view-change", { detail: { view: v }, bubbles: true, composed: true }));
|
|
95
|
+
};
|
|
96
|
+
this._onTabKeydown = (e) => {
|
|
97
|
+
const k = e.key;
|
|
98
|
+
if (k !== "ArrowLeft" && k !== "ArrowRight" && k !== "Home" && k !== "End")
|
|
99
|
+
return;
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
const next = k === "Home" ? "preview" :
|
|
102
|
+
k === "End" ? "code" :
|
|
103
|
+
this.view === "preview" ? "code" : "preview";
|
|
104
|
+
this._setView(next);
|
|
105
|
+
// Move focus to the newly-selected tab once render lands.
|
|
106
|
+
requestAnimationFrame(() => {
|
|
107
|
+
this.querySelector(`.artifact__view-btn[data-view="${next}"]`)?.focus();
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
this._onRefresh = () => {
|
|
111
|
+
this.dispatchEvent(new CustomEvent("refresh", { bubbles: true, composed: true }));
|
|
112
|
+
};
|
|
113
|
+
this._onClose = () => {
|
|
114
|
+
this.dispatchEvent(new CustomEvent("close", { bubbles: true, composed: true }));
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
createRenderRoot() {
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
connectedCallback() {
|
|
121
|
+
super.connectedCallback();
|
|
122
|
+
// Make the host transparent to the parent's grid/flex layout so the
|
|
123
|
+
// inner <section class="artifact"> behaves as the direct child. This
|
|
124
|
+
// matters for selectors like `.chat-shell__artifact-slot > .artifact`
|
|
125
|
+
// in components/chat/index.css.
|
|
126
|
+
if (!this.style.display)
|
|
127
|
+
this.style.display = "contents";
|
|
128
|
+
// Snapshot authored bodies by data-view; remove from DOM so lit's
|
|
129
|
+
// render() doesn't replace them mid-flight. We re-attach in updated().
|
|
130
|
+
const preview = [];
|
|
131
|
+
const code = [];
|
|
132
|
+
for (const child of Array.from(this.childNodes)) {
|
|
133
|
+
if (child.nodeType !== Node.ELEMENT_NODE) {
|
|
134
|
+
if (child.nodeType === Node.TEXT_NODE && !child.textContent?.trim()) {
|
|
135
|
+
// strip whitespace-only text — they'd otherwise bucket into preview
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
preview.push(child);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const v = child.getAttribute("data-view");
|
|
142
|
+
if (v === "code")
|
|
143
|
+
code.push(child);
|
|
144
|
+
else
|
|
145
|
+
preview.push(child);
|
|
146
|
+
}
|
|
147
|
+
for (const n of [...preview, ...code])
|
|
148
|
+
n.remove();
|
|
149
|
+
this._bodies = { preview, code };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Imperatively set the preview/code body nodes from a host. Replaces any
|
|
153
|
+
* authored or previously-set bodies. Used by <xm-chat-shell> to hand the
|
|
154
|
+
* panel its slot content; the chat shell can't author them as direct
|
|
155
|
+
* children of the lit-rendered <xm-artifact> tag without the snapshot
|
|
156
|
+
* timing race that this method sidesteps.
|
|
157
|
+
*/
|
|
158
|
+
setBodies(preview, code) {
|
|
159
|
+
this._bodies = { preview: preview.slice(), code: code.slice() };
|
|
160
|
+
this.requestUpdate();
|
|
161
|
+
}
|
|
162
|
+
_subItems() {
|
|
163
|
+
if (Array.isArray(this.sub) && this.sub.length)
|
|
164
|
+
return this.sub;
|
|
165
|
+
if (this.subCsv)
|
|
166
|
+
return this.subCsv.split(",").map((s) => s.trim()).filter(Boolean);
|
|
167
|
+
return [];
|
|
168
|
+
}
|
|
169
|
+
render() {
|
|
170
|
+
const sub = this._subItems();
|
|
171
|
+
const v = this.view === "code" ? "code" : "preview";
|
|
172
|
+
const previewTabId = `${this._uid}-tab-preview`;
|
|
173
|
+
const codeTabId = `${this._uid}-tab-code`;
|
|
174
|
+
const bodyId = `${this._uid}-body`;
|
|
175
|
+
return html `
|
|
176
|
+
<section class="artifact" aria-label="${this.title || "Artifact"}">
|
|
177
|
+
<header class="artifact__head">
|
|
178
|
+
<div class="artifact__head-left">
|
|
179
|
+
<div
|
|
180
|
+
class="artifact__view-toggle"
|
|
181
|
+
role="tablist"
|
|
182
|
+
aria-label="Artifact view"
|
|
183
|
+
@keydown=${this._onTabKeydown}
|
|
184
|
+
>
|
|
185
|
+
<xm-button
|
|
186
|
+
id="${previewTabId}"
|
|
187
|
+
variant="ghost"
|
|
188
|
+
size="sm"
|
|
189
|
+
icon-only
|
|
190
|
+
?selected=${v === "preview"}
|
|
191
|
+
role="tab"
|
|
192
|
+
data-view="preview"
|
|
193
|
+
aria-selected="${v === "preview"}"
|
|
194
|
+
aria-controls="${bodyId}"
|
|
195
|
+
tabindex="${v === "preview" ? 0 : -1}"
|
|
196
|
+
class="artifact__view-btn"
|
|
197
|
+
title="Preview"
|
|
198
|
+
@click=${() => this._setView("preview")}
|
|
199
|
+
>${ICON_EYE()}</xm-button>
|
|
200
|
+
<xm-button
|
|
201
|
+
id="${codeTabId}"
|
|
202
|
+
variant="ghost"
|
|
203
|
+
size="sm"
|
|
204
|
+
icon-only
|
|
205
|
+
?selected=${v === "code"}
|
|
206
|
+
role="tab"
|
|
207
|
+
data-view="code"
|
|
208
|
+
aria-selected="${v === "code"}"
|
|
209
|
+
aria-controls="${bodyId}"
|
|
210
|
+
tabindex="${v === "code" ? 0 : -1}"
|
|
211
|
+
class="artifact__view-btn"
|
|
212
|
+
title="Source"
|
|
213
|
+
@click=${() => this._setView("code")}
|
|
214
|
+
>${ICON_CODE()}</xm-button>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<div class="artifact__head-mid">
|
|
219
|
+
${this.title
|
|
220
|
+
? html `<span class="artifact__title">${this.title}</span>`
|
|
221
|
+
: nothing}
|
|
222
|
+
${sub.length
|
|
223
|
+
? html `<span class="artifact__sub">
|
|
224
|
+
${sub.map((s, i) => html `
|
|
225
|
+
${i > 0 ? html `<span class="ds-sep">·</span>` : nothing}
|
|
226
|
+
<span>${s}</span>
|
|
227
|
+
`)}
|
|
228
|
+
</span>`
|
|
229
|
+
: nothing}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div class="artifact__head-right">
|
|
233
|
+
${this.showClose
|
|
234
|
+
? html `<xm-button
|
|
235
|
+
variant="ghost"
|
|
236
|
+
size="sm"
|
|
237
|
+
icon-only
|
|
238
|
+
aria-label="Close artifact"
|
|
239
|
+
title="Close"
|
|
240
|
+
@click=${this._onClose}
|
|
241
|
+
>${ICON_CLOSE()}</xm-button>`
|
|
242
|
+
: nothing}
|
|
243
|
+
</div>
|
|
244
|
+
</header>
|
|
245
|
+
|
|
246
|
+
<div
|
|
247
|
+
id="${bodyId}"
|
|
248
|
+
role="tabpanel"
|
|
249
|
+
aria-labelledby="${v === "code" ? codeTabId : previewTabId}"
|
|
250
|
+
class="artifact__body scroll-canvas"
|
|
251
|
+
data-mount="body"
|
|
252
|
+
></div>
|
|
253
|
+
</section>
|
|
254
|
+
`;
|
|
255
|
+
}
|
|
256
|
+
updated() {
|
|
257
|
+
const host = this.querySelector('[data-mount="body"]');
|
|
258
|
+
if (!host)
|
|
259
|
+
return;
|
|
260
|
+
const v = this.view === "code" ? "code" : "preview";
|
|
261
|
+
const wanted = this._bodies[v] || [];
|
|
262
|
+
// _bodies is the source of truth — the arrays still reference any
|
|
263
|
+
// currently-mounted nodes, since appendChild moves rather than copies.
|
|
264
|
+
// Just clear the host and re-mount the active view.
|
|
265
|
+
while (host.firstChild)
|
|
266
|
+
host.firstChild.remove();
|
|
267
|
+
for (const n of wanted)
|
|
268
|
+
host.appendChild(n);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
__decorate([
|
|
272
|
+
property({ type: String })
|
|
273
|
+
], XmArtifact.prototype, "title", void 0);
|
|
274
|
+
__decorate([
|
|
275
|
+
property({ attribute: false })
|
|
276
|
+
], XmArtifact.prototype, "sub", void 0);
|
|
277
|
+
__decorate([
|
|
278
|
+
property({ type: String, attribute: "sub-csv" })
|
|
279
|
+
], XmArtifact.prototype, "subCsv", void 0);
|
|
280
|
+
__decorate([
|
|
281
|
+
property({ type: String, reflect: true })
|
|
282
|
+
], XmArtifact.prototype, "view", void 0);
|
|
283
|
+
__decorate([
|
|
284
|
+
property({ type: Boolean, attribute: "show-refresh" })
|
|
285
|
+
], XmArtifact.prototype, "showRefresh", void 0);
|
|
286
|
+
__decorate([
|
|
287
|
+
property({ type: Boolean, attribute: "show-close" })
|
|
288
|
+
], XmArtifact.prototype, "showClose", void 0);
|
|
289
|
+
__decorate([
|
|
290
|
+
state()
|
|
291
|
+
], XmArtifact.prototype, "_bodies", void 0);
|
|
292
|
+
XmArtifact = __decorate([
|
|
293
|
+
customElement("xm-artifact")
|
|
294
|
+
], XmArtifact);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
xm-autocomplete — filtering select for XmField (Story 2.4).
|
|
3
|
+
|
|
4
|
+
Same composition as xm-select: XmField (chrome) + xm-overlay (anchored
|
|
5
|
+
listbox). This file styles ONLY the bespoke parts — the text-input control
|
|
6
|
+
row + chevron, the option rows + match highlight, and the inline no-match
|
|
7
|
+
empty-state. Positioning / elevation of the panel surface is owned by
|
|
8
|
+
xm-overlay.
|
|
9
|
+
|
|
10
|
+
Surface & ink (AD-13): control + panel ride the inverse-surface card tier →
|
|
11
|
+
inverse-on-surface ink, on-surface-variant placeholder. The SELECTED option
|
|
12
|
+
tints coral primary-container + on-primary-container ink. The match highlight
|
|
13
|
+
carries coral ink AND an underline so the cue survives grayscale — color is
|
|
14
|
+
never the sole carrier (NFR-15). Coral = selection / match, never severity
|
|
15
|
+
(AD-11).
|
|
16
|
+
|
|
17
|
+
BEM block: `autocomplete`. Registered in scripts/check-bem.sh STRICT_BLOCKS.
|
|
18
|
+
============================================ */
|
|
19
|
+
|
|
20
|
+
/* ---------- Control row — text input + chevron in the field wrapper ---------- */
|
|
21
|
+
.autocomplete__control {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
flex: 1;
|
|
25
|
+
min-width: 0;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* The native <input> is already transparent / full-bleed via the base
|
|
31
|
+
`.field__control input` rule; here only the trailing chevron needs room. */
|
|
32
|
+
.autocomplete__input {
|
|
33
|
+
flex: 1;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.autocomplete__chevron {
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
padding-right: var(--s-3);
|
|
42
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
43
|
+
transition: transform var(--md-sys-motion-duration-short3)
|
|
44
|
+
var(--md-sys-motion-easing-standard);
|
|
45
|
+
}
|
|
46
|
+
.autocomplete__chevron--open {
|
|
47
|
+
transform: rotate(180deg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ---------- Listbox panel content (inside the overlay) ---------- */
|
|
51
|
+
.autocomplete__listbox {
|
|
52
|
+
list-style: none;
|
|
53
|
+
margin: 0;
|
|
54
|
+
padding: 0;
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
gap: var(--s-0-5);
|
|
58
|
+
min-width: 220px;
|
|
59
|
+
max-height: 320px;
|
|
60
|
+
overflow-y: auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.autocomplete__option {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: var(--s-2);
|
|
67
|
+
padding: var(--s-2) var(--s-3);
|
|
68
|
+
border-radius: var(--md-sys-shape-corner-small);
|
|
69
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
70
|
+
font:
|
|
71
|
+
var(--md-sys-typescale-body-medium-weight)
|
|
72
|
+
var(--md-sys-typescale-body-medium-size) /
|
|
73
|
+
var(--md-sys-typescale-body-medium-line-height)
|
|
74
|
+
var(--md-sys-typescale-body-medium-font);
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
user-select: none;
|
|
77
|
+
}
|
|
78
|
+
.autocomplete__option-label {
|
|
79
|
+
flex: 1;
|
|
80
|
+
min-width: 0;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
text-overflow: ellipsis;
|
|
84
|
+
}
|
|
85
|
+
.autocomplete__option-check {
|
|
86
|
+
display: inline-flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
flex-shrink: 0;
|
|
89
|
+
color: var(--md-sys-color-primary);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Match highlight — coral ink + underline (dual cue, NFR-15). Background stays
|
|
93
|
+
transparent so it reads on both the panel ink and the selected coral tint. */
|
|
94
|
+
.autocomplete__mark {
|
|
95
|
+
background: transparent;
|
|
96
|
+
color: var(--md-sys-color-primary);
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
text-decoration: underline;
|
|
99
|
+
text-underline-offset: 2px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* Active (keyboard / hover) — MD3 state layer over the panel ink. */
|
|
103
|
+
.autocomplete__option--active {
|
|
104
|
+
background: color-mix(
|
|
105
|
+
in oklab,
|
|
106
|
+
var(--md-sys-color-inverse-on-surface)
|
|
107
|
+
var(--md-sys-state-hover-state-layer-opacity),
|
|
108
|
+
transparent
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Selected — coral primary-container tint + paired ink (AD-13). */
|
|
113
|
+
.autocomplete__option--selected {
|
|
114
|
+
background: var(--md-sys-color-primary-container);
|
|
115
|
+
color: var(--md-sys-color-on-primary-container);
|
|
116
|
+
}
|
|
117
|
+
.autocomplete__option--selected .autocomplete__option-check {
|
|
118
|
+
color: var(--md-sys-color-on-primary-container);
|
|
119
|
+
}
|
|
120
|
+
.autocomplete__option--selected .autocomplete__mark {
|
|
121
|
+
color: var(--md-sys-color-on-primary-container);
|
|
122
|
+
}
|
|
123
|
+
.autocomplete__option--selected.autocomplete__option--active {
|
|
124
|
+
background: color-mix(
|
|
125
|
+
in oklab,
|
|
126
|
+
var(--md-sys-color-on-primary-container)
|
|
127
|
+
var(--md-sys-state-hover-state-layer-opacity),
|
|
128
|
+
var(--md-sys-color-primary-container)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.autocomplete__option--disabled {
|
|
133
|
+
opacity: 0.4;
|
|
134
|
+
cursor: not-allowed;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* ---------- No-match empty-state (inline; → xm-empty-state in Epic 4) ---------- */
|
|
138
|
+
.autocomplete__empty {
|
|
139
|
+
display: flex;
|
|
140
|
+
flex-direction: column;
|
|
141
|
+
align-items: center;
|
|
142
|
+
text-align: center;
|
|
143
|
+
gap: var(--s-1);
|
|
144
|
+
padding: var(--s-5) var(--s-4);
|
|
145
|
+
min-width: 220px;
|
|
146
|
+
}
|
|
147
|
+
.autocomplete__empty-icon {
|
|
148
|
+
display: inline-flex;
|
|
149
|
+
align-items: center;
|
|
150
|
+
justify-content: center;
|
|
151
|
+
margin-bottom: var(--s-1);
|
|
152
|
+
color: var(--xm-color-inverse-on-surface-muted);
|
|
153
|
+
}
|
|
154
|
+
.autocomplete__empty-title {
|
|
155
|
+
margin: 0;
|
|
156
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
157
|
+
font:
|
|
158
|
+
var(--md-sys-typescale-title-small-weight)
|
|
159
|
+
var(--md-sys-typescale-title-small-size) /
|
|
160
|
+
var(--md-sys-typescale-title-small-line-height)
|
|
161
|
+
var(--md-sys-typescale-title-small-font);
|
|
162
|
+
}
|
|
163
|
+
.autocomplete__empty-copy {
|
|
164
|
+
margin: 0;
|
|
165
|
+
color: var(--xm-color-inverse-on-surface-muted);
|
|
166
|
+
font:
|
|
167
|
+
var(--md-sys-typescale-body-small-weight)
|
|
168
|
+
var(--md-sys-typescale-body-small-size) /
|
|
169
|
+
var(--md-sys-typescale-body-small-line-height)
|
|
170
|
+
var(--md-sys-typescale-body-small-font);
|
|
171
|
+
}
|