@dile/ui 3.4.4 → 3.4.6

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 (185) hide show
  1. package/components/accordion/accordion-item.js +3 -1
  2. package/components/accordion/accordion.js +3 -1
  3. package/components/app-drawer/app-drawer.js +3 -1
  4. package/components/avatar/avatar.js +3 -1
  5. package/components/avatar/avatar.spec.js +25 -0
  6. package/components/box-selector/box-selector-item.js +3 -1
  7. package/components/box-selector/box-selector-item.spec.js +27 -0
  8. package/components/box-selector/box-selector.js +3 -1
  9. package/components/box-selector/box-selector.spec.js +27 -0
  10. package/components/breadcrumbs/breadcrumbs-item.js +3 -1
  11. package/components/breadcrumbs/breadcrumbs-item.spec.js +28 -0
  12. package/components/breadcrumbs/breadcrumbs.js +3 -1
  13. package/components/breadcrumbs/breadcrumbs.spec.js +33 -0
  14. package/components/button/__screenshots__/button.spec.js/dile-button-Native-form-submission--DEBUG---timing-probe--does-not-leak-a-stale-value-when-the-form-is-submitted-again-without-a-button-click-1.png +0 -0
  15. package/components/button/__screenshots__/button.spec.js/dile-button-Native-form-submission--DEBUG---timing-probe--includes-name-value-in-the-formdata-event-entry-list-when-clicked-1.png +0 -0
  16. package/components/button/__screenshots__/button.spec.js/dile-button-Native-form-submission--DEBUG---timing-probe--includes-name-value-when-read-synchronously-inside-the-submit-listener-1.png +0 -0
  17. package/components/button/__screenshots__/button.spec.js/dile-button-Native-form-submission--DEBUG---timing-probe--only-includes-the-clicked-button-among-two-submit-buttons-with-different-values-1.png +0 -0
  18. package/components/button/button-icon.js +3 -1
  19. package/components/button/button.js +3 -1
  20. package/components/button/button.spec.js +134 -0
  21. package/components/button/src/DileButton.js +8 -2
  22. package/components/card/card-slide.js +3 -1
  23. package/components/card/card-slide.spec.js +30 -0
  24. package/components/card/card.js +3 -1
  25. package/components/card/card.spec.js +26 -0
  26. package/components/checkbox/checkbox.js +3 -1
  27. package/components/checkbox/checkbox.spec.js +235 -0
  28. package/components/checkbox/src/DileCheckbox.js +27 -15
  29. package/components/chip/chip.js +3 -1
  30. package/components/chip/chip.spec.js +31 -0
  31. package/components/color-picker/color-picker.js +3 -1
  32. package/components/color-picker/color-picker.spec.js +16 -0
  33. package/components/color-picker/src/DileColorPicker.js +1 -1
  34. package/components/confirm/confirm.js +3 -1
  35. package/components/confirm/confirm.spec.js +32 -0
  36. package/components/confirm-text/confirm-text.js +3 -1
  37. package/components/confirm-text/confirm-text.spec.js +29 -0
  38. package/components/copy-markdown-url/copy-markdown-url.spec.js +27 -0
  39. package/components/copy-markdown-url/index.js +3 -1
  40. package/components/copy-text/__screenshots__/copy-text.spec.js/dile-copy-text-dispatches-dile-text-copied-with-the-content-on-copy---1.png +0 -0
  41. package/components/copy-text/copy-text.spec.js +38 -0
  42. package/components/copy-text/index.js +3 -1
  43. package/components/drop-file/drop-file.js +3 -1
  44. package/components/drop-file/drop-file.spec.js +33 -0
  45. package/components/drop-file/file-preview.js +3 -1
  46. package/components/drop-file/file-preview.spec.js +35 -0
  47. package/components/drop-file/src/DileDropFile.js +1 -1
  48. package/components/floating-feedback/floating-feedback.spec.js +30 -0
  49. package/components/floating-feedback/index.js +3 -1
  50. package/components/floating-menu/floating-menu-item.js +3 -1
  51. package/components/floating-menu/floating-menu-item.spec.js +27 -0
  52. package/components/floating-menu/floating-menu.js +3 -1
  53. package/components/floating-menu/floating-menu.spec.js +31 -0
  54. package/components/form-separator/form-separator.js +3 -1
  55. package/components/form-separator/form-separator.spec.js +28 -0
  56. package/components/hamburger/hamburger.js +3 -1
  57. package/components/hamburger/hamburger.spec.js +28 -0
  58. package/components/icon/icon.js +3 -1
  59. package/components/icon/icon.spec.js +31 -0
  60. package/components/icon-switch/icon-switch-group.js +3 -1
  61. package/components/icon-switch/icon-switch-group.spec.js +46 -0
  62. package/components/icon-switch/icon-switch.js +3 -1
  63. package/components/icon-switch/icon-switch.spec.js +35 -0
  64. package/components/info-box/info-box.js +3 -1
  65. package/components/info-box/info-box.spec.js +38 -0
  66. package/components/inline-feedback/inline-feedback.js +3 -1
  67. package/components/inline-feedback/inline-feedback.spec.js +46 -0
  68. package/components/input/__screenshots__/input-number-mask.spec.js/dile-input-number-mask-masks-digit-keydowns-following-the-given-pattern-1.png +0 -0
  69. package/components/input/input-contact.js +3 -1
  70. package/components/input/input-contact.spec.js +32 -0
  71. package/components/input/input-integer.js +3 -1
  72. package/components/input/input-integer.spec.js +31 -0
  73. package/components/input/input-message.js +3 -1
  74. package/components/input/input-message.spec.js +27 -0
  75. package/components/input/input-money.js +3 -1
  76. package/components/input/input-money.spec.js +30 -0
  77. package/components/input/input-number-mask.js +3 -1
  78. package/components/input/input-number-mask.spec.js +32 -0
  79. package/components/input/input-percentage.js +3 -1
  80. package/components/input/input-percentage.spec.js +31 -0
  81. package/components/input/input-search.js +3 -1
  82. package/components/input/input-search.spec.js +39 -0
  83. package/components/input/input.js +3 -1
  84. package/components/input/input.spec.js +47 -0
  85. package/components/input/src/DileInput.js +1 -1
  86. package/components/light-mode-switch/light-mode-switch.js +3 -1
  87. package/components/light-mode-switch/light-mode-switch.spec.js +34 -0
  88. package/components/menu-hamburger/menu-hamburger.js +3 -1
  89. package/components/menu-hamburger/menu-hamburger.spec.js +34 -0
  90. package/components/menu-overlay/menu-overlay.js +3 -1
  91. package/components/menu-overlay/menu-overlay.spec.js +44 -0
  92. package/components/message/message.js +3 -1
  93. package/components/message/message.spec.js +42 -0
  94. package/components/modal/__screenshots__/modal-help.spec.js/dile-modal-help-opens-the-inner-modal-on-open---1.png +0 -0
  95. package/components/modal/modal-help.js +3 -1
  96. package/components/modal/modal-help.spec.js +32 -0
  97. package/components/modal/modal.js +3 -1
  98. package/components/modal/modal.spec.js +46 -0
  99. package/components/nav/nav.js +3 -1
  100. package/components/nav/nav.spec.js +33 -0
  101. package/components/number-picker/number-picker-element.js +3 -1
  102. package/components/number-picker/number-picker-element.spec.js +30 -0
  103. package/components/number-picker/number-picker.js +3 -1
  104. package/components/number-picker/number-picker.spec.js +30 -0
  105. package/components/number-picker/src/DileNumberPicker.js +1 -1
  106. package/components/order-switch/order-switch.js +3 -1
  107. package/components/order-switch/order-switch.spec.js +31 -0
  108. package/components/otp-input/otp-input.js +3 -1
  109. package/components/otp-input/otp-input.spec.js +41 -0
  110. package/components/otp-input/src/DileOtpInput.js +1 -1
  111. package/components/pages/pages.js +3 -1
  112. package/components/pages/pages.spec.js +44 -0
  113. package/components/password/password.js +3 -1
  114. package/components/password/password.spec.js +35 -0
  115. package/components/progress-bar/progress-bar.js +3 -1
  116. package/components/progress-bar/progress-bar.spec.js +28 -0
  117. package/components/radio-group/radio-group.js +3 -1
  118. package/components/radio-group/radio-group.spec.js +44 -0
  119. package/components/radio-group/radio.js +3 -1
  120. package/components/radio-group/radio.spec.js +32 -0
  121. package/components/radio-group/src/DileRadioGroup.js +1 -1
  122. package/components/range/range.js +3 -1
  123. package/components/range/range.spec.js +36 -0
  124. package/components/range/src/DileRange.js +1 -1
  125. package/components/rating/rating.js +3 -1
  126. package/components/rating/rating.spec.js +34 -0
  127. package/components/rating/star.js +3 -1
  128. package/components/rating/star.spec.js +30 -0
  129. package/components/rating-scale-question/rating-scale-option.js +3 -1
  130. package/components/rating-scale-question/rating-scale-option.spec.js +32 -0
  131. package/components/rating-scale-question/rating-scale-question.js +3 -1
  132. package/components/rating-scale-question/rating-scale-question.spec.js +35 -0
  133. package/components/select/select-ajax.js +3 -1
  134. package/components/select/select-boolean.js +3 -1
  135. package/components/select/select.js +3 -1
  136. package/components/select/select.spec.js +39 -0
  137. package/components/select/src/DileSelect.js +1 -1
  138. package/components/select/src/DileSelectAjax.js +1 -1
  139. package/components/select/src/DileSelectBoolean.js +1 -1
  140. package/components/selector/selector-item.js +3 -1
  141. package/components/selector/selector-item.spec.js +40 -0
  142. package/components/selector/selector-overlay.js +3 -1
  143. package/components/selector/selector.js +3 -1
  144. package/components/selector/selector.spec.js +45 -0
  145. package/components/slide-menu/slide-menu.js +3 -1
  146. package/components/slide-menu/slide-menu.spec.js +38 -0
  147. package/components/slide-show/slide-show.js +3 -1
  148. package/components/slide-show/slide-show.spec.js +34 -0
  149. package/components/social-icon/social-icon.js +3 -1
  150. package/components/social-icon/social-icon.spec.js +26 -0
  151. package/components/spinner/spinner-bar.js +3 -1
  152. package/components/spinner/spinner-bar.spec.js +26 -0
  153. package/components/spinner/spinner-block.js +3 -1
  154. package/components/spinner/spinner-block.spec.js +21 -0
  155. package/components/spinner/spinner-horizontal.js +3 -1
  156. package/components/spinner/spinner-horizontal.spec.js +25 -0
  157. package/components/spinner/spinner-icon.js +3 -1
  158. package/components/spinner/spinner-icon.spec.js +25 -0
  159. package/components/spinner/spinner-modal.js +3 -1
  160. package/components/spinner/spinner-modal.spec.js +26 -0
  161. package/components/spinner/spinner.js +3 -1
  162. package/components/spinner/spinner.spec.js +26 -0
  163. package/components/switch/switch.js +3 -1
  164. package/components/switch/switch.spec.js +33 -0
  165. package/components/tabs/tab.js +3 -1
  166. package/components/tabs/tabs.js +3 -1
  167. package/components/tabs/tabs.spec.js +45 -0
  168. package/components/textarea/src/DileTextarea.js +1 -1
  169. package/components/textarea/textarea.js +3 -1
  170. package/components/textarea/textarea.spec.js +39 -0
  171. package/components/time-picker/src/DileTimePicker.js +1 -1
  172. package/components/time-picker/time-picker.js +3 -1
  173. package/components/time-picker/time-picker.spec.js +26 -0
  174. package/components/timer/timer.js +3 -1
  175. package/components/timer/timer.spec.js +31 -0
  176. package/components/toast/toast-item.js +3 -1
  177. package/components/toast/toast-persistent.js +3 -1
  178. package/components/toast/toast.js +3 -1
  179. package/components/tooltip/chip-tooltip.js +3 -1
  180. package/components/tooltip/chip-tooltip.spec.js +29 -0
  181. package/components/tooltip/tooltip.js +3 -1
  182. package/components/tooltip/tooltip.spec.js +32 -0
  183. package/components/web-visor/web-visor.js +3 -1
  184. package/components/web-visor/web-visor.spec.js +32 -0
  185. package/package.json +3 -3
@@ -0,0 +1,235 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './checkbox.js';
3
+
4
+ describe('dile-checkbox', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderCheckbox(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-checkbox');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ function getControl(el) {
17
+ return el.shadowRoot.querySelector('[role="checkbox"]');
18
+ }
19
+
20
+ describe('Rendering', () => {
21
+ it('starts unchecked by default', async () => {
22
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
23
+ expect(el.checked).toBe(false);
24
+ expect(getControl(el).getAttribute('aria-checked')).toBe('false');
25
+ });
26
+
27
+ it('renders the slotted label content', async () => {
28
+ const el = await renderCheckbox('<dile-checkbox name="terms">Accept terms</dile-checkbox>');
29
+ const label = el.shadowRoot.querySelector('label.label');
30
+ expect(label).toBeTruthy();
31
+ expect(el.textContent.trim()).toBe('Accept terms');
32
+ });
33
+
34
+ it('does not render a label element when there is no slotted content', async () => {
35
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
36
+ expect(el.shadowRoot.querySelector('label.label')).toBeNull();
37
+ });
38
+
39
+ it('renders a message when the message property is set', async () => {
40
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
41
+ el.message = 'You must accept';
42
+ el.errored = true;
43
+ await el.updateComplete;
44
+
45
+ const message = el.shadowRoot.querySelector('dile-input-message');
46
+ expect(message).toBeTruthy();
47
+ expect(message.getAttribute('message')).toBe('You must accept');
48
+ });
49
+ });
50
+
51
+ describe('Toggling', () => {
52
+ it('toggles checked state on click', async () => {
53
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
54
+ el.shadowRoot.querySelector('div').click();
55
+ await el.updateComplete;
56
+
57
+ expect(el.checked).toBe(true);
58
+ });
59
+
60
+ it('toggles checked state on Space key press', async () => {
61
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
62
+ const control = getControl(el);
63
+ control.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true, cancelable: true }));
64
+ await el.updateComplete;
65
+
66
+ expect(el.checked).toBe(true);
67
+ });
68
+
69
+ it('does not toggle when disabled', async () => {
70
+ const el = await renderCheckbox('<dile-checkbox name="terms" disabled></dile-checkbox>');
71
+ el.shadowRoot.querySelector('div').click();
72
+ await el.updateComplete;
73
+
74
+ expect(el.checked).toBe(false);
75
+ });
76
+
77
+ it('clears the checked state via clear()', async () => {
78
+ const el = await renderCheckbox('<dile-checkbox name="terms" checked></dile-checkbox>');
79
+ expect(el.checked).toBe(true);
80
+
81
+ el.clear();
82
+ await el.updateComplete;
83
+
84
+ expect(el.checked).toBe(false);
85
+ });
86
+
87
+ it('mirrors checked through the value property', async () => {
88
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
89
+ el.value = true;
90
+ await el.updateComplete;
91
+
92
+ expect(el.checked).toBe(true);
93
+ expect(el.value).toBe(true);
94
+ });
95
+ });
96
+
97
+ describe('Events', () => {
98
+ it('dispatches dile-checkbox-changed with checked and name', async () => {
99
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
100
+ let detail = null;
101
+ el.addEventListener('dile-checkbox-changed', (e) => { detail = e.detail; });
102
+
103
+ el.shadowRoot.querySelector('div').click();
104
+ await el.updateComplete;
105
+
106
+ expect(detail).toEqual({ checked: true, name: 'terms' });
107
+ });
108
+
109
+ it('dispatches element-changed with name and value', async () => {
110
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
111
+ let detail = null;
112
+ el.addEventListener('element-changed', (e) => { detail = e.detail; });
113
+
114
+ el.shadowRoot.querySelector('div').click();
115
+ await el.updateComplete;
116
+
117
+ expect(detail).toEqual({ name: 'terms', value: true });
118
+ });
119
+
120
+ it('clears the error state on toggle when hideErrorOnInput is set', async () => {
121
+ const el = await renderCheckbox('<dile-checkbox name="terms" hideErrorOnInput></dile-checkbox>');
122
+ el.message = 'Required';
123
+ el.errored = true;
124
+ await el.updateComplete;
125
+
126
+ el.shadowRoot.querySelector('div').click();
127
+ await el.updateComplete;
128
+
129
+ expect(el.message).toBe('');
130
+ expect(el.errored).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe('Native form submission', () => {
135
+ it('participates in FormData when name is set as an attribute', async () => {
136
+ document.body.innerHTML = `
137
+ <form id="testForm">
138
+ <dile-checkbox name="terms" checked></dile-checkbox>
139
+ </form>
140
+ `;
141
+ const el = document.body.querySelector('dile-checkbox');
142
+ await el.updateComplete;
143
+
144
+ const data = new FormData(document.getElementById('testForm'));
145
+ expect(data.get('terms')).toBe('true');
146
+ });
147
+
148
+ it('participates in FormData when name is set as a JS property', async () => {
149
+ document.body.innerHTML = `
150
+ <form id="testForm">
151
+ <dile-checkbox checked></dile-checkbox>
152
+ </form>
153
+ `;
154
+ const el = document.body.querySelector('dile-checkbox');
155
+ el.name = 'terms';
156
+ await el.updateComplete;
157
+
158
+ expect(el.getAttribute('name')).toBe('terms');
159
+
160
+ const data = new FormData(document.getElementById('testForm'));
161
+ expect(data.get('terms')).toBe('true');
162
+ });
163
+
164
+ it('is excluded from FormData when unchecked', async () => {
165
+ document.body.innerHTML = `
166
+ <form id="testForm">
167
+ <dile-checkbox name="terms"></dile-checkbox>
168
+ </form>
169
+ `;
170
+ const el = document.body.querySelector('dile-checkbox');
171
+ await el.updateComplete;
172
+
173
+ const data = new FormData(document.getElementById('testForm'));
174
+ expect(data.get('terms')).toBeNull();
175
+ });
176
+ });
177
+
178
+ describe('Accessibility', () => {
179
+ it('exposes role="checkbox" on the interactive control', async () => {
180
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
181
+ expect(getControl(el)).toBeTruthy();
182
+ });
183
+
184
+ it('keeps aria-checked in sync with the checked state', async () => {
185
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
186
+ const control = getControl(el);
187
+ expect(control.getAttribute('aria-checked')).toBe('false');
188
+
189
+ el.checked = true;
190
+ await el.updateComplete;
191
+
192
+ expect(control.getAttribute('aria-checked')).toBe('true');
193
+ });
194
+
195
+ it('is keyboard focusable when enabled', async () => {
196
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
197
+ const control = getControl(el);
198
+ expect(control.getAttribute('tabindex')).toBe('0');
199
+ });
200
+
201
+ it('is removed from the tab order and marked aria-disabled when disabled', async () => {
202
+ const el = await renderCheckbox('<dile-checkbox name="terms" disabled></dile-checkbox>');
203
+ const control = getControl(el);
204
+ expect(control.getAttribute('tabindex')).toBe('-1');
205
+ expect(control.getAttribute('aria-disabled')).toBe('true');
206
+ });
207
+
208
+ it('links the control to the slotted label via aria-labelledby', async () => {
209
+ const el = await renderCheckbox('<dile-checkbox name="terms">Accept terms</dile-checkbox>');
210
+ const control = getControl(el);
211
+ const labelledbyId = control.getAttribute('aria-labelledby');
212
+
213
+ expect(labelledbyId).toBeTruthy();
214
+ expect(el.shadowRoot.getElementById(labelledbyId)).toBeTruthy();
215
+ });
216
+
217
+ it('does not set aria-labelledby when there is no slotted label', async () => {
218
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
219
+ expect(getControl(el).hasAttribute('aria-labelledby')).toBe(false);
220
+ });
221
+
222
+ it('links the control to the message via aria-describedby when a message is set', async () => {
223
+ const el = await renderCheckbox('<dile-checkbox name="terms"></dile-checkbox>');
224
+ el.message = 'Required';
225
+ el.errored = true;
226
+ await el.updateComplete;
227
+
228
+ const control = getControl(el);
229
+ const describedbyId = control.getAttribute('aria-describedby');
230
+
231
+ expect(describedbyId).toBeTruthy();
232
+ expect(el.shadowRoot.getElementById(describedbyId)).toBeTruthy();
233
+ });
234
+ });
235
+ });
@@ -1,5 +1,6 @@
1
1
  import { html, css, LitElement } from "lit";
2
- import { DileEmmitChange } from '../../../mixins/form/index.js';
2
+ import { ifDefined } from 'lit/directives/if-defined.js';
3
+ import { DileEmmitChange } from '../../../mixins/form/index.js';
3
4
  import { checkboxBlankIcon, checkboxCheckedIcon } from '@dile/icons';
4
5
  import '../../icon/icon.js';
5
6
  import '../../input/input-message.js';
@@ -10,7 +11,7 @@ export class DileCheckbox extends DileEmmitChange(LitElement) {
10
11
  checked: { type: Boolean, reflect: true, },
11
12
  disabled: { type: Boolean, reflect: true, },
12
13
  _hasInner: { type: Boolean },
13
- name: { type: String },
14
+ name: { type: String, reflect: true, },
14
15
  message: { type: String },
15
16
  errored: { type: Boolean },
16
17
  hideErrorOnInput: { type: Boolean },
@@ -24,9 +25,11 @@ export class DileCheckbox extends DileEmmitChange(LitElement) {
24
25
  constructor() {
25
26
  super();
26
27
  this.checked = false;
28
+ this.disabled = false;
27
29
  this.name = '';
28
30
  this.internals = this.attachInternals();
29
- this.message = ''
31
+ this.message = '';
32
+ this._id = `dile-checkbox-${Math.random().toString(36).substr(2, 9)}`;
30
33
  }
31
34
 
32
35
  static get styles() {
@@ -50,6 +53,10 @@ export class DileCheckbox extends DileEmmitChange(LitElement) {
50
53
  align-items: center;
51
54
  justify-content: center;
52
55
  }
56
+ .checkbox:focus-visible {
57
+ outline: var(--dile-checkbox-focus-outline, 2px solid #4A90E2);
58
+ outline-offset: 2px;
59
+ }
53
60
  .isUnchecked {
54
61
  --dile-icon-color: var(--dile-checkbox-unchecked-color, var(--dile-on-background-color, #303030));
55
62
  }
@@ -86,13 +93,22 @@ export class DileCheckbox extends DileEmmitChange(LitElement) {
86
93
  render() {
87
94
  return html`
88
95
  <div @click="${this.doClick}" class="${this.disabled ? "disabled" : ""}">
89
- <a href="#" @click="${this.linkClick}" @keypress="${this.doKeyPress}" class="checkbox ${this.checked ? "isChecked" : "isUnchecked"}">
96
+ <span
97
+ role="checkbox"
98
+ aria-checked="${this.checked}"
99
+ aria-disabled="${this.disabled ? "true" : "false"}"
100
+ aria-labelledby="${ifDefined(this._hasInner ? `${this._id}-label` : undefined)}"
101
+ aria-describedby="${ifDefined(this.message ? `${this._id}-message` : undefined)}"
102
+ tabindex="${this.disabled ? "-1" : "0"}"
103
+ @keydown="${this.doKeyDown}"
104
+ class="checkbox ${this.checked ? "isChecked" : "isUnchecked"}"
105
+ >
90
106
  ${this.checked ? this.checkedIcon : this.unCheckedIcon}
91
- </a>
107
+ </span>
92
108
  ${this.innerTemplate}
93
109
  </div>
94
110
  ${this.message
95
- ? html`<dile-input-message message="${this.message}" ?errored=${this.errored}></dile-input-message>`
111
+ ? html`<dile-input-message id="${this._id}-message" message="${this.message}" ?errored=${this.errored}></dile-input-message>`
96
112
  : ''
97
113
  }
98
114
  `;
@@ -101,14 +117,14 @@ export class DileCheckbox extends DileEmmitChange(LitElement) {
101
117
  get innerTemplate() {
102
118
  return html`
103
119
  ${this._hasInner
104
- ? html` <label class="label">
120
+ ? html` <label id="${this._id}-label" class="label">
105
121
  <slot></slot>
106
122
  </label>`
107
123
  : ""
108
124
  }
109
125
  `
110
126
  }
111
-
127
+
112
128
  doClick() {
113
129
  if (this.disabled) {
114
130
  return;
@@ -137,16 +153,12 @@ export class DileCheckbox extends DileEmmitChange(LitElement) {
137
153
  return html`<dile-icon .icon="${checkboxBlankIcon}"></dile-icon>`;
138
154
  }
139
155
 
140
- doKeyPress(e) {
141
- e.preventDefault();
142
- if (e.keyCode == 32 || e.code == "Space") {
156
+ doKeyDown(e) {
157
+ if (e.key == " " || e.code == "Space" || e.keyCode == 32) {
158
+ e.preventDefault();
143
159
  this.doClick();
144
160
  }
145
161
  }
146
-
147
- linkClick(e) {
148
- e.preventDefault();
149
- }
150
162
 
151
163
  clear() {
152
164
  this.checked = false;
@@ -1,2 +1,4 @@
1
1
  import { DileChip } from "./src/DileChip.js";
2
- customElements.define('dile-chip', DileChip);
2
+ if (!customElements.get('dile-chip')) {
3
+ customElements.define('dile-chip', DileChip);
4
+ }
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './chip.js';
3
+
4
+ describe('dile-chip', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderChip(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-chip');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ it('renders the slotted content and a close icon', async () => {
17
+ const el = await renderChip('<dile-chip>Tag</dile-chip>');
18
+ expect(el.textContent.trim()).toBe('Tag');
19
+ expect(el.shadowRoot.querySelector('dile-icon')).toBeTruthy();
20
+ });
21
+
22
+ it('dispatches dile-chip-icon-click with the name when the icon is clicked', async () => {
23
+ const el = await renderChip('<dile-chip name="tag1">Tag</dile-chip>');
24
+ let detail = null;
25
+ el.addEventListener('dile-chip-icon-click', (e) => { detail = e.detail; });
26
+
27
+ el.shadowRoot.querySelector('dile-icon').click();
28
+
29
+ expect(detail).toEqual({ name: 'tag1' });
30
+ });
31
+ });
@@ -1,3 +1,5 @@
1
1
  import { DileColorPicker } from './src/DileColorPicker.js';
2
2
 
3
- window.customElements.define('dile-color-picker', DileColorPicker);
3
+ if (!customElements.get('dile-color-picker')) {
4
+ window.customElements.define('dile-color-picker', DileColorPicker);
5
+ }
@@ -114,4 +114,20 @@ describe('dile-color-picker', () => {
114
114
 
115
115
  expect(data.get('color')).toBe('#123456');
116
116
  });
117
+
118
+ it('participates in FormData when name is set as a JS property', async () => {
119
+ document.body.innerHTML = `
120
+ <form id="testForm">
121
+ <dile-color-picker value="#123456"></dile-color-picker>
122
+ </form>
123
+ `;
124
+ const el = document.body.querySelector('dile-color-picker');
125
+ el.name = 'color';
126
+ await el.updateComplete;
127
+
128
+ expect(el.getAttribute('name')).toBe('color');
129
+
130
+ const data = new FormData(document.getElementById('testForm'));
131
+ expect(data.get('color')).toBe('#123456');
132
+ });
117
133
  });
@@ -26,7 +26,7 @@ export class DileColorPicker extends DileOverlay(
26
26
  label: { type: String },
27
27
 
28
28
  /** Name for this input field */
29
- name: { type: String },
29
+ name: { type: String, reflect: true },
30
30
 
31
31
  /** Set initial value to the input. Must be a 6-digit hex color, e.g. "#3366ff" */
32
32
  value: { type: String },
@@ -1,3 +1,5 @@
1
1
  import { DileConfirm } from './src/DileConfirm.js';
2
2
 
3
- customElements.define("dile-confirm", DileConfirm);
3
+ if (!customElements.get("dile-confirm")) {
4
+ customElements.define("dile-confirm", DileConfirm);
5
+ }
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './confirm.js';
3
+
4
+ describe('dile-confirm', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderConfirm(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-confirm');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ it('renders a dile-modal with the default action labels', async () => {
17
+ const el = await renderConfirm('<dile-confirm>Are you sure?</dile-confirm>');
18
+ expect(el.shadowRoot.querySelector('dile-modal')).toBeTruthy();
19
+ expect(el.shadowRoot.querySelector('.accept').textContent.trim()).toBe('Accept');
20
+ expect(el.shadowRoot.querySelector('.cancel').textContent.trim()).toBe('Cancell');
21
+ });
22
+
23
+ it('dispatches dile-confirm-accepted when accept is clicked', async () => {
24
+ const el = await renderConfirm('<dile-confirm>Are you sure?</dile-confirm>');
25
+ let accepted = false;
26
+ el.addEventListener('dile-confirm-accepted', () => { accepted = true; });
27
+
28
+ el.shadowRoot.querySelector('.accept').click();
29
+
30
+ expect(accepted).toBe(true);
31
+ });
32
+ });
@@ -1,3 +1,5 @@
1
1
  import { DileConfirmText } from './src/DileConfirmText.js';
2
2
 
3
- window.customElements.define('dile-confirm-text', DileConfirmText);
3
+ if (!customElements.get('dile-confirm-text')) {
4
+ window.customElements.define('dile-confirm-text', DileConfirmText);
5
+ }
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './confirm-text.js';
3
+
4
+ describe('dile-confirm-text', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderConfirmText(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-confirm-text');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ it('renders the title and an input to type the confirmation text', async () => {
17
+ const el = await renderConfirmText('<dile-confirm-text title="Delete item">This cannot be undone</dile-confirm-text>');
18
+ expect(el.shadowRoot.querySelector('.title').textContent.trim()).toBe('Delete item');
19
+ expect(el.shadowRoot.querySelector('dile-input')).toBeTruthy();
20
+ });
21
+
22
+ it('shows negative feedback when accepting with an empty text field', async () => {
23
+ const el = await renderConfirmText('<dile-confirm-text>Confirm</dile-confirm-text>');
24
+ el.shadowRoot.querySelector('.accept').click();
25
+ await el.updateComplete;
26
+
27
+ expect(el.feedback.shadowRoot.textContent).toContain(el.emptyTextMessage);
28
+ });
29
+ });
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './copy-markdown-url.js';
3
+
4
+ describe('dile-copy-markdown-url', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderCopyMarkdownUrl(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-copy-markdown-url');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ it('renders a dile-copy-text with the slotted content', async () => {
17
+ const el = await renderCopyMarkdownUrl('<dile-copy-markdown-url url="/doc.md">Copy link</dile-copy-markdown-url>');
18
+ expect(el.shadowRoot.querySelector('dile-copy-text')).toBeTruthy();
19
+ expect(el.textContent.trim()).toBe('Copy link');
20
+ });
21
+
22
+ it('defaults to an empty url and content', async () => {
23
+ const el = await renderCopyMarkdownUrl('<dile-copy-markdown-url></dile-copy-markdown-url>');
24
+ expect(el.url).toBe('');
25
+ expect(el.content).toBe('');
26
+ });
27
+ });
@@ -1,5 +1,7 @@
1
1
  import { DileCopyMarkdownUrl } from './src/DileCopyMarkdownUrl.js';
2
2
 
3
- window.customElements.define('dile-copy-markdown-url', DileCopyMarkdownUrl);
3
+ if (!customElements.get('dile-copy-markdown-url')) {
4
+ window.customElements.define('dile-copy-markdown-url', DileCopyMarkdownUrl);
5
+ }
4
6
 
5
7
  export { DileCopyMarkdownUrl };
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect, afterEach, vi } from 'vitest';
2
+ import './copy-text.js';
3
+
4
+ describe('dile-copy-text', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderCopyText(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-copy-text');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ it('renders the slotted content inside a clickable link', async () => {
17
+ const el = await renderCopyText('<dile-copy-text content="hello">Copy me</dile-copy-text>');
18
+ const link = el.shadowRoot.querySelector('a');
19
+ expect(link).toBeTruthy();
20
+ expect(el.textContent.trim()).toBe('Copy me');
21
+ });
22
+
23
+ it('dispatches dile-text-copied with the content on copy()', async () => {
24
+ const el = await renderCopyText('<dile-copy-text content="hello">Copy me</dile-copy-text>');
25
+ const writeText = vi.fn().mockResolvedValue(undefined);
26
+ vi.stubGlobal('navigator', { ...navigator, clipboard: { writeText } });
27
+
28
+ let detail = null;
29
+ el.addEventListener('dile-text-copied', (e) => { detail = e.detail; });
30
+
31
+ await el.copy();
32
+
33
+ expect(writeText).toHaveBeenCalledWith('hello');
34
+ expect(detail).toEqual({ text: 'hello' });
35
+
36
+ vi.unstubAllGlobals();
37
+ });
38
+ });
@@ -1,5 +1,7 @@
1
1
  import { DileCopyText } from './src/DileCopyText.js';
2
2
 
3
- window.customElements.define('dile-copy-text', DileCopyText);
3
+ if (!customElements.get('dile-copy-text')) {
4
+ window.customElements.define('dile-copy-text', DileCopyText);
5
+ }
4
6
 
5
7
  export { DileCopyText };
@@ -1,3 +1,5 @@
1
1
  import { DileDropFile } from "./src/DileDropFile.js";
2
2
 
3
- customElements.define('dile-drop-file', DileDropFile);
3
+ if (!customElements.get('dile-drop-file')) {
4
+ customElements.define('dile-drop-file', DileDropFile);
5
+ }
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './drop-file.js';
3
+
4
+ describe('dile-drop-file', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderDropFile(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-drop-file');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ it('renders the drop zone with label and file input', async () => {
17
+ const el = await renderDropFile('<dile-drop-file label="Upload"></dile-drop-file>');
18
+ expect(el.shadowRoot.querySelector('#dropZone')).toBeTruthy();
19
+ expect(el.shadowRoot.querySelector('label').textContent.trim()).toBe('Upload');
20
+ expect(el.shadowRoot.querySelector('input[type="file"]')).toBeTruthy();
21
+ });
22
+
23
+ it('clears the selected file name', async () => {
24
+ const el = await renderDropFile('<dile-drop-file></dile-drop-file>');
25
+ el.fileName = 'test.png';
26
+ await el.updateComplete;
27
+
28
+ el.clear();
29
+ await el.updateComplete;
30
+
31
+ expect(el.fileName).toBe('');
32
+ });
33
+ });
@@ -1,2 +1,4 @@
1
1
  import { DileFilePreview } from "./src/DileFilePreview.js";
2
- customElements.define('dile-file-preview', DileFilePreview);
2
+ if (!customElements.get('dile-file-preview')) {
3
+ customElements.define('dile-file-preview', DileFilePreview);
4
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import './file-preview.js';
3
+
4
+ describe('dile-file-preview', () => {
5
+ afterEach(() => {
6
+ document.body.innerHTML = '';
7
+ });
8
+
9
+ async function renderFilePreview(html) {
10
+ document.body.innerHTML = html;
11
+ const el = document.body.querySelector('dile-file-preview');
12
+ await el.updateComplete;
13
+ return el;
14
+ }
15
+
16
+ it('renders nothing when there is no fileName', async () => {
17
+ const el = await renderFilePreview('<dile-file-preview></dile-file-preview>');
18
+ expect(el.shadowRoot.querySelector('div')).toBeNull();
19
+ });
20
+
21
+ it('renders the file name and a clear link when fileName is set', async () => {
22
+ const el = await renderFilePreview('<dile-file-preview fileName="report.pdf" selectedFileLabel="Selected"></dile-file-preview>');
23
+ expect(el.shadowRoot.querySelector('div span').textContent).toContain('report.pdf');
24
+ });
25
+
26
+ it('dispatches dile-file-clear when the clear link is clicked', async () => {
27
+ const el = await renderFilePreview('<dile-file-preview fileName="report.pdf"></dile-file-preview>');
28
+ let detail = null;
29
+ el.addEventListener('dile-file-clear', (e) => { detail = e.detail; });
30
+
31
+ el.shadowRoot.querySelector('a').click();
32
+
33
+ expect(detail).toEqual({ fileName: 'report.pdf' });
34
+ });
35
+ });