@getflip/swirl-components 0.87.2 → 0.87.3

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 (30) hide show
  1. package/components.json +1 -1
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/swirl-autocomplete.cjs.entry.js +8 -3
  4. package/dist/cjs/swirl-components.cjs.js +1 -1
  5. package/dist/cjs/swirl-icon-check-small_3.cjs.entry.js +8 -1
  6. package/dist/cjs/swirl-option-list_2.cjs.entry.js +19 -16
  7. package/dist/collection/components/swirl-autocomplete/swirl-autocomplete.js +8 -3
  8. package/dist/collection/components/swirl-autocomplete/swirl-autocomplete.stories.js +190 -0
  9. package/dist/collection/components/swirl-option-list/swirl-option-list.js +19 -16
  10. package/dist/collection/components/swirl-option-list/swirl-option-list.spec.js +1 -1
  11. package/dist/collection/components/swirl-option-list/swirl-option-list.stories.js +5 -5
  12. package/dist/collection/components/swirl-option-list-item/swirl-option-list-item.js +10 -2
  13. package/dist/components/swirl-autocomplete.js +8 -3
  14. package/dist/components/swirl-option-list-item2.js +10 -2
  15. package/dist/components/swirl-option-list2.js +19 -16
  16. package/dist/esm/loader.js +1 -1
  17. package/dist/esm/swirl-autocomplete.entry.js +8 -3
  18. package/dist/esm/swirl-components.js +1 -1
  19. package/dist/esm/swirl-icon-check-small_3.entry.js +8 -1
  20. package/dist/esm/swirl-option-list_2.entry.js +19 -16
  21. package/dist/swirl-components/p-1f3ce187.entry.js +1 -0
  22. package/dist/swirl-components/p-4bec5f4c.entry.js +1 -0
  23. package/dist/swirl-components/p-89898ac6.entry.js +1 -0
  24. package/dist/swirl-components/swirl-components.esm.js +1 -1
  25. package/dist/types/components/swirl-option-list/swirl-option-list.d.ts +1 -1
  26. package/dist/types/components/swirl-option-list-item/swirl-option-list-item.d.ts +3 -0
  27. package/package.json +1 -1
  28. package/dist/swirl-components/p-07957b9a.entry.js +0 -1
  29. package/dist/swirl-components/p-7dafac36.entry.js +0 -1
  30. package/dist/swirl-components/p-f7f2f11c.entry.js +0 -1
@@ -53,6 +53,12 @@ const SwirlOptionListItem = class {
53
53
  this.toggleDrag.emit(this.el);
54
54
  }
55
55
  };
56
+ this.onBlur = () => {
57
+ this.focused = false;
58
+ };
59
+ this.onFocus = () => {
60
+ this.focused = true;
61
+ };
56
62
  this.allowDrag = undefined;
57
63
  this.context = "single-select";
58
64
  this.disabled = undefined;
@@ -65,6 +71,7 @@ const SwirlOptionListItem = class {
65
71
  this.swirlAriaRole = "option";
66
72
  this.value = undefined;
67
73
  this.iconSize = 24;
74
+ this.focused = undefined;
68
75
  }
69
76
  componentDidLoad() {
70
77
  this.forceIconProps(this.desktopMediaQuery.matches);
@@ -93,7 +100,7 @@ const SwirlOptionListItem = class {
93
100
  "option-list-item--dragging": this.dragging,
94
101
  "option-list-item--selected": this.selected,
95
102
  });
96
- return (index.h(index.Host, null, index.h("div", { "aria-checked": this.swirlAriaRole === "menuitemradio" ? ariaSelected : undefined, "aria-disabled": ariaDisabled, "aria-selected": this.swirlAriaRole === "option" ? ariaSelected : undefined, class: className, part: "option-list-item", role: this.swirlAriaRole }, showIcon && (index.h("span", { class: "option-list-item__icon", innerHTML: this.icon, ref: (el) => (this.iconEl = el) })), showCheckbox && (index.h("span", { class: "option-list-item__checkbox" }, index.h("span", { class: "option-list-item__checkbox-box" }, this.selected && (index.h("swirl-icon-check-strong", { class: "option-list-item__checkbox-icon", size: 16 }))))), index.h("span", { class: "option-list-item__label", part: "option-list-item__label" }, this.label), showSelectionIcon && (index.h("span", { class: "option-list-item__selection-icon" }, index.h("swirl-icon-check-small", { size: this.iconSize })))), this.allowDrag && (index.h("button", { "aria-describedby": this.dragHandleDescription, "aria-label": `${this.dragHandleLabel} "${this.label}"`, class: "option-list-item__drag-handle", onKeyDown: this.onDragHandleKeyDown, type: "button" }, index.h("swirl-icon-drag-handle", { size: this.iconSize })))));
103
+ return (index.h(index.Host, null, index.h("div", { "aria-checked": this.swirlAriaRole === "menuitemradio" ? ariaSelected : undefined, "aria-disabled": ariaDisabled, "aria-selected": this.swirlAriaRole === "option" ? ariaSelected : undefined, class: className, onBlur: this.onBlur, onFocus: this.onFocus, part: "option-list-item", role: this.swirlAriaRole }, showIcon && (index.h("span", { class: "option-list-item__icon", innerHTML: this.icon, ref: (el) => (this.iconEl = el) })), showCheckbox && (index.h("span", { class: "option-list-item__checkbox" }, index.h("span", { class: "option-list-item__checkbox-box" }, this.selected && (index.h("swirl-icon-check-strong", { class: "option-list-item__checkbox-icon", size: 16 }))))), index.h("span", { class: "option-list-item__label", part: "option-list-item__label" }, this.label), showSelectionIcon && (index.h("span", { class: "option-list-item__selection-icon" }, index.h("swirl-icon-check-small", { size: this.iconSize })))), this.allowDrag && (index.h("button", { "aria-describedby": this.dragHandleDescription, "aria-label": `${this.dragHandleLabel} "${this.label}"`, class: "option-list-item__drag-handle", onKeyDown: this.onDragHandleKeyDown, tabIndex: this.focused ? 0 : -1, type: "button" }, index.h("swirl-icon-drag-handle", { size: this.iconSize })))));
97
104
  }
98
105
  get el() { return index.getElement(this); }
99
106
  };
@@ -14,19 +14,6 @@ const SwirlOptionList = class {
14
14
  index.registerInstance(this, hostRef);
15
15
  this.itemDrop = index.createEvent(this, "itemDrop", 7);
16
16
  this.valueChange = index.createEvent(this, "valueChange", 7);
17
- this.onFocus = async (event) => {
18
- if (this.listboxEl.contains(event.relatedTarget)) {
19
- return;
20
- }
21
- // prevent focus from canceling the drag event in Safari
22
- await new Promise((resolve) => setTimeout(resolve));
23
- if (Boolean(this.focusedItem)) {
24
- this.focusItem(this.getActiveItemIndex());
25
- }
26
- else {
27
- this.focusItem(0);
28
- }
29
- };
30
17
  this.onClick = (event) => {
31
18
  event.preventDefault();
32
19
  const target = event.target;
@@ -59,6 +46,12 @@ const SwirlOptionList = class {
59
46
  }
60
47
  }
61
48
  else if (event.code === "Space" || event.code === "Enter") {
49
+ const startingDrag = event.target.classList.contains("option-list-item__drag-handle");
50
+ if (!startingDrag && Boolean(this.dragging)) {
51
+ event.preventDefault();
52
+ this.stopDrag(this.dragging);
53
+ return;
54
+ }
62
55
  const target = event.composedPath()[0];
63
56
  const optionFocused = Boolean(utils.closestPassShadow(target, '[role="option"]'));
64
57
  if (!optionFocused) {
@@ -116,6 +109,7 @@ const SwirlOptionList = class {
116
109
  this.assistiveText = `${this.assistiveTextItemMoved} ${newIndex + 1}`;
117
110
  this.itemDrop.emit({ item, oldIndex: this.draggingStartIndex, newIndex });
118
111
  this.draggingStartIndex = undefined;
112
+ this.focusItem(newIndex);
119
113
  };
120
114
  this.allowDeselect = true;
121
115
  this.allowDrag = undefined;
@@ -138,6 +132,9 @@ const SwirlOptionList = class {
138
132
  this.syncItemsWithValue();
139
133
  this.setupDragDrop();
140
134
  }
135
+ componentDidRender() {
136
+ this.setupDragDrop();
137
+ }
141
138
  disconnectedCallback() {
142
139
  this.observer?.disconnect();
143
140
  this.sortable?.destroy();
@@ -162,12 +159,17 @@ const SwirlOptionList = class {
162
159
  this.setItemDisabledState();
163
160
  this.setItemContext();
164
161
  this.syncItemsWithValue();
165
- this.setupDragDrop();
166
162
  });
167
163
  this.observer.observe(this.listboxEl, { childList: true });
168
164
  }
169
165
  updateItems() {
170
166
  this.items = utils.querySelectorAllDeep(this.el, "swirl-option-list-item");
167
+ this.items.forEach((item) => item.querySelector('[role="option"]')?.removeAttribute("tabIndex"));
168
+ const item = this.items[0]?.querySelector('[role="option"]');
169
+ if (!Boolean(item)) {
170
+ return;
171
+ }
172
+ item.setAttribute("tabIndex", "0");
171
173
  }
172
174
  setItemDisabledState() {
173
175
  if (this.disabled) {
@@ -249,6 +251,7 @@ const SwirlOptionList = class {
249
251
  else {
250
252
  this.updateValue(this.value.filter((v) => v !== item.value));
251
253
  }
254
+ this.focusItem(index);
252
255
  }
253
256
  updateValue(value) {
254
257
  this.value = value;
@@ -331,8 +334,8 @@ const SwirlOptionList = class {
331
334
  }
332
335
  render() {
333
336
  const ariaMultiselectable = this.multiSelect ? "true" : undefined;
334
- const tabIndex = this.disabled ? -1 : 0;
335
- return (index.h(index.Host, null, index.h("swirl-visually-hidden", { role: "alert" }, this.assistiveText), index.h("div", { "aria-label": this.label, "aria-multiselectable": ariaMultiselectable, class: "option-list", id: this.optionListId, onClick: this.onClick, onFocus: this.onFocus, onKeyDown: this.onKeyDown, ref: (el) => (this.listboxEl = el), role: "listbox", tabIndex: tabIndex }, index.h("slot", null))));
337
+ const tabIndex = Boolean(this.dragging) ? 0 : undefined;
338
+ return (index.h(index.Host, null, index.h("swirl-visually-hidden", { role: "alert" }, this.assistiveText), index.h("div", { "aria-label": this.label, "aria-multiselectable": ariaMultiselectable, class: "option-list", id: this.optionListId, onClick: this.onClick, onKeyDown: this.onKeyDown, ref: (el) => (this.listboxEl = el), role: "listbox", tabIndex: tabIndex }, index.h("slot", null))));
336
339
  }
337
340
  get el() { return index.getElement(this); }
338
341
  static get watchers() { return {
@@ -44,8 +44,10 @@ export class SwirlAutocomplete {
44
44
  }
45
45
  this.value = suggestion;
46
46
  this.valueChange.emit(this.value);
47
- this.inputEl.querySelector("input")?.focus();
48
- this.close();
47
+ queueMicrotask(() => {
48
+ this.inputEl.querySelector("input")?.focus();
49
+ this.close();
50
+ });
49
51
  }
50
52
  }
51
53
  };
@@ -77,7 +79,10 @@ export class SwirlAutocomplete {
77
79
  this.onInputKeyDown = (event) => {
78
80
  if (event.code === "ArrowDown") {
79
81
  event.preventDefault();
80
- this.listboxEl.querySelector('[role="listbox"]').focus();
82
+ this.listboxEl
83
+ .querySelector('[role="listbox"]')
84
+ .querySelector('[tabIndex="0"]')
85
+ ?.focus();
81
86
  }
82
87
  };
83
88
  this.autoSelect = undefined;
@@ -72,6 +72,196 @@ const Template = (args) => {
72
72
  id: "item-3",
73
73
  label: "Item #3 with a longer label",
74
74
  },
75
+ {
76
+ id: "item-4",
77
+ label: "Item #4",
78
+ },
79
+ {
80
+ id: "item-5",
81
+ label: "Item #5",
82
+ },
83
+ {
84
+ id: "item-6",
85
+ label: "Item #6 with some additional information",
86
+ },
87
+ {
88
+ id: "item-7",
89
+ label: "Item #7",
90
+ },
91
+ {
92
+ disabled: true,
93
+ id: "item-8",
94
+ label: "Item #8",
95
+ },
96
+ {
97
+ id: "item-9",
98
+ label: "Item #9 with a lengthy label to test wrapping",
99
+ },
100
+ {
101
+ id: "item-10",
102
+ label: "Item #10",
103
+ },
104
+ {
105
+ id: "item-11",
106
+ label: "Item #11",
107
+ },
108
+ {
109
+ id: "item-12",
110
+ label: "Item #12",
111
+ },
112
+ {
113
+ id: "item-13",
114
+ label: "Item #13",
115
+ },
116
+ {
117
+ id: "item-14",
118
+ label: "Item #14 with a descriptive label",
119
+ },
120
+ {
121
+ id: "item-15",
122
+ label: "Item #15",
123
+ },
124
+ {
125
+ id: "item-16",
126
+ label: "Item #16",
127
+ },
128
+ {
129
+ id: "item-17",
130
+ label: "Item #17",
131
+ },
132
+ {
133
+ disabled: true,
134
+ id: "item-18",
135
+ label: "Item #18 with special considerations",
136
+ },
137
+ {
138
+ id: "item-19",
139
+ label: "Item #19",
140
+ },
141
+ {
142
+ id: "item-20",
143
+ label: "Item #20",
144
+ },
145
+ {
146
+ id: "item-21",
147
+ label: "Item #21",
148
+ },
149
+ {
150
+ id: "item-22",
151
+ label: "Item #22",
152
+ },
153
+ {
154
+ id: "item-23",
155
+ label: "Item #23 with an extended label",
156
+ },
157
+ {
158
+ id: "item-24",
159
+ label: "Item #24",
160
+ },
161
+ {
162
+ id: "item-25",
163
+ label: "Item #25",
164
+ },
165
+ {
166
+ id: "item-26",
167
+ label: "Item #26",
168
+ },
169
+ {
170
+ id: "item-27",
171
+ label: "Item #27",
172
+ },
173
+ {
174
+ id: "item-28",
175
+ label: "Item #28 with unique properties",
176
+ },
177
+ {
178
+ id: "item-29",
179
+ label: "Item #29",
180
+ },
181
+ {
182
+ id: "item-30",
183
+ label: "Item #30",
184
+ },
185
+ {
186
+ id: "item-31",
187
+ label: "Item #31",
188
+ },
189
+ {
190
+ id: "item-32",
191
+ label: "Item #32",
192
+ },
193
+ {
194
+ id: "item-33",
195
+ label: "Item #33 with additional notes",
196
+ },
197
+ {
198
+ id: "item-34",
199
+ label: "Item #34",
200
+ },
201
+ {
202
+ id: "item-35",
203
+ label: "Item #35",
204
+ },
205
+ {
206
+ id: "item-36",
207
+ label: "Item #36",
208
+ },
209
+ {
210
+ id: "item-37",
211
+ label: "Item #37",
212
+ },
213
+ {
214
+ id: "item-38",
215
+ label: "Item #38 with a customized label",
216
+ },
217
+ {
218
+ id: "item-39",
219
+ label: "Item #39",
220
+ },
221
+ {
222
+ id: "item-40",
223
+ label: "Item #40",
224
+ },
225
+ {
226
+ id: "item-41",
227
+ label: "Item #41",
228
+ },
229
+ {
230
+ id: "item-42",
231
+ label: "Item #42",
232
+ },
233
+ {
234
+ id: "item-43",
235
+ label: "Item #43 with specific details",
236
+ },
237
+ {
238
+ id: "item-44",
239
+ label: "Item #44",
240
+ },
241
+ {
242
+ id: "item-45",
243
+ label: "Item #45",
244
+ },
245
+ {
246
+ id: "item-46",
247
+ label: "Item #46",
248
+ },
249
+ {
250
+ id: "item-47",
251
+ label: "Item #47",
252
+ },
253
+ {
254
+ id: "item-48",
255
+ label: "Item #48 with optional settings",
256
+ },
257
+ {
258
+ id: "item-49",
259
+ label: "Item #49",
260
+ },
261
+ {
262
+ id: "item-50",
263
+ label: "Item #50",
264
+ },
75
265
  ];
76
266
  element.generateSuggestions = async (value) => {
77
267
  if (!Boolean(value)) {
@@ -3,19 +3,6 @@ import { closestPassShadow, querySelectorAllDeep, } from "../../utils";
3
3
  import Sortable from "sortablejs";
4
4
  export class SwirlOptionList {
5
5
  constructor() {
6
- this.onFocus = async (event) => {
7
- if (this.listboxEl.contains(event.relatedTarget)) {
8
- return;
9
- }
10
- // prevent focus from canceling the drag event in Safari
11
- await new Promise((resolve) => setTimeout(resolve));
12
- if (Boolean(this.focusedItem)) {
13
- this.focusItem(this.getActiveItemIndex());
14
- }
15
- else {
16
- this.focusItem(0);
17
- }
18
- };
19
6
  this.onClick = (event) => {
20
7
  event.preventDefault();
21
8
  const target = event.target;
@@ -48,6 +35,12 @@ export class SwirlOptionList {
48
35
  }
49
36
  }
50
37
  else if (event.code === "Space" || event.code === "Enter") {
38
+ const startingDrag = event.target.classList.contains("option-list-item__drag-handle");
39
+ if (!startingDrag && Boolean(this.dragging)) {
40
+ event.preventDefault();
41
+ this.stopDrag(this.dragging);
42
+ return;
43
+ }
51
44
  const target = event.composedPath()[0];
52
45
  const optionFocused = Boolean(closestPassShadow(target, '[role="option"]'));
53
46
  if (!optionFocused) {
@@ -105,6 +98,7 @@ export class SwirlOptionList {
105
98
  this.assistiveText = `${this.assistiveTextItemMoved} ${newIndex + 1}`;
106
99
  this.itemDrop.emit({ item, oldIndex: this.draggingStartIndex, newIndex });
107
100
  this.draggingStartIndex = undefined;
101
+ this.focusItem(newIndex);
108
102
  };
109
103
  this.allowDeselect = true;
110
104
  this.allowDrag = undefined;
@@ -127,6 +121,9 @@ export class SwirlOptionList {
127
121
  this.syncItemsWithValue();
128
122
  this.setupDragDrop();
129
123
  }
124
+ componentDidRender() {
125
+ this.setupDragDrop();
126
+ }
130
127
  disconnectedCallback() {
131
128
  this.observer?.disconnect();
132
129
  this.sortable?.destroy();
@@ -151,12 +148,17 @@ export class SwirlOptionList {
151
148
  this.setItemDisabledState();
152
149
  this.setItemContext();
153
150
  this.syncItemsWithValue();
154
- this.setupDragDrop();
155
151
  });
156
152
  this.observer.observe(this.listboxEl, { childList: true });
157
153
  }
158
154
  updateItems() {
159
155
  this.items = querySelectorAllDeep(this.el, "swirl-option-list-item");
156
+ this.items.forEach((item) => item.querySelector('[role="option"]')?.removeAttribute("tabIndex"));
157
+ const item = this.items[0]?.querySelector('[role="option"]');
158
+ if (!Boolean(item)) {
159
+ return;
160
+ }
161
+ item.setAttribute("tabIndex", "0");
160
162
  }
161
163
  setItemDisabledState() {
162
164
  if (this.disabled) {
@@ -238,6 +240,7 @@ export class SwirlOptionList {
238
240
  else {
239
241
  this.updateValue(this.value.filter((v) => v !== item.value));
240
242
  }
243
+ this.focusItem(index);
241
244
  }
242
245
  updateValue(value) {
243
246
  this.value = value;
@@ -320,8 +323,8 @@ export class SwirlOptionList {
320
323
  }
321
324
  render() {
322
325
  const ariaMultiselectable = this.multiSelect ? "true" : undefined;
323
- const tabIndex = this.disabled ? -1 : 0;
324
- return (h(Host, null, h("swirl-visually-hidden", { role: "alert" }, this.assistiveText), h("div", { "aria-label": this.label, "aria-multiselectable": ariaMultiselectable, class: "option-list", id: this.optionListId, onClick: this.onClick, onFocus: this.onFocus, onKeyDown: this.onKeyDown, ref: (el) => (this.listboxEl = el), role: "listbox", tabIndex: tabIndex }, h("slot", null))));
326
+ const tabIndex = Boolean(this.dragging) ? 0 : undefined;
327
+ return (h(Host, null, h("swirl-visually-hidden", { role: "alert" }, this.assistiveText), h("div", { "aria-label": this.label, "aria-multiselectable": ariaMultiselectable, class: "option-list", id: this.optionListId, onClick: this.onClick, onKeyDown: this.onKeyDown, ref: (el) => (this.listboxEl = el), role: "listbox", tabIndex: tabIndex }, h("slot", null))));
325
328
  }
326
329
  static get is() { return "swirl-option-list"; }
327
330
  static get encapsulation() { return "scoped"; }
@@ -43,7 +43,7 @@ describe("swirl-option-list", () => {
43
43
  expect(page.root).toEqualHtml(`
44
44
  <swirl-option-list label="Option List" multi-select="true">
45
45
  <swirl-visually-hidden role="alert"></swirl-visually-hidden>
46
- <div aria-label="Option List" aria-multiselectable="true" class="option-list" role="listbox" tabindex="0">
46
+ <div aria-label="Option List" aria-multiselectable="true" class="option-list" role="listbox">
47
47
  <swirl-option-list-item label="This is an option" value="1"></swirl-option-list-item>
48
48
  <swirl-option-list-item label="This is an option" value="2"></swirl-option-list-item>
49
49
  <swirl-option-list-item label="This is an option" value="3"></swirl-option-list-item>
@@ -13,11 +13,11 @@ export default {
13
13
  const Template = (args) => {
14
14
  const element = generateStoryElement("swirl-option-list", args);
15
15
  element.innerHTML = `
16
- <swirl-option-list-item label="This is option 1" value="1"></swirl-option-list-item>
17
- <swirl-option-list-item label="This is option 2" value="2"></swirl-option-list-item>
18
- <swirl-option-list-item label="This is option 3" value="3"></swirl-option-list-item>
19
- <swirl-option-list-item label="This is option 4" value="4"></swirl-option-list-item>
20
- `;
16
+ <swirl-option-list-item label="This is option 1" value="1"></swirl-option-list-item>
17
+ <swirl-option-list-item label="This is option 2" value="2"></swirl-option-list-item>
18
+ <swirl-option-list-item label="This is option 3" value="3"></swirl-option-list-item>
19
+ <swirl-option-list-item label="This is option 4" value="4"></swirl-option-list-item>
20
+ `;
21
21
  return element;
22
22
  };
23
23
  export const SwirlOptionList = Template.bind({});
@@ -14,6 +14,12 @@ export class SwirlOptionListItem {
14
14
  this.toggleDrag.emit(this.el);
15
15
  }
16
16
  };
17
+ this.onBlur = () => {
18
+ this.focused = false;
19
+ };
20
+ this.onFocus = () => {
21
+ this.focused = true;
22
+ };
17
23
  this.allowDrag = undefined;
18
24
  this.context = "single-select";
19
25
  this.disabled = undefined;
@@ -26,6 +32,7 @@ export class SwirlOptionListItem {
26
32
  this.swirlAriaRole = "option";
27
33
  this.value = undefined;
28
34
  this.iconSize = 24;
35
+ this.focused = undefined;
29
36
  }
30
37
  componentDidLoad() {
31
38
  this.forceIconProps(this.desktopMediaQuery.matches);
@@ -54,7 +61,7 @@ export class SwirlOptionListItem {
54
61
  "option-list-item--dragging": this.dragging,
55
62
  "option-list-item--selected": this.selected,
56
63
  });
57
- return (h(Host, null, h("div", { "aria-checked": this.swirlAriaRole === "menuitemradio" ? ariaSelected : undefined, "aria-disabled": ariaDisabled, "aria-selected": this.swirlAriaRole === "option" ? ariaSelected : undefined, class: className, part: "option-list-item", role: this.swirlAriaRole }, showIcon && (h("span", { class: "option-list-item__icon", innerHTML: this.icon, ref: (el) => (this.iconEl = el) })), showCheckbox && (h("span", { class: "option-list-item__checkbox" }, h("span", { class: "option-list-item__checkbox-box" }, this.selected && (h("swirl-icon-check-strong", { class: "option-list-item__checkbox-icon", size: 16 }))))), h("span", { class: "option-list-item__label", part: "option-list-item__label" }, this.label), showSelectionIcon && (h("span", { class: "option-list-item__selection-icon" }, h("swirl-icon-check-small", { size: this.iconSize })))), this.allowDrag && (h("button", { "aria-describedby": this.dragHandleDescription, "aria-label": `${this.dragHandleLabel} "${this.label}"`, class: "option-list-item__drag-handle", onKeyDown: this.onDragHandleKeyDown, type: "button" }, h("swirl-icon-drag-handle", { size: this.iconSize })))));
64
+ return (h(Host, null, h("div", { "aria-checked": this.swirlAriaRole === "menuitemradio" ? ariaSelected : undefined, "aria-disabled": ariaDisabled, "aria-selected": this.swirlAriaRole === "option" ? ariaSelected : undefined, class: className, onBlur: this.onBlur, onFocus: this.onFocus, part: "option-list-item", role: this.swirlAriaRole }, showIcon && (h("span", { class: "option-list-item__icon", innerHTML: this.icon, ref: (el) => (this.iconEl = el) })), showCheckbox && (h("span", { class: "option-list-item__checkbox" }, h("span", { class: "option-list-item__checkbox-box" }, this.selected && (h("swirl-icon-check-strong", { class: "option-list-item__checkbox-icon", size: 16 }))))), h("span", { class: "option-list-item__label", part: "option-list-item__label" }, this.label), showSelectionIcon && (h("span", { class: "option-list-item__selection-icon" }, h("swirl-icon-check-small", { size: this.iconSize })))), this.allowDrag && (h("button", { "aria-describedby": this.dragHandleDescription, "aria-label": `${this.dragHandleLabel} "${this.label}"`, class: "option-list-item__drag-handle", onKeyDown: this.onDragHandleKeyDown, tabIndex: this.focused ? 0 : -1, type: "button" }, h("swirl-icon-drag-handle", { size: this.iconSize })))));
58
65
  }
59
66
  static get is() { return "swirl-option-list-item"; }
60
67
  static get encapsulation() { return "scoped"; }
@@ -276,7 +283,8 @@ export class SwirlOptionListItem {
276
283
  }
277
284
  static get states() {
278
285
  return {
279
- "iconSize": {}
286
+ "iconSize": {},
287
+ "focused": {}
280
288
  };
281
289
  }
282
290
  static get events() {
@@ -66,8 +66,10 @@ const SwirlAutocomplete$1 = /*@__PURE__*/ proxyCustomElement(class SwirlAutocomp
66
66
  }
67
67
  this.value = suggestion;
68
68
  this.valueChange.emit(this.value);
69
- this.inputEl.querySelector("input")?.focus();
70
- this.close();
69
+ queueMicrotask(() => {
70
+ this.inputEl.querySelector("input")?.focus();
71
+ this.close();
72
+ });
71
73
  }
72
74
  }
73
75
  };
@@ -99,7 +101,10 @@ const SwirlAutocomplete$1 = /*@__PURE__*/ proxyCustomElement(class SwirlAutocomp
99
101
  this.onInputKeyDown = (event) => {
100
102
  if (event.code === "ArrowDown") {
101
103
  event.preventDefault();
102
- this.listboxEl.querySelector('[role="listbox"]').focus();
104
+ this.listboxEl
105
+ .querySelector('[role="listbox"]')
106
+ .querySelector('[tabIndex="0"]')
107
+ ?.focus();
103
108
  }
104
109
  };
105
110
  this.autoSelect = undefined;
@@ -23,6 +23,12 @@ const SwirlOptionListItem = /*@__PURE__*/ proxyCustomElement(class SwirlOptionLi
23
23
  this.toggleDrag.emit(this.el);
24
24
  }
25
25
  };
26
+ this.onBlur = () => {
27
+ this.focused = false;
28
+ };
29
+ this.onFocus = () => {
30
+ this.focused = true;
31
+ };
26
32
  this.allowDrag = undefined;
27
33
  this.context = "single-select";
28
34
  this.disabled = undefined;
@@ -35,6 +41,7 @@ const SwirlOptionListItem = /*@__PURE__*/ proxyCustomElement(class SwirlOptionLi
35
41
  this.swirlAriaRole = "option";
36
42
  this.value = undefined;
37
43
  this.iconSize = 24;
44
+ this.focused = undefined;
38
45
  }
39
46
  componentDidLoad() {
40
47
  this.forceIconProps(this.desktopMediaQuery.matches);
@@ -63,7 +70,7 @@ const SwirlOptionListItem = /*@__PURE__*/ proxyCustomElement(class SwirlOptionLi
63
70
  "option-list-item--dragging": this.dragging,
64
71
  "option-list-item--selected": this.selected,
65
72
  });
66
- return (h(Host, null, h("div", { "aria-checked": this.swirlAriaRole === "menuitemradio" ? ariaSelected : undefined, "aria-disabled": ariaDisabled, "aria-selected": this.swirlAriaRole === "option" ? ariaSelected : undefined, class: className, part: "option-list-item", role: this.swirlAriaRole }, showIcon && (h("span", { class: "option-list-item__icon", innerHTML: this.icon, ref: (el) => (this.iconEl = el) })), showCheckbox && (h("span", { class: "option-list-item__checkbox" }, h("span", { class: "option-list-item__checkbox-box" }, this.selected && (h("swirl-icon-check-strong", { class: "option-list-item__checkbox-icon", size: 16 }))))), h("span", { class: "option-list-item__label", part: "option-list-item__label" }, this.label), showSelectionIcon && (h("span", { class: "option-list-item__selection-icon" }, h("swirl-icon-check-small", { size: this.iconSize })))), this.allowDrag && (h("button", { "aria-describedby": this.dragHandleDescription, "aria-label": `${this.dragHandleLabel} "${this.label}"`, class: "option-list-item__drag-handle", onKeyDown: this.onDragHandleKeyDown, type: "button" }, h("swirl-icon-drag-handle", { size: this.iconSize })))));
73
+ return (h(Host, null, h("div", { "aria-checked": this.swirlAriaRole === "menuitemradio" ? ariaSelected : undefined, "aria-disabled": ariaDisabled, "aria-selected": this.swirlAriaRole === "option" ? ariaSelected : undefined, class: className, onBlur: this.onBlur, onFocus: this.onFocus, part: "option-list-item", role: this.swirlAriaRole }, showIcon && (h("span", { class: "option-list-item__icon", innerHTML: this.icon, ref: (el) => (this.iconEl = el) })), showCheckbox && (h("span", { class: "option-list-item__checkbox" }, h("span", { class: "option-list-item__checkbox-box" }, this.selected && (h("swirl-icon-check-strong", { class: "option-list-item__checkbox-icon", size: 16 }))))), h("span", { class: "option-list-item__label", part: "option-list-item__label" }, this.label), showSelectionIcon && (h("span", { class: "option-list-item__selection-icon" }, h("swirl-icon-check-small", { size: this.iconSize })))), this.allowDrag && (h("button", { "aria-describedby": this.dragHandleDescription, "aria-label": `${this.dragHandleLabel} "${this.label}"`, class: "option-list-item__drag-handle", onKeyDown: this.onDragHandleKeyDown, tabIndex: this.focused ? 0 : -1, type: "button" }, h("swirl-icon-drag-handle", { size: this.iconSize })))));
67
74
  }
68
75
  get el() { return this; }
69
76
  static get style() { return swirlOptionListItemCss; }
@@ -79,7 +86,8 @@ const SwirlOptionListItem = /*@__PURE__*/ proxyCustomElement(class SwirlOptionLi
79
86
  "selected": [1028],
80
87
  "swirlAriaRole": [1, "swirl-aria-role"],
81
88
  "value": [1],
82
- "iconSize": [32]
89
+ "iconSize": [32],
90
+ "focused": [32]
83
91
  }]);
84
92
  function defineCustomElement() {
85
93
  if (typeof customElements === "undefined") {
@@ -11,19 +11,6 @@ const SwirlOptionList = /*@__PURE__*/ proxyCustomElement(class SwirlOptionList e
11
11
  this.__registerHost();
12
12
  this.itemDrop = createEvent(this, "itemDrop", 7);
13
13
  this.valueChange = createEvent(this, "valueChange", 7);
14
- this.onFocus = async (event) => {
15
- if (this.listboxEl.contains(event.relatedTarget)) {
16
- return;
17
- }
18
- // prevent focus from canceling the drag event in Safari
19
- await new Promise((resolve) => setTimeout(resolve));
20
- if (Boolean(this.focusedItem)) {
21
- this.focusItem(this.getActiveItemIndex());
22
- }
23
- else {
24
- this.focusItem(0);
25
- }
26
- };
27
14
  this.onClick = (event) => {
28
15
  event.preventDefault();
29
16
  const target = event.target;
@@ -56,6 +43,12 @@ const SwirlOptionList = /*@__PURE__*/ proxyCustomElement(class SwirlOptionList e
56
43
  }
57
44
  }
58
45
  else if (event.code === "Space" || event.code === "Enter") {
46
+ const startingDrag = event.target.classList.contains("option-list-item__drag-handle");
47
+ if (!startingDrag && Boolean(this.dragging)) {
48
+ event.preventDefault();
49
+ this.stopDrag(this.dragging);
50
+ return;
51
+ }
59
52
  const target = event.composedPath()[0];
60
53
  const optionFocused = Boolean(closestPassShadow(target, '[role="option"]'));
61
54
  if (!optionFocused) {
@@ -113,6 +106,7 @@ const SwirlOptionList = /*@__PURE__*/ proxyCustomElement(class SwirlOptionList e
113
106
  this.assistiveText = `${this.assistiveTextItemMoved} ${newIndex + 1}`;
114
107
  this.itemDrop.emit({ item, oldIndex: this.draggingStartIndex, newIndex });
115
108
  this.draggingStartIndex = undefined;
109
+ this.focusItem(newIndex);
116
110
  };
117
111
  this.allowDeselect = true;
118
112
  this.allowDrag = undefined;
@@ -135,6 +129,9 @@ const SwirlOptionList = /*@__PURE__*/ proxyCustomElement(class SwirlOptionList e
135
129
  this.syncItemsWithValue();
136
130
  this.setupDragDrop();
137
131
  }
132
+ componentDidRender() {
133
+ this.setupDragDrop();
134
+ }
138
135
  disconnectedCallback() {
139
136
  this.observer?.disconnect();
140
137
  this.sortable?.destroy();
@@ -159,12 +156,17 @@ const SwirlOptionList = /*@__PURE__*/ proxyCustomElement(class SwirlOptionList e
159
156
  this.setItemDisabledState();
160
157
  this.setItemContext();
161
158
  this.syncItemsWithValue();
162
- this.setupDragDrop();
163
159
  });
164
160
  this.observer.observe(this.listboxEl, { childList: true });
165
161
  }
166
162
  updateItems() {
167
163
  this.items = querySelectorAllDeep(this.el, "swirl-option-list-item");
164
+ this.items.forEach((item) => item.querySelector('[role="option"]')?.removeAttribute("tabIndex"));
165
+ const item = this.items[0]?.querySelector('[role="option"]');
166
+ if (!Boolean(item)) {
167
+ return;
168
+ }
169
+ item.setAttribute("tabIndex", "0");
168
170
  }
169
171
  setItemDisabledState() {
170
172
  if (this.disabled) {
@@ -246,6 +248,7 @@ const SwirlOptionList = /*@__PURE__*/ proxyCustomElement(class SwirlOptionList e
246
248
  else {
247
249
  this.updateValue(this.value.filter((v) => v !== item.value));
248
250
  }
251
+ this.focusItem(index);
249
252
  }
250
253
  updateValue(value) {
251
254
  this.value = value;
@@ -328,8 +331,8 @@ const SwirlOptionList = /*@__PURE__*/ proxyCustomElement(class SwirlOptionList e
328
331
  }
329
332
  render() {
330
333
  const ariaMultiselectable = this.multiSelect ? "true" : undefined;
331
- const tabIndex = this.disabled ? -1 : 0;
332
- return (h(Host, null, h("swirl-visually-hidden", { role: "alert" }, this.assistiveText), h("div", { "aria-label": this.label, "aria-multiselectable": ariaMultiselectable, class: "option-list", id: this.optionListId, onClick: this.onClick, onFocus: this.onFocus, onKeyDown: this.onKeyDown, ref: (el) => (this.listboxEl = el), role: "listbox", tabIndex: tabIndex }, h("slot", null))));
334
+ const tabIndex = Boolean(this.dragging) ? 0 : undefined;
335
+ return (h(Host, null, h("swirl-visually-hidden", { role: "alert" }, this.assistiveText), h("div", { "aria-label": this.label, "aria-multiselectable": ariaMultiselectable, class: "option-list", id: this.optionListId, onClick: this.onClick, onKeyDown: this.onKeyDown, ref: (el) => (this.listboxEl = el), role: "listbox", tabIndex: tabIndex }, h("slot", null))));
333
336
  }
334
337
  get el() { return this; }
335
338
  static get watchers() { return {