@a2ui/angular 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 +9 -0
- package/angular.json +35 -0
- package/ng-package.json +8 -0
- package/package.json +59 -0
- package/src/lib/catalog/audio.ts +50 -0
- package/src/lib/catalog/button.ts +56 -0
- package/src/lib/catalog/card.ts +57 -0
- package/src/lib/catalog/checkbox.ts +73 -0
- package/src/lib/catalog/column.ts +96 -0
- package/src/lib/catalog/datetime-input.ts +127 -0
- package/src/lib/catalog/default.ts +185 -0
- package/src/lib/catalog/divider.ts +37 -0
- package/src/lib/catalog/icon.ts +44 -0
- package/src/lib/catalog/image.ts +62 -0
- package/src/lib/catalog/list.ts +63 -0
- package/src/lib/catalog/modal.ts +113 -0
- package/src/lib/catalog/multiple-choice.ts +77 -0
- package/src/lib/catalog/row.ts +100 -0
- package/src/lib/catalog/slider.ts +73 -0
- package/src/lib/catalog/surface.ts +82 -0
- package/src/lib/catalog/tabs.ts +72 -0
- package/src/lib/catalog/text-field.ts +86 -0
- package/src/lib/catalog/text.ts +137 -0
- package/src/lib/catalog/video.ts +50 -0
- package/src/lib/config.ts +25 -0
- package/src/lib/data/index.ts +18 -0
- package/src/lib/data/markdown.ts +114 -0
- package/src/lib/data/processor.ts +47 -0
- package/src/lib/data/types.ts +29 -0
- package/src/lib/rendering/catalog.ts +36 -0
- package/src/lib/rendering/dynamic-component.ts +100 -0
- package/src/lib/rendering/index.ts +20 -0
- package/src/lib/rendering/renderer.ts +109 -0
- package/src/lib/rendering/theming.ts +22 -0
- package/src/public-api.ts +21 -0
- package/tsconfig.json +23 -0
- package/tsconfig.lib.json +16 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +12 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Component, computed, input } from '@angular/core';
|
|
18
|
+
import { Primitives } from '@a2ui/lit/0.8';
|
|
19
|
+
import { DynamicComponent } from '../rendering/dynamic-component';
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: '[a2ui-slider]',
|
|
23
|
+
template: `
|
|
24
|
+
<section [class]="theme.components.Slider.container">
|
|
25
|
+
<label [class]="theme.components.Slider.label" [for]="inputId">
|
|
26
|
+
{{ label() }}
|
|
27
|
+
</label>
|
|
28
|
+
|
|
29
|
+
<input
|
|
30
|
+
autocomplete="off"
|
|
31
|
+
type="range"
|
|
32
|
+
[value]="resolvedValue()"
|
|
33
|
+
[min]="minValue()"
|
|
34
|
+
[max]="maxValue()"
|
|
35
|
+
[id]="inputId"
|
|
36
|
+
(input)="handleInput($event)"
|
|
37
|
+
[class]="theme.components.Slider.element"
|
|
38
|
+
[style]="theme.additionalStyles?.Slider"
|
|
39
|
+
/>
|
|
40
|
+
</section>
|
|
41
|
+
`,
|
|
42
|
+
styles: `
|
|
43
|
+
:host {
|
|
44
|
+
display: block;
|
|
45
|
+
flex: var(--weight);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
input {
|
|
49
|
+
display: block;
|
|
50
|
+
width: 100%;
|
|
51
|
+
box-sizing: border-box;
|
|
52
|
+
}
|
|
53
|
+
`,
|
|
54
|
+
})
|
|
55
|
+
export class Slider extends DynamicComponent {
|
|
56
|
+
readonly value = input.required<Primitives.NumberValue | null>();
|
|
57
|
+
readonly label = input('');
|
|
58
|
+
readonly minValue = input.required<number | undefined>();
|
|
59
|
+
readonly maxValue = input.required<number | undefined>();
|
|
60
|
+
|
|
61
|
+
protected readonly inputId = super.getUniqueId('a2ui-slider');
|
|
62
|
+
protected resolvedValue = computed(() => super.resolvePrimitive(this.value()) ?? 0);
|
|
63
|
+
|
|
64
|
+
protected handleInput(event: Event) {
|
|
65
|
+
const path = this.value()?.path;
|
|
66
|
+
|
|
67
|
+
if (!(event.target instanceof HTMLInputElement) || !path) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.processor.setData(this.component(), path, event.target.valueAsNumber, this.surfaceId());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Component, computed, input } from '@angular/core';
|
|
18
|
+
import { Types } from '@a2ui/lit/0.8';
|
|
19
|
+
import { Renderer } from '../rendering/renderer';
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'a2ui-surface',
|
|
23
|
+
imports: [Renderer],
|
|
24
|
+
template: `
|
|
25
|
+
@let surfaceId = this.surfaceId();
|
|
26
|
+
@let surface = this.surface();
|
|
27
|
+
|
|
28
|
+
@if (surfaceId && surface) {
|
|
29
|
+
<ng-container a2ui-renderer [surfaceId]="surfaceId" [component]="surface.componentTree!" />
|
|
30
|
+
}
|
|
31
|
+
`,
|
|
32
|
+
styles: `
|
|
33
|
+
:host {
|
|
34
|
+
display: flex;
|
|
35
|
+
min-height: 0;
|
|
36
|
+
max-height: 100%;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: 16px;
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
host: {
|
|
42
|
+
'[style]': 'styles()',
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
export class Surface {
|
|
46
|
+
readonly surfaceId = input.required<Types.SurfaceID | null>();
|
|
47
|
+
readonly surface = input.required<Types.Surface | null>();
|
|
48
|
+
|
|
49
|
+
protected readonly styles = computed(() => {
|
|
50
|
+
const surface = this.surface();
|
|
51
|
+
const styles: Record<string, string> = {};
|
|
52
|
+
|
|
53
|
+
if (surface?.styles) {
|
|
54
|
+
for (const [key, value] of Object.entries(surface.styles)) {
|
|
55
|
+
switch (key) {
|
|
56
|
+
case 'primaryColor': {
|
|
57
|
+
// Ignored for now. This is due to the fact that the sample agents
|
|
58
|
+
// produce values for these and if they are used here then they will
|
|
59
|
+
// override the values at the app level.
|
|
60
|
+
//
|
|
61
|
+
// for (let i = 0; i <= 100; i++) {
|
|
62
|
+
// styles[`--p-${i}`] = `color-mix(in srgb, ${value} ${100 - i}%, #fff ${i}%)`;
|
|
63
|
+
// }
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case 'font': {
|
|
68
|
+
// Ignored for now. This is due to the fact that the sample agents
|
|
69
|
+
// produce values for these and if they are used here then they will
|
|
70
|
+
// override the values at the app level.
|
|
71
|
+
//
|
|
72
|
+
// styles['--font-family'] = value;
|
|
73
|
+
// styles['--font-family-flex'] = value;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return styles;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Component, computed, input, signal } from '@angular/core';
|
|
18
|
+
import { DynamicComponent } from '../rendering/dynamic-component';
|
|
19
|
+
import { Renderer } from '../rendering/renderer';
|
|
20
|
+
import { Styles, Types } from '@a2ui/lit/0.8';
|
|
21
|
+
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'a2ui-tabs',
|
|
24
|
+
imports: [Renderer],
|
|
25
|
+
template: `
|
|
26
|
+
@let tabs = this.tabs();
|
|
27
|
+
@let selectedIndex = this.selectedIndex();
|
|
28
|
+
|
|
29
|
+
<section [class]="theme.components.Tabs.container" [style]="theme.additionalStyles?.Tabs">
|
|
30
|
+
<div [class]="theme.components.Tabs.element">
|
|
31
|
+
@for (tab of tabs; track tab) {
|
|
32
|
+
<button
|
|
33
|
+
(click)="this.selectedIndex.set($index)"
|
|
34
|
+
[disabled]="selectedIndex === $index"
|
|
35
|
+
[class]="buttonClasses()[selectedIndex]"
|
|
36
|
+
>
|
|
37
|
+
{{ resolvePrimitive(tab.title) }}
|
|
38
|
+
</button>
|
|
39
|
+
}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<ng-container
|
|
43
|
+
a2ui-renderer
|
|
44
|
+
[surfaceId]="surfaceId()!"
|
|
45
|
+
[component]="tabs[selectedIndex].child"
|
|
46
|
+
/>
|
|
47
|
+
</section>
|
|
48
|
+
`,
|
|
49
|
+
styles: `
|
|
50
|
+
:host {
|
|
51
|
+
display: block;
|
|
52
|
+
flex: var(--weight);
|
|
53
|
+
}
|
|
54
|
+
`,
|
|
55
|
+
})
|
|
56
|
+
export class Tabs extends DynamicComponent {
|
|
57
|
+
protected selectedIndex = signal(0);
|
|
58
|
+
readonly tabs = input.required<Types.ResolvedTabItem[]>();
|
|
59
|
+
|
|
60
|
+
protected readonly buttonClasses = computed(() => {
|
|
61
|
+
const selectedIndex = this.selectedIndex();
|
|
62
|
+
|
|
63
|
+
return this.tabs().map((_, index) => {
|
|
64
|
+
return index === selectedIndex
|
|
65
|
+
? Styles.merge(
|
|
66
|
+
this.theme.components.Tabs.controls.all,
|
|
67
|
+
this.theme.components.Tabs.controls.selected,
|
|
68
|
+
)
|
|
69
|
+
: this.theme.components.Tabs.controls.all;
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { computed, Component, input } from '@angular/core';
|
|
18
|
+
import { Primitives, Types } from '@a2ui/lit/0.8';
|
|
19
|
+
import { DynamicComponent } from '../rendering/dynamic-component';
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'a2ui-text-field',
|
|
23
|
+
styles: `
|
|
24
|
+
:host {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex: var(--weight);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
section,
|
|
30
|
+
input,
|
|
31
|
+
label {
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
input {
|
|
36
|
+
display: block;
|
|
37
|
+
width: 100%;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
label {
|
|
41
|
+
display: block;
|
|
42
|
+
margin-bottom: 4px;
|
|
43
|
+
}
|
|
44
|
+
`,
|
|
45
|
+
template: `
|
|
46
|
+
@let resolvedLabel = this.resolvedLabel();
|
|
47
|
+
|
|
48
|
+
<section [class]="theme.components.TextField.container">
|
|
49
|
+
@if (resolvedLabel) {
|
|
50
|
+
<label [for]="inputId" [class]="theme.components.TextField.label">{{
|
|
51
|
+
resolvedLabel
|
|
52
|
+
}}</label>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
<input
|
|
56
|
+
autocomplete="off"
|
|
57
|
+
[class]="theme.components.TextField.element"
|
|
58
|
+
[style]="theme.additionalStyles?.TextField"
|
|
59
|
+
(input)="handleInput($event)"
|
|
60
|
+
[id]="inputId"
|
|
61
|
+
[value]="inputValue()"
|
|
62
|
+
placeholder="Please enter a value"
|
|
63
|
+
[type]="inputType() === 'number' ? 'number' : 'text'"
|
|
64
|
+
/>
|
|
65
|
+
</section>
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class TextField extends DynamicComponent {
|
|
69
|
+
readonly text = input.required<Primitives.StringValue | null>();
|
|
70
|
+
readonly label = input.required<Primitives.StringValue | null>();
|
|
71
|
+
readonly inputType = input.required<Types.ResolvedTextField['type'] | null>();
|
|
72
|
+
|
|
73
|
+
protected inputValue = computed(() => super.resolvePrimitive(this.text()) || '');
|
|
74
|
+
protected resolvedLabel = computed(() => super.resolvePrimitive(this.label()));
|
|
75
|
+
protected inputId = super.getUniqueId('a2ui-input');
|
|
76
|
+
|
|
77
|
+
protected handleInput(event: Event) {
|
|
78
|
+
const path = this.text()?.path;
|
|
79
|
+
|
|
80
|
+
if (!(event.target instanceof HTMLInputElement) || !path) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.processor.setData(this.component(), path, event.target.value, this.surfaceId());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Component, computed, inject, input, ViewEncapsulation } from '@angular/core';
|
|
18
|
+
import { DynamicComponent } from '../rendering/dynamic-component';
|
|
19
|
+
import { Primitives, Styles, Types } from '@a2ui/lit/0.8';
|
|
20
|
+
import { MarkdownRenderer } from '../data/markdown';
|
|
21
|
+
|
|
22
|
+
interface HintedStyles {
|
|
23
|
+
h1: Record<string, string>;
|
|
24
|
+
h2: Record<string, string>;
|
|
25
|
+
h3: Record<string, string>;
|
|
26
|
+
h4: Record<string, string>;
|
|
27
|
+
h5: Record<string, string>;
|
|
28
|
+
body: Record<string, string>;
|
|
29
|
+
caption: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Component({
|
|
33
|
+
selector: 'a2ui-text',
|
|
34
|
+
template: `
|
|
35
|
+
<section
|
|
36
|
+
[class]="classes()"
|
|
37
|
+
[style]="additionalStyles()"
|
|
38
|
+
[innerHTML]="resolvedText()"
|
|
39
|
+
></section>
|
|
40
|
+
`,
|
|
41
|
+
encapsulation: ViewEncapsulation.None,
|
|
42
|
+
styles: `
|
|
43
|
+
a2ui-text {
|
|
44
|
+
display: block;
|
|
45
|
+
flex: var(--weight);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
a2ui-text h1,
|
|
49
|
+
a2ui-text h2,
|
|
50
|
+
a2ui-text h3,
|
|
51
|
+
a2ui-text h4,
|
|
52
|
+
a2ui-text h5 {
|
|
53
|
+
line-height: inherit;
|
|
54
|
+
font: inherit;
|
|
55
|
+
}
|
|
56
|
+
`,
|
|
57
|
+
})
|
|
58
|
+
export class Text extends DynamicComponent {
|
|
59
|
+
private markdownRenderer = inject(MarkdownRenderer);
|
|
60
|
+
readonly text = input.required<Primitives.StringValue | null>();
|
|
61
|
+
readonly usageHint = input.required<Types.ResolvedText['usageHint'] | null>();
|
|
62
|
+
|
|
63
|
+
protected resolvedText = computed(() => {
|
|
64
|
+
const usageHint = this.usageHint();
|
|
65
|
+
let value = super.resolvePrimitive(this.text());
|
|
66
|
+
|
|
67
|
+
if (value == null) {
|
|
68
|
+
return '(empty)';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
switch (usageHint) {
|
|
72
|
+
case 'h1':
|
|
73
|
+
value = `# ${value}`;
|
|
74
|
+
break;
|
|
75
|
+
case 'h2':
|
|
76
|
+
value = `## ${value}`;
|
|
77
|
+
break;
|
|
78
|
+
case 'h3':
|
|
79
|
+
value = `### ${value}`;
|
|
80
|
+
break;
|
|
81
|
+
case 'h4':
|
|
82
|
+
value = `#### ${value}`;
|
|
83
|
+
break;
|
|
84
|
+
case 'h5':
|
|
85
|
+
value = `##### ${value}`;
|
|
86
|
+
break;
|
|
87
|
+
case 'caption':
|
|
88
|
+
value = `*${value}*`;
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
value = String(value);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this.markdownRenderer.render(
|
|
96
|
+
value,
|
|
97
|
+
Styles.appendToAll(this.theme.markdown, ['ol', 'ul', 'li'], {}),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
protected classes = computed(() => {
|
|
102
|
+
const usageHint = this.usageHint();
|
|
103
|
+
|
|
104
|
+
return Styles.merge(
|
|
105
|
+
this.theme.components.Text.all,
|
|
106
|
+
usageHint ? this.theme.components.Text[usageHint] : {},
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
protected additionalStyles = computed(() => {
|
|
111
|
+
const usageHint = this.usageHint();
|
|
112
|
+
const styles = this.theme.additionalStyles?.Text;
|
|
113
|
+
|
|
114
|
+
if (!styles) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let additionalStyles: Record<string, string> = {};
|
|
119
|
+
|
|
120
|
+
if (this.areHintedStyles(styles)) {
|
|
121
|
+
additionalStyles = styles[usageHint ?? 'body'];
|
|
122
|
+
} else {
|
|
123
|
+
additionalStyles = styles;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return additionalStyles;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
private areHintedStyles(styles: unknown): styles is HintedStyles {
|
|
130
|
+
if (typeof styles !== 'object' || !styles || Array.isArray(styles)) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const expected = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'caption', 'body'];
|
|
135
|
+
return expected.every((v) => v in styles);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Component, computed, input } from '@angular/core';
|
|
18
|
+
import { DynamicComponent } from '../rendering/dynamic-component';
|
|
19
|
+
import { Primitives } from '@a2ui/lit/0.8';
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'a2ui-video',
|
|
23
|
+
template: `
|
|
24
|
+
@let resolvedUrl = this.resolvedUrl();
|
|
25
|
+
|
|
26
|
+
@if (resolvedUrl) {
|
|
27
|
+
<section [class]="theme.components.Video" [style]="theme.additionalStyles?.Video">
|
|
28
|
+
<video controls [src]="resolvedUrl"></video>
|
|
29
|
+
</section>
|
|
30
|
+
}
|
|
31
|
+
`,
|
|
32
|
+
styles: `
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
flex: var(--weight);
|
|
36
|
+
min-height: 0;
|
|
37
|
+
overflow: auto;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
video {
|
|
41
|
+
display: block;
|
|
42
|
+
width: 100%;
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
}
|
|
45
|
+
`,
|
|
46
|
+
})
|
|
47
|
+
export class Video extends DynamicComponent {
|
|
48
|
+
readonly url = input.required<Primitives.StringValue | null>();
|
|
49
|
+
protected readonly resolvedUrl = computed(() => this.resolvePrimitive(this.url()));
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
|
|
18
|
+
import { Catalog, Theme } from './rendering';
|
|
19
|
+
|
|
20
|
+
export function provideA2UI(config: { catalog: Catalog; theme: Theme }): EnvironmentProviders {
|
|
21
|
+
return makeEnvironmentProviders([
|
|
22
|
+
{ provide: Catalog, useValue: config.catalog },
|
|
23
|
+
{ provide: Theme, useValue: config.theme },
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export * from './processor';
|
|
18
|
+
export * from './types';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 Google LLC
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { inject, Injectable, SecurityContext } from '@angular/core';
|
|
18
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
19
|
+
import MarkdownIt from 'markdown-it';
|
|
20
|
+
|
|
21
|
+
@Injectable({ providedIn: 'root' })
|
|
22
|
+
export class MarkdownRenderer {
|
|
23
|
+
private originalClassMap = new Map<string, any>();
|
|
24
|
+
private sanitizer = inject(DomSanitizer);
|
|
25
|
+
|
|
26
|
+
private markdownIt = MarkdownIt({
|
|
27
|
+
highlight: (str, lang) => {
|
|
28
|
+
if (lang === 'html') {
|
|
29
|
+
const iframe = document.createElement('iframe');
|
|
30
|
+
iframe.classList.add('html-view');
|
|
31
|
+
iframe.srcdoc = str;
|
|
32
|
+
iframe.sandbox = '';
|
|
33
|
+
return iframe.innerHTML;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return str;
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
render(value: string, tagClassMap?: Record<string, string[]>) {
|
|
41
|
+
if (tagClassMap) {
|
|
42
|
+
this.applyTagClassMap(tagClassMap);
|
|
43
|
+
}
|
|
44
|
+
const htmlString = this.markdownIt.render(value);
|
|
45
|
+
this.unapplyTagClassMap();
|
|
46
|
+
return this.sanitizer.sanitize(SecurityContext.HTML, htmlString);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private applyTagClassMap(tagClassMap: Record<string, string[]>) {
|
|
50
|
+
Object.entries(tagClassMap).forEach(([tag, classes]) => {
|
|
51
|
+
let tokenName;
|
|
52
|
+
switch (tag) {
|
|
53
|
+
case 'p':
|
|
54
|
+
tokenName = 'paragraph';
|
|
55
|
+
break;
|
|
56
|
+
case 'h1':
|
|
57
|
+
case 'h2':
|
|
58
|
+
case 'h3':
|
|
59
|
+
case 'h4':
|
|
60
|
+
case 'h5':
|
|
61
|
+
case 'h6':
|
|
62
|
+
tokenName = 'heading';
|
|
63
|
+
break;
|
|
64
|
+
case 'ul':
|
|
65
|
+
tokenName = 'bullet_list';
|
|
66
|
+
break;
|
|
67
|
+
case 'ol':
|
|
68
|
+
tokenName = 'ordered_list';
|
|
69
|
+
break;
|
|
70
|
+
case 'li':
|
|
71
|
+
tokenName = 'list_item';
|
|
72
|
+
break;
|
|
73
|
+
case 'a':
|
|
74
|
+
tokenName = 'link';
|
|
75
|
+
break;
|
|
76
|
+
case 'strong':
|
|
77
|
+
tokenName = 'strong';
|
|
78
|
+
break;
|
|
79
|
+
case 'em':
|
|
80
|
+
tokenName = 'em';
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!tokenName) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const key = `${tokenName}_open`;
|
|
89
|
+
const original = this.markdownIt.renderer.rules[key];
|
|
90
|
+
this.originalClassMap.set(key, original);
|
|
91
|
+
|
|
92
|
+
this.markdownIt.renderer.rules[key] = (tokens, idx, options, env, self) => {
|
|
93
|
+
const token = tokens[idx];
|
|
94
|
+
for (const clazz of classes) {
|
|
95
|
+
token.attrJoin('class', clazz);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (original) {
|
|
99
|
+
return original.call(this, tokens, idx, options, env, self);
|
|
100
|
+
} else {
|
|
101
|
+
return self.renderToken(tokens, idx, options);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private unapplyTagClassMap() {
|
|
108
|
+
for (const [key, original] of this.originalClassMap) {
|
|
109
|
+
this.markdownIt.renderer.rules[key] = original;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.originalClassMap.clear();
|
|
113
|
+
}
|
|
114
|
+
}
|