@origostudio/origo-design 0.0.35 → 0.0.37
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/CHANGELOG.md +2 -2
- package/dist/packages/angular-renderer/CHANGELOG.md +21 -0
- package/dist/packages/angular-renderer/package.json +3 -3
- package/dist/packages/angular-renderer/src/components/primitives/button/button.component.js +13 -2
- package/dist/packages/angular-renderer/src/components/primitives/button/button.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.d.ts +2 -0
- package/dist/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.js +10 -2
- package/dist/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/components/primitives/chip/chip.component.d.ts +22 -0
- package/dist/packages/angular-renderer/src/components/primitives/chip/chip.component.js +79 -0
- package/dist/packages/angular-renderer/src/components/primitives/chip/chip.component.js.map +1 -0
- package/dist/packages/angular-renderer/src/components/primitives/form-field/form-field.component.d.ts +4 -0
- package/dist/packages/angular-renderer/src/components/primitives/form-field/form-field.component.js +16 -1
- package/dist/packages/angular-renderer/src/components/primitives/form-field/form-field.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/components/primitives/hbox/hbox.component.d.ts +4 -0
- package/dist/packages/angular-renderer/src/components/primitives/hbox/hbox.component.js +15 -1
- package/dist/packages/angular-renderer/src/components/primitives/hbox/hbox.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/components/primitives/label/label.component.d.ts +2 -0
- package/dist/packages/angular-renderer/src/components/primitives/label/label.component.js +10 -2
- package/dist/packages/angular-renderer/src/components/primitives/label/label.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.d.ts +2 -1
- package/dist/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.js +11 -5
- package/dist/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/components/primitives/switch/switch.component.d.ts +22 -0
- package/dist/packages/angular-renderer/src/components/primitives/switch/switch.component.js +80 -0
- package/dist/packages/angular-renderer/src/components/primitives/switch/switch.component.js.map +1 -0
- package/dist/packages/angular-renderer/src/components/primitives/text-input/text-input.component.d.ts +0 -1
- package/dist/packages/angular-renderer/src/components/primitives/text-input/text-input.component.js +14 -5
- package/dist/packages/angular-renderer/src/components/primitives/text-input/text-input.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/components/primitives/vbox/vbox.component.d.ts +4 -0
- package/dist/packages/angular-renderer/src/components/primitives/vbox/vbox.component.js +15 -1
- package/dist/packages/angular-renderer/src/components/primitives/vbox/vbox.component.js.map +1 -1
- package/dist/packages/angular-renderer/src/lib/primitives.provider.js +4 -0
- package/dist/packages/angular-renderer/src/lib/primitives.provider.js.map +1 -1
- package/dist/packages/cli/CHANGELOG.md +8 -0
- package/dist/packages/cli/package.json +2 -2
- package/dist/packages/cli/package.json.bak +2 -2
- package/dist/packages/core/CHANGELOG.md +8 -0
- package/dist/packages/core/package.json +1 -1
- package/dist/packages/core/package.json.bak +1 -1
- package/dist/packages/design-tokens/CHANGELOG.md +8 -0
- package/dist/packages/design-tokens/package.json +1 -1
- package/dist/packages/playground/browser/{chunk-CVJk2D1R.js → chunk-94bHXqp1.js} +1 -1
- package/dist/packages/playground/browser/{chunk-Dx7lJ4Em.js → chunk-B0HlAWSR.js} +1 -1
- package/dist/packages/playground/browser/{chunk-1BPuEyON.js → chunk-BI1AmKI_.js} +1 -1
- package/dist/packages/playground/browser/{chunk-BB6BGIio.js → chunk-BkTn1FbH.js} +1 -1
- package/dist/packages/playground/browser/{chunk-9WsVM2Jr.js → chunk-BxusNwwt.js} +1 -1
- package/dist/packages/playground/browser/{chunk-Dys-y4r2.js → chunk-CXHFI87Z.js} +1 -1
- package/dist/packages/playground/browser/{chunk-r7FVf56g.js → chunk-D2bm1B8y.js} +1 -1
- package/dist/packages/playground/browser/{chunk-DOAdNQyb.js → chunk-DT7GQcHj.js} +1 -1
- package/dist/packages/playground/browser/{chunk-CTnGRYtS.js → chunk-DXxUFvGw.js} +1 -1
- package/dist/packages/playground/browser/{chunk-2UNSqVln.js → chunk-DbylCYBP.js} +1 -1
- package/dist/packages/playground/browser/{chunk-CKqlZgol.js → chunk-Dd9ht4SU.js} +1 -1
- package/dist/packages/playground/browser/{chunk-BfpxpuVT.js → chunk-ZeGfaGFy.js} +1 -1
- package/dist/packages/playground/browser/{chunk-DNTfJ2xw.js → chunk-iSLqhP12.js} +1 -1
- package/dist/packages/playground/browser/{chunk-CKyrqF9T.js → chunk-rK1aqCb7.js} +1 -1
- package/dist/packages/playground/browser/{chunk-Ym0QZNDJ.js → chunk-zg5PqW0l.js} +1 -1
- package/dist/packages/playground/browser/index.html +1 -1
- package/dist/packages/playground/browser/{main-JTGXHRFU.js → main-BBGOLU74.js} +85 -85
- package/package.json +1 -1
- package/packages/angular-renderer/CHANGELOG.md +21 -0
- package/packages/angular-renderer/package.json +3 -3
- package/packages/angular-renderer/src/components/primitives/button/button.component.ts +13 -4
- package/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.html +1 -0
- package/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.ts +11 -2
- package/packages/angular-renderer/src/components/primitives/chip/chip.component.html +12 -0
- package/packages/angular-renderer/src/components/primitives/chip/chip.component.scss +40 -0
- package/packages/angular-renderer/src/components/primitives/chip/chip.component.ts +85 -0
- package/packages/angular-renderer/src/components/primitives/form-field/form-field.component.ts +18 -1
- package/packages/angular-renderer/src/components/primitives/hbox/hbox.component.ts +18 -1
- package/packages/angular-renderer/src/components/primitives/label/label.component.html +5 -1
- package/packages/angular-renderer/src/components/primitives/label/label.component.ts +11 -2
- package/packages/angular-renderer/src/components/primitives/primitives.a11y.pw.ts +125 -6
- package/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.html +1 -1
- package/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.ts +12 -7
- package/packages/angular-renderer/src/components/primitives/switch/switch.component.html +16 -0
- package/packages/angular-renderer/src/components/primitives/switch/switch.component.scss +66 -0
- package/packages/angular-renderer/src/components/primitives/switch/switch.component.ts +86 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.ts +14 -8
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.ts +18 -1
- package/packages/angular-renderer/src/lib/primitives.provider.ts +4 -1
- package/packages/cli/CHANGELOG.md +8 -0
- package/packages/cli/package.json +2 -2
- package/packages/core/CHANGELOG.md +8 -0
- package/packages/core/package.json +1 -1
- package/packages/design-tokens/CHANGELOG.md +8 -0
- package/packages/design-tokens/package.json +1 -1
- package/packages/devtools/CHANGELOG.md +8 -0
- package/packages/devtools/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/devtools/package.json +1 -1
- package/packages/playground/CHANGELOG.md +8 -0
- package/packages/playground/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/playground/package.json +3 -3
package/package.json
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## 0.0.37 (2026-09-24)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- implement switch and chip form primitives in angular-renderer ([2067756](https://github.com/OrigoStudio/origo-design/commit/2067756))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Hardik Patel @hardikpatelse
|
|
10
|
+
|
|
11
|
+
## 0.0.36 (2026-09-23)
|
|
12
|
+
|
|
13
|
+
### 🚀 Features
|
|
14
|
+
|
|
15
|
+
- **angular-renderer:** add primitive UI components and their unit tests ([e2418b1](https://github.com/OrigoStudio/origo-design/commit/e2418b1))
|
|
16
|
+
- implement primitive UI components and accessibility tests for angular-renderer ([c584dad](https://github.com/OrigoStudio/origo-design/commit/c584dad))
|
|
17
|
+
|
|
18
|
+
### ❤️ Thank You
|
|
19
|
+
|
|
20
|
+
- Hardik Patel @hardikpatelse
|
|
21
|
+
|
|
1
22
|
## 0.0.35 (2026-09-22)
|
|
2
23
|
|
|
3
24
|
### 🚀 Features
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "angular-renderer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.37",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@origo/core": "0.0.
|
|
10
|
-
"@origo/design-tokens": "0.0.
|
|
9
|
+
"@origo/core": "0.0.37",
|
|
10
|
+
"@origo/design-tokens": "0.0.37",
|
|
11
11
|
"tslib": "^2.3.0",
|
|
12
12
|
"@axe-core/playwright": "^4.13.0",
|
|
13
13
|
"@playwright/test": "^1.36.0"
|
|
@@ -28,6 +28,7 @@ export interface ButtonProps {
|
|
|
28
28
|
encapsulation: ViewEncapsulation.ShadowDom,
|
|
29
29
|
host: {
|
|
30
30
|
'[class.origo-button]': 'true',
|
|
31
|
+
'[attr.data-testid]': 'contract().id ?? ""',
|
|
31
32
|
},
|
|
32
33
|
})
|
|
33
34
|
export class ButtonComponent implements OrigoAdapter<ButtonProps> {
|
|
@@ -46,10 +47,18 @@ export class ButtonComponent implements OrigoAdapter<ButtonProps> {
|
|
|
46
47
|
const type = this.contract().props?.type;
|
|
47
48
|
return type === 'button' || type === 'submit' || type === 'reset' ? type : 'button';
|
|
48
49
|
});
|
|
49
|
-
computedAriaLabel = computed(() =>
|
|
50
|
-
|
|
51
|
-
()
|
|
52
|
-
|
|
50
|
+
computedAriaLabel = computed(() => {
|
|
51
|
+
const label = this.contract().props?.['aria-label'];
|
|
52
|
+
return label !== undefined && label !== null && String(label).trim() !== ''
|
|
53
|
+
? String(label)
|
|
54
|
+
: undefined;
|
|
55
|
+
});
|
|
56
|
+
computedAriaDescribedBy = computed(() => {
|
|
57
|
+
const desc = this.contract().props?.['aria-describedby'];
|
|
58
|
+
return desc !== undefined && desc !== null && String(desc).trim() !== ''
|
|
59
|
+
? String(desc)
|
|
60
|
+
: undefined;
|
|
61
|
+
});
|
|
53
62
|
|
|
54
63
|
action = output<void>();
|
|
55
64
|
|
|
@@ -18,6 +18,7 @@ export interface CheckboxProps {
|
|
|
18
18
|
label?: string;
|
|
19
19
|
disabled?: boolean;
|
|
20
20
|
'aria-label'?: string;
|
|
21
|
+
'aria-describedby'?: string;
|
|
21
22
|
required?: boolean;
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -30,7 +31,7 @@ export interface CheckboxProps {
|
|
|
30
31
|
encapsulation: ViewEncapsulation.ShadowDom,
|
|
31
32
|
host: {
|
|
32
33
|
'[class.origo-checkbox]': 'true',
|
|
33
|
-
'[attr.data-testid]': 'contract().id',
|
|
34
|
+
'[attr.data-testid]': 'contract().id ?? ""',
|
|
34
35
|
},
|
|
35
36
|
})
|
|
36
37
|
export class CheckboxComponent implements OrigoAdapter<CheckboxProps> {
|
|
@@ -50,7 +51,15 @@ export class CheckboxComponent implements OrigoAdapter<CheckboxProps> {
|
|
|
50
51
|
computedRequired = computed(() => !!this.contract().props?.required);
|
|
51
52
|
computedAriaLabel = computed(() => {
|
|
52
53
|
const label = this.contract().props?.['aria-label'];
|
|
53
|
-
return label !== undefined && label !== null
|
|
54
|
+
return label !== undefined && label !== null && String(label).trim() !== ''
|
|
55
|
+
? String(label)
|
|
56
|
+
: undefined;
|
|
57
|
+
});
|
|
58
|
+
computedAriaDescribedBy = computed(() => {
|
|
59
|
+
const desc = this.contract().props?.['aria-describedby'];
|
|
60
|
+
return desc !== undefined && desc !== null && String(desc).trim() !== ''
|
|
61
|
+
? String(desc)
|
|
62
|
+
: undefined;
|
|
54
63
|
});
|
|
55
64
|
|
|
56
65
|
private experienceAdapter = inject(WebExperienceAdapterService);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<button
|
|
2
|
+
type="button"
|
|
3
|
+
[id]="contract().id"
|
|
4
|
+
[disabled]="computedDisabled()"
|
|
5
|
+
[class.origo-chip--selected]="selected()"
|
|
6
|
+
[attr.aria-pressed]="selected().toString()"
|
|
7
|
+
[attr.aria-label]="computedAriaLabel() ?? undefined"
|
|
8
|
+
[attr.aria-describedby]="computedAriaDescribedBy() ?? undefined"
|
|
9
|
+
(click)="onClick()"
|
|
10
|
+
>
|
|
11
|
+
<span class="chip-label">{{ computedLabel() }}</span>
|
|
12
|
+
</button>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
button {
|
|
6
|
+
display: inline-flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
padding-inline: var(--origo-spacing-container-padding, 12px);
|
|
10
|
+
padding-block: var(--origo-spacing-container-padding-block, 6px);
|
|
11
|
+
border-radius: var(--origo-radius-chip, 16px);
|
|
12
|
+
border: var(--origo-border-width, 1px) solid var(--origo-color-border, #cccccc);
|
|
13
|
+
background-color: var(--origo-color-surface-variant, #f5f5f5);
|
|
14
|
+
color: var(--origo-color-text-primary, #333333);
|
|
15
|
+
font-family: var(--origo-typography-button-font-family, inherit);
|
|
16
|
+
font-size: var(--origo-typography-button-font-size, 0.875rem);
|
|
17
|
+
font-weight: 500;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
transition: all 0.2s ease-in-out;
|
|
20
|
+
|
|
21
|
+
&:hover:not(:disabled) {
|
|
22
|
+
background-color: var(--origo-color-surface-variant-hover, #ebebeb);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&.origo-chip--selected {
|
|
26
|
+
background-color: var(--origo-color-accent, #005fcc);
|
|
27
|
+
color: var(--origo-color-on-accent, #ffffff);
|
|
28
|
+
border-color: var(--origo-color-accent, #005fcc);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&:focus-visible {
|
|
32
|
+
outline: 2px solid var(--origo-color-focus, #005fcc);
|
|
33
|
+
outline-offset: 2px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&:disabled {
|
|
37
|
+
cursor: not-allowed;
|
|
38
|
+
opacity: var(--origo-opacity-disabled, 0.5);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
input,
|
|
4
|
+
model,
|
|
5
|
+
computed,
|
|
6
|
+
ChangeDetectionStrategy,
|
|
7
|
+
ViewEncapsulation,
|
|
8
|
+
inject,
|
|
9
|
+
effect,
|
|
10
|
+
untracked,
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { InteractionContract } from '@origo/core';
|
|
13
|
+
import { OrigoAdapter, coerceContractProps } from '../../../adapters/web/adapter';
|
|
14
|
+
import { WebExperienceAdapterService } from '../../../adapters/web/experience-adapter.service';
|
|
15
|
+
|
|
16
|
+
export interface ChipProps {
|
|
17
|
+
selected?: boolean;
|
|
18
|
+
label?: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
'aria-label'?: string;
|
|
21
|
+
'aria-describedby'?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Component({
|
|
25
|
+
selector: 'origo-chip',
|
|
26
|
+
standalone: true,
|
|
27
|
+
templateUrl: './chip.component.html',
|
|
28
|
+
styleUrls: ['./chip.component.scss'],
|
|
29
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
30
|
+
encapsulation: ViewEncapsulation.ShadowDom,
|
|
31
|
+
host: {
|
|
32
|
+
'[class.origo-chip]': 'true',
|
|
33
|
+
'[attr.data-testid]': 'contract().id ?? ""',
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
export class ChipComponent implements OrigoAdapter<ChipProps> {
|
|
37
|
+
static readonly contractSchema: Record<
|
|
38
|
+
string,
|
|
39
|
+
'string' | 'number' | 'boolean' | 'object' | 'array'
|
|
40
|
+
> = {
|
|
41
|
+
selected: 'boolean',
|
|
42
|
+
label: 'string',
|
|
43
|
+
disabled: 'boolean',
|
|
44
|
+
};
|
|
45
|
+
static readonly strictContract = false;
|
|
46
|
+
|
|
47
|
+
contract = input.required<InteractionContract<ChipProps>>();
|
|
48
|
+
selected = model<boolean>(false);
|
|
49
|
+
|
|
50
|
+
computedLabel = computed(() => this.contract().props?.label ?? '');
|
|
51
|
+
computedDisabled = computed(() => !!this.contract().props?.disabled);
|
|
52
|
+
computedAriaLabel = computed(() => {
|
|
53
|
+
const v = this.contract().props?.['aria-label'];
|
|
54
|
+
return v !== undefined && v !== null && String(v).trim() !== '' ? String(v) : undefined;
|
|
55
|
+
});
|
|
56
|
+
computedAriaDescribedBy = computed(() => {
|
|
57
|
+
const v = this.contract().props?.['aria-describedby'];
|
|
58
|
+
return v !== undefined && v !== null && String(v).trim() !== '' ? String(v) : undefined;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
private experienceAdapter = inject(WebExperienceAdapterService);
|
|
62
|
+
|
|
63
|
+
constructor() {
|
|
64
|
+
effect(() => {
|
|
65
|
+
const props = this.contract().props;
|
|
66
|
+
const coerced = props
|
|
67
|
+
? coerceContractProps<Record<string, any>>(props, ChipComponent.contractSchema)
|
|
68
|
+
: {};
|
|
69
|
+
const contractVal =
|
|
70
|
+
coerced['selected'] !== undefined ? !!coerced['selected'] : this.selected();
|
|
71
|
+
untracked(() => {
|
|
72
|
+
if (contractVal === this.selected()) return;
|
|
73
|
+
this.selected.set(contractVal);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
onClick() {
|
|
79
|
+
if (this.computedDisabled() || !this.contract().id) return;
|
|
80
|
+
|
|
81
|
+
const nextSelected = !this.selected();
|
|
82
|
+
this.selected.set(nextSelected);
|
|
83
|
+
this.experienceAdapter.updateState(this.contract().id, 'selected', nextSelected);
|
|
84
|
+
}
|
|
85
|
+
}
|
package/packages/angular-renderer/src/components/primitives/form-field/form-field.component.ts
CHANGED
|
@@ -16,6 +16,8 @@ export interface FormFieldProps {
|
|
|
16
16
|
error?: string;
|
|
17
17
|
hint?: string;
|
|
18
18
|
required?: boolean;
|
|
19
|
+
'aria-label'?: string;
|
|
20
|
+
'aria-describedby'?: string;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
@Component({
|
|
@@ -29,7 +31,10 @@ export interface FormFieldProps {
|
|
|
29
31
|
host: {
|
|
30
32
|
'[class.origo-form-field]': 'true',
|
|
31
33
|
'[class.has-error]': '!!computedError()',
|
|
32
|
-
'[attr.data-testid]': 'contract().id',
|
|
34
|
+
'[attr.data-testid]': 'contract().id ?? ""',
|
|
35
|
+
'[attr.role]': 'computedAriaLabel() ? "group" : null',
|
|
36
|
+
'[attr.aria-label]': 'computedAriaLabel()',
|
|
37
|
+
'[attr.aria-describedby]': 'computedAriaDescribedBy()',
|
|
33
38
|
},
|
|
34
39
|
})
|
|
35
40
|
export class FormFieldComponent implements OrigoAdapter<FormFieldProps>, ContainerComponent {
|
|
@@ -49,6 +54,18 @@ export class FormFieldComponent implements OrigoAdapter<FormFieldProps>, Contain
|
|
|
49
54
|
computedError = computed(() => this.contract().props?.error);
|
|
50
55
|
computedHint = computed(() => this.contract().props?.hint);
|
|
51
56
|
computedRequired = computed(() => !!this.contract().props?.required);
|
|
57
|
+
computedAriaLabel = computed(() => {
|
|
58
|
+
const label = this.contract().props?.['aria-label'];
|
|
59
|
+
return label !== undefined && label !== null && String(label).trim() !== ''
|
|
60
|
+
? String(label)
|
|
61
|
+
: undefined;
|
|
62
|
+
});
|
|
63
|
+
computedAriaDescribedBy = computed(() => {
|
|
64
|
+
const desc = this.contract().props?.['aria-describedby'];
|
|
65
|
+
return desc !== undefined && desc !== null && String(desc).trim() !== ''
|
|
66
|
+
? String(desc)
|
|
67
|
+
: undefined;
|
|
68
|
+
});
|
|
52
69
|
|
|
53
70
|
labelContract = computed<InteractionContract<LabelProps>>(() => {
|
|
54
71
|
const parentId = this.contract().id;
|
|
@@ -14,6 +14,8 @@ export interface HBoxProps {
|
|
|
14
14
|
gap?: number | string;
|
|
15
15
|
alignment?: 'start' | 'center' | 'end' | 'stretch';
|
|
16
16
|
padding?: number | string;
|
|
17
|
+
'aria-label'?: string;
|
|
18
|
+
'aria-describedby'?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
@Component({
|
|
@@ -25,11 +27,13 @@ export interface HBoxProps {
|
|
|
25
27
|
encapsulation: ViewEncapsulation.ShadowDom,
|
|
26
28
|
host: {
|
|
27
29
|
'[class.origo-hbox]': 'true',
|
|
28
|
-
'[attr.data-testid]': 'contract().id',
|
|
30
|
+
'[attr.data-testid]': 'contract().id ?? ""',
|
|
29
31
|
'[style.gap]': 'computedGap()',
|
|
30
32
|
'[style.align-items]': 'computedAlignment()',
|
|
31
33
|
'[style.padding-inline]': 'computedPadding()',
|
|
32
34
|
'[style.padding-block]': 'computedPadding()',
|
|
35
|
+
'[attr.aria-label]': 'computedAriaLabel()',
|
|
36
|
+
'[attr.aria-describedby]': 'computedAriaDescribedBy()',
|
|
33
37
|
},
|
|
34
38
|
})
|
|
35
39
|
export class HBoxComponent implements OrigoAdapter<HBoxProps>, ContainerComponent {
|
|
@@ -73,4 +77,17 @@ export class HBoxComponent implements OrigoAdapter<HBoxProps>, ContainerComponen
|
|
|
73
77
|
const num = Number(padding);
|
|
74
78
|
return !isNaN(num) ? `${num}px` : String(padding);
|
|
75
79
|
});
|
|
80
|
+
|
|
81
|
+
computedAriaLabel = computed(() => {
|
|
82
|
+
const label = this.contract().props?.['aria-label'];
|
|
83
|
+
return label !== undefined && label !== null && String(label).trim() !== ''
|
|
84
|
+
? String(label)
|
|
85
|
+
: undefined;
|
|
86
|
+
});
|
|
87
|
+
computedAriaDescribedBy = computed(() => {
|
|
88
|
+
const desc = this.contract().props?.['aria-describedby'];
|
|
89
|
+
return desc !== undefined && desc !== null && String(desc).trim() !== ''
|
|
90
|
+
? String(desc)
|
|
91
|
+
: undefined;
|
|
92
|
+
});
|
|
76
93
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
<label
|
|
1
|
+
<label
|
|
2
|
+
[attr.for]="computedFor()"
|
|
3
|
+
[attr.aria-label]="computedAriaLabel()"
|
|
4
|
+
[attr.aria-describedby]="computedAriaDescribedBy()"
|
|
5
|
+
>
|
|
2
6
|
{{ computedText() }}
|
|
3
7
|
@if (computedRequired()) {
|
|
4
8
|
<span class="required-indicator" aria-hidden="true">*</span>
|
|
@@ -13,6 +13,7 @@ export interface LabelProps {
|
|
|
13
13
|
for?: string;
|
|
14
14
|
required?: boolean;
|
|
15
15
|
'aria-label'?: string;
|
|
16
|
+
'aria-describedby'?: string;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
@Component({
|
|
@@ -24,7 +25,7 @@ export interface LabelProps {
|
|
|
24
25
|
encapsulation: ViewEncapsulation.ShadowDom,
|
|
25
26
|
host: {
|
|
26
27
|
'[class.origo-label]': 'true',
|
|
27
|
-
'[attr.data-testid]': 'contract().id',
|
|
28
|
+
'[attr.data-testid]': 'contract().id ?? ""',
|
|
28
29
|
},
|
|
29
30
|
})
|
|
30
31
|
export class LabelComponent implements OrigoAdapter<LabelProps> {
|
|
@@ -45,6 +46,14 @@ export class LabelComponent implements OrigoAdapter<LabelProps> {
|
|
|
45
46
|
computedRequired = computed(() => !!this.contract().props?.required);
|
|
46
47
|
computedAriaLabel = computed(() => {
|
|
47
48
|
const label = this.contract().props?.['aria-label'];
|
|
48
|
-
return label !== undefined && label !== null
|
|
49
|
+
return label !== undefined && label !== null && String(label).trim() !== ''
|
|
50
|
+
? String(label)
|
|
51
|
+
: undefined;
|
|
52
|
+
});
|
|
53
|
+
computedAriaDescribedBy = computed(() => {
|
|
54
|
+
const desc = this.contract().props?.['aria-describedby'];
|
|
55
|
+
return desc !== undefined && desc !== null && String(desc).trim() !== ''
|
|
56
|
+
? String(desc)
|
|
57
|
+
: undefined;
|
|
49
58
|
});
|
|
50
59
|
}
|
|
@@ -13,7 +13,7 @@ test.describe('Primitives Accessibility', () => {
|
|
|
13
13
|
<script>
|
|
14
14
|
const host = document.getElementById('host');
|
|
15
15
|
const shadow = host.attachShadow({mode: 'open'});
|
|
16
|
-
shadow.innerHTML = '<button class="origo-button" aria-label="Accessible Button">Click Me</button>';
|
|
16
|
+
shadow.innerHTML = '<button class="origo-button" aria-label="Accessible Button">Click Me</button><button class="origo-button" aria-label="Disabled Button" disabled>Disabled</button>';
|
|
17
17
|
</script>
|
|
18
18
|
</main>
|
|
19
19
|
`);
|
|
@@ -31,7 +31,7 @@ test.describe('Primitives Accessibility', () => {
|
|
|
31
31
|
<script>
|
|
32
32
|
const host = document.getElementById('host');
|
|
33
33
|
const shadow = host.attachShadow({mode: 'open'});
|
|
34
|
-
shadow.innerHTML = '<label for="test-input">Test Input</label><input id="test-input" type="text" class="origo-text-input" placeholder="Enter text" />';
|
|
34
|
+
shadow.innerHTML = '<label for="test-input">Test Input</label><input id="test-input" type="text" class="origo-text-input" placeholder="Enter text" /><label for="test-input-disabled">Test Input Disabled</label><input id="test-input-disabled" type="text" class="origo-text-input" placeholder="Disabled text" disabled />';
|
|
35
35
|
</script>
|
|
36
36
|
</main>
|
|
37
37
|
`);
|
|
@@ -49,7 +49,7 @@ test.describe('Primitives Accessibility', () => {
|
|
|
49
49
|
<script>
|
|
50
50
|
const host = document.getElementById('host');
|
|
51
51
|
const shadow = host.attachShadow({mode: 'open'});
|
|
52
|
-
shadow.innerHTML = '<label for="test-select">Test Select</label><select id="test-select" class="origo-select"><option value="1">One</option></select>';
|
|
52
|
+
shadow.innerHTML = '<label for="test-select">Test Select</label><select id="test-select" class="origo-select"><option value="1">One</option></select><label for="test-select-disabled">Test Select Disabled</label><select id="test-select-disabled" class="origo-select" disabled><option value="1">One</option></select>';
|
|
53
53
|
</script>
|
|
54
54
|
</main>
|
|
55
55
|
`);
|
|
@@ -66,7 +66,7 @@ test.describe('Primitives Accessibility', () => {
|
|
|
66
66
|
<script>
|
|
67
67
|
const host = document.getElementById('host');
|
|
68
68
|
const shadow = host.attachShadow({mode: 'open'});
|
|
69
|
-
shadow.innerHTML = '<input type="checkbox" id="test-check" class="origo-checkbox" /><label for="test-check">Test Checkbox</label>';
|
|
69
|
+
shadow.innerHTML = '<input type="checkbox" id="test-check" class="origo-checkbox" /><label for="test-check">Test Checkbox</label><input type="checkbox" id="test-check-disabled" class="origo-checkbox" disabled /><label for="test-check-disabled">Disabled Checkbox</label>';
|
|
70
70
|
</script>
|
|
71
71
|
</main>
|
|
72
72
|
`);
|
|
@@ -83,7 +83,7 @@ test.describe('Primitives Accessibility', () => {
|
|
|
83
83
|
<script>
|
|
84
84
|
const host = document.getElementById('host');
|
|
85
85
|
const shadow = host.attachShadow({mode: 'open'});
|
|
86
|
-
shadow.innerHTML = '<fieldset class="origo-radio-group"><legend>Radio Group</legend><input type="radio" id="radio-1" name="rg" value="1" /><label for="radio-1">One</label></fieldset>';
|
|
86
|
+
shadow.innerHTML = '<fieldset class="origo-radio-group"><legend>Radio Group</legend><input type="radio" id="radio-1" name="rg" value="1" /><label for="radio-1">One</label></fieldset><fieldset class="origo-radio-group" disabled><legend>Disabled Radio Group</legend><input type="radio" id="radio-2" name="rg2" value="2" disabled /><label for="radio-2">Two</label></fieldset>';
|
|
87
87
|
</script>
|
|
88
88
|
</main>
|
|
89
89
|
`);
|
|
@@ -100,7 +100,7 @@ test.describe('Primitives Accessibility', () => {
|
|
|
100
100
|
<script>
|
|
101
101
|
const host = document.getElementById('host');
|
|
102
102
|
const shadow = host.attachShadow({mode: 'open'});
|
|
103
|
-
shadow.innerHTML = '<label for="test-textarea">Test Textarea</label><textarea id="test-textarea" class="origo-textarea"></textarea>';
|
|
103
|
+
shadow.innerHTML = '<label for="test-textarea">Test Textarea</label><textarea id="test-textarea" class="origo-textarea"></textarea><label for="test-textarea-disabled">Disabled Textarea</label><textarea id="test-textarea-disabled" class="origo-textarea" disabled></textarea>';
|
|
104
104
|
</script>
|
|
105
105
|
</main>
|
|
106
106
|
`);
|
|
@@ -260,4 +260,123 @@ test.describe('Primitives Accessibility', () => {
|
|
|
260
260
|
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
|
|
261
261
|
expect(accessibilityScanResults.violations).toEqual([]);
|
|
262
262
|
});
|
|
263
|
+
test('VBox should not have any automatically detectable accessibility issues', async ({
|
|
264
|
+
page,
|
|
265
|
+
}) => {
|
|
266
|
+
await page.setContent(`
|
|
267
|
+
<main>
|
|
268
|
+
<div id="host"></div>
|
|
269
|
+
<script>
|
|
270
|
+
const host = document.getElementById('host');
|
|
271
|
+
const shadow = host.attachShadow({mode: 'open'});
|
|
272
|
+
shadow.innerHTML = '<div class="origo-vbox" style="display: flex; flex-direction: column; gap: 10px;"><div>Item 1</div></div>';
|
|
273
|
+
</script>
|
|
274
|
+
</main>
|
|
275
|
+
`);
|
|
276
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
|
|
277
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
278
|
+
});
|
|
279
|
+
// Story 9.5 — Switch primitive
|
|
280
|
+
test('Switch should not have any automatically detectable accessibility issues (default)', async ({
|
|
281
|
+
page,
|
|
282
|
+
}) => {
|
|
283
|
+
await page.setContent(`
|
|
284
|
+
<main>
|
|
285
|
+
<div id="host"></div>
|
|
286
|
+
<script>
|
|
287
|
+
const host = document.getElementById('host');
|
|
288
|
+
const shadow = host.attachShadow({mode: 'open'});
|
|
289
|
+
shadow.innerHTML = '<label><input type="checkbox" role="switch" aria-checked="false" /> Notifications</label>';
|
|
290
|
+
</script>
|
|
291
|
+
</main>
|
|
292
|
+
`);
|
|
293
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).include('#host').analyze();
|
|
294
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('Switch should not have any automatically detectable accessibility issues (checked)', async ({
|
|
298
|
+
page,
|
|
299
|
+
}) => {
|
|
300
|
+
await page.setContent(`
|
|
301
|
+
<main>
|
|
302
|
+
<div id="host"></div>
|
|
303
|
+
<script>
|
|
304
|
+
const host = document.getElementById('host');
|
|
305
|
+
const shadow = host.attachShadow({mode: 'open'});
|
|
306
|
+
shadow.innerHTML = '<label><input type="checkbox" role="switch" aria-checked="true" checked /> Notifications</label>';
|
|
307
|
+
</script>
|
|
308
|
+
</main>
|
|
309
|
+
`);
|
|
310
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).include('#host').analyze();
|
|
311
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('Switch should not have any automatically detectable accessibility issues (disabled)', async ({
|
|
315
|
+
page,
|
|
316
|
+
}) => {
|
|
317
|
+
await page.setContent(`
|
|
318
|
+
<main>
|
|
319
|
+
<div id="host"></div>
|
|
320
|
+
<script>
|
|
321
|
+
const host = document.getElementById('host');
|
|
322
|
+
const shadow = host.attachShadow({mode: 'open'});
|
|
323
|
+
shadow.innerHTML = '<label><input type="checkbox" role="switch" aria-checked="false" disabled /> Notifications</label>';
|
|
324
|
+
</script>
|
|
325
|
+
</main>
|
|
326
|
+
`);
|
|
327
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).include('#host').analyze();
|
|
328
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Story 9.5 — Chip primitive
|
|
332
|
+
test('Chip should not have any automatically detectable accessibility issues (default)', async ({
|
|
333
|
+
page,
|
|
334
|
+
}) => {
|
|
335
|
+
await page.setContent(`
|
|
336
|
+
<main>
|
|
337
|
+
<div id="host"></div>
|
|
338
|
+
<script>
|
|
339
|
+
const host = document.getElementById('host');
|
|
340
|
+
const shadow = host.attachShadow({mode: 'open'});
|
|
341
|
+
shadow.innerHTML = '<button type="button" class="origo-chip" aria-pressed="false">Angular</button>';
|
|
342
|
+
</script>
|
|
343
|
+
</main>
|
|
344
|
+
`);
|
|
345
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).include('#host').analyze();
|
|
346
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('Chip should not have any automatically detectable accessibility issues (selected)', async ({
|
|
350
|
+
page,
|
|
351
|
+
}) => {
|
|
352
|
+
await page.setContent(`
|
|
353
|
+
<main>
|
|
354
|
+
<div id="host"></div>
|
|
355
|
+
<script>
|
|
356
|
+
const host = document.getElementById('host');
|
|
357
|
+
const shadow = host.attachShadow({mode: 'open'});
|
|
358
|
+
shadow.innerHTML = '<button type="button" class="origo-chip origo-chip--selected" aria-pressed="true">Angular</button>';
|
|
359
|
+
</script>
|
|
360
|
+
</main>
|
|
361
|
+
`);
|
|
362
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).include('#host').analyze();
|
|
363
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('Chip should not have any automatically detectable accessibility issues (disabled)', async ({
|
|
367
|
+
page,
|
|
368
|
+
}) => {
|
|
369
|
+
await page.setContent(`
|
|
370
|
+
<main>
|
|
371
|
+
<div id="host"></div>
|
|
372
|
+
<script>
|
|
373
|
+
const host = document.getElementById('host');
|
|
374
|
+
const shadow = host.attachShadow({mode: 'open'});
|
|
375
|
+
shadow.innerHTML = '<button type="button" class="origo-chip" aria-pressed="false" disabled>Angular</button>';
|
|
376
|
+
</script>
|
|
377
|
+
</main>
|
|
378
|
+
`);
|
|
379
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).include('#host').analyze();
|
|
380
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
381
|
+
});
|
|
263
382
|
});
|
package/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.html
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<fieldset [disabled]="computedDisabled()" [attr.aria-
|
|
1
|
+
<fieldset [disabled]="computedDisabled()" [attr.aria-describedby]="computedAriaDescribedBy()">
|
|
2
2
|
<legend class="visually-hidden">{{ computedAriaLabel() ?? 'Radio Group' }}</legend>
|
|
3
3
|
|
|
4
4
|
<div class="radio-options">
|
package/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.ts
CHANGED
|
@@ -8,9 +8,7 @@ import {
|
|
|
8
8
|
inject,
|
|
9
9
|
effect,
|
|
10
10
|
untracked,
|
|
11
|
-
SecurityContext,
|
|
12
11
|
} from '@angular/core';
|
|
13
|
-
import { DomSanitizer } from '@angular/platform-browser';
|
|
14
12
|
import { InteractionContract } from '@origo/core';
|
|
15
13
|
import { OrigoAdapter } from '../../../adapters/web/adapter';
|
|
16
14
|
import { WebExperienceAdapterService } from '../../../adapters/web/experience-adapter.service';
|
|
@@ -20,6 +18,7 @@ export interface RadioGroupProps {
|
|
|
20
18
|
value?: string;
|
|
21
19
|
disabled?: boolean;
|
|
22
20
|
'aria-label'?: string;
|
|
21
|
+
'aria-describedby'?: string;
|
|
23
22
|
required?: boolean;
|
|
24
23
|
}
|
|
25
24
|
|
|
@@ -32,7 +31,7 @@ export interface RadioGroupProps {
|
|
|
32
31
|
encapsulation: ViewEncapsulation.ShadowDom,
|
|
33
32
|
host: {
|
|
34
33
|
'[class.origo-radio-group]': 'true',
|
|
35
|
-
'[attr.data-testid]': 'contract().id',
|
|
34
|
+
'[attr.data-testid]': 'contract().id ?? ""',
|
|
36
35
|
},
|
|
37
36
|
})
|
|
38
37
|
export class RadioGroupComponent implements OrigoAdapter<RadioGroupProps> {
|
|
@@ -55,11 +54,18 @@ export class RadioGroupComponent implements OrigoAdapter<RadioGroupProps> {
|
|
|
55
54
|
computedRequired = computed(() => !!this.contract().props?.required);
|
|
56
55
|
computedAriaLabel = computed(() => {
|
|
57
56
|
const label = this.contract().props?.['aria-label'];
|
|
58
|
-
return label !== undefined && label !== null
|
|
57
|
+
return label !== undefined && label !== null && String(label).trim() !== ''
|
|
58
|
+
? String(label)
|
|
59
|
+
: undefined;
|
|
60
|
+
});
|
|
61
|
+
computedAriaDescribedBy = computed(() => {
|
|
62
|
+
const desc = this.contract().props?.['aria-describedby'];
|
|
63
|
+
return desc !== undefined && desc !== null && String(desc).trim() !== ''
|
|
64
|
+
? String(desc)
|
|
65
|
+
: undefined;
|
|
59
66
|
});
|
|
60
67
|
|
|
61
68
|
private experienceAdapter = inject(WebExperienceAdapterService);
|
|
62
|
-
private sanitizer = inject(DomSanitizer);
|
|
63
69
|
|
|
64
70
|
constructor() {
|
|
65
71
|
effect(() => {
|
|
@@ -78,8 +84,7 @@ export class RadioGroupComponent implements OrigoAdapter<RadioGroupProps> {
|
|
|
78
84
|
if (!target || !target.checked || this.computedDisabled()) return;
|
|
79
85
|
|
|
80
86
|
const rawValue = target.value;
|
|
81
|
-
const sanitizedValue =
|
|
82
|
-
this.sanitizer.sanitize(SecurityContext.HTML, rawValue) || rawValue || '';
|
|
87
|
+
const sanitizedValue = String(rawValue);
|
|
83
88
|
|
|
84
89
|
this.value.set(sanitizedValue);
|
|
85
90
|
this.experienceAdapter.updateState(this.contract().id, 'value', sanitizedValue);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<label [for]="contract().id" [class.disabled]="computedDisabled()">
|
|
2
|
+
<input
|
|
3
|
+
type="checkbox"
|
|
4
|
+
role="switch"
|
|
5
|
+
[id]="contract().id"
|
|
6
|
+
[checked]="checked()"
|
|
7
|
+
[disabled]="computedDisabled()"
|
|
8
|
+
[attr.aria-checked]="checked().toString()"
|
|
9
|
+
[attr.aria-label]="computedAriaLabel() ?? undefined"
|
|
10
|
+
[attr.aria-describedby]="computedAriaDescribedBy() ?? undefined"
|
|
11
|
+
(change)="onChange($event)"
|
|
12
|
+
/>
|
|
13
|
+
@if (computedLabel()) {
|
|
14
|
+
<span class="label-text">{{ computedLabel() }}</span>
|
|
15
|
+
}
|
|
16
|
+
</label>
|