@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.
Files changed (93) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/packages/angular-renderer/CHANGELOG.md +21 -0
  3. package/dist/packages/angular-renderer/package.json +3 -3
  4. package/dist/packages/angular-renderer/src/components/primitives/button/button.component.js +13 -2
  5. package/dist/packages/angular-renderer/src/components/primitives/button/button.component.js.map +1 -1
  6. package/dist/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.d.ts +2 -0
  7. package/dist/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.js +10 -2
  8. package/dist/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.js.map +1 -1
  9. package/dist/packages/angular-renderer/src/components/primitives/chip/chip.component.d.ts +22 -0
  10. package/dist/packages/angular-renderer/src/components/primitives/chip/chip.component.js +79 -0
  11. package/dist/packages/angular-renderer/src/components/primitives/chip/chip.component.js.map +1 -0
  12. package/dist/packages/angular-renderer/src/components/primitives/form-field/form-field.component.d.ts +4 -0
  13. package/dist/packages/angular-renderer/src/components/primitives/form-field/form-field.component.js +16 -1
  14. package/dist/packages/angular-renderer/src/components/primitives/form-field/form-field.component.js.map +1 -1
  15. package/dist/packages/angular-renderer/src/components/primitives/hbox/hbox.component.d.ts +4 -0
  16. package/dist/packages/angular-renderer/src/components/primitives/hbox/hbox.component.js +15 -1
  17. package/dist/packages/angular-renderer/src/components/primitives/hbox/hbox.component.js.map +1 -1
  18. package/dist/packages/angular-renderer/src/components/primitives/label/label.component.d.ts +2 -0
  19. package/dist/packages/angular-renderer/src/components/primitives/label/label.component.js +10 -2
  20. package/dist/packages/angular-renderer/src/components/primitives/label/label.component.js.map +1 -1
  21. package/dist/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.d.ts +2 -1
  22. package/dist/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.js +11 -5
  23. package/dist/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.js.map +1 -1
  24. package/dist/packages/angular-renderer/src/components/primitives/switch/switch.component.d.ts +22 -0
  25. package/dist/packages/angular-renderer/src/components/primitives/switch/switch.component.js +80 -0
  26. package/dist/packages/angular-renderer/src/components/primitives/switch/switch.component.js.map +1 -0
  27. package/dist/packages/angular-renderer/src/components/primitives/text-input/text-input.component.d.ts +0 -1
  28. package/dist/packages/angular-renderer/src/components/primitives/text-input/text-input.component.js +14 -5
  29. package/dist/packages/angular-renderer/src/components/primitives/text-input/text-input.component.js.map +1 -1
  30. package/dist/packages/angular-renderer/src/components/primitives/vbox/vbox.component.d.ts +4 -0
  31. package/dist/packages/angular-renderer/src/components/primitives/vbox/vbox.component.js +15 -1
  32. package/dist/packages/angular-renderer/src/components/primitives/vbox/vbox.component.js.map +1 -1
  33. package/dist/packages/angular-renderer/src/lib/primitives.provider.js +4 -0
  34. package/dist/packages/angular-renderer/src/lib/primitives.provider.js.map +1 -1
  35. package/dist/packages/cli/CHANGELOG.md +8 -0
  36. package/dist/packages/cli/package.json +2 -2
  37. package/dist/packages/cli/package.json.bak +2 -2
  38. package/dist/packages/core/CHANGELOG.md +8 -0
  39. package/dist/packages/core/package.json +1 -1
  40. package/dist/packages/core/package.json.bak +1 -1
  41. package/dist/packages/design-tokens/CHANGELOG.md +8 -0
  42. package/dist/packages/design-tokens/package.json +1 -1
  43. package/dist/packages/playground/browser/{chunk-CVJk2D1R.js → chunk-94bHXqp1.js} +1 -1
  44. package/dist/packages/playground/browser/{chunk-Dx7lJ4Em.js → chunk-B0HlAWSR.js} +1 -1
  45. package/dist/packages/playground/browser/{chunk-1BPuEyON.js → chunk-BI1AmKI_.js} +1 -1
  46. package/dist/packages/playground/browser/{chunk-BB6BGIio.js → chunk-BkTn1FbH.js} +1 -1
  47. package/dist/packages/playground/browser/{chunk-9WsVM2Jr.js → chunk-BxusNwwt.js} +1 -1
  48. package/dist/packages/playground/browser/{chunk-Dys-y4r2.js → chunk-CXHFI87Z.js} +1 -1
  49. package/dist/packages/playground/browser/{chunk-r7FVf56g.js → chunk-D2bm1B8y.js} +1 -1
  50. package/dist/packages/playground/browser/{chunk-DOAdNQyb.js → chunk-DT7GQcHj.js} +1 -1
  51. package/dist/packages/playground/browser/{chunk-CTnGRYtS.js → chunk-DXxUFvGw.js} +1 -1
  52. package/dist/packages/playground/browser/{chunk-2UNSqVln.js → chunk-DbylCYBP.js} +1 -1
  53. package/dist/packages/playground/browser/{chunk-CKqlZgol.js → chunk-Dd9ht4SU.js} +1 -1
  54. package/dist/packages/playground/browser/{chunk-BfpxpuVT.js → chunk-ZeGfaGFy.js} +1 -1
  55. package/dist/packages/playground/browser/{chunk-DNTfJ2xw.js → chunk-iSLqhP12.js} +1 -1
  56. package/dist/packages/playground/browser/{chunk-CKyrqF9T.js → chunk-rK1aqCb7.js} +1 -1
  57. package/dist/packages/playground/browser/{chunk-Ym0QZNDJ.js → chunk-zg5PqW0l.js} +1 -1
  58. package/dist/packages/playground/browser/index.html +1 -1
  59. package/dist/packages/playground/browser/{main-JTGXHRFU.js → main-BBGOLU74.js} +85 -85
  60. package/package.json +1 -1
  61. package/packages/angular-renderer/CHANGELOG.md +21 -0
  62. package/packages/angular-renderer/package.json +3 -3
  63. package/packages/angular-renderer/src/components/primitives/button/button.component.ts +13 -4
  64. package/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.html +1 -0
  65. package/packages/angular-renderer/src/components/primitives/checkbox/checkbox.component.ts +11 -2
  66. package/packages/angular-renderer/src/components/primitives/chip/chip.component.html +12 -0
  67. package/packages/angular-renderer/src/components/primitives/chip/chip.component.scss +40 -0
  68. package/packages/angular-renderer/src/components/primitives/chip/chip.component.ts +85 -0
  69. package/packages/angular-renderer/src/components/primitives/form-field/form-field.component.ts +18 -1
  70. package/packages/angular-renderer/src/components/primitives/hbox/hbox.component.ts +18 -1
  71. package/packages/angular-renderer/src/components/primitives/label/label.component.html +5 -1
  72. package/packages/angular-renderer/src/components/primitives/label/label.component.ts +11 -2
  73. package/packages/angular-renderer/src/components/primitives/primitives.a11y.pw.ts +125 -6
  74. package/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.html +1 -1
  75. package/packages/angular-renderer/src/components/primitives/radio-group/radio-group.component.ts +12 -7
  76. package/packages/angular-renderer/src/components/primitives/switch/switch.component.html +16 -0
  77. package/packages/angular-renderer/src/components/primitives/switch/switch.component.scss +66 -0
  78. package/packages/angular-renderer/src/components/primitives/switch/switch.component.ts +86 -0
  79. package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.ts +14 -8
  80. package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.ts +18 -1
  81. package/packages/angular-renderer/src/lib/primitives.provider.ts +4 -1
  82. package/packages/cli/CHANGELOG.md +8 -0
  83. package/packages/cli/package.json +2 -2
  84. package/packages/core/CHANGELOG.md +8 -0
  85. package/packages/core/package.json +1 -1
  86. package/packages/design-tokens/CHANGELOG.md +8 -0
  87. package/packages/design-tokens/package.json +1 -1
  88. package/packages/devtools/CHANGELOG.md +8 -0
  89. package/packages/devtools/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  90. package/packages/devtools/package.json +1 -1
  91. package/packages/playground/CHANGELOG.md +8 -0
  92. package/packages/playground/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  93. package/packages/playground/package.json +3 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@origostudio/origo-design",
3
- "version": "0.0.35",
3
+ "version": "0.0.37",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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.35",
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.35",
10
- "@origo/design-tokens": "0.0.35",
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(() => this.contract().props?.['aria-label'] as string | undefined);
50
- computedAriaDescribedBy = computed(
51
- () => this.contract().props?.['aria-describedby'] as string | undefined
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
 
@@ -5,6 +5,7 @@
5
5
  [disabled]="computedDisabled()"
6
6
  [required]="computedRequired()"
7
7
  [attr.aria-label]="computedAriaLabel()"
8
+ [attr.aria-describedby]="computedAriaDescribedBy()"
8
9
  (change)="onChange($event)"
9
10
  [checked]="checked()"
10
11
  />
@@ -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 ? String(label) : undefined;
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
+ }
@@ -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 [attr.for]="computedFor()" [attr.aria-label]="computedAriaLabel()">
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 ? String(label) : undefined;
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
  });
@@ -1,4 +1,4 @@
1
- <fieldset [disabled]="computedDisabled()" [attr.aria-label]="computedAriaLabel()">
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">
@@ -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 ? String(label) : undefined;
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>