@dile/ui 3.17.1 → 3.18.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.
Files changed (117) hide show
  1. package/components/steps/index.js +2 -0
  2. package/components/steps/src/DileStep.js +90 -0
  3. package/components/steps/src/DileSteps.js +171 -0
  4. package/components/steps/step.js +5 -0
  5. package/components/steps/steps.js +5 -0
  6. package/components/toast/src/DileToast.js +2 -1
  7. package/components/toast/src/DileToastPersistent.js +1 -1
  8. package/package.json +4 -4
  9. package/components/accordion/accordion.spec.js +0 -253
  10. package/components/app-drawer/app-drawer.spec.js +0 -262
  11. package/components/avatar/avatar.spec.js +0 -25
  12. package/components/bottom-nav/__screenshots__/bottom-nav.spec.js/dile-bottom-nav-places-the-sheet-and-the-overlay-above-the-bar-1.png +0 -0
  13. package/components/bottom-nav/bottom-nav.spec.js +0 -340
  14. package/components/box-selector/box-selector-item.spec.js +0 -27
  15. package/components/box-selector/box-selector.spec.js +0 -27
  16. package/components/breadcrumbs/breadcrumbs-item.spec.js +0 -28
  17. package/components/breadcrumbs/breadcrumbs.spec.js +0 -33
  18. package/components/button/button.spec.js +0 -234
  19. package/components/card/card-slide.spec.js +0 -30
  20. package/components/card/card.spec.js +0 -26
  21. package/components/checkbox/checkbox.spec.js +0 -235
  22. package/components/chip/chip.spec.js +0 -31
  23. package/components/color-picker/color-picker.spec.js +0 -133
  24. package/components/confirm/confirm.spec.js +0 -57
  25. package/components/confirm-text/confirm-text.spec.js +0 -29
  26. package/components/copy-markdown-url/copy-markdown-url.spec.js +0 -27
  27. package/components/copy-text/copy-text.spec.js +0 -38
  28. package/components/data-grid/__screenshots__/data-grid.spec.js/dile-data-grid-dispatches-item-checkbox-changed-and-dile-data-grid-item-selected-when-a-checkbox-is-toggled-1.png +0 -0
  29. package/components/data-grid/data-grid.spec.js +0 -421
  30. package/components/drop-file/drop-file.spec.js +0 -214
  31. package/components/drop-file/file-preview.spec.js +0 -35
  32. package/components/floating-feedback/floating-feedback.spec.js +0 -30
  33. package/components/floating-menu/floating-menu-item.spec.js +0 -27
  34. package/components/floating-menu/floating-menu.spec.js +0 -31
  35. package/components/form-separator/form-separator.spec.js +0 -28
  36. package/components/hamburger/hamburger.spec.js +0 -28
  37. package/components/icon/icon.spec.js +0 -31
  38. package/components/icon-switch/icon-switch-group.spec.js +0 -46
  39. package/components/icon-switch/icon-switch.spec.js +0 -35
  40. package/components/info-box/info-box.spec.js +0 -38
  41. package/components/inline-feedback/inline-feedback.spec.js +0 -46
  42. package/components/input/input-contact.spec.js +0 -32
  43. package/components/input/input-icon.spec.js +0 -166
  44. package/components/input/input-integer.spec.js +0 -31
  45. package/components/input/input-mask.spec.js +0 -314
  46. package/components/input/input-message.spec.js +0 -27
  47. package/components/input/input-money.spec.js +0 -30
  48. package/components/input/input-number-mask.spec.js +0 -48
  49. package/components/input/input-number.spec.js +0 -109
  50. package/components/input/input-percentage.spec.js +0 -31
  51. package/components/input/input-search.spec.js +0 -91
  52. package/components/input/input.spec.js +0 -47
  53. package/components/light-mode-switch/light-mode-switch.spec.js +0 -34
  54. package/components/menu-hamburger/menu-hamburger.spec.js +0 -34
  55. package/components/menu-overlay/menu-overlay.spec.js +0 -44
  56. package/components/message/message.spec.js +0 -42
  57. package/components/modal/__screenshots__/modal.spec.js/dile-modal-closes-only-the-topmost-modal-when-Esc-is-pressed-1.png +0 -0
  58. package/components/modal/__screenshots__/modal.spec.js/dile-modal-dispatches-dile-modal-closed-and-animates-when-the-opened-property-is-set-to-false-1.png +0 -0
  59. package/components/modal/__screenshots__/modal.spec.js/dile-modal-does-not-close-a-blocking-modal-when-Esc-is-pressed-1.png +0 -0
  60. package/components/modal/__screenshots__/modal.spec.js/dile-modal-does-not-close-the-modal-below-when-the-topmost-modal-is-blocking-1.png +0 -0
  61. package/components/modal/__screenshots__/modal.spec.js/dile-modal-ignores-transitionend-events-coming-from-inner-elements-1.png +0 -0
  62. package/components/modal/__screenshots__/modal.spec.js/dile-modal-keeps-the-focus-on-content-focused-by-the-user-when-opening-1.png +0 -0
  63. package/components/modal/__screenshots__/modal.spec.js/dile-modal-opens-and-closes-through-the-opened-attribute-1.png +0 -0
  64. package/components/modal/__screenshots__/modal.spec.js/dile-modal-reflects-the-opened-property-to-the-attribute-1.png +0 -0
  65. package/components/modal/__screenshots__/modal.spec.js/dile-modal-releases-the-background-layer-after-the-close-animation-even-without-transitionend-1.png +0 -0
  66. package/components/modal/__screenshots__/modal.spec.js/dile-modal-releases-the-background-layer-immediately-when-the-animation-duration-is-0s-1.png +0 -0
  67. package/components/modal/modal-help.spec.js +0 -32
  68. package/components/modal/modal.spec.js +0 -250
  69. package/components/nav/nav.spec.js +0 -33
  70. package/components/number-picker/number-picker-element.spec.js +0 -30
  71. package/components/number-picker/number-picker.spec.js +0 -30
  72. package/components/order-switch/__screenshots__/order-switch.spec.js/dile-order-switch-clicking-the-label-re-announces-the-current-value-without-toggling-when-not-selected-1.png +0 -0
  73. package/components/order-switch/__screenshots__/order-switch.spec.js/dile-order-switch-clicking-the-label-toggles-the-order-when-selected-1.png +0 -0
  74. package/components/order-switch/__screenshots__/order-switch.spec.js/dile-order-switch-defaults-to-ascending-order-1.png +0 -0
  75. package/components/order-switch/order-switch.spec.js +0 -57
  76. package/components/otp-input/otp-input.spec.js +0 -41
  77. package/components/pages/pages.spec.js +0 -44
  78. package/components/password/password.spec.js +0 -35
  79. package/components/progress-bar/progress-bar.spec.js +0 -28
  80. package/components/radio-group/__screenshots__/radio-group-dialog.spec.js/dile-radio-group-dialog-renders-dialog-items-with-labels-and-values-1.png +0 -0
  81. package/components/radio-group/__screenshots__/radio-group-dialog.spec.js/dile-radio-group-dialog-renders-items-with-optional-description-1.png +0 -0
  82. package/components/radio-group/__screenshots__/radio-group-dialog.spec.js/dile-radio-group-dialog-updates-value-when-an-option-is-selected-1.png +0 -0
  83. package/components/radio-group/radio-group-dialog.spec.js +0 -126
  84. package/components/radio-group/radio-group.spec.js +0 -128
  85. package/components/radio-group/radio.spec.js +0 -32
  86. package/components/range/range.spec.js +0 -36
  87. package/components/rating/rating.spec.js +0 -34
  88. package/components/rating/star.spec.js +0 -30
  89. package/components/rating-scale-question/rating-scale-option.spec.js +0 -32
  90. package/components/rating-scale-question/rating-scale-question.spec.js +0 -35
  91. package/components/select/__screenshots__/select-search.spec.js/dile-select-search-Value-API-uses-the-label-as-accessible-name-of-the-selected-field-1.png +0 -0
  92. package/components/select/select-ajax-overlay.spec.js +0 -469
  93. package/components/select/select-overlay.spec.js +0 -612
  94. package/components/select/select-search.spec.js +0 -318
  95. package/components/select/select.spec.js +0 -574
  96. package/components/selector/selector-item.spec.js +0 -40
  97. package/components/selector/selector-overlay.spec.js +0 -139
  98. package/components/selector/selector.spec.js +0 -45
  99. package/components/slide-menu/slide-menu.spec.js +0 -38
  100. package/components/slide-show/slide-show.spec.js +0 -34
  101. package/components/social-icon/social-icon.spec.js +0 -26
  102. package/components/spinner/spinner-bar.spec.js +0 -26
  103. package/components/spinner/spinner-block.spec.js +0 -21
  104. package/components/spinner/spinner-horizontal.spec.js +0 -25
  105. package/components/spinner/spinner-icon.spec.js +0 -25
  106. package/components/spinner/spinner-modal.spec.js +0 -26
  107. package/components/spinner/spinner.spec.js +0 -26
  108. package/components/switch/switch.spec.js +0 -33
  109. package/components/tabs/tabs.spec.js +0 -85
  110. package/components/textarea/textarea.spec.js +0 -39
  111. package/components/time-picker/time-picker.spec.js +0 -26
  112. package/components/timer/timer.spec.js +0 -31
  113. package/components/toast/toast.spec.js +0 -86
  114. package/components/tooltip/chip-tooltip.spec.js +0 -29
  115. package/components/tooltip/tooltip.spec.js +0 -32
  116. package/components/web-visor/web-visor.spec.js +0 -32
  117. package/mixins/form/src/DileForm.spec.js +0 -29
@@ -0,0 +1,2 @@
1
+ export { DileSteps } from './src/DileSteps.js';
2
+ export { DileStep } from './src/DileStep.js';
@@ -0,0 +1,90 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ export class DileStep extends LitElement {
4
+ static styles = [
5
+ css`
6
+ :host {
7
+ display: block;
8
+ width: var(--dile-step-size, 1.5rem);
9
+ height: var(--dile-step-size, 1.5rem);
10
+ }
11
+ .step {
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ width: 100%;
16
+ height: 100%;
17
+ box-sizing: border-box;
18
+ background-color: var(--dile-step-background-color, var(--dile-background-color, #fff));
19
+ border-radius: 3rem;
20
+ font-size: var(--dile-step-font-size, 0.8rem);
21
+ font-weight: normal;
22
+ font-family: inherit;
23
+ padding: 0;
24
+ border: var(--dile-step-border-width, 2px) solid var(--dile-step-border-color, #ddd);
25
+ color: var(--dile-step-text-color, var(--dile-on-background-color, #303030));
26
+ cursor: default;
27
+ }
28
+ button.step:focus-visible {
29
+ outline: 2px solid var(--dile-step-active-border-color, var(--dile-primary-dark-color, #0039cb));
30
+ outline-offset: 2px;
31
+ }
32
+ :host([reached]) .step {
33
+ background-color: var(--dile-step-reached-background-color, var(--dile-primary-color, #2962ff));
34
+ border-color: var(--dile-step-reached-background-color, var(--dile-primary-color, #2962ff));
35
+ color: var(--dile-step-reached-text-color, var(--dile-on-primary-color, #fff));
36
+ }
37
+ :host([reached]:not([readonly])) .step {
38
+ cursor: pointer;
39
+ }
40
+ :host([active]) .step {
41
+ border-color: var(--dile-step-active-border-color, var(--dile-primary-dark-color, #0039cb));
42
+ font-weight: bold;
43
+ }
44
+ `
45
+ ];
46
+
47
+ static get properties() {
48
+ return {
49
+ /** Step label, used as accessible name of the button */
50
+ label: { type: String },
51
+ /** Zero-based position of the step */
52
+ index: { type: Number },
53
+ /** The step is the current one */
54
+ active: { type: Boolean, reflect: true },
55
+ /** The step can be navigated to */
56
+ reached: { type: Boolean, reflect: true },
57
+ /** Display only: renders the bubble without a button */
58
+ readonly: { type: Boolean, reflect: true },
59
+ };
60
+ }
61
+
62
+ constructor() {
63
+ super();
64
+ this.index = 0;
65
+ this.active = false;
66
+ this.reached = false;
67
+ this.readonly = false;
68
+ }
69
+
70
+ render() {
71
+ if (this.readonly) {
72
+ return html`<span class="step" aria-hidden="true">${this.index + 1}</span>`;
73
+ }
74
+ return html`<button
75
+ type="button"
76
+ class="step"
77
+ ?disabled=${!this.reached}
78
+ aria-label=${this.label ?? ''}
79
+ @click=${this.doClick}
80
+ >${this.index + 1}</button>`;
81
+ }
82
+
83
+ doClick() {
84
+ this.dispatchEvent(new CustomEvent('dile-step-click', {
85
+ bubbles: true,
86
+ composed: true,
87
+ detail: { index: this.index, label: this.label }
88
+ }));
89
+ }
90
+ }
@@ -0,0 +1,171 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import '../step.js';
3
+
4
+ export class DileSteps extends LitElement {
5
+ static styles = [
6
+ css`
7
+ :host {
8
+ display: block;
9
+ }
10
+
11
+ /* ── Horizontal ── */
12
+ .horizontal {
13
+ display: flex;
14
+ position: relative;
15
+ padding-bottom: 0.5rem;
16
+ }
17
+ .line-bg {
18
+ position: absolute;
19
+ top: calc(var(--dile-step-size, 1.5rem) / 2 - var(--dile-steps-line-width, 0.5rem) / 2);
20
+ left: 0.75rem;
21
+ right: 0.75rem;
22
+ height: var(--dile-steps-line-width, 0.5rem);
23
+ background-color: var(--dile-steps-line-color, var(--dile-primary-color, #2962ff));
24
+ border-radius: 2rem;
25
+ }
26
+ .h-step {
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: center;
30
+ flex: 1;
31
+ position: relative;
32
+ z-index: 1;
33
+ }
34
+ .h-label {
35
+ font-size: var(--dile-step-label-font-size, 0.7rem);
36
+ text-align: center;
37
+ margin-top: 0.3rem;
38
+ color: var(--dile-step-label-color, #606060);
39
+ line-height: 1.2;
40
+ }
41
+
42
+ /* ── Vertical ── */
43
+ .vertical {
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+ .v-step {
48
+ display: flex;
49
+ flex-direction: row;
50
+ align-items: flex-start;
51
+ gap: 0.75rem;
52
+ }
53
+ .v-track {
54
+ display: flex;
55
+ flex-direction: column;
56
+ align-items: center;
57
+ width: var(--dile-step-size, 1.5rem);
58
+ flex-shrink: 0;
59
+ }
60
+ .connector {
61
+ width: var(--dile-steps-connector-width, 0.35rem);
62
+ height: var(--dile-steps-connector-height, 1rem);
63
+ background-color: var(--dile-steps-line-color, var(--dile-primary-color, #2962ff));
64
+ border-radius: 2rem;
65
+ }
66
+ .v-label {
67
+ font-size: var(--dile-step-label-font-size, 0.85rem);
68
+ color: var(--dile-step-label-color, #606060);
69
+ padding-top: 0.2rem;
70
+ line-height: 1.2;
71
+ }
72
+
73
+ .active .h-label,
74
+ .active .v-label {
75
+ color: var(--dile-step-active-label-color, var(--dile-step-active-border-color, var(--dile-primary-dark-color, #0039cb)));
76
+ font-weight: 600;
77
+ }
78
+ `
79
+ ];
80
+
81
+ static get properties() {
82
+ return {
83
+ /** Array with the label of each step */
84
+ steps: { type: Array },
85
+ /** Zero-based index of the current step */
86
+ current: { type: Number },
87
+ /** Highest step index reached so far */
88
+ reached: { type: Number },
89
+ /** All steps can be selected, regardless of reached */
90
+ freeNavigation: { type: Boolean },
91
+ /** Vertical layout */
92
+ vertical: { type: Boolean },
93
+ /** Display only: steps can not be clicked */
94
+ readonly: { type: Boolean },
95
+ };
96
+ }
97
+
98
+ constructor() {
99
+ super();
100
+ this.steps = [];
101
+ this.current = 0;
102
+ this.reached = 0;
103
+ this.freeNavigation = false;
104
+ this.vertical = false;
105
+ this.readonly = false;
106
+ }
107
+
108
+ isReached(index) {
109
+ return this.freeNavigation || index <= this.reached;
110
+ }
111
+
112
+ render() {
113
+ return this.vertical ? this.renderVertical() : this.renderHorizontal();
114
+ }
115
+
116
+ renderHorizontal() {
117
+ return html`
118
+ <div class="horizontal" role="list" @dile-step-click=${this.onStepClick}>
119
+ <div class="line-bg"></div>
120
+ ${this.steps.map((step, index) => html`
121
+ <div class="h-step ${index === this.current ? 'active' : ''}" role="listitem" aria-current=${index === this.current ? 'step' : 'false'}>
122
+ ${this.stepTemplate(step, index)}
123
+ <span class="h-label">${step}</span>
124
+ </div>
125
+ `)}
126
+ </div>
127
+ `;
128
+ }
129
+
130
+ renderVertical() {
131
+ return html`
132
+ <div class="vertical" role="list" @dile-step-click=${this.onStepClick}>
133
+ ${this.steps.map((step, index) => html`
134
+ <div class="v-step ${index === this.current ? 'active' : ''}" role="listitem" aria-current=${index === this.current ? 'step' : 'false'}>
135
+ <div class="v-track">
136
+ ${this.stepTemplate(step, index)}
137
+ ${index < this.steps.length - 1
138
+ ? html`<div class="connector"></div>`
139
+ : ''}
140
+ </div>
141
+ <span class="v-label">${step}</span>
142
+ </div>
143
+ `)}
144
+ </div>
145
+ `;
146
+ }
147
+
148
+ stepTemplate(step, index) {
149
+ return html`
150
+ <dile-step
151
+ ?active=${index === this.current}
152
+ ?reached=${this.isReached(index)}
153
+ ?readonly=${this.readonly}
154
+ label="${step}"
155
+ index="${index}"
156
+ ></dile-step>
157
+ `;
158
+ }
159
+
160
+ onStepClick(e) {
161
+ e.stopPropagation();
162
+ if (this.readonly) {
163
+ return;
164
+ }
165
+ this.dispatchEvent(new CustomEvent('dile-steps-select', {
166
+ bubbles: true,
167
+ composed: true,
168
+ detail: { index: e.detail.index, label: e.detail.label }
169
+ }));
170
+ }
171
+ }
@@ -0,0 +1,5 @@
1
+ import { DileStep } from './src/DileStep.js';
2
+
3
+ if (!customElements.get('dile-step')) {
4
+ customElements.define('dile-step', DileStep);
5
+ }
@@ -0,0 +1,5 @@
1
+ import { DileSteps } from './src/DileSteps.js';
2
+
3
+ if (!customElements.get('dile-steps')) {
4
+ customElements.define('dile-steps', DileSteps);
5
+ }
@@ -28,8 +28,9 @@ export class DileToast extends LitElement {
28
28
  display: flex;
29
29
  flex-direction: column-reverse;
30
30
  position: fixed;
31
- bottom: 20px;
31
+ bottom: var(--dile-toast-bottom, 20px);
32
32
  left: 15px;
33
+ z-index: var(--dile-toast-z-index, 1001);
33
34
  }
34
35
  `;
35
36
  }
@@ -6,7 +6,7 @@ export class DileToastPersistent extends LitElement {
6
6
  :host {
7
7
  display: block;
8
8
  position: fixed;
9
- bottom: var(--dile-toast-persistent-gap, 1rem);
9
+ bottom: var(--dile-toast-persistent-bottom, var(--dile-toast-persistent-gap, 1rem));
10
10
  left: var(--dile-toast-persistent-gap, 1rem);
11
11
  z-index: var(--dile-toast-persistent-z-index, 999);
12
12
  width: var(--dile-toast-persistent-width, 300px);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/ui",
3
- "version": "3.17.1",
3
+ "version": "3.18.1",
4
4
  "description": "UI Core components from dile-components.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -21,12 +21,12 @@
21
21
  },
22
22
  "homepage": "https://dile-components.com/",
23
23
  "dependencies": {
24
- "@dile/iconlib": "^2.0.1",
25
- "@dile/icons": "^2.5.1",
24
+ "@dile/iconlib": "^2.0.2",
25
+ "@dile/icons": "^2.5.2",
26
26
  "lit": "^2.7.0 || ^3.0.0"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
- "gitHead": "eb8d274006be88ab2b44ed574f5c2829f27a2ec3"
31
+ "gitHead": "6724d25c93731ed35e6710228a6d0b18e5b6ebb1"
32
32
  }
@@ -1,253 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import './accordion.js';
3
- import './accordion-item.js';
4
-
5
- describe('DileAccordion - Accessibility', () => {
6
- afterEach(() => {
7
- document.body.innerHTML = '';
8
- });
9
-
10
- async function renderAccordion(html) {
11
- document.body.innerHTML = html;
12
- const el = document.body.querySelector('dile-accordion-item') || document.body.querySelector('dile-accordion');
13
- await el.updateComplete;
14
- return el;
15
- }
16
-
17
- async function renderAccordionGroup(html) {
18
- document.body.innerHTML = html;
19
- const el = document.body.querySelector('dile-accordion');
20
- const items = el.querySelectorAll('dile-accordion-item');
21
- await el.updateComplete;
22
- for (const item of items) {
23
- await item.updateComplete;
24
- }
25
- return { accordion: el, items };
26
- }
27
-
28
- describe('Semantic Structure & ARIA', () => {
29
- it('should have region role with aria-label on container', async () => {
30
- const { accordion } = await renderAccordionGroup(`
31
- <dile-accordion>
32
- <dile-accordion-item title="Item 1">
33
- <div slot="accordion-item-content">Content 1</div>
34
- </dile-accordion-item>
35
- </dile-accordion>
36
- `);
37
-
38
- const region = accordion.shadowRoot.querySelector('[role="region"]');
39
-
40
- expect(region).toBeTruthy();
41
- expect(region.getAttribute('aria-label')).toBe('Accordion');
42
- });
43
-
44
- it('button should have aria-expanded attribute', async () => {
45
- const el = await renderAccordion(`
46
- <dile-accordion-item title="Expandable">
47
- <div slot="accordion-item-content">Content</div>
48
- </dile-accordion-item>
49
- `);
50
-
51
- const button = el.shadowRoot.querySelector('button');
52
- expect(button.getAttribute('aria-expanded')).toBe('false');
53
-
54
- button.click();
55
- await el.updateComplete;
56
-
57
- expect(button.getAttribute('aria-expanded')).toBe('true');
58
- });
59
-
60
- it('button should have aria-controls pointing to content', async () => {
61
- const el = await renderAccordion(`
62
- <dile-accordion-item title="Item">
63
- <div slot="accordion-item-content">Content</div>
64
- </dile-accordion-item>
65
- `);
66
-
67
- const button = el.shadowRoot.querySelector('button');
68
- const contentContainer = el.shadowRoot.querySelector('.contentContainer');
69
-
70
- const controlsId = button.getAttribute('aria-controls');
71
- expect(controlsId).toBeTruthy();
72
- expect(contentContainer.id).toBe(controlsId);
73
- });
74
-
75
- it('content region should have aria-labelledby pointing to button', async () => {
76
- const el = await renderAccordion(`
77
- <dile-accordion-item title="Section Title">
78
- <div slot="accordion-item-content">Content</div>
79
- </dile-accordion-item>
80
- `);
81
-
82
- const button = el.shadowRoot.querySelector('button');
83
- const contentRegion = el.shadowRoot.querySelector('[role="region"]');
84
-
85
- expect(contentRegion.getAttribute('aria-labelledby')).toBe(button.id);
86
- });
87
-
88
- it('icon should be hidden from screen readers with aria-hidden', async () => {
89
- const el = await renderAccordion(`
90
- <dile-accordion-item title="Item">
91
- <div slot="accordion-item-content">Content</div>
92
- </dile-accordion-item>
93
- `);
94
-
95
- const icon = el.shadowRoot.querySelector('span.icon');
96
- expect(icon.getAttribute('aria-hidden')).toBe('true');
97
- });
98
- });
99
-
100
- describe('Keyboard Navigation', () => {
101
- it('button should be keyboard focusable', async () => {
102
- const el = await renderAccordion(`
103
- <dile-accordion-item title="Item">
104
- <div slot="accordion-item-content">Content</div>
105
- </dile-accordion-item>
106
- `);
107
-
108
- const button = el.shadowRoot.querySelector('button');
109
- button.focus();
110
-
111
- expect(el.shadowRoot.activeElement).toBe(button);
112
- });
113
-
114
- it('button should toggle on click', async () => {
115
- const el = await renderAccordion(`
116
- <dile-accordion-item title="Item">
117
- <div slot="accordion-item-content">Content</div>
118
- </dile-accordion-item>
119
- `);
120
-
121
- const button = el.shadowRoot.querySelector('button');
122
-
123
- expect(el.opened).toBe(false);
124
- button.click();
125
- await el.updateComplete;
126
-
127
- expect(el.opened).toBe(true);
128
- });
129
- });
130
-
131
- describe('Focus Management', () => {
132
- it('should have visible focus outline (focus-visible)', async () => {
133
- const el = await renderAccordion(`
134
- <dile-accordion-item title="Item">
135
- <div slot="accordion-item-content">Content</div>
136
- </dile-accordion-item>
137
- `);
138
-
139
- const button = el.shadowRoot.querySelector('button');
140
-
141
- // Verify focus-visible CSS exists
142
- const styles = el.shadowRoot.adoptedStyleSheets[0];
143
- let hasFocusVisible = false;
144
- for (const rule of styles.cssRules) {
145
- if (rule.selectorText && rule.selectorText.includes('focus-visible')) {
146
- hasFocusVisible = true;
147
- break;
148
- }
149
- }
150
-
151
- expect(hasFocusVisible).toBe(true);
152
- });
153
- });
154
-
155
- describe('Live Region Announcements', () => {
156
- it('should dispatch live region announcement on expand', async () => {
157
- const el = await renderAccordion(`
158
- <dile-accordion-item title="Settings">
159
- <div slot="accordion-item-content">Expanded content</div>
160
- </dile-accordion-item>
161
- `);
162
-
163
- const button = el.shadowRoot.querySelector('button');
164
- button.click();
165
- await el.updateComplete;
166
- await new Promise(resolve => setTimeout(resolve, 100));
167
-
168
- // Verify that a live region element exists with status role
169
- const liveRegions = el.querySelectorAll('[role="status"][aria-live]');
170
- expect(liveRegions.length).toBeGreaterThanOrEqual(0);
171
- });
172
- });
173
-
174
- describe('Content Visibility & Structure', () => {
175
- it('content should be hidden when closed', async () => {
176
- const el = await renderAccordion(`
177
- <dile-accordion-item title="Item">
178
- <div slot="accordion-item-content">Hidden Content</div>
179
- </dile-accordion-item>
180
- `);
181
-
182
- expect(el.opened).toBe(false);
183
- const button = el.shadowRoot.querySelector('button');
184
- expect(button.className).not.toContain('opened');
185
- });
186
-
187
- it('content should be visible when opened', async () => {
188
- const el = await renderAccordion(`
189
- <dile-accordion-item title="Item" opened>
190
- <div slot="accordion-item-content">Visible Content</div>
191
- </dile-accordion-item>
192
- `);
193
-
194
- // Give time for animation to trigger
195
- await new Promise(resolve => setTimeout(resolve, 150));
196
-
197
- expect(el.opened).toBe(true);
198
- const button = el.shadowRoot.querySelector('button');
199
- expect(button.className).toContain('opened');
200
- });
201
- });
202
-
203
- describe('Multiple Items', () => {
204
- it('only one item should be open at a time (mutually exclusive)', async () => {
205
- const { accordion, items } = await renderAccordionGroup(`
206
- <dile-accordion>
207
- <dile-accordion-item title="Item 1">
208
- <div slot="accordion-item-content">Content 1</div>
209
- </dile-accordion-item>
210
- <dile-accordion-item title="Item 2">
211
- <div slot="accordion-item-content">Content 2</div>
212
- </dile-accordion-item>
213
- </dile-accordion>
214
- `);
215
-
216
- const button1 = items[0].shadowRoot.querySelector('button');
217
- const button2 = items[1].shadowRoot.querySelector('button');
218
-
219
- button1.click();
220
- await items[0].updateComplete;
221
- expect(items[0].opened).toBe(true);
222
-
223
- button2.click();
224
- await items[1].updateComplete;
225
- await items[0].updateComplete;
226
-
227
- expect(items[0].opened).toBe(false);
228
- expect(items[1].opened).toBe(true);
229
- });
230
- });
231
-
232
- describe('Unique IDs', () => {
233
- it('each accordion item should have unique IDs', async () => {
234
- const { items } = await renderAccordionGroup(`
235
- <dile-accordion>
236
- <dile-accordion-item title="Item 1">
237
- <div slot="accordion-item-content">Content 1</div>
238
- </dile-accordion-item>
239
- <dile-accordion-item title="Item 2">
240
- <div slot="accordion-item-content">Content 2</div>
241
- </dile-accordion-item>
242
- </dile-accordion>
243
- `);
244
-
245
- const button1Id = items[0].shadowRoot.querySelector('button').id;
246
- const button2Id = items[1].shadowRoot.querySelector('button').id;
247
-
248
- expect(button1Id).toBeTruthy();
249
- expect(button2Id).toBeTruthy();
250
- expect(button1Id).not.toBe(button2Id);
251
- });
252
- });
253
- });