Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
6ab973e
Add gemignore
jacobsimionato Jan 29, 2026
eecd6d1
Add initial design doc for v0.9 web renderers
jacobsimionato Jan 30, 2026
6ba8646
iterate on rendering doc
jacobsimionato Feb 1, 2026
299fde4
Update web renderers docs
jacobsimionato Feb 3, 2026
5db3f0c
minor updates
jacobsimionato Feb 3, 2026
5ca80d8
Switch to composition rather than inheritance
jacobsimionato Feb 4, 2026
0886ebe
Add data Context
jacobsimionato Feb 4, 2026
6fe1b91
Restructure design doc
jacobsimionato Feb 4, 2026
6069c50
Combine docs
jacobsimionato Feb 4, 2026
42d0c5a
Remove base class language and other cruft
jacobsimionato Feb 4, 2026
04e47f2
readd mermaid and references
jacobsimionato Feb 4, 2026
7fbf5c3
Remove some content
jacobsimionato Feb 4, 2026
d8cc118
Remove testing notes
jacobsimionato Feb 4, 2026
18dade6
Add standard catalog factory
jacobsimionato Feb 4, 2026
9277c41
Add custom catalog section
jacobsimionato Feb 4, 2026
074647e
Add rename surface state to surface context
jacobsimionato Feb 4, 2026
c895e3d
Initial version of lit and core renderers, not working
jacobsimionato Feb 5, 2026
9778509
Initial version of shell app, not tested
jacobsimionato Feb 5, 2026
95cfe2a
initial version of restaurant finder agent for 0.9, not working
jacobsimionato Feb 5, 2026
3610333
Improvements to the renderer to fix structure of standard catalog
jacobsimionato Feb 5, 2026
f082328
Add renderer v0.9 demo app
jacobsimionato Feb 5, 2026
e370cf1
Fix button component
jacobsimionato Feb 5, 2026
c17a699
Add styling support including 'additional styles' to v0.9
jacobsimionato Feb 5, 2026
83e0598
Add many more examples
jacobsimionato Feb 5, 2026
f39d550
Remove v0.9 version of restaurant finder and shell app
jacobsimionato Feb 5, 2026
cab6535
Update this to use the flat v0.9 JSON format
jacobsimionato Feb 5, 2026
548a06d
Remove getComponent from catalog API
jacobsimionato Feb 6, 2026
79db066
Add the schema support design doc
jacobsimionato Feb 6, 2026
2459adf
Add schema handling
jacobsimionato Feb 6, 2026
cf7f111
Update use of parameters
jacobsimionato Feb 6, 2026
495a89b
revert changes to the web renderers when I added schema handling
jacobsimionato Feb 8, 2026
1f6f2c7
update docs on data model API changes
jacobsimionato Feb 8, 2026
12ab66e
Add some more details on zod schema stuff
jacobsimionato Feb 8, 2026
ea34f4f
Update data model and component context etc
jacobsimionato Feb 9, 2026
d12b7e7
Improve data model test slightly
jacobsimionato Feb 9, 2026
13f3267
Fix handling of weight and accessibility common params
jacobsimionato Feb 9, 2026
016c80f
Fix lit implementation of weight, a11y etc
jacobsimionato Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .geminiignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Ignore large lock files that aren't useful for context
**/pnpm-lock.yaml
**/package-lock.json
**/uv.lock
3 changes: 1 addition & 2 deletions renderers/lit/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion renderers/lit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
"./ui": {
"types": "./dist/src/0.8/ui/ui.d.ts",
"default": "./dist/src/0.8/ui/ui.js"
},
"./v0_9": {
"types": "./dist/src/v0_9/index.d.ts",
"default": "./dist/src/v0_9/index.js"
}
},
"type": "module",
Expand All @@ -36,7 +40,7 @@
"service": true
},
"test": {
"command": "node --test --enable-source-maps --test-reporter spec dist/src/0.8/*.test.js",
"command": "node --test --enable-source-maps --test-reporter spec dist/src/0.8 dist/src/v0_9",
"dependencies": [
"build"
]
Expand Down
4 changes: 4 additions & 0 deletions renderers/lit/src/v0_9/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

export * from './renderer/lit-component-context.js';
export * from './standard_catalog/index.js';
export * from './ui/surface.js';
9 changes: 9 additions & 0 deletions renderers/lit/src/v0_9/renderer/lit-component-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

import { TemplateResult } from 'lit';
import { ComponentContext as CoreComponentContext } from '@a2ui/web_core/v0_9';

// Lit currently doesn't add much on top of the generic context in v0.9 design,
// as the reactivity is handled by the `updateCallback` passed from the Surface.
// However, we might want to specialize 'renderChild' if needed, or just alias it.

export type LitComponentContext = CoreComponentContext<TemplateResult>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { describe, it } from 'node:test';
import { litAudioPlayer } from './audio-player.js';
import { createLitTestContext, assertTemplateContains } from '../../test/test-utils.js';

describe('Lit AudioPlayer', () => {
it('renders audio element', () => {
const context = createLitTestContext({ url: 'audio.mp3' });
const result = litAudioPlayer.render(context);
assertTemplateContains(result, '<audio');
assertTemplateContains(result, 'src="audio.mp3"');
});
});
28 changes: 28 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/audio-player.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { AudioPlayerComponent } from '@a2ui/web_core/v0_9';
import { getAccessibilityAttributes } from '../../ui/utils.js';

export const litAudioPlayer = new AudioPlayerComponent<TemplateResult>(
({ url, description, weight }, context) => {
const classes = context.surfaceContext.theme.components.AudioPlayer;
const a11y = getAccessibilityAttributes(context);
const styles: Record<string, string> = {};
if (weight !== undefined) {
styles['flex-grow'] = String(weight);
}

return html`
<audio
src="${url}"
controls
class=${classMap(classes)}
style=${styleMap(styles)}
title=${description || a11y['aria-label'] || null}
aria-label=${a11y['aria-label'] || null}
aria-description=${a11y['aria-description'] || description || null}
></audio>
`;
}
);
18 changes: 18 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/button.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, it } from 'node:test';
import { litButton } from './button.js';
import { createLitTestContext, assertTemplateContains } from '../../test/test-utils.js';

describe('Lit Button', () => {
it('renders button element', () => {
const context = createLitTestContext({ label: 'Click Me' });
const result = litButton.render(context);
assertTemplateContains(result, '<button');
assertTemplateContains(result, 'Click Me');
});

it('renders child content overrides label', () => {
const context = createLitTestContext({ label: 'Label', child: 'Custom Content' });
const result = litButton.render(context);
assertTemplateContains(result, 'Custom Content');
});
});
24 changes: 24 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { resolve } from 'path';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import resolve from the path module is not used in this file and should be removed to keep the code clean.

import { html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { ButtonComponent } from '@a2ui/web_core/v0_9';
import { getStyleMap, getAccessibilityAttributes } from '../../ui/utils.js';

export const litButton = new ButtonComponent<TemplateResult>(
({ label, disabled, onAction, child, weight }, context) => {
const classes = context.surfaceContext.theme.components.Button;
const styles = getStyleMap(context, 'Button');
const a11y = getAccessibilityAttributes(context);

if (weight !== undefined) {
styles['flex-grow'] = String(weight);
}

return html`
<button class=${classMap(classes)} style=${styleMap(styles)} ?disabled=${disabled} @click=${onAction} aria-label=${a11y['aria-label'] || label} aria-description=${a11y['aria-description'] || null}>
${child ? child : label}
</button>
`;
}
);
11 changes: 11 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/card.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, it } from 'node:test';
import { litCard } from './card.js';
import { createLitTestContext, assertTemplateContains } from '../../test/test-utils.js';

describe('Lit Card', () => {
it('renders card container', () => {
const context = createLitTestContext({ child: null });
const result = litCard.render(context);
assertTemplateContains(result, 'class="a2ui-card"');
});
});
23 changes: 23 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { CardComponent } from '@a2ui/web_core/v0_9';
import { getStyleMap, getAccessibilityAttributes } from '../../ui/utils.js';

export const litCard = new CardComponent<TemplateResult>(
({ child, weight }, context) => {
const classes = context.surfaceContext.theme.components.Card;
const styles = getStyleMap(context, 'Card');
const a11y = getAccessibilityAttributes(context);

if (weight !== undefined) {
styles['flex-grow'] = String(weight);
}

return html`
<div class=${classMap(classes)} style=${styleMap(styles)} aria-label=${a11y['aria-label'] || null} aria-description=${a11y['aria-description'] || null}>
${child}
</div>
`;
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, it } from 'node:test';
import { litCheckBox } from './check-box.js';
import { createLitTestContext, assertTemplateContains } from '../../test/test-utils.js';

describe('Lit CheckBox', () => {
it('renders label and checked state', () => {
const context = createLitTestContext({ label: 'Accept Terms', value: true });
const result = litCheckBox.render(context);
assertTemplateContains(result, 'Accept Terms');
// Lit boolean attributes often show as ?checked or checked depending on binding.
// In string template: .checked="${value}" -> bound via property
// We can't easily verify the PROPERTY value in static analysis of TemplateResult strings mostly.
// But we can verify the structure exists.
assertTemplateContains(result, 'type="checkbox"');
});
});
31 changes: 31 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/check-box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { CheckBoxComponent } from '@a2ui/web_core/v0_9';
import { getAccessibilityAttributes } from '../../ui/utils.js';

export const litCheckBox = new CheckBoxComponent<TemplateResult>(
({ label, value, onChange, weight }, context) => {
const classes = context.surfaceContext.theme.components.CheckBox;
const a11y = getAccessibilityAttributes(context);
const styles: Record<string, string> = {};
if (weight !== undefined) {
styles['flex-grow'] = String(weight);
}

return html`
<div class=${classMap(classes)} style=${styleMap(styles)}>
<label>
<input
type="checkbox"
.checked="${value}"
@change="${(e: Event) => onChange((e.target as HTMLInputElement).checked)}"
aria-label=${a11y['aria-label'] || label}
aria-description=${a11y['aria-description'] || null}
/>
${label}
</label>
</div>
`;
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { describe, it } from 'node:test';
import { litChoicePicker } from './choice-picker.js';
import { createLitTestContext, assertTemplateContains } from '../../test/test-utils.js';

describe('Lit ChoicePicker', () => {
it('renders options', () => {
const context = createLitTestContext({
label: 'Choose',
value: ['a'],
options: [{ label: 'Option A', value: 'a' }, { label: 'Option B', value: 'b' }]
});
const result = litChoicePicker.render(context);
assertTemplateContains(result, 'Choose');
assertTemplateContains(result, 'Option A');
assertTemplateContains(result, 'Option B');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { ChoicePickerComponent } from '@a2ui/web_core/v0_9';
import { getAccessibilityAttributes } from '../../ui/utils.js';

export const litChoicePicker = new ChoicePickerComponent<TemplateResult>(
({ label, options, value, variant, onChange, weight }, context) => {
const classes = context.surfaceContext.theme.components.ChoicePicker;
const a11y = getAccessibilityAttributes(context);
const styles: Record<string, string> = {};
if (weight !== undefined) {
styles['flex-grow'] = String(weight);
}

// Simple select implementation for now
const isMultiple = variant === 'multipleSelection';
const handleChange = (e: Event) => {
const select = e.target as HTMLSelectElement;
if (isMultiple) {
const values = Array.from(select.selectedOptions).map(opt => opt.value);
onChange(values);
} else {
onChange([select.value]);
}
};

return html`
<div class=${classMap(classes)} style=${styleMap(styles)}>
<label>${label}</label>
<select
?multiple="${isMultiple}"
@change="${handleChange}"
aria-label=${a11y['aria-label'] || label}
aria-description=${a11y['aria-description'] || null}
>
${options.map(opt => html`
<option
value="${opt.value}"
?selected="${value.includes(opt.value)}"
>
${opt.label}
</option>
`)}
</select>
</div>
`;
}
);
13 changes: 13 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/column.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

import { describe, it } from 'node:test';
import { litColumn } from './column.js';
import { createLitTestContext, assertTemplateContains } from '../../test/test-utils.js';

describe('Lit Column', () => {
it('renders column', () => {
const context = createLitTestContext({ children: [], direction: 'column' });
const result = litColumn.render(context);
assertTemplateContains(result, 'display: flex');
assertTemplateContains(result, 'flex-direction: column');
});
});
40 changes: 40 additions & 0 deletions renderers/lit/src/v0_9/standard_catalog/components/column.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

import { html, TemplateResult } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { ColumnComponent } from '@a2ui/web_core/v0_9';
import { getAccessibilityAttributes } from '../../ui/utils.js';

export const litColumn = new ColumnComponent<TemplateResult>(
({ children, justify, align, weight }, context) => {
const classes = context.surfaceContext.theme.components.Column;
const a11y = getAccessibilityAttributes(context);

// Map justify/align
const alignClasses: Record<string, boolean> = {};
if (justify) {
// For Column, justify is vertical (main axis)
const map: Record<string, string> = { 'start': 'layout-sp-s', 'center': 'layout-sp-c', 'end': 'layout-sp-e', 'between': 'layout-sp-bt', 'evenly': 'layout-sp-ev' };
if (map[justify]) alignClasses[map[justify]] = true;
}
if (align) {
// For Column, align is horizontal (cross axis)
const map: Record<string, string> = { 'start': 'layout-al-fs', 'center': 'layout-al-c', 'end': 'layout-al-fe', 'stretch': 'layout-al-st' };
if (map[align]) alignClasses[map[align]] = true;
}

const styles: Record<string, string> = {
display: 'flex',
'flex-direction': 'column',
};
if (weight !== undefined) {
styles['flex-grow'] = String(weight);
}

return html`
<div class=${classMap({ ...classes, ...alignClasses })} style=${styleMap(styles)} aria-label=${a11y['aria-label'] || null} aria-description=${a11y['aria-description'] || null}>
${children}
</div>
`;
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, it } from 'node:test';
import { litDateTimeInput } from './date-time-input.js';
import { createLitTestContext, assertTemplateContains } from '../../test/test-utils.js';

describe('Lit DateTimeInput', () => {
it('renders date input', () => {
const context = createLitTestContext({ label: 'Birthday', value: '2000-01-01', enableDate: true });
const result = litDateTimeInput.render(context);
assertTemplateContains(result, 'Birthday');
assertTemplateContains(result, 'type="date"');
});

it('renders time input', () => {
const context = createLitTestContext({ label: 'Alarm', value: '12:00', enableTime: true, enableDate: false });
const result = litDateTimeInput.render(context);
assertTemplateContains(result, 'type="time"');
});
});
Loading
Loading