@adia-ai/web-components 0.0.11 → 0.0.13

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 CHANGED
@@ -5,9 +5,9 @@ elements**, a reactive core, a trait system, and a renderer that turns
5
5
  A2UI protocol messages into live DOM.
6
6
 
7
7
  > This package ships UI atoms only. The generation pipeline lives in
8
- > [`@adia-ai/gen-ui`](../gen-ui); the pattern corpus in
9
- > [`@adia-ai/gen-ui-training`](../gen-ui-training); the MCP server in
10
- > [`@adia-ai/gen-ui-mcp`](../gen-ui-mcp).
8
+ > [`@adia-ai/a2ui-compose`](../a2ui/compose); the pattern corpus in
9
+ > [`@adia-ai/a2ui-corpus`](../a2ui/corpus); the MCP server in
10
+ > [`@adia-ai/a2ui-mcp`](../a2ui/mcp).
11
11
 
12
12
  ## Quick start
13
13
 
@@ -80,7 +80,7 @@ web-components/
80
80
  Build + dev utilities (including `build-a2ui-data.mjs`, `qa-training.mjs`,
81
81
  `a2ui-to-html.cjs`, `mcp-call.cjs`, `mcp-pipeline.cjs`, `screenshot.cjs`)
82
82
  live at the repo-root `scripts/` directory rather than inside this
83
- package — they span the monorepo (MCP server, gen-ui-training data,
83
+ package — they span the monorepo (MCP server, a2ui-corpus data,
84
84
  component catalog) and aren't scoped to web-components alone.
85
85
 
86
86
  ## Component contract
@@ -165,7 +165,7 @@ output is robust to name drift.
165
165
  npm run build:components # regenerate all .a2ui.json from YAML
166
166
  ```
167
167
 
168
- The build also writes `packages/gen-ui-training/catalog-a2ui_0_9.json` and
168
+ The build also writes `packages/a2ui/corpus/catalog-a2ui_0_9.json` and
169
169
  `catalog-a2ui_0_9_rules.txt` — the flat-file catalog the MCP server and
170
170
  generation engine consume.
171
171
 
@@ -189,11 +189,11 @@ Each is a CSS-variable override; no class toggles, no re-imports.
189
189
  ## Dependency direction
190
190
 
191
191
  ```
192
- gen-ui ──reads──> gen-ui-training ←─reads── web-components
193
- gen-ui-mcp ──reads──> gen-ui, gen-ui-training
192
+ a2ui-compose ──reads──> a2ui-corpus ←─reads── web-components
193
+ a2ui-mcp ──reads──> a2ui-compose, a2ui-corpus
194
194
  ```
195
195
 
196
- Web-components never imports from gen-ui or gen-ui-mcp. The A2UI renderer
196
+ Web-components never imports from a2ui-compose or a2ui-mcp. The A2UI renderer
197
197
  consumes a protocol, not a generator — anything that emits valid A2UI
198
198
  messages drives it.
199
199
 
@@ -19,7 +19,8 @@
19
19
  --calendar-picker-trigger-fg: var(--a-ui-text-subtle);
20
20
  --calendar-picker-trigger-border: var(--a-ui-border);
21
21
  --calendar-picker-trigger-border-hover: var(--a-ui-border-hover);
22
- --calendar-picker-trigger-border-focus: var(--a-ui-border-active);
22
+ --calendar-picker-trigger-focus-ring: var(--a-focus-ring);
23
+ --calendar-picker-trigger-focus-ring-invalid: var(--a-focus-ring-invalid);
23
24
  --calendar-picker-trigger-placeholder-fg: var(--a-ui-text-placeholder);
24
25
  --calendar-picker-trigger-fg-disabled: var(--a-ui-text-disabled);
25
26
 
@@ -138,7 +139,12 @@
138
139
 
139
140
  :scope:focus-visible { outline: none; }
140
141
  :scope:focus-visible [slot="trigger"] {
141
- border-color: var(--calendar-picker-trigger-border-focus);
142
+ /* Canonical ring via L3 token (see semantics.css FOCUS block). */
143
+ box-shadow: var(--calendar-picker-trigger-focus-ring);
144
+ }
145
+ :scope[aria-invalid="true"]:focus-visible [slot="trigger"],
146
+ :scope[error]:focus-visible [slot="trigger"] {
147
+ box-shadow: var(--calendar-picker-trigger-focus-ring-invalid);
142
148
  }
143
149
 
144
150
  [slot="display"] {
@@ -11,10 +11,18 @@
11
11
  /* ── Colors ── */
12
12
  --chat-input-bg: var(--a-canvas-0);
13
13
  --chat-input-border: var(--a-border-subtle);
14
- --chat-input-border-focus: var(--a-fg);
15
14
  --chat-input-caret-color: var(--a-fg-subtle);
16
15
  --chat-input-border-disabled: var(--a-border-subtle);
17
16
 
17
+ /* Canonical focus ring — chat-input is a *nested-control host*.
18
+ See semantics.css FOCUS block + the nested-control pattern
19
+ note below. The inner textarea-ui suppresses its own ring
20
+ inside this @scope; the host paints the ring via
21
+ :focus-within so it wraps BOTH the editable area and the
22
+ toolbar as a single affordance. */
23
+ --chat-input-focus-ring: var(--a-focus-ring);
24
+ --chat-input-focus-ring-invalid: var(--a-focus-ring-invalid);
25
+
18
26
  /* ── Image preview ── */
19
27
  --chat-input-image-size: 3rem; /* 48px at d=1 */
20
28
  --chat-input-image-radius: var(--a-radius-sm);
@@ -42,11 +50,30 @@
42
50
  border: 1px solid var(--chat-input-border);
43
51
  border-radius: var(--chat-input-radius);
44
52
  background: var(--chat-input-bg);
45
- transition: border-color var(--chat-input-duration) var(--chat-input-easing);
46
53
  }
47
54
 
48
- :scope:has(textarea-ui :focus) {
49
- border-color: var(--chat-input-border-focus);
55
+ /* ── Nested-control focus pattern ────────────────────────────────────
56
+ chat-input-ui is a composite whose "primary surface" is the
57
+ combined textarea + toolbar, not the textarea alone. Focus should
58
+ wrap the whole composite, not just the inner input. Two rules
59
+ enforce that:
60
+
61
+ 1. `:scope:focus-within` paints the canonical ring around the
62
+ composite via box-shadow (matches every other form control).
63
+ 2. The inner textarea-ui's own focus treatments are suppressed
64
+ inside this @scope — the host owns the affordance.
65
+
66
+ The @scope block's containment is the signal: no data attribute
67
+ or explicit opt-in required. Any future composite that wants the
68
+ same pattern drops its inner control(s) into its @scope and
69
+ suppresses their focus rules with equivalent rules below.
70
+ ─────────────────────────────────────────────────────────────── */
71
+ :scope:focus-within {
72
+ box-shadow: var(--chat-input-focus-ring);
73
+ }
74
+ :scope[aria-invalid="true"]:focus-within,
75
+ :scope[error]:focus-within {
76
+ box-shadow: var(--chat-input-focus-ring-invalid);
50
77
  }
51
78
 
52
79
  /* Textarea: no border/bg of its own — container handles it. The
@@ -66,7 +93,16 @@
66
93
  padding: var(--chat-input-textarea-pt) var(--chat-input-textarea-px) 0;
67
94
  }
68
95
 
69
- textarea-ui [slot="text"]:focus {
96
+ /* Suppress the nested textarea-ui's own focus ring — the host (this
97
+ scope) owns the affordance. Required because textarea.css's default
98
+ rule paints a ring via box-shadow; without this override, the
99
+ composite would show both the host's outer ring AND the inner
100
+ control's ring simultaneously.
101
+
102
+ textarea.css's rule is `:scope:not([disabled]) [slot="text"]:focus`
103
+ with specificity (0,4,0); we need to beat that — using the same
104
+ `:not([disabled])` guard on the host lifts ours to (0,4,1). */
105
+ :scope textarea-ui:not([disabled]) [slot="text"]:focus {
70
106
  border: none;
71
107
  box-shadow: none;
72
108
  }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * code-editor — CodeMirror 6 runtime bundled for <code-ui>.
3
+ *
4
+ * Single file for the whole integration: CM core re-exports, the AdiaUI
5
+ * base theme + highlight style, the per-language lazy-load map, and the
6
+ * load-timeout helper. `code.js` does `import('./code-editor.js')` once
7
+ * per element mount.
8
+ *
9
+ * Styling contract: `EditorView.theme()` can't emit CSS custom properties
10
+ * (per CodeMirror docs), so the base theme below is structural-only.
11
+ * Every color, padding, border, and syntax-token style lives in
12
+ * `code.css` under `@scope (code-ui)`. See SPEC-CODE-EDITOR-001 §6.
13
+ *
14
+ * In Vite dev, the bare-specifier imports resolve through node_modules.
15
+ * In production, `scripts/build-site.mjs` runs esbuild with
16
+ * `splitting: true` on this file; each `languages[…]()` dynamic import
17
+ * becomes its own chunk, lazy-loaded on first use.
18
+ */
19
+
20
+ import {
21
+ EditorState, EditorSelection, Compartment, StateEffect,
22
+ } from '@codemirror/state';
23
+ import {
24
+ EditorView,
25
+ lineNumbers, highlightActiveLine, highlightActiveLineGutter,
26
+ placeholder, drawSelection, keymap,
27
+ } from '@codemirror/view';
28
+ import {
29
+ defaultKeymap, history, historyKeymap, indentWithTab,
30
+ } from '@codemirror/commands';
31
+ import {
32
+ HighlightStyle, syntaxHighlighting, LanguageSupport, defaultHighlightStyle,
33
+ } from '@codemirror/language';
34
+ import { tags as t } from '@lezer/highlight';
35
+
36
+ // ── Base theme (structural only — colors live in code.css) ───────────
37
+
38
+ export const adiaBaseTheme = EditorView.theme({
39
+ '&': { fontFamily: 'inherit', fontSize: 'inherit', color: 'inherit', backgroundColor: 'transparent' },
40
+ '.cm-content': { padding: '0', caretColor: 'inherit' },
41
+ '.cm-focused': { outline: 'none' },
42
+ });
43
+
44
+ // ── Syntax highlight (class-based so CSS owns the colors) ────────────
45
+
46
+ export const adiaHighlightStyle = HighlightStyle.define([
47
+ { tag: [t.comment, t.lineComment, t.blockComment, t.docComment], class: 'tok-comment' },
48
+ { tag: [t.keyword, t.controlKeyword, t.modifier, t.operatorKeyword], class: 'tok-keyword' },
49
+ { tag: [t.string, t.character, t.regexp, t.escape, t.special(t.string)], class: 'tok-string' },
50
+ { tag: [t.number, t.integer, t.float], class: 'tok-number' },
51
+ { tag: [t.bool, t.null, t.atom], class: 'tok-boolean' },
52
+ { tag: [t.operator, t.logicOperator, t.arithmeticOperator, t.compareOperator,
53
+ t.updateOperator, t.definitionOperator], class: 'tok-operator' },
54
+ { tag: [t.punctuation, t.bracket, t.paren, t.brace, t.squareBracket,
55
+ t.angleBracket, t.separator], class: 'tok-punctuation' },
56
+ { tag: [t.function(t.variableName), t.function(t.propertyName), t.macroName], class: 'tok-function' },
57
+ { tag: [t.variableName, t.local(t.variableName), t.self], class: 'tok-variable' },
58
+ { tag: [t.typeName, t.className, t.namespace], class: 'tok-type' },
59
+ { tag: [t.propertyName, t.labelName, t.definition(t.variableName)], class: 'tok-property' },
60
+ { tag: [t.tagName, t.heading, t.contentSeparator], class: 'tok-tag' },
61
+ { tag: [t.attributeName, t.attributeValue], class: 'tok-attribute' },
62
+ { tag: [t.url, t.link], class: 'tok-url' },
63
+ { tag: [t.invalid, t.deleted], class: 'tok-invalid' },
64
+ ]);
65
+
66
+ // ── Per-language lazy loaders ────────────────────────────────────────
67
+
68
+ export const languages = {
69
+ json: () => import('@codemirror/lang-json').then((m) => ({ extension: m.json() })),
70
+ html: () => import('@codemirror/lang-html').then((m) => ({ extension: m.html() })),
71
+ javascript: () => import('@codemirror/lang-javascript').then((m) => ({ extension: m.javascript({ jsx: false, typescript: false }) })),
72
+ css: () => import('@codemirror/lang-css').then((m) => ({ extension: m.css() })),
73
+ markdown: () => import('@codemirror/lang-markdown').then((m) => ({ extension: m.markdown() })),
74
+ yaml: () => import('@codemirror/lang-yaml').then((m) => ({ extension: m.yaml() })),
75
+ };
76
+
77
+ // ── Load helper ──────────────────────────────────────────────────────
78
+
79
+ /** 10s ceiling on any single dynamic import. Per SPEC-CODE-EDITOR-001 §7.4. */
80
+ export const LOAD_TIMEOUT_MS = 10_000;
81
+
82
+ export function importWithTimeout(loader, label) {
83
+ return Promise.race([
84
+ loader(),
85
+ new Promise((_, reject) =>
86
+ setTimeout(
87
+ () => reject(new Error(`code-editor: ${label} load timed out after ${LOAD_TIMEOUT_MS}ms`)),
88
+ LOAD_TIMEOUT_MS,
89
+ ),
90
+ ),
91
+ ]);
92
+ }
93
+
94
+ // ── Re-exports (so code.js gets everything from one import) ─────────
95
+
96
+ export {
97
+ EditorState, EditorSelection, Compartment, StateEffect,
98
+ EditorView,
99
+ lineNumbers, highlightActiveLine, highlightActiveLineGutter,
100
+ placeholder, drawSelection, keymap,
101
+ defaultKeymap, history, historyKeymap, indentWithTab,
102
+ HighlightStyle, syntaxHighlighting, LanguageSupport, defaultHighlightStyle,
103
+ };
@@ -24,6 +24,39 @@
24
24
  /* ── Transition ── */
25
25
  --code-duration: var(--a-duration-fast);
26
26
  --code-easing: var(--a-easing);
27
+
28
+ /* ── Editor chrome (CodeMirror 6) ──
29
+ Active only when `[language]` triggers the CM mount. Still safe
30
+ to emit in read-only mode; unused until `.cm-editor` is in the
31
+ DOM. See docs/specs/code-editor.md §6. */
32
+ --code-gutter-bg: var(--a-bg-subtle);
33
+ --code-gutter-fg: var(--a-fg-muted);
34
+ --code-active-line-bg: color-mix(in oklch, var(--a-accent-muted) 40%, transparent);
35
+ --code-selection-bg: color-mix(in oklch, var(--a-accent-muted) 60%, transparent);
36
+ --code-selection-match: color-mix(in oklch, var(--a-accent-muted) 30%, transparent);
37
+ --code-cursor: var(--a-accent-strong);
38
+ --code-focus-ring: var(--a-focus-ring);
39
+
40
+ /* ── Syntax highlight tokens ──
41
+ Map Lezer/CodeMirror highlight tag names to AdiaUI semantic
42
+ tokens. Overridable per-instance by setting `--code-tok-*` on
43
+ any ancestor. See docs/specs/code-editor.md §6.3 for the tag →
44
+ class → role map. */
45
+ --code-tok-comment: var(--a-fg-subtle);
46
+ --code-tok-keyword: var(--a-accent-strong);
47
+ --code-tok-string: var(--a-success-strong);
48
+ --code-tok-number: var(--a-info-strong);
49
+ --code-tok-boolean: var(--a-info-strong);
50
+ --code-tok-operator: var(--a-fg);
51
+ --code-tok-punctuation: var(--a-fg-muted);
52
+ --code-tok-function: var(--a-brand-strong);
53
+ --code-tok-variable: var(--a-fg);
54
+ --code-tok-type: var(--a-warning-strong);
55
+ --code-tok-property: var(--a-fg);
56
+ --code-tok-tag: var(--a-accent-strong);
57
+ --code-tok-attribute: var(--a-warning-strong);
58
+ --code-tok-url: var(--a-info-strong);
59
+ --code-tok-invalid: var(--a-danger-strong);
27
60
  }
28
61
 
29
62
  /* ── Block (default) ── */
@@ -132,4 +165,117 @@
132
165
  font-size: 0.9em;
133
166
  overflow: visible;
134
167
  }
168
+
169
+ /* ── Bare mode — no chrome ──
170
+ Strips border, background, radius, and padding. Header is hidden
171
+ via #stampBlock (it's never added in bare). Used by the A2UI Editor
172
+ and any pane-level context where <code-ui> is framed by a parent
173
+ surface and should not carry its own. Height flows from the parent
174
+ container, so CM's internal scroll handles overflow. */
175
+ :scope[bare] {
176
+ border: none;
177
+ border-radius: 0;
178
+ background: transparent;
179
+ overflow: visible;
180
+ height: 100%;
181
+ }
182
+ :scope[bare] > pre {
183
+ padding: 0;
184
+ height: 100%;
185
+ }
186
+ :scope[bare] > [data-cm-mount] {
187
+ height: 100%;
188
+ }
189
+ :scope[bare] .cm-editor {
190
+ height: 100%;
191
+ }
192
+ :scope[bare] .cm-scroller {
193
+ padding: 0;
194
+ }
195
+ :scope[bare] .cm-gutters {
196
+ background: transparent;
197
+ }
198
+
199
+ /* ── CodeMirror mount ──
200
+ `<code-ui>` inserts `<div data-cm-mount>` in place of `<pre><code>`
201
+ when `[language]` triggers the CM lazy-load. Styles below apply to
202
+ the CodeMirror DOM that CM renders inside that mount.
203
+
204
+ Selector discipline: `.cm-*` classes are CodeMirror's; `.tok-*`
205
+ classes come from our `adiaHighlightStyle` (see core/_cm-theme.js).
206
+ CM's CSS-in-JS stylesheets load first + at lower specificity, so
207
+ these rules override without !important. */
208
+
209
+ > [data-cm-mount] {
210
+ display: block;
211
+ overflow: hidden;
212
+ }
213
+
214
+ :scope .cm-editor {
215
+ background: transparent;
216
+ color: var(--code-fg);
217
+ font-family: var(--code-font);
218
+ font-size: var(--code-font-size);
219
+ line-height: 1.5;
220
+ }
221
+
222
+ :scope .cm-scroller {
223
+ font-family: inherit;
224
+ line-height: inherit;
225
+ padding: var(--code-py) var(--code-px);
226
+ }
227
+
228
+ :scope .cm-content {
229
+ padding: 0;
230
+ caret-color: var(--code-cursor);
231
+ }
232
+
233
+ :scope .cm-editor.cm-focused {
234
+ outline: 2px solid var(--code-focus-ring);
235
+ outline-offset: -2px;
236
+ }
237
+
238
+ :scope .cm-gutters {
239
+ background: var(--code-gutter-bg);
240
+ color: var(--code-gutter-fg);
241
+ border-right: 1px solid var(--code-border);
242
+ }
243
+
244
+ :scope .cm-activeLine {
245
+ background: var(--code-active-line-bg);
246
+ }
247
+ :scope .cm-activeLineGutter {
248
+ background: var(--code-active-line-bg);
249
+ color: var(--code-fg);
250
+ }
251
+
252
+ :scope .cm-selectionBackground,
253
+ :scope ::selection {
254
+ background: var(--code-selection-bg);
255
+ }
256
+
257
+ :scope .cm-selectionMatch {
258
+ background: var(--code-selection-match);
259
+ }
260
+
261
+ :scope .cm-cursor {
262
+ border-left-color: var(--code-cursor);
263
+ }
264
+
265
+ /* Syntax highlight — one rule per token role (see core/_cm-theme.js) */
266
+ :scope .tok-comment { color: var(--code-tok-comment); font-style: italic; }
267
+ :scope .tok-keyword { color: var(--code-tok-keyword); font-weight: 500; }
268
+ :scope .tok-string { color: var(--code-tok-string); }
269
+ :scope .tok-number { color: var(--code-tok-number); }
270
+ :scope .tok-boolean { color: var(--code-tok-boolean); }
271
+ :scope .tok-operator { color: var(--code-tok-operator); }
272
+ :scope .tok-punctuation { color: var(--code-tok-punctuation); }
273
+ :scope .tok-function { color: var(--code-tok-function); }
274
+ :scope .tok-variable { color: var(--code-tok-variable); }
275
+ :scope .tok-type { color: var(--code-tok-type); font-weight: 500; }
276
+ :scope .tok-property { color: var(--code-tok-property); }
277
+ :scope .tok-tag { color: var(--code-tok-tag); }
278
+ :scope .tok-attribute { color: var(--code-tok-attribute); }
279
+ :scope .tok-url { color: var(--code-tok-url); text-decoration: underline; }
280
+ :scope .tok-invalid { color: var(--code-tok-invalid); text-decoration: wavy underline; }
135
281
  }